web-dev-qa-db-ja.com

配列から重複するエントリを削除するにはどうすればよいですか?

次の例では、"Algorithms in C++"は2回存在します。

$unset修飾子は特定のフィールドを削除できますが、フィールドからエントリを削除するにはどうすればよいですか?

{
  "_id" : ObjectId("4f6cd3c47156522f4f45b26f"), 
  "favorites" : {
    "books" : [
      "Algorithms in C++",    
      "The Art of Computer Programming", 
      "Graph Theory",      
      "Algorithms in C++"
    ]
  }, 
  "name" : "robert"
}
22
P K

あなたがしなければならないことは、重複するタグを検出してカウントするためにmap reduceを使用することです..次に、_$set_を使用して{ "_id" : ObjectId("4f6cd3c47156522f4f45b26f"),に基づいて本全体を置き換えます

これはここで何度も議論されています..ご覧ください

MapReduceを使用して重複レコードを削除

mongodbのインデックス付き列で重複を見つける高速な方法

http://csanz.posterous.com/look-for-duplicates-using-mongodb-mapreduce

http://www.mongodb.org/display/DOCS/MapReduce

MapReduceによってMongoDBの重複レコードを削除する方法は?

3
Baba

MongoDB 2.2以降、これを実現するために$unwind$group、および$projectステージで 集約フレームワーク を使用できます。

db.users.aggregate([{$unwind: '$favorites.books'},
                    {$group: {_id: '$_id',
                              books: {$addToSet: '$favorites.books'},
                              name: {$first: '$name'}}},
                    {$project: {'favorites.books': '$books', name: '$name'}}
                   ])

$project集計フィールドはネストできないため、$groupfavoritesフィールドの名前を変更する必要があることに注意してください。

33
kynan

最も簡単な解決策は、 setUnion (Mongo 2.6+)を使用することです。

db.users.aggregate([
    {'$addFields': {'favorites.books': {'$setUnion': ['$favorites.books', []]}}}
])

@kynanの answer のアイデアに基づいているが、他のすべてのフィールドを明示的に指定せずに保持する別の(より長い)バージョン(Mongo 3.4+):

> db.users.aggregate([
    {'$unwind': {
        'path': '$favorites.books',
        // output the document even if its list of books is empty
        'preserveNullAndEmptyArrays': true
    }},
    {'$group': {
        '_id': '$_id',
        'books': {'$addToSet': '$favorites.books'},
        // arbitrary name that doesn't exist on any document
        '_other_fields': {'$first': '$$ROOT'},
    }},
    {
      // the field, in the resulting document, has the value from the last document merged for the field. (c) docs
      // so the new deduped array value will be used
      '$replaceRoot': {'newRoot': {'$mergeObjects': ['$_other_fields', "$$ROOT"]}}
    },
    // this stage wouldn't be necessary if the field wasn't nested
    {'$addFields': {'favorites.books': '$books'}},
    {'$project': {'_other_fields': 0, 'books': 0}}
])

{ "_id" : ObjectId("4f6cd3c47156522f4f45b26f"), "name" : "robert", "favorites" : 
{ "books" : [ "The Art of Computer Programmning", "Graph Theory", "Algorithms in C++" ] } }    
8

Mongo 4.4以降、 $function 集計演算子を使用すると、カスタムjavascript関数を適用して、MongoDBクエリ言語でサポートされていない動作を実装できます。

たとえば、配列から重複を削除するには、次のようにします。

// {
//   "favorites" : { "books" : [
//     "Algorithms in C++",
//     "The Art of Computer Programming",
//     "Graph Theory",
//     "Algorithms in C++"
//   ]},
//   "name" : "robert"
// }
db.collection.aggregate(
  { $set:
    { "favorites.books":
      { $function: {
          body: function(books) { return books.filter((v, i, a) => a.indexOf(v) === i) },
          args: ["$favorites.books"],
          lang: "js"
      }}
    }
  }
)
// {
//   "favorites" : { "books" : [
//     "Algorithms in C++",
//     "The Art of Computer Programming",
//     "Graph Theory"
//   ]},
//   "name" : "robert"
// }

これには次の利点があります。

  • 配列の元の順序を維持します(それが要件でない場合は、 @ DennisGolomazovの$ setUnion回答 を優先します)
  • 高価な$unwindステージと$groupステージの組み合わせよりも効率的です。

$functionは3つのパラメータを取ります:

  • body。これは適用する関数であり、そのパラメーターは変更する配列です。
  • args。これには、body関数がパラメーターとして受け取るレコードのフィールドが含まれます。私たちの場合は"$favorites.books"です。
  • langは、body関数が記述されている言語です。現在、jsのみが利用可能です。
0
Xavier Guihot