Recuperare i dati con le operazioni della pipeline Firestore

Contesto

Le operazioni di pipeline forniscono una nuova interfaccia di query per Cloud Firestore che supporta funzionalità di query avanzate ed espressioni complesse. Introduce molte nuove funzioni, tra cui min(...), max(...), substring(...), regex_match(...) e array_contains_all(...), e fasi per poter eseguire trasformazioni complesse.

Per iniziare

Per installare e inizializzare gli SDK client, consulta le istruzioni nelle seguenti guide:

Sintassi

Le sezioni seguenti forniscono una panoramica della sintassi per le operazioni della pipeline.

Concetti

Una differenza notevole rispetto alle operazioni della pipeline è l'introduzione di un ordine esplicito delle "fasi". In questo modo è possibile esprimere query più complesse. Tuttavia, si tratta di una deviazione significativa dall'interfaccia di query esistente che utilizza le operazioni di base in cui l'ordine delle fasi era implicito. Considera il seguente esempio di operazioni della pipeline:

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)
)
Vai
pipeline := client.Pipeline().
	Collection("cities").
	Where(firestore.FieldOf("population").GreaterThan(100000)).
	Sort(firestore.Orders(firestore.Ascending(firestore.FieldOf("name")))).
	Limit(10)

Inizializzazione

Le operazioni della pipeline hanno una sintassi molto simile a quella delle query Cloud Firestore esistenti. Per iniziare, inizializza una query scrivendo quanto segue:

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()
Vai
client, err := firestore.NewClientWithDatabase(ctx, projectID, databaseID)
if err != nil {
	fmt.Fprintf(w, "firestore.NewClientWithDatabase failed: %v", err)
	return err
}
defer client.Close()
pipeline := client.Pipeline().Collection("books")

Struttura

Ci sono alcuni termini importanti da comprendere quando si creano operazioni di pipeline: fasi, espressioni e funzioni e wrapper di subquery.

Esempio che mostra le fasi e le espressioni in una query

Fasi: una pipeline può essere composta da una o più fasi. Logicamente, rappresentano la serie di passaggi (o fasi) eseguiti per eseguire la query.

Espressioni: le fasi spesso accettano un'espressione che consente di esprimere query più complesse. L'espressione può essere semplice e costituita da una singola funzione come eq("a", 1). Puoi anche esprimere espressioni più complesse nidificando espressioni come and(eq("a", 1), eq("b", 2)).

Wrapper di subquery: funzioni come array() e scalar() consentono di incorporare una pipeline nidificata come espressione all'interno di una fase.

Campi / Costanti / Variabili

Le operazioni della pipeline supportano espressioni complesse. Pertanto, è importante distinguere se un valore rappresenta un campo, una costante o una variabile.

Mentre i campi fanno riferimento ai dati all'interno dei documenti e le costanti consentono di specificare qualsiasi valore come argomento di un'espressione, le variabili consentono di definire e utilizzare valori temporanei che hanno come ambito l'esecuzione della query anziché i documenti in fase di elaborazione. Di seguito viene fornita una panoramica di questi concetti. Per saperne di più su come leggere e scrivere variabili durante l'esecuzione delle query, consulta la fase let(...).

Campi Costanti Variabili
Purpose accedere ai campi o archiviarli nei documenti specificare un valore fisso utilizzare valori temporanei durante l'esecuzione della pipeline
Utilizzo dell'SDK field("name") constant("val") variable("name")
Ambito locale rispetto al documento corrente globale globali per la pipeline e le pipeline secondarie
Undefined Reference restituisce absent N/D genera un errore di runtime

Esempi:

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")))
)
Vai
pipeline := client.Pipeline().Collection("cities").
	Where(firestore.FieldOf("name").Equal(firestore.ConstantOf("Toronto")))

Fasi

Fasi di input

