Daten mit Firestore-Pipelinevorgängen abrufen

Hintergrund

Pipeline-Vorgänge bieten eine neue Abfrageschnittstelle für Cloud Firestore die erweiterte Abfragefunktionen und komplexe Ausdrücke unterstützt. Es werden viele neue Funktionen eingeführt, darunter min(...), max(...), substring(...), regex_match(...) und array_contains_all(...), sowie Phasen, um komplexe Transformationen auszuführen.

Erste Schritte

Informationen zum Installieren und Initialisieren von Client-SDKs finden Sie in den folgenden Anleitungen:

Syntax

In den folgenden Abschnitten erhalten Sie einen Überblick über die Syntax für Pipeline-Vorgänge.

Konzepte

Ein bemerkenswerter Unterschied bei Pipeline-Vorgängen ist die Einführung einer expliziten Reihenfolge der Phasen. Dadurch können komplexere Abfragen ausgedrückt werden. Dies ist jedoch eine bemerkenswerte Abweichung von der vorhandenen Abfrageschnittstelle mit Core-Vorgängen, bei der die Reihenfolge der Phasen impliziert war. Hier ein Beispiel für Pipeline-Vorgänge:

Web

const pipeline = db.pipeline()
  // Step 1: Start a query with collection scope
  .collection("cities")
  // Step 2: Filter the collection
  .where(field("population").greaterThan(100000))
  // Step 3: Sort the remaining documents
  .sort(field("name").ascending())
  // Step 4: Return the top 10. Note applying the limit earlier in the
  // pipeline would have unintentional results.
  .limit(10);
Swift
let pipeline = db.pipeline()
  // Step 1: Start a query with collection scope
  .collection("cities")
  // Step 2: Filter the collection
  .where(Field("population").greaterThan(100000))
  // Step 3: Sort the remaining documents
  .sort([Field("name").ascending()])
  // Step 4: Return the top 10. Note applying the limit earlier in the pipeline would have
  // unintentional results.
  .limit(10)

Kotlin

val pipeline = db.pipeline()
    // Step 1: Start a query with collection scope
    .collection("cities")
    // Step 2: Filter the collection
    .where(field("population").greaterThan(100000))
    // Step 3: Sort the remaining documents
    .sort(field("name").ascending())
    // Step 4: Return the top 10. Note applying the limit earlier in the pipeline would have
    // unintentional results.
    .limit(10)

Java

Pipeline pipeline = db.pipeline()
    // Step 1: Start a query with collection scope
    .collection("cities")
    // Step 2: Filter the collection
    .where(field("population").greaterThan(100000))
    // Step 3: Sort the remaining documents
    .sort(field("name").ascending())
    // Step 4: Return the top 10. Note applying the limit earlier in the pipeline would have
    // unintentional results.
    .limit(10);
Python
from google.cloud.firestore_v1.pipeline_expressions import Field

pipeline = (
    client.pipeline()
    .collection("cities")
    .where(Field.of("population").greater_than(100_000))
    .sort(Field.of("name").ascending())
    .limit(10)
)

Initialisierung

Pipeline-Vorgänge haben eine sehr vertraute Syntax, die von vorhandenen Cloud Firestore Abfragen stammt. Initialisieren Sie eine Abfrage, indem Sie Folgendes schreiben:

Web

const { getFirestore } = require("firebase/firestore");
const { execute } = require("firebase/firestore/pipelines");
const database = getFirestore(app, "enterprise");
const pipeline = database.pipeline();
Swift
let firestore = Firestore.firestore(database: "enterprise")
let pipeline = firestore.pipeline()

Kotlin

val firestore = Firebase.firestore("enterprise")
val pipeline = firestore.pipeline()

Java

FirebaseFirestore firestore = FirebaseFirestore.getInstance("enterprise");
PipelineSource pipeline = firestore.pipeline();
Python
firestore_client = firestore.client(default_app, "your-new-enterprise-database")
pipeline = firestore_client.pipeline()

Struktur

Beim Erstellen von Pipeline-Vorgängen sind einige Begriffe wichtig: Phasen, Ausdrücke und Funktionen.

Beispiel für Phasen und Ausdrücke in einer Abfrage

