web-dev-qa-db-ja.com

mongoDBで和集合を実行する

MongoDBのアグリゲートで一種のユニオンを実行する方法を知りたいです。コレクション内の次のドキュメントをイメージしてみましょう(構造は例のためです):

{
  linkedIn: {
    people : [
    {
      name : 'Fred'
     },
     {
       name : 'Matilda'
     }
   ]
  },
  Twitter: {
    people : [
    {
       name : 'Hanna'
    },
    {
       name : 'Walter'
    }
   ]
  }
 }

TwitterとLinkedInの人々の和集合を返す集計を作成するにはどうすればよいですか?

{
 { name :'Fred', source : 'LinkedIn'},
 { name :'Matilda', source : 'LinkedIn'},
 { name :'Hanna', source : 'Twitter'},
 { name :'Walter', source : 'Twitter'},
}
13
ic3

これには、 aggregate メソッドを使用できるいくつかのアプローチがあります。

db.collection.aggregate([
    // Assign an array of constants to each document
    { "$project": {
        "linkedIn": 1,
        "Twitter": 1,
        "source": { "$cond": [1, ["linkedIn", "Twitter"],0 ] }
    }},

    // Unwind the array
    { "$unwind": "$source" },

    // Conditionally Push the fields based on the matching constant
    { "$group": { 
        "_id": "$_id",
        "data": { "$Push": {
            "$cond": [
                { "$eq": [ "$source", "linkedIn" ] },
                { "source": "$source", "people": "$linkedIn.people" },
                { "source": "$source", "people": "$Twitter.people" }
            ]
        }}
    }},

    // Unwind that array
    { "$unwind": "$data" },

    // Unwind the underlying people array
    { "$unwind": "$data.people" },

    // Project the required fields
    { "$project": {
        "_id": 0,
        "name": "$data.people.name",
        "source": "$data.source"
    }}
])

または、MongoDB 2.6のいくつかの演算子を使用した別のアプローチでは、次のようになります。

db.people.aggregate([
    // Unwind the "linkedIn" people
    { "$unwind": "$linkedIn.people" },

    // Tag their source and re-group the array
    { "$group": {
        "_id": "$_id",
        "linkedIn": { "$Push": {
            "name": "$linkedIn.people.name",
            "source": { "$literal": "linkedIn" }
        }},
        "Twitter": { "$first": "$Twitter" }
    }},

    // Unwind the "Twitter" people
    { "$unwind": "$Twitter.people" },

    // Tag their source and re-group the array
    { "$group": {
        "_id": "$_id",
        "linkedIn": { "$first": "$linkedIn" },
        "Twitter": { "$Push": {
            "name":  "$Twitter.people.name",
            "source": { "$literal": "Twitter" }
        }}
    }},

    // Merge the sets with "$setUnion"
    { "$project": {
        "data": { "$setUnion": [ "$Twitter", "$linkedIn" ] }
    }},

    // Unwind the union array
    { "$unwind": "$data" },

    // Project the fields
    { "$project": {
        "_id": 0,
        "name": "$data.name",
        "source": "$data.source"
    }}
])

そしてもちろん、ソースが何であるかを単に気にしない場合:

db.collection.aggregate([
    // Union the two arrays
    { "$project": {
        "data": { "$setUnion": [
            "$linkedIn.people",
            "$Twitter.people"
        ]}
    }},

    // Unwind the union array
    { "$unwind": "$data" },

    // Project the fields
    { "$project": {
        "_id": 0,
        "name": "$data.name",
    }}

])
13
Neil Lunn

その種の操作にはmap-reduceよりもaggregateの使用が推奨されるかどうかはわかりませんが、以下はあなたが求めていることを実行しています(.aggregate()関数で$ constをまったく問題なく使用できるかどうかはわかりません):

aggregate([ 
   { $project: { linkedIn: '$linkedIn', Twitter: '$Twitter', idx: { $const: [0,1] }}},
   { $unwind: '$idx' },
   { $group: { _id : '$_id', data: { $Push: { $cond:[ {$eq:['$idx', 0]}, { source: {$const: 'LinkedIn'}, people: '$linkedIn.people' } , { source: {$const: 'Twitter'}, people: '$Twitter.people' } ] }}}},
   { $unwind: '$data'},
   { $unwind: '$data.people'},
   { $project: { _id: 0, name: '$data.people.name', source: '$data.source' }}
])
2
Marc Polizzi