My app can search through a database of resources using MongoDB's aggregation pipeline. Some of these documents have the property sponsored: true
.
I want to move exactly one of these sponsored entries to the top of the search results, but keep natural ordering up for the remaining ones (no matter if sponsored or not).
Below is my code. My idea was to make use of addFields
but change the logic so that it only applies to the first element that meets the condition. Is this possible?
[...]
const aggregationResult = await Resource.aggregate()
.search({
compound: {
must: [
[...]
],
should: [
[...]
]
}
})
[...]
//only do this for the first sponsored result
.addFields({
selectedForSponsoredSlot: { $cond: [{ $eq: ['$sponsored', true] }, true, false] }
})
.sort(
{
selectedForSponsoredSlot: -1,
_id: 1
}
)
.facet({
results: [
{ $match: matchFilter },
{ $skip: (page - 1) * pageSize },
{ $limit: pageSize },
],
totalResultCount: [
{ $match: matchFilter },
{ $group: { _id: null, count: { $sum: 1 } } }
],
[...]
})
.exec();
[...]
CodePudding user response:
Update:
One option is to change your $facet
a bit:
- You can get the
$match
out of the$facet
since it is relevant to all pipelines. - instead of two pipelines, one for the results and one for the counting, we have now three: one more for
sponsored
documents only. - remove items that were already seen previously according to the
sposerted
item relevance score. - remove the item that is in the
sponserd
array from theallDocs
array (if it is in this page). $slice
theallDocs
array to be in the right size to complete the sponsered items to the wantedpageSize
$project
to concatenatesponsored
andallDocs
docs
db.collection.aggregate([
{$sort: {relevance: -1, _id: 1}},
{$match: matchFilter},
{$facet: {
allDocs: [{$skip: (page - 1) * (pageSize - 1)}, {$limit: pageSize 1 }],
sposerted: [{$match: {sponsored: true}}, {$limit: 1}],
count: [{$count: "total"}]
}},
{$set: {
allDocs: {
$slice: [
"$allDocs",
{$cond: [{$gte: [{$first: "$sposerted.relevance"},
{$first: "$allDocs.relevance"}]}, 1, 0]},
pageSize 1
]
}
}},
{$set: {
allDocs: {
$filter: {
input: "$allDocs",
cond: {$not: {$in: ["$$this._id", "$sposerted._id"]}}
}
}
}},
{$set: {allDocs: {$slice: ["$allDocs", 0, (pageSize - 1)]}}},
{$project: {
results: {
$concatArrays: [ "$sposerted", "$allDocs"]},
totalResultCount: {$first: "$count.total"}
}}
])
See how it works on the playground example