Phasen:Eine Pipeline kann aus einer oder mehreren Phasen bestehen. Logisch stellen diese die Reihe von Schritten (oder Phasen) dar, die zum Ausführen der Abfrage erforderlich sind. Hinweis: In der Praxis werden Phasen möglicherweise in einer anderen Reihenfolge ausgeführt, um die Leistung zu verbessern. Dies ändert jedoch nichts an der Absicht oder Richtigkeit der Abfrage.

Ausdrücke:Phasen akzeptieren oft einen Ausdruck, mit dem Sie komplexere Abfragen ausdrücken können. Ausdrücke können einfach sein und aus einer einzelnen Funktion wie eq("a", 1) bestehen. Sie können auch komplexere Ausdrücke erstellen, indem Sie Ausdrücke wie and(eq("a", 1), eq("b", 2)). verschachteln.

Feld- und Konstantenverweise

Pipeline-Vorgänge unterstützen komplexe Ausdrücke. Daher ist es möglicherweise erforderlich, zu unterscheiden, ob ein Wert ein Feld oder eine Konstante darstellt. Dazu ein Beispiel:

Web

const pipeline = db.pipeline()
  .collection("cities")
  .where(field("name").equal(constant("Toronto")));
Swift
let pipeline = db.pipeline()
  .collection("cities")
  .where(Field("name").equal(Constant("Toronto")))

Kotlin

val pipeline = db.pipeline()
    .collection("cities")
    .where(field("name").equal(constant("Toronto")))

Java

Pipeline pipeline = db.pipeline()
    .collection("cities")
    .where(field("name").equal(constant("Toronto")));
Python
from google.cloud.firestore_v1.pipeline_expressions import Field, Constant

pipeline = (
    client.pipeline()
    .collection("cities")
    .where(Field.of("name").equal(Constant.of("Toronto")))
)

Phasen

Eingabephasen

Die Eingabephase ist die erste Phase einer Abfrage. Sie definiert die anfängliche Menge von Dokumenten, die Sie abfragen. Bei Pipeline-Vorgängen ähnelt dies weitgehend den vorhandenen Abfragen, bei denen die meisten Abfragen mit einer collection(...)- oder collection_group(...)-Phase beginnen. Zwei neue Eingabephasen sind database() und documents(...). Mit database() können alle Dokumente in der Datenbank zurückgegeben werden, während documents(...) wie ein Batch-Lesevorgang funktioniert.

Web

let results;

// Return all restaurants in San Francisco
results = await execute(db.pipeline().collection("cities/sf/restaurants"));

// Return all restaurants
results = await execute(db.pipeline().collectionGroup("restaurants"));

// Return all documents across all collections in the database (the entire database)
results = await execute(db.pipeline().database());

// Batch read of 3 documents
results = await execute(db.pipeline().documents([
  doc(db, "cities", "SF"),
  doc(db, "cities", "DC"),
  doc(db, "cities", "NY")
]));
Swift
var results: Pipeline.Snapshot

// Return all restaurants in San Francisco
results = try await db.pipeline().collection("cities/sf/restaurants").execute()

// Return all restaurants
results = try await db.pipeline().collectionGroup("restaurants").execute()

// Return all documents across all collections in the database (the entire database)
results = try await db.pipeline().database().execute()

// Batch read of 3 documents
results = try await db.pipeline().documents([
  db.collection("cities").document("SF"),
  db.collection("cities").document("DC"),
  db.collection("cities").document("NY")
]).execute()

Kotlin

var results: Task<Pipeline.Snapshot>

// Return all restaurants in San Francisco
results = db.pipeline().collection("cities/sf/restaurants").execute()

// Return all restaurants
results = db.pipeline().collectionGroup("restaurants").execute()

// Return all documents across all collections in the database (the entire database)
results = db.pipeline().database().execute()

// Batch read of 3 documents
results = db.pipeline().documents(
    db.collection("cities").document("SF"),
    db.collection("cities").document("DC"),
    db.collection("cities").document("NY")
).execute()

Java

Task<Pipeline.Snapshot> results;

// Return all restaurants in San Francisco
results = db.pipeline().collection("cities/sf/restaurants").execute();

// Return all restaurants
results = db.pipeline().collectionGroup("restaurants").execute();

// Return all documents across all collections in the database (the entire database)
results = db.pipeline().database().execute();

