Pobieranie danych za pomocą operacji potoków Firestore

Tło

Operacje potoku zapewniają nowy interfejs zapytań dla Cloud Firestore, który obsługuje zaawansowane funkcje zapytań i złożone wyrażenia. Wprowadza wiele nowych funkcji, w tym min(...), max(...), substring(...), regex_match(...) i array_contains_all(...), oraz etapy umożliwiające wykonywanie złożonych przekształceń.

Pierwsze kroki

Aby zainstalować i zainicjować pakiety SDK klienta, zapoznaj się z instrukcjami w tych przewodnikach:

Składnia

W sekcjach poniżej znajdziesz omówienie składni operacji potoku.

Pojęcia

Jedną z istotnych różnic w operacjach potoku jest wprowadzenie jawnej kolejności „etapów”. Umożliwia to tworzenie bardziej złożonych zapytań. Jest to jednak znaczące odstępstwo od dotychczasowego interfejsu zapytań, który korzysta z operacji podstawowych, gdzie kolejność etapów była domyślna. Rozważ ten przykład operacji potoku:

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

Zdarzenie inicjujące

Operacje potoku mają bardzo podobną składnię do istniejących zapytań Cloud Firestore. Aby rozpocząć, zainicjuj zapytanie, wpisując:

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

Struktura

Podczas tworzenia operacji potoku warto znać kilka ważnych pojęć: etapy, wyrażenia, funkcje i opakowania podzapytania.

Przykład pokazujący etapy i wyrażenia w zapytaniu

Etapy: potok może składać się z jednego lub kilku etapów. Logicznie reprezentują one serię kroków (lub etapów) wykonywanych w celu wykonania zapytania.

Wyrażenia: etapy często akceptują wyrażenia, co pozwala na tworzenie bardziej złożonych zapytań. Wyrażenie może być proste i składać się z jednej funkcji, np. eq("a", 1). Możesz też tworzyć bardziej złożone wyrażenia, zagnieżdżając je, np. and(eq("a", 1), eq("b", 2)).

Otoki podzapytania: funkcje takie jak array() i scalar() umożliwiają umieszczenie zagnieżdżonego potoku jako wyrażenia na etapie.

Pola / Stałe / Zmienne

Operacje potoku obsługują złożone wyrażenia. Dlatego ważne jest, aby odróżnić, czy wartość reprezentuje pole, stałą czy zmienną.

Pola odnoszą się do danych w dokumentach, a stałe umożliwiają określenie dowolnej wartości jako argumentu wyrażenia. Zmienne pozwalają natomiast definiować i używać wartości tymczasowych, które są ograniczone do wykonywania zapytania, a nie do przetwarzanych dokumentów. Poniżej znajdziesz omówienie tych pojęć. Więcej informacji o odczytywaniu i zapisywaniu zmiennych podczas wykonywania zapytania znajdziesz na etapie let(...).

Pola Stałe Zmienne
Purpose uzyskiwać dostępu do pól w dokumentach ani ich przechowywać; określić stałą wartość, używanie wartości tymczasowych podczas wykonywania potoku
Korzystanie z pakietu SDK field("name") constant("val") variable("name")
Zakres lokalny dla bieżącego dokumentu globalny globalne dla potoku i potoków podrzędnych
Niezdefiniowany plik referencyjny wynosi absent Nie dotyczy generuje błąd środowiska wykonawczego

Przykłady:

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

Etapy

Etapy wejściowe

Etap wejściowy reprezentuje pierwszy etap zapytania. Określa początkowy zestaw dokumentów, do których wysyłasz zapytania. W przypadku operacji potoku jest to w dużej mierze podobne do istniejących zapytań, gdzie większość zapytań zaczyna się od etapu collection(...) lub collection_group(...). Dwa nowe etapy wejściowe to database() i documents(...), gdzie database() umożliwia zwrócenie wszystkich dokumentów w bazie danych, a documents(...) działa identycznie jak odczyt zbiorczy.

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()
)
Go
// 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
}

Podobnie jak w przypadku wszystkich innych etapów kolejność wyników z tych etapów wejściowych nie jest stabilna. Jeśli wymagana jest określona kolejność, zawsze należy dodać operator sort(...).

Gdzie

