Subcolección

Descripción

La etapa de entrada subcollection(...) facilita la realización de uniones entre elementos superiores y secundarios con el campo integrado __name__.

Se pueden encadenar etapas adicionales a la etapa subcollection(...) para realizar el filtrado o la agregación en los documentos anidados. Ten en cuenta que cualquier referencia de campo que se use en las etapas posteriores se refiere a los documentos de la colección anidada, no al documento superior. Para hacer referencia a los campos en el alcance superior, primero usa la etapa let(...) para definir variables y, luego, haz referencia a esas variables en el alcance local.

Ejemplos

Node.js

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

Comportamiento

La etapa subcollection(...) se debe usar en el contexto de una subconsulta. Usa __name__ (la referencia del documento) del documento actual en el alcance superior para determinar qué subcolección recuperar. Por ejemplo, si el documento superior es /restaurants/pizza-place, entonces subcollection("reviews") devuelve todos los documentos de la /restaurants/pizza-place/reviews colección.

Si se cambió el nombre de la referencia del documento o no es posible definir un campo con __name__, aún es posible escribir la unión de forma manual de la siguiente manera:

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

Esto se debe a que, fundamentalmente, esta etapa es solo una sintaxis edulcorada sobre este formato de unión más complejo.

Para los siguientes 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 });

La siguiente consulta recupera cada restaurante y resume sus reseñas en un nuevo 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();

y produce los siguientes 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 } },