I have a posts collection and I want to return the latest posts and featured posts with 1 query.
post document sample
{
"title":"Hello World",
"author":"Bob Paul",
"featured":True,
"published":True,
"created_at":"2019-01-15 10:27:16.354Z"
}
This is what I want returned:
{
"latest": [post1,post2,post3],
"featured": [post7,post10]
}
Seems like you’d have to do 2 match expressions on the same collection. Is that even possible?
I was thinking I could extract the featured posts in a different collection and use $unionWith.
2
Answers
You can use $facet into an aggregation pipeline to create two outputs on the same query:
Example here
Depending on the frequency of this operation and the definitions of the two queries, I would actually suggest sticking with
$unionWith
mentioned in the original question as opposed to$facet
. This is primarily for performance reasons. While it may make the pipeline a little bit less intuitive to read, I suspect that it will perform and scale much better.The problem with
$facet
is that it currently has the following behavior:This means that the
$facet
approach will scan the entire collection every single time this operation executes. This will probably be too slow (and resource intensive) to support the needs of the application.For the purposes of this answer, let’s say that
query1
is something like:And
query2
:Obviously your situation may differ, but this is probably reasonably close. In this situation, there would be two important indexes that are relevant:
Individually, these queries efficiently use their associated indexes:
Combined with
$facet
, however, they cannot:Alternatively, we can construct an aggregation that retrieves the same documents via
$unionWith
as follows:Now the
explain
shows something very different (many lines removed for brevity/readability):Both of the nested queries now use the indexes as expected.
If needed, you can append some additional stages to restructure the data into the format originally requested. In fact, we can actually use the
$facet
stage to do that work. Note that because$facet
is no longer at the beginning of the aggregation pipeline it is being fed data from those preceding stages and the warning about a collection scan is no longer relevant.Based on the incoming data, we can define
facet
as:Appending that to the existing aggregation yields the following results:
The final full pipeline in our example is:
See how it works in this playground example