Contexto
As operações de pipeline são uma nova interface de consulta para Cloud Firestore. Essa interface oferece funcionalidades avançadas de consulta, incluindo expressões complexas. A edição Enterprise do Firestore oferece suporte a junções de estilo relacional por meio de subconsultas correlacionadas. Ao contrário de muitos bancos de dados NoSQL que geralmente exigem a desnormalização de dados ou a execução de várias solicitações do lado do cliente, as subconsultas permitem combinar e agregar dados de coleções ou subcoleções relacionadas diretamente no servidor.
As subconsultas são expressões que executam um pipeline aninhado para cada documento processado pela consulta externa. Isso permite padrões complexos de recuperação de dados, como buscar um documento junto com os itens da subcoleção relacionada ou unir dados logicamente vinculados em coleções raiz diferentes.
Conceitos
Esta seção apresenta os principais conceitos por trás do uso de subconsultas para realizar junções em operações de pipeline.
Subconsultas como expressões
Uma subconsulta não é um estágio de nível superior. Em vez disso, é uma expressão que
pode ser usada em qualquer estágio que aceite expressões, como
select(...),
add_fields(...),
where(...), ou sort(...).
Cloud Firestore oferece suporte a três tipos de subconsultas:
- Subconsultas de matriz:materializam todo o conjunto de resultados da subconsulta como uma matriz de documentos.
- Subconsultas escalares:avaliam um único valor, como uma contagem, uma média ou um campo específico de um documento relacionado.
- Subconsultas
subcollection(...):junções simplificadas para uma relação pai-filho um para muitos.
Escopo e variáveis
Ao escrever uma junção, a subconsulta aninhada geralmente precisa fazer referência a campos do documento "externo" (o pai). Para unir esses escopos, use o
let(...) estágio (referido como define(...) em alguns
SDKs) para definir variáveis no escopo pai que podem ser referenciadas na
subconsulta usando a variable(...) função.
Sintaxe
As seções a seguir oferecem uma visão geral da sintaxe para realizar junções.
O estágio let(...)
O estágio let(...) (referido como define(...) em alguns
SDKs) é um estágio não filtrado que traz explicitamente dados do escopo pai
para uma variável nomeada para uso em escopos aninhados subsequentes.
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); }
Subconsultas de matriz
Uma subconsulta de matriz é um caso especial de subconsulta de expressão que materializa todo o conjunto de resultados da subconsulta em uma matriz. Se a subconsulta não retornar nenhuma linha, ela será avaliada como uma matriz vazia. Ela nunca retorna uma matriz null. Essas consultas são úteis quando os resultados completos são necessários no resultado final, como ao materializar uma coleção aninhada ou correlacionada.
As consultas podem filtrar, classificar e agregar na subconsulta para reduzir a quantidade de dados que precisam ser buscados e retornados, o que ajuda a reduzir o custo da consulta. A ordem da subconsulta é respeitada, o que significa que um estágio sort(...) na subconsulta controla a ordem dos resultados na matriz final.
Use o wrapper do SDK toArrayExpression() para converter uma consulta em uma matriz.
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"
});
}
Resposta
{
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" }); }
Resposta
{ 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) }
Resposta
{ 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); }
Resposta
{ id: "project_1", name: "Alpha Build", taskTitles: [ "System Architecture", "Database Schema Design" ] }
Subconsultas escalares
As subconsultas escalares são frequentemente usadas em um estágio select(...) ou
where(...), pois permitem filtrar ou resultar o
resultado de uma subconsulta sem materializar a consulta completa diretamente.
Uma subconsulta escalar que produz zero resultados será avaliada como null, enquanto uma subconsulta que é avaliada como vários elementos resultará em um erro de execução.
Quando uma subconsulta escalar produz apenas um único campo por resultado, o campo é elevado para ser o resultado de nível superior da subconsulta. Isso é mais comum
quando a subconsulta termina com um select(field("user_name")) ou
aggregate(countAll().as("total")) em que o esquema da subconsulta é apenas um
único campo. Caso contrário, quando uma subconsulta pode produzir vários campos, eles são encapsulados em um mapa.
Use o wrapper do SDK toScalarExpression() para converter uma consulta em uma expressão escalar.
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
});
}
Resposta
{
"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 ])
Resposta
{ "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) }
Resposta
{ "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); }
Resposta
{ "id": "author_202", "name": "Charles Dickens", "averageBookRating": 4.65 }
Subconsultas subcollection(...)
Embora oferecido como um estágio, o
subcollection(...) estágio de entrada permite
realizar junções no modelo de dados hierárquico do Cloud Firestore. Em um modelo hierárquico, as consultas geralmente precisam recuperar um documento junto com dados das próprias subcoleções. Embora seja possível fazer isso usando um
collection_group(...) estágio de entrada seguido
por um filtro na referência pai, subcollection(...) oferece uma sintaxe muito mais
concisa.
Além da condição de junção implícita, isso funciona de maneira semelhante a uma subconsulta de matriz, retornando um resultado vazio se nenhum documento for correspondente, mesmo que a coleção aninhada não exista.
É fundamentalmente um açúcar sintático: ele usa automaticamente o __name__ de
o documento no escopo externo como a chave de junção para resolver a relação hierárquica. Isso o torna a maneira preferida de realizar pesquisas em coleções vinculadas em uma relação pai-filho.
Práticas recomendadas
- Gerenciar a memória com
toArrayExpression(): tenha cuidado comtoArrayExpression()subconsultas, já que a materialização de um grande número de documentos pode esgotar o limite de memória da consulta (128 MiB). Para atenuar isso, useselect(...)na subconsulta para retornar apenas os campos necessários e apliquewhere(...)filtros para limitar o número de documentos retornados. Considere usarlimit(...), se apropriado, para limitar o número de documentos retornados pela subconsulta. - Indexação: verifique se os campos usados na cláusula
where(...)de uma subconsulta estão indexados. As junções de desempenho dependem da capacidade de realizar buscas de índice em vez de verificações completas de tabelas.
Para mais práticas recomendadas de consulta, consulte nosso guia sobre otimização de consultas.
Limitações
subcollection(...)escopo: Osubcollection(...)estágio de entrada só é compatível com subconsultas, já que exige o contexto de um documento pai para resolver a relação hierárquica e realizar a junção.- Profundidade de aninhamento:as subconsultas podem ser aninhadas em até 20 camadas de profundidade.
- Uso de memória:o limite de 128 MiB em dados materializados se aplica a toda a consulta, incluindo todos os documentos unidos.