Question

Supposons que j'ai une collection avec un ensemble de documents. quelque chose comme ça.

{ "_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"}

Je veux trouver toutes les entrées dupliquées de cette collection par le champ "Nom". Par exemple, "Foo" apparaît deux fois et "Bar" apparaît 3 fois.

Était-ce utile?

La solution

Remarque: Cette solution est la plus facile à comprendre, mais pas la meilleure.

Vous pouvez utiliser mapReduce Pour savoir combien de fois un document contient un certain champ:

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});

Autres conseils

La réponse acceptée est terriblement lente sur les grandes collections et ne renvoie pas le _ids des enregistrements en double.

L'agrégation est beaucoup plus rapide et peut retourner le _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 }
]);

Dans la première étape du pipeline d'agrégation, le $ groupel'opérateur agréga les documents par le name champ et stores dans uniqueIds chaque _id Valeur des enregistrements groupés. La $ somme L'opérateur additionne les valeurs des champs qui lui sont transmis, dans ce cas la constante 1 - comptant ainsi le nombre d'enregistrements groupés dans le count champ.

Dans la deuxième étape du pipeline, nous utilisons $ matchpour filtrer les documents avec un count d'au moins 2, c'est-à-dire des doublons.

Ensuite, nous trierons d'abord les doublons les plus fréquents et limitons les résultats au top 10.

Cette requête sortira jusqu'à $limit enregistrements avec des noms en double, ainsi que leur _ids. Par exemple:

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

Pour une solution générique mongo, voir le Recette de livre de cuisine MongoDB pour trouver des doublons en utilisant group. Notez que l'agrégation est plus rapide et plus puissante en ce qu'elle peut retourner _ids des enregistrements en double.

Pour , la réponse acceptée (en utilisant MapReduce) n'est pas si efficace. Au lieu de cela, nous pouvons utiliser le groupe méthode:

$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);

La sortie sera la suivante:

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

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

        )

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

La requête SQL équivalente serait: SELECT name, COUNT(name) FROM prb GROUP BY name. Notez que nous devons encore filtrer les éléments avec un décompte de 0 dans le tableau. Encore une fois, reportez-vous au Recette de livre de cuisine MongoDB pour trouver des doublons en utilisant group pour la solution canonique en utilisant group.

J'ai trouvé des informations utiles sur le blog officiel de Mongo Lab:http://blog.mongolab.com/2014/03/finding-duplicate-keys-with-the-mongodb-aggregation-ramework/

La réponse la plus acceptée ici a ceci:

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

Cela vous reviendrait également un nouveau champ appelé UNIQUEID avec une liste d'ID. Mais que se passe-t-il si vous voulez juste le champ et son décompte? Ensuite, ce serait ceci:

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

Pour expliquer cela, si vous venez de bases de données SQL comme MySQL et PostgreSQL, vous êtes habitué à des fonctions agrégées (par exemple Count (), Sum (), Min (), Max ()) qui fonctionnent avec le groupe par déclaration permettant, pour Exemple, pour trouver le nombre total d'une valeur de colonne apparaît dans un tableau.

SELECT COUNT(*), my_type FROM table GROUP BY my_type;
+----------+-----------------+
| COUNT(*) | my_type         |
+----------+-----------------+
|        3 | Contact         |
|        1 | Practice        |
|        1 | Prospect        |
|        1 | Task            |
+----------+-----------------+

Comme vous pouvez le voir, notre sortie montre le nombre que chaque valeur my_type apparaît. Pour trouver des doublons dans MongoDB, nous aborderions le problème de la même manière. MongoDB possède des opérations d'agrégation, qui groupent la valeur de plusieurs documents ensemble, et peuvent effectuer une variété d'opérations sur les données groupées pour renvoyer un seul résultat. C'est un concept similaire aux fonctions agrégées dans SQL.

En supposant une collection appelée Contacts, la configuration initiale semble comme suit:

db.contacts.aggregate([ ... ]);

Cette fonction globale prend un tableau d'opérateurs d'agrégation, et dans notre cas, nous désirons l'opérateur de groupe $, car notre objectif est de regrouper les données par le dénombrement du domaine, c'est-à-dire le nombre d'occasions de la valeur du champ.

db.contacts.aggregate([  
    {$group: { 
        _id: {name: "$name"} 
        } 
    }
]);

Il y a un peu d'idiosyncratie à cette approche. Le champ _ID est nécessaire pour utiliser le groupe par opérateur. Dans ce cas, nous regroupons le champ $ Name. Le nom de clé dans _ID peut avoir n'importe quel nom. Mais nous utilisons le nom car il est intuitif ici.

En exécutant l'agrégation en utilisant uniquement l'opérateur de groupe $, nous obtiendrons une liste de tous les champs de noms (indépendamment s'ils apparaissent une ou plus d'une fois dans la collection):

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" } }

Remarquez ci-dessus comment fonctionne l'agrégation. Il a pris des documents avec des champs de noms et renvoie une nouvelle collection des champs de nom extraits.

Mais ce que nous voulons savoir, c'est combien de fois la valeur du champ réapparaît. L'opérateur de groupe $ prend un champ de décompte qui utilise l'opérateur $ SUM pour ajouter l'expression 1 au total pour chaque document du groupe. Ainsi, le groupe $ et $ SUM renvoie ensemble la somme collective de toutes les valeurs numériques qui résultent pour un champ donné (par exemple le nom).

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 }

Étant donné que l'objectif était d'éliminer les doublons, il nécessite une étape supplémentaire. Pour obtenir uniquement les groupes qui ont un compte de plus d'un, nous pouvons utiliser l'opérateur de correspondance $ pour filtrer nos résultats. Au sein de l'opérateur $ Match, nous lui dirons de regarder le champ Count et de lui dire de rechercher des comptes supérieurs à un en utilisant l'opérateur $ GT représentant "supérieur à" et le numéro 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 }

En guise de note, si vous utilisez MongoDB via un orm comme mongoïde pour Ruby, vous pourriez obtenir cette erreur:

The 'cursor' option is required, except for aggregate with the explain argument 

Cela signifie très probablement que votre ORM est obsolète et effectue des opérations que MongoDB ne prend plus en charge. Par conséquent, mettez à jour votre ORM ou trouvez un correctif. Pour Mongoid, c'était la solution pour moi:

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
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top