La fase di input rappresenta la prima fase di una query. Definisce l'insieme iniziale di documenti su cui stai eseguendo la query. Per le operazioni di pipeline, questo è in gran parte simile alle query esistenti, in cui la maggior parte delle query inizia con una fase collection(...) o collection_group(...). Le due nuove fasi di input sono database() e documents(...), dove database() consente di restituire tutti i documenti nel database, mentre documents(...) agisce in modo identico a una lettura batch.

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()
)
Vai
// Return all restaurants in San Francisco
results1, err := client.Pipeline().Collection("cities/sf/restaurants").Execute(ctx).Results().GetAll()
if err != nil {
	fmt.Fprintf(w, "GetAll failed: %v", err)
	return err
}

// Return all restaurants
results2, err := client.Pipeline().CollectionGroup("restaurants").Execute(ctx).Results().GetAll()
if err != nil {
	fmt.Fprintf(w, "GetAll failed: %v", err)
	return err
}

// Return all documents across all collections in the database (the entire database)
results3, err := client.Pipeline().Database().Execute(ctx).Results().GetAll()
if err != nil {
	fmt.Fprintf(w, "GetAll failed: %v", err)
	return err
}

// Batch read of 3 documents
results4, err := client.Pipeline().
	Documents([]*firestore.DocumentRef{
		client.Collection("cities").Doc("SF"),
		client.Collection("cities").Doc("DC"),
		client.Collection("cities").Doc("NY"),
	}).
	Execute(ctx).Results().GetAll()
if err != nil {
	fmt.Fprintf(w, "GetAll failed: %v", err)
	return err
}

Come per tutte le altre fasi, l'ordine dei risultati di queste fasi di input non è stabile. Se è richiesto un ordinamento specifico, è necessario aggiungere sempre un operatore sort(...).

Dove

La fase where(...) funge da operazione di filtro standard sui documenti generati dalla fase precedente e rispecchia per lo più la sintassi "where" esistente per le query esistenti. Qualsiasi documento in cui una determinata espressione restituisce un valore diverso da true viene filtrato dai documenti restituiti.

È possibile concatenare più istruzioni where(...), che fungono da espressione and(...). Ad esempio, le seguenti due query sono logicamente equivalenti e possono essere utilizzate in modo intercambiabile.

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()
)
Vai
results1, err := client.Pipeline().
	Collection("books").
	Where(firestore.FieldOf("rating").Equal(5)).
	Where(firestore.FieldOf("published").LessThan(1900)).
	Execute(ctx).Results().GetAll()
if err != nil {
	fmt.Fprintf(w, "GetAll failed: %v", err)
	return err
}

results2, err := client.Pipeline().
	Collection("books").
	Where(firestore.And(
		firestore.FieldOf("rating").Equal(5),
		firestore.FieldOf("published").LessThan(1900),
	)).
	Execute(ctx).Results().GetAll()
if err != nil {
	fmt.Fprintf(w, "GetAll failed: %v", err)
	return err
}

Seleziona / Aggiungi e rimuovi campi

select(...), add_fields(...) e remove_fields(...) consentono di modificare i campi restituiti da una fase precedente. Queste tre fasi sono generalmente chiamate fasi di tipo proiezione.

select(...) e add_fields(...) consentono di specificare il risultato di un'espressione in un nome di campo fornito dall'utente. select(...) restituirà solo i documenti con i nomi dei campi specificati, mentre add_fields(...) estende lo schema della fase precedente (potenzialmente sovrascrivendo i valori con nomi di campo identici).

remove_fields(...) consente di specificare un insieme di campi da rimuovere dalla fase precedente. La specifica di nomi di campi che non esistono è un'operazione senza effetto.

Consulta la sezione Limita i campi da restituire di seguito, ma in generale l'utilizzo di una fase di questo tipo per limitare il risultato ai soli campi necessari nel client è utile per ridurre i costi e la latenza per la maggior parte delle query.

Aggregazione / Distinct