// Batch read of 3 documents
results = db.pipeline().documents(
    db.collection("cities").document("SF"),
    db.collection("cities").document("DC"),
    db.collection("cities").document("NY")
).execute();
Python
# Return all restaurants in San Francisco
results = client.pipeline().collection("cities/sf/restaurants").execute()

# Return all restaurants
results = client.pipeline().collection_group("restaurants").execute()

# Return all documents across all collections in the database (the entire database)
results = client.pipeline().database().execute()

# Batch read of 3 documents
results = (
    client.pipeline()
    .documents(
        client.collection("cities").document("SF"),
        client.collection("cities").document("DC"),
        client.collection("cities").document("NY"),
    )
    .execute()
)

Wie bei allen anderen Phasen ist die Reihenfolge der Ergebnisse aus diesen Eingabephasen nicht stabil. Wenn eine bestimmte Reihenfolge gewünscht ist, sollte immer ein sort(...)-Operator hinzugefügt werden.

Dabei gilt:

Die where(...)-Phase fungiert als herkömmlicher Filtervorgang für Dokumente, die in der vorherigen Phase generiert wurden, und entspricht weitgehend der vorhandenen „where“-Syntax für vorhandene Abfragen. Alle Dokumente, für die ein bestimmter Ausdruck nicht true ergibt, werden aus den zurückgegebenen Dokumenten herausgefiltert.

Mehrere where(...)-Anweisungen können miteinander verknüpft werden und fungieren als and(...)-Ausdruck. Die folgenden beiden Abfragen sind beispielsweise logisch gleichwertig und können austauschbar verwendet werden.

Web

let results;

results = await execute(db.pipeline().collection("books")
  .where(field("rating").equal(5))
  .where(field("published").lessThan(1900))
);

results = await execute(db.pipeline().collection("books")
  .where(and(field("rating").equal(5), field("published").lessThan(1900)))
);
Swift
var results: Pipeline.Snapshot

results = try await db.pipeline().collection("books")
  .where(Field("rating").equal(5))
  .where(Field("published").lessThan(1900))
  .execute()

results = try await db.pipeline().collection("books")
  .where(Field("rating").equal(5) && Field("published").lessThan(1900))
  .execute()

Kotlin

var results: Task<Pipeline.Snapshot>

results = db.pipeline().collection("books")
    .where(field("rating").equal(5))
    .where(field("published").lessThan(1900))
    .execute()

results = db.pipeline().collection("books")
    .where(Expression.and(field("rating").equal(5),
      field("published").lessThan(1900)))
    .execute()

Java

Task<Pipeline.Snapshot> results;

results = db.pipeline().collection("books")
    .where(field("rating").equal(5))
    .where(field("published").lessThan(1900))
    .execute();

results = db.pipeline().collection("books")
    .where(Expression.and(
        field("rating").equal(5),
        field("published").lessThan(1900)
    ))
    .execute();
Python
from google.cloud.firestore_v1.pipeline_expressions import And, Field

results = (
    client.pipeline()
    .collection("books")
    .where(Field.of("rating").equal(5))
    .where(Field.of("published").less_than(1900))
    .execute()
)

results = (
    client.pipeline()
    .collection("books")
    .where(And(Field.of("rating").equal(5), Field.of("published").less_than(1900)))
    .execute()
)

Auswählen / Felder hinzufügen und entfernen

Mit select(...), add_fields(...) und remove_fields(...) können Sie die Felder ändern, die in einer vorherigen Phase zurückgegeben werden. Diese drei werden im Allgemeinen als Phasen im Projektionsstil bezeichnet.

Mit select(...) und add_fields(...) können Sie das Ergebnis eines Ausdrucks einem vom Nutzer angegebenen Feldnamen zuweisen. Ein Ausdruck, der zu einem Fehler führt, ergibt einen null-Wert. select(...) gibt nur die Dokumente mit den angegebenen Feldnamen zurück, während add_fields(...) das Schema der vorherigen Phase erweitert (wobei Werte mit identischen Feldnamen möglicherweise überschrieben werden).

Mit remove_fields(...) können Sie eine Reihe von Feldern angeben, die aus der vorherigen Phase entfernt werden sollen. Das Angeben von Feldnamen, die nicht vorhanden sind, hat keine Auswirkungen.

Weitere Informationen finden Sie unten im Abschnitt Felder einschränken, die zurückgegeben werden sollen. Im Allgemeinen ist es jedoch hilfreich, eine solche Phase zu verwenden, um das Ergebnis auf die Felder zu beschränken, die im Client benötigt werden, um die Kosten und die Latenz für die meisten Abfragen zu reduzieren.

