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();
[...]
Update:
One option is to change your $facet
a bit:
$match
out of the $facet
since it is relevant to all pipelines.sponsored
documents only.sposerted
item relevance score.sponserd
array from the allDocs
array (if it is in this page).$slice
the allDocs
array to be in the right size to complete the sponsered items to the wanted pageSize
$project
to concatenate sponsored
and allDocs
docsdb.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