Etap where(...) działa jak standardowa operacja filtrowania dokumentów wygenerowanych na poprzednim etapie i w większości odzwierciedla istniejącą składnię „where” w przypadku istniejących zapytań. Każdy dokument, w którym dane wyrażenie ma wartość inną niż true, jest odfiltrowywany ze zwracanych dokumentów.

Można połączyć kilka instrukcji where(...), które będą działać jako wyrażenie and(...). Na przykład te 2 zapytania są logicznie równoważne i można ich używać zamiennie.

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

Wybieranie, dodawanie i usuwanie pól

select(...), add_fields(...) i remove_fields(...) umożliwiają modyfikowanie pól zwracanych z poprzedniego etapu. Te 3 sceny są ogólnie określane jako sceny projekcyjne.

Funkcje select(...) i add_fields(...) umożliwiają określenie wyniku wyrażenia w podanej przez użytkownika nazwie pola. select(...) zwraca tylko dokumenty z określonymi nazwami pól, a add_fields(...) rozszerza schemat poprzedniego etapu (potencjalnie zastępując wartości o identycznych nazwach pól).

remove_fields(...) umożliwia określenie zbioru pól, które mają zostać usunięte z poprzedniego etapu. Podanie nazw pól, które nie istnieją, nie powoduje żadnego działania.

Zobacz sekcję Ograniczanie zwracanych pól poniżej, ale ogólnie użycie takiego etapu do ograniczenia wyniku tylko do pól potrzebnych w kliencie pomaga zmniejszyć koszty i czas oczekiwania w przypadku większości zapytań.

Zbiorcze / niepowtarzalne

Etap aggregate(...) umożliwia wykonanie serii agregacji na dokumentach wejściowych. Domyślnie wszystkie dokumenty są agregowane razem, ale można podać opcjonalny argument grouping, który umożliwia agregowanie dokumentów wejściowych w różnych zasobnikach.

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

Jeśli nie podasz wartości groupings, ten etap wygeneruje tylko 1 dokument. W przeciwnym razie zostanie wygenerowany dokument dla każdej unikalnej kombinacji wartości groupings.

Etap distinct(...) to uproszczony operator agregacji, który umożliwia generowanie tylko unikalnych wartości groupings bez żadnych akumulatorów. Pod wszystkimi innymi względami działa identycznie jak aggregate(...). Poniższy przykład pokazuje:

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

Funkcje

Funkcje są elementem składowym do tworzenia wyrażeń i złożonych zapytań. Pełną listę funkcji wraz z przykładami znajdziesz w dokumentacji funkcji. Przypomnijmy strukturę typowego zapytania:

Przykład pokazujący etapy i funkcje w zapytaniu

Wiele etapów akceptuje wyrażenia zawierające co najmniej 1 funkcję. Najczęstsze zastosowanie funkcji można znaleźć na etapach where(...) i select(...). Istnieją 2 główne typy funkcji, które warto znać:

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()
)
Go
// 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
}

Limity

W większości przypadków wersja Enterprise nie nakłada ograniczeń na kształt zapytania. Innymi słowy, nie musisz ograniczać się do niewielkiej liczby wartości w zapytaniu IN lub OR. Zamiast tego musisz pamiętać o 2 głównych ograniczeniach:

  • Termin: 60 sekund (identycznie jak w wersji Standard).
  • Wykorzystanie pamięci: limit 128 MiB na ilość danych zmaterializowanych podczas wykonywania zapytania.

Błędy

Zapytania mogą się nie powieść z różnych powodów. Oto link do typowych błędów i powiązanych z nimi działań, które możesz podjąć:

Kod błędu Czynność
DEADLINE_EXCEEDED Wykonywane zapytanie przekracza 60-sekundowy limit czasu i wymaga dodatkowej optymalizacji. Wskazówki znajdziesz w sekcji dotyczącej skuteczności. Jeśli nie możesz znaleźć głównej przyczyny problemu, skontaktuj się z zespołem.
RESOURCE_EXHAUSTED Wykonywane zapytanie przekracza limity pamięci i wymaga dodatkowej optymalizacji. Wskazówki znajdziesz w sekcji dotyczącej skuteczności. Jeśli nie możesz znaleźć głównej przyczyny problemu, skontaktuj się z zespołem.
INTERNAL Skontaktuj się z zespołem, aby uzyskać pomoc.