Aggregieren / Eindeutig

Mit der aggregate(...)-Phase können Sie eine Reihe von Aggregationen für die Eingabedokumente ausführen. Standardmäßig werden alle Dokumente zusammen aggregiert. Sie können jedoch optional ein grouping-Argument angeben, mit dem die Eingabedokumente in verschiedenen Buckets aggregiert werden können.

Web

const results = await execute(db.pipeline()
  .collection("books")
  .aggregate(
    field("rating").average().as("avg_rating")
  )
  .distinct(field("genre"))
);
Swift
let results = try await db.pipeline()
  .collection("books")
  .aggregate([
    Field("rating").average().as("avg_rating")
  ], groups: [
    Field("genre")
  ])
  .execute()

Kotlin

val results = db.pipeline()
    .collection("books")
    .aggregate(
        AggregateStage
            .withAccumulators(AggregateFunction.average("rating").alias("avg_rating"))
            .withGroups(field("genre"))
    )
    .execute()

Java

Task<Pipeline.Snapshot> results = db.pipeline()
    .collection("books")
    .aggregate(AggregateStage
        .withAccumulators(
            AggregateFunction.average("rating").alias("avg_rating"))
        .withGroups(field("genre")))
    .execute();
Python
from google.cloud.firestore_v1.pipeline_expressions import Field

results = (
    client.pipeline()
    .collection("books")
    .aggregate(
        Field.of("rating").average().as_("avg_rating"), groups=[Field.of("genre")]
    )
    .execute()
)

Wenn groupings nicht angegeben ist, wird in dieser Phase nur ein einzelnes Dokument erstellt. Andernfalls wird für jede eindeutige Kombination von groupings-Werten ein Dokument generiert.

Die distinct(...)-Phase ist ein vereinfachter Aggregationsoperator, mit dem nur die eindeutigen groupings ohne Akkumulatoren generiert werden können. In allen anderen Punkten verhält sie sich identisch mit aggregate(...). Hier ein Beispiel:

Web

const results = await execute(db.pipeline()
  .collection("books")
  .distinct(
    field("author").toUpper().as("author"),
    field("genre")
  )
);
Swift
let results = try await db.pipeline()
  .collection("books")
  .distinct([
    Field("author").toUpper().as("author"),
    Field("genre")
  ])
  .execute()

Kotlin

val results = db.pipeline()
    .collection("books")
    .distinct(
        field("author").toUpper().alias("author"),
        field("genre")
    )
    .execute()

Java

Task<Pipeline.Snapshot> results = db.pipeline()
    .collection("books")
    .distinct(
        field("author").toUpper().alias("author"),
        field("genre")
    )
    .execute();
Python
from google.cloud.firestore_v1.pipeline_expressions import Field

results = (
    client.pipeline()
    .collection("books")
    .distinct(Field.of("author").to_upper().as_("author"), "genre")
    .execute()
)

Funktionen

Funktionen sind ein Baustein zum Erstellen von Ausdrücken und komplexen Abfragen. Eine vollständige Liste der Funktionen mit Beispielen finden Sie in der Funktionsreferenz. Zur Erinnerung: Hier ist die Struktur einer typischen Abfrage:

Beispiel für Phasen und Funktionen in einer Abfrage

Viele Phasen akzeptieren Ausdrücke, die eine oder mehrere Funktionen enthalten. Die häufigste Verwendung von Funktionen findet sich in den Phasen where(...) und select(...). Es gibt zwei Haupttypen von Funktionen, mit denen Sie vertraut sein sollten:

Web

let results;

// Type 1: Scalar (for use in non-aggregation stages)
// Example: Return the min store price for each book.
results = await execute(db.pipeline().collection("books")
  .select(field("current").logicalMinimum(field("updated")).as("price_min"))
);

// Type 2: Aggregation (for use in aggregate stages)
// Example: Return the min price of all books.
results = await execute(db.pipeline().collection("books")
  .aggregate(field("price").minimum().as("min_price"))
);
Swift
var results: Pipeline.Snapshot

// Type 1: Scalar (for use in non-aggregation stages)
// Example: Return the min store price for each book.
results = try await db.pipeline().collection("books")
  .select([
    Field("current").logicalMinimum(["updated"]).as("price_min")
  ])
  .execute()

// Type 2: Aggregation (for use in aggregate stages)
// Example: Return the min price of all books.
results = try await db.pipeline().collection("books")
  .aggregate([Field("price").minimum().as("min_price")])
  .execute()

Kotlin

var results: Task<Pipeline.Snapshot>

// Type 1: Scalar (for use in non-aggregation stages)
// Example: Return the min store price for each book.
results = db.pipeline().collection("books")
    .select(
        field("current").logicalMinimum("updated").alias("price_min")
    )
    .execute()

// Type 2: Aggregation (for use in aggregate stages)
// Example: Return the min price of all books.
results = db.pipeline().collection("books")
    .aggregate(AggregateFunction.minimum("price").alias("min_price"))
    .execute()

Java

Task<Pipeline.Snapshot> results;

// Type 1: Scalar (for use in non-aggregation stages)
// Example: Return the min store price for each book.
results = db.pipeline().collection("books")
    .select(
        field("current").logicalMinimum("updated").alias("price_min")
    )
    .execute();

// Type 2: Aggregation (for use in aggregate stages)
// Example: Return the min price of all books.
results = db.pipeline().collection("books")
    .aggregate(AggregateFunction.minimum("price").alias("min_price"))
    .execute();
Python
from google.cloud.firestore_v1.pipeline_expressions import Field

# Type 1: Scalar (for use in non-aggregation stages)
# Example: Return the min store price for each book.
results = (
    client.pipeline()
    .collection("books")
    .select(
        Field.of("current").logical_minimum(Field.of("updated")).as_("price_min")
    )
    .execute()
)

# Type 2: Aggregation (for use in aggregate stages)
# Example: Return the min price of all books.
results = (
    client.pipeline()
    .collection("books")
    .aggregate(Field.of("price").minimum().as_("min_price"))
    .execute()
)

Limits

In den meisten Fällen gibt es in der Enterprise Edition keine Einschränkungen für die Form der Abfrage. Mit anderen Worten: Sie sind nicht auf eine kleine Anzahl von Werten in einer IN- oder OR-Abfrage beschränkt. Stattdessen gibt es zwei Hauptlimits, die Sie beachten sollten:

  • Frist:60 Sekunden (identisch mit der Standard Edition).
  • Arbeitsspeichernutzung:128 MiB-Limit für die Menge der materialisierten Daten während der Abfrageausführung.

Fehler

Es kann verschiedene Gründe geben, warum Abfragen fehlschlagen. Hier finden Sie einen Link zu häufigen Fehlern und den zugehörigen Maßnahmen, die Sie ergreifen können:

Fehlercode Aktion
DEADLINE_EXCEEDED Die Abfrage, die Sie ausführen, überschreitet eine Frist von 60 Sekunden und muss zusätzlich optimiert werden. Tipps finden Sie im Abschnitt zur Leistung. Wenn Sie die Ursache des Problems nicht finden können, wenden Sie sich an das Team.
RESOURCE_EXHAUSTED Die Abfrage, die Sie ausführen, überschreitet die Arbeitsspeicherlimits und muss zusätzlich optimiert werden. Tipps finden Sie im Abschnitt zur Leistung. Wenn Sie die Ursache des Problems nicht finden können, wenden Sie sich an das Team.
INTERNAL Wenden Sie sich an das Team, um Support zu erhalten.

Leistung

Im Gegensatz zu vorhandenen Abfragen ist bei Pipeline-Vorgängen nicht immer ein Index erforderlich. Das bedeutet, dass eine Abfrage eine höhere Latenz aufweisen kann als vorhandene Abfragen, die sofort mit einem FAILED_PRECONDITION-Fehler aufgrund eines fehlenden Index fehlgeschlagen wären. Es gibt einige Schritte, die Sie unternehmen können, um die Leistung von Pipeline-Vorgängen zu verbessern.

Indexe erstellen

Verwendeter Index

Mit Query Explain können Sie feststellen, ob Ihre Abfrage von einem Index verarbeitet wird oder auf einen weniger effizienten Vorgang wie einen Tabellenscan zurückgreift. Wenn Ihre Abfrage nicht vollständig von einem Index verarbeitet wird, können Sie einen Index erstellen. Folgen Sie dazu der Anleitung.

