Search code examples
mongodbmongooseaggregation-frameworkaggregate

Get min and max value in single query in mongodb


Considering the following Documents in "Words":

[{
  _id: 1,
  usages: 2,
  word: "Name"
}, {
  _id: 2,
  usages: 1,
  word: "Street"
}, {
  _id: 3,
  usages: 1,
  word: "House"
}, {
  _id: 4,
  usages: 3,
  word: "Table"
}, {
  _id: 5,
  usages: 3,
  word: "Bread"
}, {
  _id: 6,
  usages: 4,
  word: "Door"
}]

How can i get all the records where the number of usages is the lowest or highest? Lowest should return id 2 and 3(and their word), highest should return id 6 with its word.

I need to aggregate this data into a random amount of lowest/highest records (50 to be exact) so it needs to be an aggregate.

It should be a single lookup, so the min/max cannot be found using another query with $max or $min.

The MongoDB version is 3.4.7, Mongoose version 5.0.0-rc1. Mongoose solution not requred since I can use a raw query. (It is preferred however!)

Example:

Words.aggregate([ 
  { 
    $match: { _
      usages: { WHAT_SHOULD_I_HAVE_HERE }
    }
  }, { 
    $sample: { 
      size: 50 
    } 
  }
])

Thanks!


Solution

  • You can try below aggregation

    $facet will give you the two lowest and highest value for the usages and you can easily $project through them using $filter operator

    db.collection.aggregate([
      { "$facet": {
        "minUsages": [{ "$sort": { "usages": -1 } }],
        "maxUsages": [{ "$sort": { "usages": 1 } }]
      }},
      { "$addFields": {
        "lowestUsages": {
          "$arrayElemAt": ["$minUsages", 0]
        },
        "highestUsages": {
          "$arrayElemAt": ["$maxUsages", 0]
        }
      }},
      { "$project": {
        "minUsages": {
          "$filter": {
            "input": "$minUsages",
            "as": "minUsage",
            "cond": {
              "$eq": ["$$minUsage.usages", "$lowestUsages.usages"]
            }
          }
        },
        "maxUsages": {
          "$filter": {
            "input": "$maxUsages",
            "as": "maxUsage",
            "cond": {
              "$eq": ["$$maxUsage.usages", "$highestUsages.usages"]
            }
          }
        }
      }}
    ])
    

    Or you can simply do this with find query as well

    const minUsage = await db.collection.find({}).sort({ "usages": -1 }).limit(1)
    const maxUsage = await db.collection.find({}).sort({ "usages": 1 }).limit(1)
    
    const minUsages = await db.collection.find({ "usages": { "$in": [minUsages[0].usages] } })
    const maxUsages = await db.collection.find({ "usages": { "$in": [maxUsages[0].usages] } })
    

    Take a look here