Home > Back-end >  Sort element with property: true to the top, but only one out of many
Sort element with property: true to the top, but only one out of many

Time:07-20

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:

  1. You can get the $match out of the $facet since it is relevant to all pipelines.
  2. instead of two pipelines, one for the results and one for the counting, we have now three: one more for sponsored documents only.
  3. remove items that were already seen previously according to the sposerted item relevance score.
  4. remove the item that is in the sponserd array from the allDocs array (if it is in this page).
  5. $slice the allDocs array to be in the right size to complete the sponsered items to the wanted pageSize
  6. $project to concatenate sponsored and allDocs 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

  • Related