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
$matchout of the$facetsince 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
sponsoreddocuments only. - remove items that were already seen previously according to the
sposerteditem relevance score. - remove the item that is in the
sponserdarray from theallDocsarray (if it is in this page). $slicetheallDocsarray to be in the right size to complete the sponsered items to the wantedpageSize$projectto concatenatesponsoredandallDocsdocs
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
