Subcollection (Input Stage)
Description
The subcollection(...)
input stage makes it easy to perform parent-child joins
using the built-in __name__
field.
Additional stages can be chained onto the subcollection(...)
stage to perform
filtering or aggregation over the nested documents. Note that any field
references used in subsequent stages refer to the documents from the nested
collection, not the parent document. To refer to fields in the parent scope,
first use the let
stage to define variables, then reference those variables in
the local scope.
Syntax
Node.js
db
.
pipeline
()
.
collection
(
"/restaurants"
)
.
add_fields
(
subcollection
(
"reviews"
)
.
aggregate
(
average
(
"rating"
).
as
(
"avg_rating"
))
.
toScalarExpression
()
.
as
(
"avg_rating"
))
Behavior
The subcollection(...)
stage must be used within the context of a
sub-pipeline. It uses the __name__
(the document reference) of the current
document in the parent scope to determine which sub-collection to fetch. For
example, if the parent document is /restaurants/pizza-place
, then subcollection("reviews")
returns all documents from the /restaurants/pizza-place/reviews
collection.
If the document reference has been renamed, or it is not possible to define a
field with __name__
then manually writing the join like:
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"
))
is still possible as fundamentally this stage is just syntactic sugar over this more complex join format.
Example
For the following documents:
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
});
The following query retrieves each restaurant and summarizes its reviews in a
new review_summary
field:
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
();
and produces the following results:
{
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
}
},

