Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

$setUnion to merge array from multiple documents MongoDB

I have documents in a collection as follows :

{ _id : 1 , data : [7,4,0] }
{ _id : 2 , data : [4,5,6] }
{ _id : 3 , data : [6,7,8] }

I want to union the data array from two or more documents.

The query I am using to find the union of data array of id 1 and 2 is:

db.coll.aggregate(
{
    $match : {
        _id: { $in: [1, 2] }
    }
},
{
    $group: { 
        _id: 0,
        s0: { $first: "$data"}, 
        s1: { $first: "$data"}
    }
},
{
    $project: {
        _id: 0,
        ans: { $setUnion: [ "$s0","$s1"]}
    }
}
).pretty()

But the query results in:

{7, 5, 0}

which seems to be the data of id 1 only.

How to achieve the union between two or more documents on same array field?

PS: I'm using MongoDB 3.4

like image 329
Anand Undavia Avatar asked Feb 17 '17 07:02

Anand Undavia


3 Answers

You can use $unwind and $group with $addToSet,

  • $match your conditions
  • $unwind deconstruct data array
  • $group by null and do $addToSet to get unique numbers form data
db.coll.aggregate([
  { $match: { _id: { $in: [1, 2] } } },
  { $unwind: "$data" },
  {
    $group: {
      _id: null,
      data: { $addToSet: "$data" }
    }
  }
]).pretty()

Playground

like image 61
turivishal Avatar answered Nov 13 '22 00:11

turivishal


For a more efficient query, use the $reduce operator to flatten the arrays. This will allow you to concat any number of arrays, so instead of just doing a union of the two arrays from docs 1 and 2, this will also apply for other arrays as well.

Consider running the following aggregate operation:

db.coll.aggregate([
    { "$match": { "_id": { "$in": [1, 2] } } },
    {
        "$group": {
            "_id": 0,
            "data": { "$push": "$data" }
        }
    },
    {
        "$project": {
            "data": {
                "$reduce": {
                    "input": "$data",
                    "initialValue": [],
                    "in": { "$setUnion": ["$$value", "$$this"] }
                }
            }
        }
    }
])

Sample Output

{
    "_id" : 0,
    "data" : [ 0, 4, 5, 6, 7 ]
}
like image 30
chridam Avatar answered Nov 12 '22 23:11

chridam


just use $last instead of $first for s1

so the query becomes:

db.coll.aggregate(
{
    $match : {
        _id: { $in: [1, 2] }
    }
},
{
    $group: { 
        _id: 0,
        s0: { $first: "$data"}, 
        s1: { $last: "$data"}
    }
},
{
    $project: {
        _id: 0,
        ans: { $setUnion: [ "$s0","$s1"]}
    }
}
).pretty()

output:

{ "ans" : [ 0, 4, 5, 6, 7 ] }
like image 3
felix Avatar answered Nov 13 '22 00:11

felix