简体   繁体   中英

How to find array greater than document size in MongoDB

I have schema like below

[
{
 id:"111"
 tags:[222,333,444,555]
},
{
 id: "222"
 tags:[312,345,534]
},
{
 id:"333"
 tags:[111,222,333,444,555]
},

]

I want to find all documents where tags array size is greater than document size returned by $match in aggregation pipeline, so in above Ex. the number of documents are 3 so i want to return all documents having tags array size greater that 3

[
{
 id:"111"
 tags:[222,333,444,555]
},
{
 id:"333"
 tags:[111,222,333,444,555]
},

]

I am using aggregation pipeline to process other info, I am stuck at how to have store document size so that i can find all tags greater than document size

below is query which i am using, i want to do it in aggregation and in one call

.aggregate([
        
   {
      "$match":{
         "ids":{
            "$in":[
               "111",
               "222",
                "333"
            ]
         }
      }
   })]

Facet helps you to solve this problem.

  • $facet helps to categorize the incoming documents. We use totalDoc for counting the document and allDocuments for getting all the documents
  • $arrayElemAt helps to get the first object from totalDoc where we already know that only one object should be inside the totalDoc . Because when we group it, we use _id:null
  • $unwind helps to de-structure the allDocuments array

Here is the code

db.collection.aggregate([
  {
    $facet: {
      totalDoc: [
        {
          $group: {
            _id: null,
            count: {
              $sum: 1
            }
          }
        }
      ],
      allDocuments: [
        {
          $project: {
            tags: 1
          }
        }
      ]
    }
  },
  {
    $addFields: {
      totalDoc: {
        "$arrayElemAt": [
          "$totalDoc",
          0
        ]
      }
    }
  },
  {
    $unwind: "$allDocuments"
  },
  {
    $addFields: {
      sizeGtDoc: {
        $gt: [
          {
            $size: "$allDocuments.tags"
          },
          "$totalDoc.count"
        ]
      }
    }
  },
  {
    $match: {
      sizeGtDoc: true
    }
  },
  {
    "$replaceRoot": {
      "newRoot": "$allDocuments"
    }
  }
])

Working Mongo playground

You could use $facet to get two streams ie one with the filtered documents and the counts using $count . The resulting streams can then be aggregated further with a $filter as follows to get the desired result

db.getCollection('collection').aggregate([
    { '$facet': {
        'counts': [
            { '$match': { 'id': { '$in': ['111', '222', '333'] } } }, 
            { '$count': "numberOfMatches" }
        ],
        'docs': [
            { '$match': { 'id': { '$in': ['111', '222', '333'] } } }, 
        ]  
    } },
    {  '$project': {
        'result': {
            '$filter': {
                'input': '$docs',
                'cond': {
                    '$gt': [
                        { '$size': '$$this.tags' },
                        { '$arrayElemAt': ['$counts.numberOfMatches', 0] }
                    ]
                }
            }
        } 
    } }
])

You can try,

  • $match you condition
  • $group by null and make root array of documents and get count of root documents in count
  • $unwind deconstruct root array
  • $match tags size and count greater than or not using $expr expression match
  • $replaceRoot to replace root object in root
db.collection.aggregate([
  { $match: { id: { $in: ["111", "222", "333"] } } },
  {
    $group: {
      _id: null,
      root: { $push: "$$ROOT" },
      count: { $sum: 1 }
    }
  },
  { $unwind: "$root" },
  { $match: { $expr: { $gt: [{ $size: "$root.tags" }, "$count"] } } },
  { $replaceRoot: { newRoot: "$root" } }
])

Playground


Second option:

  • first 2 stages $match and $group both are same as like above query,
  • $project to filter root array match condition if tags size and count greater than or not, this will return filtered root array
  • $unwind deconstruct root array
  • $replaceRoot replace root object to root
db.collection.aggregate([
  { $match: { id: { $in: ["111", "222", "333"] } } },
  {
    $group: {
      _id: null,
      root: { $push: "$$ROOT" },
      count: { $sum: 1 }
    }
  },
  {
    $project: {
      root: {
        $filter: {
          input: "$root",
          cond: { $gt: [{ $size: "$$this.tags" }, "$count"] }
        }
      }
    }
  },
  { $unwind: "$root" },
  { $replaceRoot: { newRoot: "$root" } }
])

Playground

You can skip $unwind and $replaceRoot stages if you want because this query always return one document in root, so you can easily access like this result[0]['root'] , you can save 2 stages processing and execution time.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM