programing

키 필드로 MongoDB 컬렉션의 모든 중복 문서 찾기

closeapi 2023. 5. 21. 11:29
반응형

키 필드로 MongoDB 컬렉션의 모든 중복 문서 찾기

제가 어떤 문서들이 있는 수집품을 가지고 있다고 가정해보죠. 이런 것들 말입니다.

{ "_id" : ObjectId("4f127fa55e7242718200002d"), "id":1, "name" : "foo"}
{ "_id" : ObjectId("4f127fa55e7242718200002d"), "id":2, "name" : "bar"}
{ "_id" : ObjectId("4f127fa55e7242718200002d"), "id":3, "name" : "baz"}
{ "_id" : ObjectId("4f127fa55e7242718200002d"), "id":4, "name" : "foo"}
{ "_id" : ObjectId("4f127fa55e7242718200002d"), "id":5, "name" : "bar"}
{ "_id" : ObjectId("4f127fa55e7242718200002d"), "id":6, "name" : "bar"}

이 컬렉션에서 중복된 모든 항목을 "이름" 필드로 찾습니다.예: "foo"는 두 번 나타나고 "bar"는 세 번 나타납니다.

승인된 답변은 대량 수집에서 매우 느리며, 반환하지 않습니다._id중복 기록의 s.

집계 속도가 훨씬 빠르며 데이터를 복구할 수 있습니다._ids:

db.collection.aggregate([
  { $group: {
    _id: { name: "$name" },   // replace `name` here twice
    uniqueIds: { $addToSet: "$_id" },
    count: { $sum: 1 } 
  } }, 
  { $match: { 
    count: { $gte: 2 } 
  } },
  { $sort : { count : -1} },
  { $limit : 10 }
]);

집계 파이프라인의 첫 번째 단계에서 $group 연산자는 문서를 다음과 같이 집계합니다.name에 있는 및 uniqueIds_id그룹화된 레코드의 값입니다.$sum 연산자는 전달된 필드의 값을 합산합니다. 이 경우 상수는1 그룹화된 를 따서그레수계를산다합니의코드라된으로 계산합니다.count밭.밭.

파이프라인의 두 번째 단계에서는 $match를 사용하여 문서를 필터링합니다.count최소 2개, 즉 중복.

그런 다음 가장 빈번한 중복 항목을 먼저 정렬하고 결과를 상위 10개로 제한합니다.

이쿼는다출력니다됩값까지 됩니다.$limit이름과 중복된 이름을 가진 _id예:

{
  "_id" : {
    "name" : "Toothpick"
},
  "uniqueIds" : [
    "xzuzJd2qatfJCSvkN",
    "9bpewBsKbrGBQexv4",
    "fi3Gscg9M64BQdArv",
  ],
  "count" : 3
},
{
  "_id" : {
    "name" : "Broom"
  },
  "uniqueIds" : [
    "3vwny3YEj2qBsmmhA",
    "gJeWGcuX6Wk69oFYD"
  ],
  "count" : 2
}

참고: 이 솔루션은 가장 이해하기 쉽지만 가장 좋은 솔루션은 아닙니다.

를 사용하여 문서에 특정 필드가 포함된 횟수를 확인할 수 있습니다.

var map = function(){
   if(this.name) {
        emit(this.name, 1);
   }
}

var reduce = function(key, values){
    return Array.sum(values);
}

var res = db.collection.mapReduce(map, reduce, {out:{ inline : 1}});
db[res.result].find({value: {$gt: 1}}).sort({value: -1});

일반적인 Mongo 솔루션의 경우, MongoDB cookbook recipe를 참조하십시오. 를 사용하여 중복 항목을 찾을 수 있습니다. 집계가 더 빠르고 강력하다는 점에 유의하십시오._id중복 기록의 s.

의 경우 (mapReduce를 사용하여) 허용된 답변은 그다지 효율적이지 않습니다.대신 그룹 방법을 사용할 수 있습니다.

$connection = 'mongodb://localhost:27017';
$con        = new Mongo($connection); // mongo db connection

$db         = $con->test; // database 
$collection = $db->prb; // table

$keys       = array("name" => 1); Select name field, group by it

// set intial values
$initial    = array("count" => 0);

// JavaScript function to perform
$reduce     = "function (obj, prev) { prev.count++; }";

$g          = $collection->group($keys, $initial, $reduce);

echo "<pre>";
print_r($g);

출력은 다음과 같습니다.

Array
(
    [retval] => Array
        (
            [0] => Array
                (
                    [name] => 
                    [count] => 1
                )

            [1] => Array
                (
                    [name] => MongoDB
                    [count] => 2
                )

        )

    [count] => 3
    [keys] => 2
    [ok] => 1
)

는 다음과 SELECT name, COUNT(name) FROM prb GROUP BY name여전히 배열에서 카운트가 0인 요소를 필터링해야 합니다.다시 한 번 MongoDB cookbook recipe를 참조하여 중복 항목을 찾으십시오. 다음을 사용하여 표준 솔루션을 사용합니다.group.

집계 파이프라인 프레임워크를 사용하여 중복된 키 값을 가진 문서를 쉽게 식별할 수 있습니다.

// Desired unique index: 
// db.collection.ensureIndex({ firstField: 1, secondField: 1 }, { unique: true})

db.collection.aggregate([
  { $group: { 
    _id: { firstField: "$firstField", secondField: "$secondField" }, 
    uniqueIds: { $addToSet: "$_id" },
    count: { $sum: 1 } 
  }}, 
  { $match: { 
    count: { $gt: 1 } 
  }}
])

참고: 공식 mongo lab 블로그의 유용한 정보:

https://blog.mlab.com/2014/03/finding-duplicate-keys-with-the-mongodb-aggregation-framework

여기서 가장 많이 인정되는 답변은 다음과 같습니다.

uniqueIds: { $addToSet: "$_id" },

그러면 ID 목록이 있는 uniqueIds라는 새 필드가 반환됩니다.하지만 당신이 필드와 카운트만 원한다면요?그러면 다음과 같습니다.

db.collection.aggregate([ 
  {$group: { _id: {name: "$name"}, 
             count: {$sum: 1} } }, 
  {$match: { count: {"$gt": 1} } } 
]);

이를 설명하자면, MySQL 및 Postgre와 같은 SQL 데이터베이스에서 온 경우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"} 
        } 
    }
]);

이 접근법에는 약간 특이한 점이 있습니다.연산자별 그룹을 사용하려면 _id 필드가 필요합니다.이 경우 $name 필드를 그룹화합니다._id 내의 키 이름은 임의의 이름을 가질 수 있습니다.하지만 여기서는 직관적이기 때문에 이름을 사용합니다.

$group 연산자만 사용하여 집계를 실행하면 모든 이름 필드의 목록을 얻을 수 있습니다(수집에 한 번 이상 나타나든 상관 없음).

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 }

중복을 제거하는 것이 목표였기 때문에 한 단계 더 필요합니다.카운트가 둘 이상인 그룹만 가져오려면 $match 연산자를 사용하여 결과를 필터링할 수 있습니다.$match 연산자 내에서 카운트 필드를 보고 "greater than"을 나타내는 $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 }

참고로 MongoDB를 사용하여 MongoDB for Ruby와 같은 ORM을 사용하는 경우 다음 오류가 발생할 수 있습니다.

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

언급URL : https://stackoverflow.com/questions/9491920/find-all-duplicate-documents-in-a-mongodb-collection-by-a-key-field

반응형