Subcoleção

Descrição

A etapa de entrada subcollection(...) facilita a realização de junções pai-filho usando o campo __name__ integrado.

Outras etapas podem ser encadeadas à etapa subcollection(...) para realizar filtragem ou agregação nos documentos aninhados. As referências de campo usadas nas etapas subsequentes se referem aos documentos da coleção aninhada, não ao documento principal. Para se referir a campos no escopo principal, primeiro use a etapa let(...) para definir variáveis e depois faça referência a elas no escopo local.

Exemplos

Node.js

db.pipeline()
  .collection("/restaurants")
  .add_fields(subcollection("reviews")
    .aggregate(average("rating").as("avg_rating"))
    .toScalarExpression()
    .as("avg_rating"))

Comportamento

A etapa subcollection(...) precisa ser usada no contexto de uma subconsulta. Ele usa o __name__ (a referência do documento) do documento atual no escopo pai para determinar qual subcoleção buscar. Por exemplo, se o documento principal for /restaurants/pizza-place, subcollection("reviews") vai retornar todos os documentos da coleção /restaurants/pizza-place/reviews.

Se a referência do documento foi renomeada ou não é possível definir um campo com __name__, escreva manualmente a junção assim:

Node.js

db.pipeline()
  .collection("/restaurants")
  .let(field("__name__").as("restaurant_name"))
  .add_fields(db.pipeline()
    .collectionGroup("reviews")
    .where(field("__name__").parent().equals(variable("restaurant_name")))
    .aggregate(average("rating").as("avg_rating"))
    .toScalarExpression()
    .as("avg_rating"))

ainda é possível, já que, fundamentalmente, essa etapa é apenas uma facilidade sintática sobre esse formato de junção mais complexo.

Para os seguintes documentos:

Node.js

const restaurant1 = db.collection("restaurants").document("pizza-place");
const restaurant2 = db.collection("restaurants").document("urban-bite");
const restaurant3 = db.collection("restaurants").document("nacho-house");

await restaurant1.create({ name: "Pizza Place" });
await restaurant2.create({ name: "Urban Bite" });
await restaurant3.create({ name: "Nacho House" });

await restaurant1.collection("reviews").doc("1").create({ rating: 5 });
await restaurant1.collection("reviews").doc("1").create({ rating: 2 });

await restaurant2.collection("reviews").doc("1").create({ rating: 3 });
await restaurant2.collection("reviews").doc("1").create({ rating: 4 });
await restaurant2.collection("reviews").doc("1").create({ rating: 5 });

A consulta a seguir recupera cada restaurante e resume as avaliações em um novo campo review_summary:

Node.js

const results = await db.pipeline()
  .collectionGroup("restaurants")
  .add_fields(subcollection("reviews")
    .aggregate(
      countAll().as("review_count"),
      average("rating").as("avg_rating"))
    .asScalarExpression()
    .as("review_summary"))
  .execute();

e produz os seguintes resultados:

{ name: "Pizza Place", review_summary: { review_count: 2, avg_rating: 3.5 } },
{ name: "Urban Bite",  review_summary: { review_count: 3, avg_rating: 4.0 } },
{ name: "Nacho House", review_summary: { review_count: 0, avg_rating: null } },