La fase aggregate(...) consente di eseguire una serie di aggregazioni sui documenti di input. Per impostazione predefinita, tutti i documenti vengono aggregati, ma è possibile fornire un argomento grouping facoltativo, che consente di aggregare i documenti di input in bucket diversi.

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()
)
Vai
snapshot := client.Pipeline().
	Collection("books").
	Aggregate(
		firestore.Accumulators(firestore.Average("rating").As("avg_rating")),
		firestore.WithAggregateGroups("genre"),
	).
	Execute(ctx)

Se groupings non è specificato, questa fase produrrà un solo documento, altrimenti verrà generato un documento per ogni combinazione univoca di valori groupings.

La fase distinct(...) è un operatore di aggregazione semplificato che consente di generare solo groupings univoci senza accumulatori. Si comporta in modo identico a quello di aggregate(...) sotto tutti gli altri aspetti. L'esempio seguente mostra:

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()
)
Vai
snapshot := client.Pipeline().
	Collection("books").
	Distinct(firestore.Fields(
		firestore.ToUpper(firestore.FieldOf("author")).As("author"),
		firestore.FieldOf("genre"),
	)).
	Execute(ctx)

Functions

Le funzioni sono un elemento di base per la creazione di espressioni e query complesse. Per un elenco completo delle funzioni con esempi, consulta il riferimento alle funzioni. Ecco un breve riepilogo della struttura di una query tipica:

Esempio che mostra le fasi e le funzioni di una query

Molte fasi accettano espressioni che contengono una o più funzioni. L'utilizzo più comune delle funzioni si trova nelle fasi where(...) e select(...). Esistono due tipi principali di funzioni che dovresti conoscere:

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()
)
Vai
// Type 1: Scalar (for use in non-aggregation stages)
// Example: Return the min store price for each book.
results1, err := client.Pipeline().
	Collection("books").
	Select(firestore.Fields(
		firestore.LogicalMinimum(firestore.FieldOf("current"), firestore.FieldOf("updated")).As("price_min"),
	)).
	Execute(ctx).Results().GetAll()
if err != nil {
	fmt.Fprintf(w, "GetAll failed: %v", err)
	return err
}

// Type 2: Aggregation (for use in aggregate stages)
// Example: Return the min price of all books.
results2, err := client.Pipeline().
	Collection("books").
	Aggregate(firestore.Accumulators(
		firestore.Minimum("price").As("min_price"),
	)).
	Execute(ctx).Results().GetAll()
if err != nil {
	fmt.Fprintf(w, "GetAll failed: %v", err)
	return err
}

Limiti

Per la maggior parte, la versione Enterprise non impone limiti alla forma della query. In altre parole, non hai un limite di valori ridotto in una query IN o OR. Esistono invece due limiti principali di cui devi tenere conto:

  • Scadenza: 60 secondi (identica alla versione Standard).
  • Memoria utilizzata: limite di 128 MiB per la quantità di dati materializzati durante l'esecuzione della query.

Errori

Potresti riscontrare query non riuscite per diversi motivi. Ecco un link agli errori comuni e alle azioni associate che puoi intraprendere:

Codice di errore Azione
DEADLINE_EXCEEDED La query che stai eseguendo supera una scadenza di 60 secondi e richiede un'ulteriore ottimizzazione. Per suggerimenti, consulta la sezione sul rendimento. Se non riesci a individuare la causa principale del problema, contatta il team.
RESOURCE_EXHAUSTED La query che stai eseguendo supera i limiti di memoria e richiede un'ulteriore ottimizzazione. Per suggerimenti, consulta la sezione sul rendimento. Se non riesci a individuare la causa principale del problema, contatta il team.
INTERNAL Contatta il team per ricevere assistenza.

Prestazioni

I database della versione Enterprise non richiedono che sia sempre presente un indice. Ciò significa che una query può mostrare una latenza maggiore rispetto alle query esistenti, che avrebbero semplicemente restituito un errore di indice mancante FAILED_PRECONDITION. Per migliorare le prestazioni delle operazioni della pipeline, puoi seguire un paio di passaggi.

