Sfondo
Le operazioni della pipeline sono una nuova interfaccia di query per Cloud Firestore. Questa interfaccia fornisce funzionalità di query avanzate che includono espressioni complesse. La versione Enterprise di Firestore supporta i join in stile relazionale tramite sottoquery correlate. A differenza di molti database NoSQL che spesso richiedono la denormalizzazione dei dati o l'esecuzione di più richieste lato client, le sottoquery consentono di combinare e aggregare i dati da raccolte o sottoraccolte correlate direttamente sul server.
Le sottoquery sono espressioni che eseguono una pipeline nidificata per ogni documento elaborato dalla query esterna. Ciò consente pattern di recupero dei dati complessi, ad esempio il recupero di un documento insieme agli elementi della sottoraccolta correlata o l'unione di dati collegati logicamente in raccolte root disparate.
Concetti
Questa sezione introduce i concetti di base per l'utilizzo delle sottoquery per eseguire i join nelle operazioni della pipeline.
Sottoquery come espressioni
Una sottoquery non è una fase di primo livello, ma un'espressione che
può essere utilizzata in qualsiasi fase che accetta espressioni, ad esempio
select(...),
add_fields(...),
where(...) o sort(...).
Cloud Firestore supporta tre tipi di sottoquery:
- Sottoquery di array: materializzano l'intero set di risultati della sottoquery come un array di documenti.
- Sottoquery scalari: restituiscono un singolo valore, ad esempio un conteggio, una media o un campo specifico di un documento correlato.
- Sottoquery
subcollection(...): join semplificati per una relazione padre-figlio uno-a-molti.
Ambito e variabili
Quando scrivi un join, la sottoquery nidificata spesso deve fare riferimento ai campi del documento "esterno" (il documento principale). Per colmare questi ambiti, utilizza la
let(...) fase (denominata define(...) in alcuni
SDK) per definire le variabili nell'ambito principale a cui è possibile fare riferimento nella
sottoquery utilizzando la funzione variable(...).
Sintassi
Le sezioni seguenti forniscono una panoramica della sintassi per l'esecuzione dei join.
La fase let(...)
La fase let(...) (denominata define(...) in alcuni
SDK) è una fase di non filtraggio che porta esplicitamente i dati dall'ambito principale
a una variabile denominata per l'utilizzo negli ambiti nidificati successivi.
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); }
Sottoquery di array
Una sottoquery di array è un caso speciale di sottoquery di espressione che materializza l'intero set di risultati della sottoquery in un array. Se la sottoquery restituisce zero righe, restituisce un array vuoto. Non restituisce mai un array null. Queste query sono utili quando i risultati completi sono necessari nel risultato finale, ad esempio quando si materializza una raccolta nidificata o correlata.
Le query possono filtrare, ordinare e aggregare nella sottoquery per ridurre anche la quantità di dati da recuperare e restituire, contribuendo a ridurre il costo della query. L'ordine della sottoquery viene rispettato, il che significa che una fase sort(...) nella sottoquery controlla l'ordine dei risultati nell'array finale.
Utilizza il wrapper SDK toArrayExpression() per convertire una query in un array.
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"
});
}
Risposta
{
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" }); }
Risposta
{ 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) }
Risposta
{ 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); }
Risposta
{ id: "project_1", name: "Alpha Build", taskTitles: [ "System Architecture", "Database Schema Design" ] }
Sottoquery scalari
Le sottoquery scalari vengono spesso utilizzate in una fase select(...) o
where(...) per consentire il filtraggio o il
risultato di una sottoquery senza materializzare direttamente la query completa.
Una sottoquery scalare che produce zero risultati restituisce null, mentre una sottoquery che restituisce più elementi genera un errore di runtime.
Quando una sottoquery scalare produce un solo campo per risultato, il campo viene elevato per essere il risultato di primo livello della sottoquery. Questo si verifica più comunemente
quando la sottoquery termina con un select(field("user_name")) o
aggregate(countAll().as("total")) in cui lo schema della sottoquery è un
singolo campo. In caso contrario, quando una sottoquery può produrre più campi, questi vengono racchiusi in una mappa.
Utilizza il wrapper SDK toScalarExpression() per convertire una query in un'espressione scalare.
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
});
}
Risposta
{
"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 ])
Risposta
{ "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) }
Risposta
{ "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); }
Risposta
{ "id": "author_202", "name": "Charles Dickens", "averageBookRating": 4.65 }
Sottoquery subcollection(...)
Sebbene sia offerta come fase, la
subcollection(...) fase di input consente
di eseguire join sul modello di dati gerarchico di Cloud Firestore. In un modello gerarchico, le query spesso devono recuperare un documento insieme ai dati delle relative sottoraccolte. Sebbene sia possibile ottenere questo risultato utilizzando una
collection_group(...) fase di input seguita
da un filtro sul riferimento principale, subcollection(...) fornisce una sintassi molto più
concisa.
Oltre alla condizione di join implicita, questa fase si comporta in modo simile a una sottoquery di array, restituendo un risultato vuoto se non vengono trovati documenti, anche se la raccolta nidificata non esiste.
Si tratta fondamentalmente di zucchero sintattico: utilizza automaticamente __name__ di
documento nell'ambito esterno come chiave di join per risolvere la relazione gerarchica. Questo è il modo preferito per eseguire ricerche nelle raccolte collegate in una relazione padre-figlio.
Best practice
- Gestire la memoria con
toArrayExpression(): fai attenzione alletoArrayExpression()sottoquery, poiché la materializzazione di un numero elevato di documenti può esaurire il limite di memoria della query (128 MiB). Per risolvere questo problema, utilizzaselect(...)all'interno della sottoquery per restituire solo i campi necessari e applica i filtriwhere(...)per limitare il numero di documenti restituiti. Se appropriato, valuta la possibilità di utilizzarelimit(...)per limitare il numero di documenti restituiti dalla sottoquery. - Indicizzazione: assicurati che i campi utilizzati nella clausola
where(...)di una sottoquery siano indicizzati. I join efficienti si basano sulla possibilità di eseguire ricerche di indici anziché scansioni complete delle tabelle.
Per ulteriori best practice relative alle query, consulta la nostra guida sull'ottimizzazione delle query.
Limitazioni
subcollection(...)ambito: la fase di inputsubcollection(...)è supportata solo all'interno delle sottoquery, in quanto richiede il contesto di un documento principale per risolvere la relazione gerarchica ed eseguire il join.- Profondità di nidificazione: le sottoquery possono essere nidificate fino a 20 livelli di profondità.
- Utilizzo della memoria: il limite di 128 MiB per i dati materializzati si applica all'intera query, inclusi tutti i documenti uniti.