Indexe erstellen

Sie können Indexe gemäß der vorhandenen Dokumentation zur Indexverwaltung erstellen. Bevor Sie einen Index erstellen, sollten Sie sich mit den allgemeinen Best Practices für Indexe in Cloud Firestore vertraut machen. Damit Ihre Abfrage Indexe nutzen kann, folgen Sie den Best Practices, um Indexe mit Feldern in der folgenden Reihenfolge zu erstellen:

  1. Alle Felder, die in Gleichheitsfiltern verwendet werden (in beliebiger Reihenfolge)
  2. Alle Felder, nach denen sortiert wird (in derselben Reihenfolge)
  3. Felder, die in Bereichs- oder Ungleichheitsfiltern verwendet werden, in absteigender Reihenfolge der Selektivität der Abfrageeinschränkung

Für die folgende Abfrage beispielsweise:

Web

const results = await execute(db.pipeline()
  .collection("books")
  .where(field("published").lessThan(1900))
  .where(field("genre").equal("Science Fiction"))
  .where(field("rating").greaterThan(4.3))
  .sort(field("published").descending())
);
Swift
let results = try await db.pipeline()
  .collection("books")
  .where(Field("published").lessThan(1900))
  .where(Field("genre").equal("Science Fiction"))
  .where(Field("rating").greaterThan(4.3))
  .sort([Field("published").descending()])
  .execute()

Kotlin

val results = db.pipeline()
    .collection("books")
    .where(field("published").lessThan(1900))
    .where(field("genre").equal("Science Fiction"))
    .where(field("rating").greaterThan(4.3))
    .sort(field("published").descending())
    .execute()

Java

Task<Pipeline.Snapshot> results = db.pipeline()
    .collection("books")
    .where(field("published").lessThan(1900))
    .where(field("genre").equal("Science Fiction"))
    .where(field("rating").greaterThan(4.3))
    .sort(field("published").descending())
    .execute();
Python
from google.cloud.firestore_v1.pipeline_expressions import Field

results = (
    client.pipeline()
    .collection("books")
    .where(Field.of("published").less_than(1900))
    .where(Field.of("genre").equal("Science Fiction"))
    .where(Field.of("rating").greater_than(4.3))
    .sort(Field.of("published").descending())
    .execute()
)

Der empfohlene Index ist ein Index mit Sammlungsbereich für books für (genre [...], published DESC, avg_rating DESC).

Indexdichte

Cloud Firestore unterstützt Sparse- und Nicht-Sparse-Indexe. Weitere Informationen finden Sie unter Indexdichte.

Abgedeckte Abfragen und sekundäre Indexe

Cloud Firestore kann das Abrufen des vollständigen Dokuments überspringen und nur Ergebnisse aus dem Index zurückgeben, wenn alle zurückgegebenen Felder in einem sekundären Index vorhanden sind. Dies führt in der Regel zu einer erheblichen Verbesserung der Latenz und der Kosten. Hier ein Beispiel für eine Abfrage:

Web

const results = await execute(db.pipeline()
  .collection("books")
  .where(field("category").like("%fantasy%"))
  .where(field("title").exists())
  .where(field("author").exists())
  .select(field("title"), field("author"))
);
Swift
let results = try await db.pipeline()
  .collection("books")
  .where(Field("category").like("%fantasy%"))
  .where(Field("title").exists())
  .where(Field("author").exists())
  .select([Field("title"), Field("author")])
  .execute()

Kotlin

val results = db.pipeline()
    .collection("books")
    .where(field("category").like("%fantasy%"))
    .where(field("title").exists())
    .where(field("author").exists())
    .select(field("title"), field("author"))
    .execute()

Java

Task<Pipeline.Snapshot> results = db.pipeline()
    .collection("books")
    .where(field("category").like("%fantasy%"))
    .where(field("title").exists())
    .where(field("author").exists())
    .select(field("title"), field("author"))
    .execute();
Python
from google.cloud.firestore_v1.pipeline_expressions import Field

results = (
    client.pipeline()
    .collection("books")
    .where(Field.of("category").like("%fantasy%"))
    .where(Field.of("title").exists())
    .where(Field.of("author").exists())
    .select("title", "author")
    .execute()
)

