Tło
Operacje na potokach to nowy interfejs zapytań dla usługi Cloud Firestore. Ten interfejs zapewnia zaawansowane funkcje zapytań, w tym złożone wyrażenia. Wersja Firestore Enterprise obsługuje łączenia w stylu relacyjnym za pomocą powiązanych podzapytań. W przeciwieństwie do wielu baz danych NoSQL, które często wymagają denormalizacji danych lub wykonywania wielu żądań po stronie klienta, podzapytania umożliwiają łączenie i agregowanie danych z powiązanych kolekcji lub podkolekcji bezpośrednio na serwerze.
Podzapytania to wyrażenia, które wykonują zagnieżdżony potok dla każdego dokumentu przetwarzanego przez zapytanie zewnętrzne. Umożliwia to złożone wzorce pobierania danych, takie jak pobieranie dokumentu wraz z powiązanymi elementami podkolekcji lub łączenie logicznie powiązanych danych z różnych kolekcji głównych.
Pojęcia
W tej sekcji przedstawiamy podstawowe koncepcje związane z używaniem podzapytań do wykonywania złączeń w operacjach potoku.
Podzapytania jako wyrażenia
Podzapytanie nie jest etapem najwyższego poziomu, ale wyrażeniem, którego można używać na dowolnym etapie akceptującym wyrażenia, np. select(...), add_fields(...), where(...) lub sort(...).
Cloud Firestore obsługuje 3 typy zapytań podrzędnych:
- Podzapytania w tablicach: materializuj cały zbiór wyników podzapytania jako tablicę dokumentów.
- Podzapytania skalarne: zwracają pojedynczą wartość, np. liczbę, średnią lub konkretne pole z powiązanego dokumentu.
subcollection(...)Podzapytania: uproszczone złączenia w relacji nadrzędny-podrzędny typu jeden do wielu.
Zakres i zmienne
Podczas pisania złączenia zagnieżdżone podzapytanie często musi odwoływać się do pól z dokumentu „zewnętrznego” (nadrzędnego). Aby połączyć te zakresy, użyj etapu let(...) (w niektórych pakietach SDK oznaczanego jako define(...)), aby zdefiniować zmienne w zakresie nadrzędnym, do których można się odwoływać w podzapytaniu za pomocą funkcji variable(...).
Składnia
W sekcjach poniżej znajdziesz omówienie składni wykonywania złączeń.
Etap let(...)
Etap let(...) (w niektórych pakietach SDK określany jako define(...)) to etap bez filtrowania, który jawnie przenosi dane z zakresu nadrzędnego do nazwanej zmiennej, aby można było ich używać w kolejnych zagnieżdżonych zakresach.
Web
async function defineStageData() {
await setDoc(doc(collection(db, "Authors"), "author_123"), {
"id": "author_123",
"name": "Jane Austen"
});
}
Swift
func defineStageData() async throws { try await db.collection("authors").document("author_123").setData([ "id": "author_123", "name": "Jane Austen" ]) }
Kotlin
fun defineStageData() { val author = hashMapOf( "id" to "author_123", "name" to "Jane Austen", ) db.collection("Authors").document("author_123").set(author) }
Java
public void defineStageData() { Map<String, Object> author = new HashMap<>(); author.put("id", "author_123"); author.put("name", "Jane Austen"); db.collection("Authors").document("author_123").set(author); }
Podzapytania tablicowe
Podzapytanie tablicowe to szczególny przypadek podzapytania wyrażenia, które materializuje cały zbiór wyników podzapytania w tablicy. Jeśli podzapytanie zwraca 0 wierszy, jest ono traktowane jako pusta tablica. Nigdy nie zwraca tablicy null. Takie zapytania są przydatne, gdy w wyniku końcowym wymagane są pełne wyniki, np. podczas materializowania zagnieżdżonej lub powiązanej kolekcji.
Zapytania mogą filtrować, sortować i agregować dane w podzapytaniu, aby zmniejszyć ilość danych, które trzeba pobrać i zwrócić, co pomaga obniżyć koszt zapytania. Kolejność podzapytania jest zachowywana, co oznacza, że etap sort(...) w podzapytaniu kontroluje kolejność wyników w tablicy końcowej.
Użyj otoki pakietu SDK toArrayExpression(), aby przekształcić zapytanie w tablicę.
Web
async function toArrayExpressionStageData() {
await setDoc(doc(collection(db, "Projects"), "project_1"), {
"id": "project_1",
"name": "Alpha Build"
});
await addDoc(collection(db, "Tasks"), {
"project_id": "project_1",
"title": "System Architecture"
});
await addDoc(collection(db, "Tasks"), {
"project_id": "project_1",
"title": "Database Schema Design"
});
}
Odpowiedź
{
id: "project_1",
name: "Alpha Build",
taskTitles: [
"System Architecture", "Database Schema Design"
]
}
Swift
async function toArrayExpressionStageData() { await setDoc(doc(collection(db, "Projects"), "project_1"), { "id": "project_1", "name": "Alpha Build" }); await addDoc(collection(db, "Tasks"), { "project_id": "project_1", "title": "System Architecture" }); await addDoc(collection(db, "Tasks"), { "project_id": "project_1", "title": "Database Schema Design" }); }
Odpowiedź
{ id: "project_1", name: "Alpha Build", taskTitles: [ "System Architecture", "Database Schema Design" ] }
Kotlin
fun toArrayExpressionData() { val project = hashMapOf( "id" to "project_1", "name" to "Alpha Build", ) db.collection("Projects").document("project_1").set(project) val task1 = hashMapOf( "project_id" to "project_1", "title" to "System Architecture", ) db.collection("Tasks").add(task1) val task2 = hashMapOf( "project_id" to "project_1", "title" to "Database Schema Design", ) db.collection("Tasks").add(task2) }
Odpowiedź
{ id: "project_1", name: "Alpha Build", taskTitles: [ "System Architecture", "Database Schema Design" ] }
Java
public void toArrayExpressionData() { Map<String, Object> project = new HashMap<>(); project.put("id", "project_1"); project.put("name", "Alpha Build"); db.collection("Projects").document("project_1").set(project); Map<String, Object> task1 = new HashMap<>(); task1.put("project_id", "project_1"); task1.put("title", "System Architecture"); db.collection("Tasks").add(task1); Map<String, Object> task2 = new HashMap<>(); task2.put("project_id", "project_1"); task2.put("title", "Database Schema Design"); db.collection("Tasks").add(task2); }
Odpowiedź
{ id: "project_1", name: "Alpha Build", taskTitles: [ "System Architecture", "Database Schema Design" ] }
Podzapytania skalarne
Podzapytania skalarne są często używane na etapie select(...) lub where(...), ponieważ umożliwiają filtrowanie lub zwracanie wyniku podzapytania bez bezpośredniego materializowania pełnego zapytania.
Podzapytanie skalarne, które nie zwraca żadnych wyników, będzie miało wartość null, natomiast podzapytanie, które zwraca wiele elementów, spowoduje błąd w czasie działania.
Gdy podzapytanie skalarne zwraca tylko jedno pole na wynik, pole jest podnoszone do poziomu wyniku najwyższego poziomu dla podzapytania. Najczęściej występuje to, gdy podzapytanie kończy się na select(field("user_name")) lub aggregate(countAll().as("total")), a schemat podzapytania zawiera tylko jedno pole. W przeciwnym razie, gdy zapytanie podrzędne może generować wiele pól, są one zawijane w mapę.
Użyj toScalarExpression() otoki pakietu SDK, aby przekształcić zapytanie w wyrażenie skalarne.
Web
async function toScalarExpressionStageData() {
await setDoc(doc(collection(db, "Authors"), "author_202"), {
"id": "author_202",
"name": "Charles Dickens"
});
await addDoc(collection(db, "Books"), {
"author_id": "author_202",
"title": "Great Expectations",
"rating": 4.8
});
await addDoc(collection(db, "Books"), {
"author_id": "author_202",
"title": "Oliver Twist",
"rating": 4.5
});
}
Odpowiedź
{
"id": "author_202",
"name": "Charles Dickens",
"averageBookRating": 4.65
}
Swift
try await db.collection("authors").document("author_202").setData([ "id": "author_202", "name": "Charles Dickens" ]) try await db.collection("books").document().setData([ "author_id": "author_202", "title": "Great Expectations", "rating": 4.8 ]) try await db.collection("books").document().setData([ "author_id": "author_202", "title": "Oliver Twist", "rating": 4.5 ])
Odpowiedź
{ "id": "author_202", "name": "Charles Dickens", "averageBookRating": 4.65 }
Kotlin
fun toScalarExpressionData() { val author = hashMapOf( "id" to "author_202", "name" to "Charles Dickens", ) db.collection("Authors").document("author_202").set(author) val book1 = hashMapOf( "author_id" to "author_202", "title" to "Great Expectations", "rating" to 4.8, ) db.collection("Books").add(book1) val book2 = hashMapOf( "author_id" to "author_202", "title" to "Oliver Twist", "rating" to 4.5, ) db.collection("Books").add(book2) }
Odpowiedź
{ "id": "author_202", "name": "Charles Dickens", "averageBookRating": 4.65 }
Java
public void toScalarExpressionData() { Map<String, Object> author = new HashMap<>(); author.put("id", "author_202"); author.put("name", "Charles Dickens"); db.collection("Authors").document("author_202").set(author); Map<String, Object> book1 = new HashMap<>(); book1.put("author_id", "author_202"); book1.put("title", "Great Expectations"); book1.put("rating", 4.8); db.collection("Books").add(book1); Map<String, Object> book2 = new HashMap<>(); book2.put("author_id", "author_202"); book2.put("title", "Oliver Twist"); book2.put("rating", 4.5); db.collection("Books").add(book2); }
Odpowiedź
{ "id": "author_202", "name": "Charles Dickens", "averageBookRating": 4.65 }
subcollection(...) Podzapytania
Etap wejściowy subcollection(...) umożliwia wykonywanie złączeń w hierarchicznym modelu danych Cloud Firestore. W modelu hierarchicznym zapytania często muszą pobierać dokument wraz z danymi z jego podzbiorów. Możesz to osiągnąć, używając etapu wejściowego collection_group(...), a następnie filtra odwołującego się do elementu nadrzędnego, ale subcollection(...) zapewnia znacznie bardziej zwięzłą składnię.
Poza niejawnym warunkiem złączenia działa podobnie do podzapytania array, zwracając pusty wynik, jeśli nie pasuje żaden dokument, nawet jeśli zagnieżdżona kolekcja nie istnieje.
Jest to w zasadzie syntaktyczny cukier: automatycznie używa __name__ dokumentu w zakresie zewnętrznym jako klucza łączenia, aby rozwiązać relację hierarchiczną. Dlatego jest to preferowany sposób wyszukiwania w kolekcjach połączonych relacją nadrzędny-podrzędny.
Sprawdzone metody
- Zarządzaj pamięcią za pomocą
toArrayExpression(): zachowaj ostrożność w przypadku zapytań podrzędnychtoArrayExpression(), ponieważ utworzenie dużej liczby dokumentów może wyczerpać limit pamięci zapytania (128 MiB). Aby temu zapobiec, użyj w podzapytaniuselect(...), aby zwracać tylko niezbędne pola, i zastosuj filtrywhere(...), aby ograniczyć liczbę zwracanych dokumentów. W razie potrzeby użyjlimit(...), aby ograniczyć liczbę dokumentów zwracanych przez podzapytanie. - Indeksowanie: sprawdź, czy pola używane w klauzuli
where(...)podzapytania są indeksowane. Wydajne złączenia opierają się na możliwości wykonywania wyszukiwań indeksów zamiast pełnych skanowań tabel.
Więcej sprawdzonych metod dotyczących zapytań znajdziesz w przewodniku po optymalizacji zapytań.
Ograniczenia
subcollection(...)Zakres: etap wejściowysubcollection(...)jest obsługiwany tylko w podzapytaniach, ponieważ wymaga kontekstu dokumentu nadrzędnego do rozwiązania relacji hierarchicznej i wykonania złączenia.- Poziom zagnieżdżenia: zapytania podrzędne mogą być zagnieżdżone do 20 poziomów.
- Wykorzystanie pamięci: limit 128 MiB dotyczący zmaterializowanych danych obowiązuje w przypadku całego zapytania, w tym wszystkich połączonych dokumentów.