1

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"
            ]
         }
      }
   })]
4
  • Does this answer your question? Mongo DB aggregation array size greater than match Commented Nov 4, 2020 at 15:06
  • no it does not as there is static size, in my case the size of array depends on document size returned by "match" in aggregate Commented Nov 4, 2020 at 15:10
  • Do you mean collection size (document count), as in the number of documents in a collections? Commented Nov 4, 2020 at 15:12
  • yes, the number of documents returned by $match in aggregation pipeline Commented Nov 4, 2020 at 15:14

3 Answers 3

3

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

Sign up to request clarification or add additional context in comments.

2 Comments

This seems to work,but query is too large for small task, i will accept this answer if i don't get any shorter answer
Ok, no issues. I'm also checking to short this one
1

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.

Comments

1

You could use $facet to get two streams i.e. 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] }
                    ]
                }
            }
        } 
    } }
])

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.