Erste Schritte mit dem Firestore-Pipelines-Vorgang

Hintergrund

Pipeline Queries ist eine neue Abfrageschnittstelle für Firestore. Sie bietet erweiterte Abfragefunktionen, einschließlich komplexer Ausdrücke. Außerdem werden viele neue Funktionen wie min, max, substring, regex_match und array_contains_all unterstützt. Bei Pipeline-Abfragen ist die Indexerstellung ebenfalls optional, was die Entwicklung neuer Abfragen vereinfacht. Bei Pipeline-Abfragen fallen auch viele Einschränkungen für die Abfrageform weg. So können Sie große in- oder or-Abfragen angeben.

Einstieg

Eine Anleitung zum Installieren und Initialisieren von Client-SDKs finden Sie im Leitfaden für die ersten Schritte.

Syntax

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

Konzepte

Ein wichtiger Unterschied zu Pipeline-Abfragen ist die Einführung einer expliziten Reihenfolge von Phasen. So lassen sich komplexere Abfragen formulieren. Dies ist jedoch eine erhebliche Abweichung von der vorhandenen Abfrageschnittstelle, in der die Reihenfolge der Phasen impliziert wurde. Betrachten Sie das folgende Beispiel für eine Pipeline-Abfrage:

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-Abfragen haben eine sehr vertraute Syntax, die von vorhandenen Cloud Firestore-Abfragen stammt. Geben Sie dazu Folgendes ein:

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-Abfragen 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 gesehen stellen sie die Abfolge der Schritte (oder Phasen) dar, die zum Ausführen der Abfrage erforderlich sind. Hinweis: In der Praxis können Phasen zur Leistungssteigerung in beliebiger Reihenfolge ausgeführt werden. Dies ändert jedoch nichts an der Intention oder Richtigkeit der Anfrage.

Ausdrücke:In vielen Phasen ist ein Ausdruck zulässig, mit dem Sie komplexere Abfragen formulieren können. Der Ausdruck kann 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 konstante Referenzen

Pipelineabfragen unterstützen komplexe Ausdrücke. Daher kann es erforderlich sein, 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 ursprüngliche Gruppe von Dokumenten, die Sie abfragen. Bei Pipeline-Abfragen ist das weitgehend ähnlich wie bei vorhandenen Abfragen, bei denen die meisten Abfragen entweder mit einer collection(...)- oder einer collection_group(...)-Phase beginnen. Es gibt zwei neue Eingabephasen: database() und documents(...). Mit database() können alle Dokumente in der Datenbank zurückgegeben werden, während documents(...) wie ein Batchlesevorgang 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. Ein sort(...)-Operator sollte immer hinzugefügt werden, wenn eine bestimmte Reihenfolge gewünscht ist.

WHERE

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

Mehrere where(...)-Anweisungen können verkettet werden und als and(...)-Ausdruck fungieren. 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()
)

Felder auswählen / hinzufügen und entfernen

Mit select(...), add_fields(...) und remove_fields(...) können Sie die Felder ändern, die in einer vorherigen Phase zurückgegeben wurden. Diese drei werden im Allgemeinen als Projektionsbühnen 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 den Wert null. Mit select(...) werden nur die Dokumente mit den angegebenen Feldnamen zurückgegeben, während add_fields(...) das Schema der vorherigen Phase erweitert (und möglicherweise Werte mit identischen Feldnamen überschreibt).

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

Weitere Informationen finden Sie unten im Abschnitt Zurückzugebende Felder einschränken. Im Allgemeinen ist es jedoch hilfreich, eine solche Phase zu verwenden, um das Ergebnis auf die im Client benötigten Felder zu beschränken. So lassen sich die Kosten und die Latenz für die meisten Anfragen reduzieren.

Zusammenfassen / Eindeutig

