ここで最も受け入れられている答えはこれです:
uniqueIds: { $addToSet: "$_id" },
また、IDのリストを含むuniqueIdsという新しいフィールドが返されます。しかし、フィールドとその数だけが必要な場合はどうでしょうか。次に、これになります:
db.collection.aggregate([
{$group: { _id: {name: "$name"},
count: {$sum: 1} } },
{$match: { count: {"$gt": 1} } }
]);
これを説明するために、MySQLやPostgreSQLなどのSQLデータベースを使用している場合は、GROUP BYステートメントで機能する集計関数(COUNT()、SUM()、MIN()、MAX()など)に慣れています。たとえば、列の値がテーブルに表示される合計数を検索します。
SELECT COUNT(*), my_type FROM table GROUP BY my_type;
+----------+-----------------+
| COUNT(*) | my_type |
+----------+-----------------+
| 3 | Contact |
| 1 | Practice |
| 1 | Prospect |
| 1 | Task |
+----------+-----------------+
ご覧のとおり、出力には、各my_type値が表示されるカウントが表示されます。MongoDBで重複を見つけるには、同様の方法で問題に取り組みます。MongoDBは、複数のドキュメントの値をグループ化する集計操作を誇り、グループ化されたデータに対してさまざまな操作を実行して単一の結果を返すことができます。これは、SQLで関数を集約するのと同様の概念です。
連絡先と呼ばれるコレクションを想定すると、初期設定は次のようになります。
db.contacts.aggregate([ ... ]);
この集計関数は集計演算子の配列を取ります。この場合、目標はフィールドの数、つまりフィールド値の出現回数でデータをグループ化することであるため、$group演算子が必要です。
db.contacts.aggregate([
{$group: {
_id: {name: "$name"}
}
}
]);
このアプローチには少し特異性があります。group by演算子を使用するには、_idフィールドが必要です。この場合、$nameフィールドをグループ化しています。_id内のキー名には、任意の名前を付けることができます。ただし、ここでは直感的であるため、名前を使用します。
$ group演算子のみを使用して集計を実行すると、すべての名前フィールドのリストが取得されます(コレクションに1回または複数回表示されているかどうかに関係なく)。
db.contacts.aggregate([
{$group: {
_id: {name: "$name"}
}
}
]);
{ "_id" : { "name" : "John" } }
{ "_id" : { "name" : "Joan" } }
{ "_id" : { "name" : "Stephen" } }
{ "_id" : { "name" : "Rod" } }
{ "_id" : { "name" : "Albert" } }
{ "_id" : { "name" : "Amanda" } }
上記の集計の仕組みに注意してください。名前フィールドを持つドキュメントを取得し、抽出された名前フィールドの新しいコレクションを返します。
しかし、知りたいのは、フィールド値が何回再表示されるかです。$ group演算子は、$ sum演算子を使用して、グループ内の各ドキュメントの合計に式1を追加するカウントフィールドを取ります。したがって、$groupと$sumを一緒に使用すると、特定のフィールド(名前など)の結果として得られるすべての数値の合計が返されます。
db.contacts.aggregate([
{$group: {
_id: {name: "$name"},
count: {$sum: 1}
}
}
]);
{ "_id" : { "name" : "John" }, "count" : 1 }
{ "_id" : { "name" : "Joan" }, "count" : 3 }
{ "_id" : { "name" : "Stephen" }, "count" : 2 }
{ "_id" : { "name" : "Rod" }, "count" : 3 }
{ "_id" : { "name" : "Albert" }, "count" : 2 }
{ "_id" : { "name" : "Amanda" }, "count" : 1 }
目標は重複を排除することだったので、1つの追加ステップが必要です。複数のカウントを持つグループのみを取得するには、$match演算子を使用して結果をフィルター処理できます。$ match演算子内で、カウントフィールドを確認し、「より大きい」を表す$gt演算子と数値1を使用して1より大きいカウントを検索するように指示します。
db.contacts.aggregate([
{$group: { _id: {name: "$name"},
count: {$sum: 1} } },
{$match: { count: {"$gt": 1} } }
]);
{ "_id" : { "name" : "Joan" }, "count" : 3 }
{ "_id" : { "name" : "Stephen" }, "count" : 2 }
{ "_id" : { "name" : "Rod" }, "count" : 3 }
{ "_id" : { "name" : "Albert" }, "count" : 2 }
ちなみに、Mongoid for RubyなどのORMを介してMongoDBを使用している場合は、次のエラーが発生する可能性があります。
The 'cursor' option is required, except for aggregate with the explain argument
これは、ORMが古く、MongoDBがサポートしなくなった操作を実行していることを意味している可能性があります。したがって、ORMを更新するか、修正を見つけてください。Mongoidの場合、これは私にとっての修正でした。
module Moped
class Collection
# Mongo 3.6 requires a `cursor` option be passed as part of aggregate queries. This overrides
# `Moped::Collection#aggregate` to include a cursor, which is not provided by Moped otherwise.
#
# Per the [MongoDB documentation](https://docs.mongodb.com/manual/reference/command/aggregate/):
#
# Changed in version 3.6: MongoDB 3.6 removes the use of `aggregate` command *without* the `cursor` option unless
# the command includes the `explain` option. Unless you include the `explain` option, you must specify the
# `cursor` option.
#
# To indicate a cursor with the default batch size, specify `cursor: {}`.
#
# To indicate a cursor with a non-default batch size, use `cursor: { batchSize: <num> }`.
#
def aggregate(*pipeline)
# Ordering of keys apparently matters to Mongo -- `aggregate` has to come before `cursor` here.
extract_result(session.command(aggregate: name, pipeline: pipeline.flatten, cursor: {}))
end
private
def extract_result(response)
response.key?("cursor") ? response["cursor"]["firstBatch"] : response["result"]
end
end
end