Wydajność

Bazy danych w wersji Enterprise nie wymagają, aby indeks był zawsze obecny. Oznacza to, że zapytanie może mieć większe opóźnienie w porównaniu z dotychczasowymi zapytaniami, które natychmiast kończyły się błędem FAILED_PRECONDITION dotyczącym braku indeksu. Aby zwiększyć wydajność operacji potoku, możesz wykonać kilka czynności.

Tworzenie indeksów

Użyty indeks

Dzięki funkcji wyjaśniania zapytania możesz sprawdzić, czy zapytanie jest obsługiwane przez indeks, czy też wraca do mniej wydajnej operacji, takiej jak skanowanie tabeli. Jeśli zapytanie nie jest w pełni obsługiwane przez indeks, możesz utworzyć indeks, postępując zgodnie z instrukcjami.

Tworzenie indeksów

Aby utworzyć indeksy, możesz postępować zgodnie z istniejącą dokumentacją dotyczącą zarządzania indeksami. Zanim utworzysz indeks, zapoznaj się z ogólnymi sprawdzonymi metodami dotyczącymi indeksów w Cloud Firestore. Aby mieć pewność, że zapytanie może korzystać z indeksów, postępuj zgodnie ze sprawdzonymi metodami tworzenia indeksów z polami w tej kolejności:

  1. Wszystkie pola, które będą używane w filtrach równości (w dowolnej kolejności).
  2. Wszystkie pola, według których będzie przeprowadzane sortowanie (w tej samej kolejności)
  3. Pola, które będą używane w filtrach zakresu lub nierówności, w kolejności malejącej według selektywności ograniczeń zapytania.

Na przykład w przypadku tego zapytania:

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

Zalecany indeks to indeks zakresu kolekcji w books dla (genre [...], published DESC, avg_rating DESC).

Gęstość indeksu

Cloud Firestore obsługuje indeksy rozproszone i nierozproszone. Więcej informacji znajdziesz w artykule na temat gęstości indeksu.

Zapytania pokryte + indeksy dodatkowe

Cloud Firestore może pominąć pobieranie pełnego dokumentu i zwrócić wyniki z indeksu, jeśli wszystkie zwracane pola znajdują się w indeksie pomocniczym. Zwykle prowadzi to do znacznego skrócenia czasu oczekiwania (i obniżenia kosztów). Użyj tego przykładowego zapytania:

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

Jeśli baza danych ma już indeks zakresu kolekcji w polu books dla (category [...], title [...], author [...]), może uniknąć pobierania danych z głównych dokumentów. W takim przypadku kolejność w indeksie nie ma znaczenia, co jest oznaczone symbolem [...].

Ograniczanie pól do zwrócenia

Domyślnie zapytanie Cloud Firestore zwraca wszystkie pola w dokumencie, podobnie jak SELECT * w systemach relacyjnych. Jeśli jednak aplikacja potrzebuje tylko podzbioru pól, możesz użyć etapów select(...) lub restrict(...), aby przenieść to filtrowanie po stronie serwera. Zmniejszy to rozmiar odpowiedzi (co obniży koszty ruchu wychodzącego z sieci) i poprawi czas oczekiwania.

Narzędzia rozwiązywania problemów

Wyjaśnienie zapytania

Funkcja Query Explain umożliwia wyświetlanie wskaźników wykonywania i szczegółów dotyczących używanych indeksów.

Dane

Operacje potoku są w pełni zintegrowane z dotychczasowymi wskaźnikami Cloud Firestore.

Znane problemy i ograniczenia

Indeksy specjalistyczne

Operacje potoku nie obsługują jeszcze istniejących typów indeksów array-contains i vector. Zamiast odrzucać takie zapytania, Cloud Firestore spróbuje użyć innych istniejących indeksów ascending i descending. Z tego powodu wyrażenia array_contains lub find_nearest będą działać wolniej niż ich dotychczasowe odpowiedniki.

Obsługa w czasie rzeczywistym i offline

Operacje potoku nie mają funkcji czasu rzeczywistego ani funkcji offline.

Co dalej?