0

I need a Mongo Query to return me common values present in an array. So if there are 4 documents in match, then the values are returned if those are present in in all the 4 documents

Suppose I have the below documents in my db

Mongo Documents

    {
    "id":"0",
    "merchants":["1","2"]
    }

    {
    "id":"1",
    "merchants":["1","2","4"]
    }

    {
    "id":"2",
    "merchants":["4","5"]
    }

Input : List of id

(i) Input with id "0" and "1"

Then it should return me merchants:["1","2"] as both are present in documents with id "0" & id "1"

(ii) Input with id "1" and "2" Then it should return me merchants:["4"] as it is common and present in both documents with id "1" & id "2"

(iii) Input with id "0" and "2" Should return empty merchants:[] as no common merchants between these 2 documents

1
  • What is your MongoDB server version? Commented Feb 17, 2017 at 16:17

3 Answers 3

3

You can try below aggregation.

db.collection.aggregate(
   {$match:{id: {$in: ["1", "2"]}}},
   {$group:{_id:null, first:{$first:"$merchants"}, second:{$last:"$merchants"}}},
   {$project: {commonToBoth: {$setIntersection: ["$first", "$second"]}, _id: 0 } }
)   
Sign up to request clarification or add additional context in comments.

3 Comments

This won't work in the case where more than two ids are inputted
Yes it does not work. This query just considers the first and last document and ignores the intermediate documents
Its obvious it doesn't work if you look at the labels first and second . I provided answer based on your sample inputs. Consider updating your post to tell exactly what you're looking for.
2

Say you have a function query that does the required DB query for you, and you'll call that function with idsToMatch which is an array containing all the elements you want to match. I have used JS here as the driver language, replace it with whatever you are using.

The following code is dynamic, will work for any number of ids you give as input:

const query = (idsToMatch) => {

    db.collectionName.aggregate([
        { $match: { id: {$in: idsToMatch} } },
        { $unwind: "$merchants" },
        { $group: { _id: { id: "$id", data: "$merchants" } } },
        { $group: { _id: "$_id.data", count: {$sum: 1} } },
        { $match: { count: { $gte: idsToMatch.length } } },
        { $group: { _id: 0, result: {$push: "$_id" } } },
        { $project: { _id: 0, result: "$result" } }    
    ])
  1. The first $group statement is to make sure you don't have any repetitions in any of your merchants attribute in a document. If you are certain that in your individual documents you won't have any repeated value for merchants, you need not include it.
  2. The real work happens only upto the 2nd $match phase. The last two phases ($group and $project) are only to prettify the result, you may choose not use them, and instead use the language of your choice to transform it in the form you want

Assuming you want to reduce the phases as per the points given above, the actual code will reduce to:

aggregate([
        { $match: { id: {$in: idsToMatch} } },
        { $unwind: "$merchants" },
        { $group: { _id: "merchants", count: {$sum: 1} } },
        { $match: { count: { $gte: idsToMatch.length } } }   
    ])

Your required values will be at the _id attribute of each element of the result array.

2 Comments

This too does not work as the intermediate match operation incorrect. The intermediate match operation should be { $match: { count: { $gt: "no_of_ids_in_1st_match_operation" } } }
Solving that part is trivial. Say you have a function query that does this DB query for you, and you call it like: query(idsToMatch), where idsToMatch is an array of ids you want to match. You can simple replace the $gt:1 part to $gt: (idsToMatch.length - 1) (assuming ur using js, otherwise use your language's length function).
0

The answer provided by @jgr0 is correct to some extent. The only mistake is the intermediate match operation

(i) So if input ids are "1" & "0" then the query becomes

     aggregate([
            {"$match":{"id":{"$in":["1","0"]}}},
            {"$unwind":"$merchants"},
            {"$group":{"_id":"$merchants","count":{"$sum":1}}},
            {"$match":{"count":{"$eq":2}}},
            {"$group":{"_id":null,"merchants":{"$push":"$_id"}}},
            {"$project":{"_id":0,"merchants":1}} 
            ])

(ii) So if input ids are "1", "0" & "2" then the query becomes

    aggregate([
        {"$match":{"id":{"$in":["1","0", "2"]}}},
        {"$unwind":"$merchants"},
        {"$group":{"_id":"$merchants","count":{"$sum":1}}},
        {"$match":{"count":{"$eq":3}}},
        {"$group":{"_id":null,"merchants":{"$push":"$_id"}}},
        {"$project":{"_id":0,"merchants":1}} 
        ])

The intermediate match operation should be the count of ids in input. So in case (i) it is 2 and in case (2) it is 3.

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.