Wenn die Datenbank bereits einen Index mit Sammlungsbereich für books für (category [...], title [...], author [...]) hat, muss nichts aus den Hauptdokumenten abgerufen werden. In diesem Fall spielt die Reihenfolge im Index keine Rolle. Das wird durch [...] angezeigt.

Felder einschränken, die zurückgegeben werden sollen

Standardmäßig gibt eine Cloud Firestore Abfrage alle Felder in einem Dokument zurück, ähnlich wie SELECT * in herkömmlichen Systemen. Wenn Ihre Anwendung jedoch nur eine Teilmenge der Felder benötigt, können Sie die Phasen select(...) oder restrict(...) verwenden, um diese Filterung serverseitig durchzuführen. Dadurch werden sowohl die Antwortgröße (und damit die Kosten für den ausgehenden Netzwerktraffic) als auch die Latenz reduziert.

Tools zur Fehlerbehebung

Query Explain

Mit Query Explain können Sie Ausführungsmesswerte und Details zu den verwendeten Indexen einsehen.

Messwerte

Pipeline-Vorgänge sind vollständig in die vorhandenen Cloud Firestore Messwerte integriert.

Bekannte Probleme und Einschränkungen

Spezialisierte Indexe

Pipeline-Vorgänge unterstützen noch nicht die vorhandenen array-contains & vector Indexe vom Typ. Anstatt solche Abfragen einfach abzulehnen, versucht Cloud Firestore, andere vorhandene ascending & descending-Indexe zu verwenden. Es wird erwartet, dass Pipeline-Vorgänge mit solchen array_contains- oder find_nearest-Ausdrücken während der privaten Vorschau langsamer sind als ihre vorhandenen Entsprechungen.

Seitenumbruch

Die Unterstützung für das einfache Paginieren eines Ergebnissatzes wird während der privaten Vorschau nicht unterstützt. Sie können dies umgehen, indem Sie entsprechende where(...)- und sort(...)-Phasen verketten, wie unten gezeigt.

Web

// Existing pagination via `startAt()`
const q =
  query(collection(db, "cities"), orderBy("population"), startAt(1000000));

// Private preview workaround using pipelines
const pageSize = 2;
const pipeline = db.pipeline()
  .collection("cities")
  .select("name", "population", "__name__")
  .sort(field("population").descending(), field("__name__").ascending());

// Page 1 results
let snapshot = await execute(pipeline.limit(pageSize));

// End of page marker
const lastDoc = snapshot.results[snapshot.results.length - 1];

// Page 2 results
snapshot = await execute(
  pipeline
    .where(
      or(
        and(
          field("population").equal(lastDoc.get("population")),
          field("__name__").greaterThan(lastDoc.ref)
        ),
        field("population").lessThan(lastDoc.get("population"))
      )
    )
    .limit(pageSize)
);
Swift
// Existing pagination via `start(at:)`
let query = db.collection("cities").order(by: "population").start(at: [1000000])

// Private preview workaround using pipelines
let pipeline = db.pipeline()
  .collection("cities")
  .where(Field("population").greaterThanOrEqual(1000000))
  .sort([Field("population").descending()])

Kotlin

// Existing pagination via `startAt()`
val query = db.collection("cities").orderBy("population").startAt(1000000)

// Private preview workaround using pipelines
val pipeline = db.pipeline()
    .collection("cities")
    .where(field("population").greaterThanOrEqual(1000000))
    .sort(field("population").descending())

Java

// Existing pagination via `startAt()`
Query query = db.collection("cities").orderBy("population").startAt(1000000);

// Private preview workaround using pipelines
Pipeline pipeline = db.pipeline()
    .collection("cities")
    .where(field("population").greaterThanOrEqual(1000000))
    .sort(field("population").descending());
Python
from google.cloud.firestore_v1.pipeline_expressions import Field

# Existing pagination via `start_at()`
query = (
    client.collection("cities")
    .order_by("population")
    .start_at({"population": 1_000_000})
)

# Private preview workaround using pipelines
pipeline = (
    client.pipeline()
    .collection("cities")
    .where(Field.of("population").greater_than_or_equal(1_000_000))
    .sort(Field.of("population").descending())
)

Emulator-Unterstützung

Der Emulator unterstützt keine Pipeline-Vorgänge.

Echtzeit- und Offlineunterstützung

Pipeline-Vorgänge bieten noch keine Echtzeit- und Offlinefunktionen.

Nächste Schritte