web-dev-qa-db-fra.com

MongoDB agrégé par champ existe

J'ai du mal à croire que cette question n'a pas été posée et que l'on y ait déjà répondu quelque part, mais je ne trouve aucune trace de celle-ci.

J'ai une requête d'agrégation MongoDB qui doit être groupée par un booléen: l'existence d'un autre champ.

Par exemple, commençons par cette collection:

> db.test.find()
{ "_id" : ObjectId("53fbede62827b89e4f86c12e"),
  "field" : ObjectId("53fbede62827b89e4f86c12d"), "name" : "Erik" }
{ "_id" : ObjectId("53fbee002827b89e4f86c12f"), "name" : "Erik" }
{ "_id" : ObjectId("53fbee092827b89e4f86c131"),
  "field" : ObjectId("53fbee092827b89e4f86c130"), "name" : "John" }
{ "_id" : ObjectId("53fbee122827b89e4f86c132"), "name" : "Ben" }

2 documents ont un "champ" et 2 ne le .. .. Notez que chaque valeur de "champ" peut être différente; nous voulons juste grouper sur son existence (ou la non-nullité fonctionne pour moi aussi, je n'ai aucune valeur NULL stockée).

J'ai essayé d'utiliser $ project, mais $ n'existe pas là-bas, et $ cond et $ ifNull ne m'ont pas aidé. Le champ semble toujours exister, même s'il ne le fait pas:

> db.test.aggregate(
  {$project:{fieldExists:{$cond:[{$eq:["$field", null]}, false, true]}}},
  {$group:{_id:"$fieldExists", count:{$sum:1}}}
)
{ "_id" : true, "count" : 4 }

Je m'attendrais à ce que l'agrégat beaucoup plus simple suivant fonctionne, mais pour une raison quelconque, $ existe n'est pas pris en charge de cette manière:

> db.test.aggregate({$group:{_id:{$exists:"$field"}, count:{$sum:1}}})
assert: command failed: {
  "errmsg" : "exception: invalid operator '$exists'",
  "code" : 15999,
  "ok" : 0
} : aggregate failed
Error: command failed: {
  "errmsg" : "exception: invalid operator '$exists'",
  "code" : 15999,
  "ok" : 0
} : aggregate failed
    at Error (<anonymous>)
    at doassert (src/mongo/Shell/assert.js:11:14)
    at Function.assert.commandWorked (src/mongo/Shell/assert.js:244:5)
    at DBCollection.aggregate (src/mongo/Shell/collection.js:1149:12)
    at (Shell):1:9
2014-08-25T19:19:42.344-0700 Error: command failed: {
  "errmsg" : "exception: invalid operator '$exists'",
  "code" : 15999,
  "ok" : 0
} : aggregate failed at src/mongo/Shell/assert.js:13

Est-ce que quelqu'un sait comment obtenir le résultat souhaité d'une collection comme celle-ci?

Résultat attendu:

{ "_id" : true, "count" : 2 }
{ "_id" : false, "count" : 2 }
31
Erik

J'ai résolu le même problème la nuit dernière, de cette façon:

> db.test.aggregate({$group:{_id:{$gt:["$field", null]}, count:{$sum:1}}})
{ "_id" : true, "count" : 2 }
{ "_id" : false, "count" : 2 }

Voir http://docs.mongodb.org/manual/reference/bson-types/#bson-types-comparison-order pour une explication complète de la procédure.

58
kdkeck

L'opérateur $exists est un opérateur "de requête". Il est donc essentiellement utilisé pour "filtrer" les résultats plutôt que pour identifier une condition logique.

En tant qu'opérateur "logique", la structure d'agrégation prend en charge l'opérateur $ifNull . Cela retourne la valeur du champ où il existe ou la valeur de remplacement fournie où il n’est pas ou autrement évalué à null

db.test.aggregate([
    { "$group": {
        "_id": { "$ifNull": [ "$field", false ] },
        "count": { "$sum": 1 }
    }}
])

Bien entendu, même si ce n'est pas une comparaison "vrai/faux", à moins que vous ne vouliez réellement renvoyer la valeur réelle du champ où il est présent, vous êtes probablement mieux avec un $cond statement un peu comme vous avez:

db.test.aggregate([
    { "$group": {
        "_id": { "$cond": [{ "$eq": [ "$field", null ] }, true, false ] },
        "count": { "$sum": 1 }
    }}
])

$ifNull peut être très utile, c'est pour remplacer des champs de tableau inexistants qui sinon provoqueraient une erreur en utilisant $unwind. Vous pouvez ensuite faire quelque chose comme retourner un seul élément ou un tableau vide afin que cela ne pose pas de problème pour le reste du traitement de votre pipeline.

6
Neil Lunn

Je l'ai résolu avec la vérification de non défini

$ne : [$var_to_check, undefined]

ou

$ne:  [ { $type : "$var_to_check"}, 'missing'] }

Cela retourne vrai si la var est définie

5
Delcon

Ma réponse est:

{'$project': {
    'field_exists': {'$or': [
        {'$eq': ['$field', null]}, 
        {'$gt': ['$field', null]},
    ]},
}}

Voici les détails. $ existe signifie que le champ existe, même s'il s'agit de null ou de toute autre valeur vide. C'est pourquoi toutes les réponses sur cette page sont incorrectes.

Essayons un peu. Vérifie ça:

// Let's take any collection that have docs
db.getCollection('collection').aggregate([
  // Get arbitrary doc, no matter which, we won't use it
  {"$limit": 1},
  // Project our own fields (just create them with $literal)
  {'$project': {
    '_id': 0,
    'null_field': {'$literal': null},
    'not_null_field': {'$literal': {}},
  }},
])

Nous aurons ceci:

{
    "null_field" : null,
    "not_null_field" : {}
}

Ensuite, clarifions quels champs existent dans cette doc:

  1. null_field - existe
  2. not_null_field - existe
  3. non_existent_field - pas.

D'accord, il est temps de tester l'étape du projet que j'ai mentionnée ci-dessus. Ajoutons-le pour chaque domaine qui nous intéresse:

{'$project': {
    'null_field_exists': {'$or': [
        {'$eq': ['$null_field', null]}, 
        {'$gt': ['$null_field', null]},
    ]},
    'not_null_field_exists': {'$or': [
        {'$eq': ['$not_null_field', null]}, 
        {'$gt': ['$not_null_field', null]},
    ]},
    'non_existent_field_exists': {'$or': [
        {'$eq': ['$non_existent_field', null]}, 
        {'$gt': ['$non_existent_field', null]},
    ]},
}},

Ce que nous obtenons est:

{
    "null_field_exists" : true,
    "not_null_field_exists" : true,
    "non_existent_field_exists" : false
}

Correct!

Et une petite remarque: nous utilisons null pour comparer car il s’agit de la plus petite valeur au moins précieuse (plus petite est simplement la non-existence).

1
egvo

Je ne sais pas comment c'était, mais en 2019, il existe une solution propre. Dans le pipeline d'agrégation faire ceci

$match: {"my_field": {$ne: null}}

La bonne chose est dans mon langage 'ne' signifie pas :)

0
djuleAyo