In der Phase aggregate(...) können Sie eine Reihe von Aggregationen für die Eingabedokumente ausführen. Standardmäßig werden alle Dokumente zusammengefasst. Sie können aber auch ein optionales grouping-Argument angeben, damit die Eingabedokumente in verschiedenen Buckets zusammengefasst werden.

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 Phase distinct(...) ist ein vereinfachter Aggregationsoperator, mit dem nur die eindeutigen groupings ohne Akkumulatoren generiert werden können. In allen anderen Punkten verhält es sich identisch mit aggregate(...). Ein Beispiel sehen Sie unten:

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: So sieht die Struktur einer typischen Anfrage aus:

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 sich vertraut machen 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 der Enterprise Edition gibt es in den meisten Fällen keine Einschränkungen hinsichtlich der Form der Abfrage. Das heißt, Sie sind bei einer IN- oder OR-Abfrage nicht auf eine kleine Anzahl von Werten beschränkt. Stattdessen gibt es zwei primäre Einschränkungen, die Sie beachten sollten:

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

Fehler

Es kann verschiedene Gründe für fehlgeschlagene Anfragen geben. 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 Ausführung der Abfrage dauert länger als 60 Sekunden und erfordert eine zusätzliche Optimierung. Tipps finden Sie im Abschnitt „Leistung“. Wenn Sie die Ursache des Problems nicht ermitteln können, wenden Sie sich an das Team.
RESOURCE_EXHAUSTED Die von Ihnen ausgeführte Abfrage überschreitet die Arbeitsspeicherlimits und muss zusätzlich optimiert werden. Tipps finden Sie im Abschnitt „Leistung“. Wenn Sie die Ursache des Problems nicht ermitteln können, wenden Sie sich an das Team.
INTERNAL Wenden Sie sich an das Team, um Unterstützung zu erhalten.

Leistung

Im Gegensatz zu vorhandenen Abfragen ist bei Pipeline-Abfragen 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 für fehlenden Index fehlgeschlagen wären. Es gibt einige Schritte, die Sie unternehmen können, um die Leistung von Pipeline-Abfragen zu verbessern.

Indexe erstellen

Verwendeter Index

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

Indexe erstellen

Sie können Indexe anhand der vorhandenen Dokumentation zur Indexverwaltung erstellen. Bevor Sie einen Index erstellen, sollten Sie sich mit den allgemeinen Best Practices für Firestore-Indizes vertraut machen. Damit Ihre Abfrage Indexe nutzen kann, sollten Sie die Best Practices zum Erstellen von Indexen mit Feldern in der folgenden Reihenfolge beachten:

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

Beispiel:

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 Sammlungsumfang für books für (genre [...], published DESC, avg_rating DESC)..

Indexdichte

Cloud Firestore unterstützt sowohl spärliche als auch nicht spärliche Indexe. Weitere Informationen finden Sie unter Indexdichte.

Abgedeckte Suchanfragen + sekundäre Indexe

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. Das führt in der Regel zu einer deutlichen Verbesserung der Latenz und der Kosten. Beispielabfrage:

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 für den 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 [...] angegeben.

Zurückzugebende Felder einschränken

Standardmäßig gibt eine 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 Filterung mithilfe der Phasen select(...) oder restrict(...) serverseitig durchführen. Dadurch wird sowohl die Antwortgröße (und damit die Kosten für ausgehenden Traffic) als auch die Latenz verringert.

Tools zur Fehlerbehebung

Query Explain

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

Messwerte

Pipeline-Abfragen sind vollständig in die vorhandenen Firestore-Messwerte integriert.

Bekannte Probleme / Einschränkungen

Spezialisierte Indexe

Für Pipelineabfragen werden die vorhandenen array-contains- und vector-Indextypen noch nicht unterstützt. Anstatt solche Anfragen einfach abzulehnen, versucht Firestore, andere vorhandene ascending- und descending-Indexe zu verwenden. Es wird erwartet, dass Pipeline-Abfragen mit solchen array_contains- oder find_nearest-Ausdrücken während der privaten Vorschau langsamer sind als ihre vorhandenen Entsprechungen.

Seitenumbruch

Die einfache Paginierung eines Ergebnissatzes wird während der privaten Vorschau nicht unterstützt. Als Behelfslösung können Sie entsprechende where(...)- und sort(...)-Phasen verketten, wie unten dargestellt.

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())
)

Emulatorunterstützung

Der Emulator unterstützt noch keine Pipeline-Abfragen.

Echtzeit- und Offlinesupport

Pipelineabfragen haben noch keine Echtzeit- und Offlinefunktionen.

Nächste Schritte