Crea indici

Indice utilizzato

La funzionalità Query Explain ti consente di identificare se la query viene gestita da un indice o se viene eseguita un'operazione meno efficiente, come una scansione della tabella. Se la query non viene gestita completamente da un indice, puoi creare un indice seguendo le istruzioni.

Creazione di indici

Per creare gli indici, puoi seguire la documentazione esistente sulla gestione degli indici. Prima di creare un indice, consulta le best practice generali per gli indici in Cloud Firestore. Per assicurarti che la query possa sfruttare gli indici, segui le best practice per creare indici con i campi nel seguente ordine:

  1. Tutti i campi che verranno utilizzati nei filtri di uguaglianza (in qualsiasi ordine)
  2. Tutti i campi in base ai quali verrà eseguito l'ordinamento (nello stesso ordine)
  3. Campi che verranno utilizzati nei filtri di intervallo o di disuguaglianza in ordine decrescente di selettività dei vincoli della query

Ad esempio, per la seguente query:

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()
)
Vai
snapshot := client.Pipeline().
	Collection("books").
	Where(firestore.FieldOf("published").LessThan(1900)).
	Where(firestore.FieldOf("genre").Equal("Science Fiction")).
	Where(firestore.FieldOf("rating").GreaterThan(4.3)).
	Sort(firestore.Orders(firestore.Descending(firestore.FieldOf("published")))).
	Execute(ctx)

L'indice consigliato è un indice con ambito della raccolta su books per (genre [...], published DESC, avg_rating DESC).

Densità dell'indice

Cloud Firestore supporta indici sparsi e non sparsi. Per saperne di più, consulta Densità dell'indice.

Query coperte + indici secondari

Cloud Firestore può saltare il recupero del documento completo e restituire solo i risultati dall'indice se tutti i campi restituiti sono presenti in un indice secondario. In genere, questo porta a un miglioramento significativo della latenza (e dei costi). Utilizzando la query di esempio riportata di seguito:

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()
)
Vai
snapshot := client.Pipeline().
	Collection("books").
	Where(firestore.FieldOf("category").Like("%fantasy%")).
	Where(firestore.FieldOf("title").FieldExists()).
	Where(firestore.FieldOf("author").FieldExists()).
	Select(firestore.Fields("title", "author")).
	Execute(ctx)

Se il database ha già un indice con ambito raccolta su books per (category [...], title [...], author [...]), può evitare di recuperare qualsiasi elemento dai documenti principali stessi. In questo caso, l'ordine nell'indice non è importante e [...] viene utilizzato per indicarlo.

Limita i campi da restituire

Per impostazione predefinita, una query Cloud Firestore restituisce tutti i campi di un documento, in modo analogo a un SELECT * nei sistemi relazionali. Tuttavia, se la tua applicazione ha bisogno solo di un sottoinsieme dei campi, puoi utilizzare le fasi select(...) o restrict(...) per eseguire questo filtro lato server. In questo modo, ridurrai le dimensioni della risposta (diminuendo il costo del traffico in uscita dalla rete) e migliorerai la latenza.

Strumenti per la risoluzione dei problemi

Spiegazione query

Query Explain ti consente di visualizzare le metriche di esecuzione e i dettagli sugli indici utilizzati.

Metriche

Le operazioni della pipeline sono completamente integrate con le metriche Cloud Firestore esistenti.

Problemi noti / limitazioni

Indici specializzati

Le operazioni della pipeline non supportano ancora i tipi di indice array-contains e vector esistenti. Anziché rifiutare semplicemente queste query, Cloud Firestore tenterà di utilizzare altri indici ascending e descending esistenti. Di conseguenza, si prevede che con queste espressioni array_contains o find_nearest saranno più lente rispetto ai loro equivalenti esistenti.

Supporto in tempo reale e offline

Le operazioni della pipeline non hanno funzionalità in tempo reale e offline.

Passaggi successivi