Rechercher les enregistrements MongoDB où le champ du tableau n'est pas vide

503

Tous mes enregistrements ont un champ appelé "images". Ce champ est un tableau de chaînes.

Je veux maintenant les 10 derniers enregistrements où ce tableau N'EST PAS vide.

J'ai fait des recherches sur Google, mais curieusement, je n'ai pas trouvé grand-chose à ce sujet. J'ai lu l'option $ where, mais je me demandais à quel point cela était lent pour les fonctions natives et s'il y avait une meilleure solution.

Et même alors, cela ne fonctionne pas:

ME.find({$where: 'this.pictures.length > 0'}).sort('-created').limit(10).execFind()

Ne renvoie rien. Laisser this.picturessans le bit de longueur fonctionne, mais cela renvoie également des enregistrements vides, bien sûr.

skerit
la source

Réponses:

831

Si vous avez également des documents qui n'ont pas la clé, vous pouvez utiliser:

ME.find({ pictures: { $exists: true, $not: {$size: 0} } })

MongoDB n'utilise pas d'index si $ size est impliqué, voici donc une meilleure solution:

ME.find({ pictures: { $exists: true, $ne: [] } })

Depuis la sortie de MongoDB 2.6, vous pouvez comparer avec l'opérateur $gtmais pourrait conduire à des résultats inattendus (vous pouvez trouver une explication détaillée dans cette réponse ):

ME.find({ pictures: { $gt: [] } })
Chris '
la source
6
Pour moi, c'est l'approche correcte, car elle garantit que le tableau existe et n'est pas vide.
LeandroCR
Comment puis-je obtenir les mêmes fonctionnalités en utilisantmongoengine
Rohit Khatri
54
ATTENTION, ME.find({ pictures: { $gt: [] } })EST DANGEREUX, même dans les nouvelles versions de MongoDB. Si vous avez un index sur votre champ de liste et que cet index est utilisé pendant la requête, vous obtiendrez des résultats inattendus. Par exemple: db.doc.find({'nums': { $gt: [] }}).hint({ _id: 1 }).count()retourne le bon numéro, tandis que db.doc.find({'nums': { $gt: [] }}).hint({ nums: 1 }).count()retourne 0.
wojcikstefan
1
Voir ma réponse détaillée ci-dessous pour savoir pourquoi cela pourrait ne pas fonctionner pour vous: stackoverflow.com/a/42601244/1579058
wojcikstefan
6
Le commentaire de @ wojcikstefan doit être surévalué pour empêcher les gens d'utiliser la dernière suggestion qui, en effet, dans certaines circonstances, ne renvoie pas les documents correspondants.
Thomas Jung
181

Après un peu plus de recherche, en particulier dans les documents mongodb, et des morceaux déroutants ensemble, ce fut la réponse:

ME.find({pictures: {$exists: true, $not: {$size: 0}}})
skerit
la source
27
Ça ne marche pas. Je ne sais pas si cela a fonctionné auparavant, mais cela retournera également des objets qui n'ont pas la clé «images».
rdsoze
17
Incroyable de voir comment cette réponse a 63 votes positifs, alors qu'en fait ce que @rdsoze a dit est vrai - la requête retournera également des enregistrements qui n'ont pas le pictureschamp.
Dan Dascalescu
5
Attention, mongoDB n'utilisera pas d'index si $ size est un lien impliqué . Il serait préférable d'inclure {$ ne: []} et éventuellement {$ ne: null}.
Levente Dobson
17
@rdsoze la toute première ligne de la question indique "Tous mes enregistrements ont un champ appelé" images ". Ce champ est un tableau" . De plus, il s'agit d'un scénario parfaitement réaliste et courant. Cette réponse n'est pas fausse, elle fonctionne pour la question telle qu'elle est écrite, et la critiquer ou la voter pour le fait qu'elle ne résout pas un problème différent est idiot.
Mark Amery
1
@Cec La documentation indique que si vous utilisez $ size dans la requête, il n'utilisera aucun index pour vous donner des résultats plus rapides. Donc, si vous avez un index sur ce champ et que vous souhaitez l'utiliser, respectez d'autres approches comme {$ ne: []}, si cela fonctionne pour vous, cela utilisera votre index.
Levente Dobson du
108

Cela pourrait également fonctionner pour vous:

ME.find({'pictures.0': {$exists: true}});
tenbatsu
la source
2
Agréable! Cela vous permet également de vérifier une taille minimale. Savez-vous si les tableaux sont toujours indexés séquentiellement? Y aurait-il jamais un cas où il pictures.2existe mais qui pictures.1n'existe pas?
anushr
2
L' $existsopérateur est un booléen, pas un offset. @tenbatsu devrait être utilisé à la trueplace de 1.
ekillaby
2
@anushr Would there ever be a case where pictures.2 exists but pictures.1 does not? Oui, ce cas pourrait se produire.
Le Bndr
@TheBndr Cela ne peut arriver que s'il picturess'agit d'un sous-document, pas d'un tableau. par exemplepictures: {'2': 123}
JohnnyHK
4
C'est agréable et intuitif, mais attention si les performances sont importantes - cela fera une analyse complète de la collection même si vous avez un index pictures.
wojcikstefan
35

Vous vous souciez de deux choses lorsque vous interrogez - la précision et les performances. Dans cet esprit, j'ai testé quelques approches différentes dans MongoDB v3.0.14.

TL; DR db.doc.find({ nums: { $gt: -Infinity }})est le plus rapide et le plus fiable (au moins dans la version MongoDB que j'ai testée).

EDIT: Cela ne fonctionne plus dans MongoDB v3.6! Voir les commentaires sous ce post pour une solution potentielle.

Installer

J'ai inséré 1 000 documents sans champ de liste, 1 000 documents avec une liste vide et 5 documents avec une liste non vide.

for (var i = 0; i < 1000; i++) { db.doc.insert({}); }
for (var i = 0; i < 1000; i++) { db.doc.insert({ nums: [] }); }
for (var i = 0; i < 5; i++) { db.doc.insert({ nums: [1, 2, 3] }); }
db.doc.createIndex({ nums: 1 });

Je reconnais que ce n'est pas une échelle suffisante pour prendre les performances aussi au sérieux que dans les tests ci-dessous, mais cela suffit pour présenter l'exactitude des diverses requêtes et le comportement des plans de requête choisis.

Les tests

db.doc.find({'nums': {'$exists': true}}) renvoie des résultats erronés (pour ce que nous essayons d'accomplir).

MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': {'$exists': true}}).count()
1005

-

db.doc.find({'nums.0': {'$exists': true}})renvoie des résultats corrects, mais il est également lent à l'aide d'une analyse complète de la collection ( COLLSCANétape de notification dans l'explication).

MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums.0': {'$exists': true}}).count()
5
MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums.0': {'$exists': true}}).explain()
{
  "queryPlanner": {
    "plannerVersion": 1,
    "namespace": "test.doc",
    "indexFilterSet": false,
    "parsedQuery": {
      "nums.0": {
        "$exists": true
      }
    },
    "winningPlan": {
      "stage": "COLLSCAN",
      "filter": {
        "nums.0": {
          "$exists": true
        }
      },
      "direction": "forward"
    },
    "rejectedPlans": [ ]
  },
  "serverInfo": {
    "host": "MacBook-Pro",
    "port": 27017,
    "version": "3.0.14",
    "gitVersion": "08352afcca24bfc145240a0fac9d28b978ab77f3"
  },
  "ok": 1
}

-

db.doc.find({'nums': { $exists: true, $gt: { '$size': 0 }}})renvoie des résultats erronés. C'est à cause d'une analyse d'index non valide qui ne fait avancer aucun document. Il sera probablement précis mais lent sans l'index.

MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $exists: true, $gt: { '$size': 0 }}}).count()
0
MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $exists: true, $gt: { '$size': 0 }}}).explain('executionStats').executionStats.executionStages
{
  "stage": "KEEP_MUTATIONS",
  "nReturned": 0,
  "executionTimeMillisEstimate": 0,
  "works": 2,
  "advanced": 0,
  "needTime": 0,
  "needFetch": 0,
  "saveState": 0,
  "restoreState": 0,
  "isEOF": 1,
  "invalidates": 0,
  "inputStage": {
    "stage": "FETCH",
    "filter": {
      "$and": [
        {
          "nums": {
            "$gt": {
              "$size": 0
            }
          }
        },
        {
          "nums": {
            "$exists": true
          }
        }
      ]
    },
    "nReturned": 0,
    "executionTimeMillisEstimate": 0,
    "works": 1,
    "advanced": 0,
    "needTime": 0,
    "needFetch": 0,
    "saveState": 0,
    "restoreState": 0,
    "isEOF": 1,
    "invalidates": 0,
    "docsExamined": 0,
    "alreadyHasObj": 0,
    "inputStage": {
      "stage": "IXSCAN",
      "nReturned": 0,
      "executionTimeMillisEstimate": 0,
      "works": 1,
      "advanced": 0,
      "needTime": 0,
      "needFetch": 0,
      "saveState": 0,
      "restoreState": 0,
      "isEOF": 1,
      "invalidates": 0,
      "keyPattern": {
        "nums": 1
      },
      "indexName": "nums_1",
      "isMultiKey": true,
      "direction": "forward",
      "indexBounds": {
        "nums": [
          "({ $size: 0.0 }, [])"
        ]
      },
      "keysExamined": 0,
      "dupsTested": 0,
      "dupsDropped": 0,
      "seenInvalidated": 0,
      "matchTested": 0
    }
  }
}

-

db.doc.find({'nums': { $exists: true, $not: { '$size': 0 }}})renvoie des résultats corrects, mais les performances sont mauvaises. Techniquement, il effectue un scan d'index, mais il avance toujours tous les documents et doit ensuite les filtrer).

MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $exists: true, $not: { '$size': 0 }}}).count()
5
MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $exists: true, $not: { '$size': 0 }}}).explain('executionStats').executionStats.executionStages
{
  "stage": "KEEP_MUTATIONS",
  "nReturned": 5,
  "executionTimeMillisEstimate": 0,
  "works": 2016,
  "advanced": 5,
  "needTime": 2010,
  "needFetch": 0,
  "saveState": 15,
  "restoreState": 15,
  "isEOF": 1,
  "invalidates": 0,
  "inputStage": {
    "stage": "FETCH",
    "filter": {
      "$and": [
        {
          "nums": {
            "$exists": true
          }
        },
        {
          "$not": {
            "nums": {
              "$size": 0
            }
          }
        }
      ]
    },
    "nReturned": 5,
    "executionTimeMillisEstimate": 0,
    "works": 2016,
    "advanced": 5,
    "needTime": 2010,
    "needFetch": 0,
    "saveState": 15,
    "restoreState": 15,
    "isEOF": 1,
    "invalidates": 0,
    "docsExamined": 2005,
    "alreadyHasObj": 0,
    "inputStage": {
      "stage": "IXSCAN",
      "nReturned": 2005,
      "executionTimeMillisEstimate": 0,
      "works": 2015,
      "advanced": 2005,
      "needTime": 10,
      "needFetch": 0,
      "saveState": 15,
      "restoreState": 15,
      "isEOF": 1,
      "invalidates": 0,
      "keyPattern": {
        "nums": 1
      },
      "indexName": "nums_1",
      "isMultiKey": true,
      "direction": "forward",
      "indexBounds": {
        "nums": [
          "[MinKey, MaxKey]"
        ]
      },
      "keysExamined": 2015,
      "dupsTested": 2015,
      "dupsDropped": 10,
      "seenInvalidated": 0,
      "matchTested": 0
    }
  }
}

-

db.doc.find({'nums': { $exists: true, $ne: [] }})renvoie des résultats corrects et est légèrement plus rapide, mais les performances ne sont toujours pas idéales. Il utilise IXSCAN qui ne fait avancer les documents qu'avec un champ de liste existant, mais doit ensuite filtrer les listes vides une par une.

MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $exists: true, $ne: [] }}).count()
5
MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $exists: true, $ne: [] }}).explain('executionStats').executionStats.executionStages
{
  "stage": "KEEP_MUTATIONS",
  "nReturned": 5,
  "executionTimeMillisEstimate": 0,
  "works": 1018,
  "advanced": 5,
  "needTime": 1011,
  "needFetch": 0,
  "saveState": 15,
  "restoreState": 15,
  "isEOF": 1,
  "invalidates": 0,
  "inputStage": {
    "stage": "FETCH",
    "filter": {
      "$and": [
        {
          "$not": {
            "nums": {
              "$eq": [ ]
            }
          }
        },
        {
          "nums": {
            "$exists": true
          }
        }
      ]
    },
    "nReturned": 5,
    "executionTimeMillisEstimate": 0,
    "works": 1017,
    "advanced": 5,
    "needTime": 1011,
    "needFetch": 0,
    "saveState": 15,
    "restoreState": 15,
    "isEOF": 1,
    "invalidates": 0,
    "docsExamined": 1005,
    "alreadyHasObj": 0,
    "inputStage": {
      "stage": "IXSCAN",
      "nReturned": 1005,
      "executionTimeMillisEstimate": 0,
      "works": 1016,
      "advanced": 1005,
      "needTime": 11,
      "needFetch": 0,
      "saveState": 15,
      "restoreState": 15,
      "isEOF": 1,
      "invalidates": 0,
      "keyPattern": {
        "nums": 1
      },
      "indexName": "nums_1",
      "isMultiKey": true,
      "direction": "forward",
      "indexBounds": {
        "nums": [
          "[MinKey, undefined)",
          "(undefined, [])",
          "([], MaxKey]"
        ]
      },
      "keysExamined": 1016,
      "dupsTested": 1015,
      "dupsDropped": 10,
      "seenInvalidated": 0,
      "matchTested": 0
    }
  }
}

-

db.doc.find({'nums': { $gt: [] }})EST DANGEREUX PARCE QUE SELON L'INDICE UTILISÉ PEUT DONNER DES RÉSULTATS INATTENDUS. C'est à cause d'une analyse d'index invalide qui ne fait avancer aucun document.

MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $gt: [] }}).count()
0
MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $gt: [] }}).hint({ nums: 1 }).count()
0
MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $gt: [] }}).hint({ _id: 1 }).count()
5

MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $gt: [] }}).explain('executionStats').executionStats.executionStages
{
  "stage": "KEEP_MUTATIONS",
  "nReturned": 0,
  "executionTimeMillisEstimate": 0,
  "works": 1,
  "advanced": 0,
  "needTime": 0,
  "needFetch": 0,
  "saveState": 0,
  "restoreState": 0,
  "isEOF": 1,
  "invalidates": 0,
  "inputStage": {
    "stage": "FETCH",
    "filter": {
      "nums": {
        "$gt": [ ]
      }
    },
    "nReturned": 0,
    "executionTimeMillisEstimate": 0,
    "works": 1,
    "advanced": 0,
    "needTime": 0,
    "needFetch": 0,
    "saveState": 0,
    "restoreState": 0,
    "isEOF": 1,
    "invalidates": 0,
    "docsExamined": 0,
    "alreadyHasObj": 0,
    "inputStage": {
      "stage": "IXSCAN",
      "nReturned": 0,
      "executionTimeMillisEstimate": 0,
      "works": 1,
      "advanced": 0,
      "needTime": 0,
      "needFetch": 0,
      "saveState": 0,
      "restoreState": 0,
      "isEOF": 1,
      "invalidates": 0,
      "keyPattern": {
        "nums": 1
      },
      "indexName": "nums_1",
      "isMultiKey": true,
      "direction": "forward",
      "indexBounds": {
        "nums": [
          "([], BinData(0, ))"
        ]
      },
      "keysExamined": 0,
      "dupsTested": 0,
      "dupsDropped": 0,
      "seenInvalidated": 0,
      "matchTested": 0
    }
  }
}

-

db.doc.find({'nums.0’: { $gt: -Infinity }}) renvoie des résultats corrects, mais a de mauvaises performances (utilise une analyse complète de la collection).

MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums.0': { $gt: -Infinity }}).count()
5
MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums.0': { $gt: -Infinity }}).explain('executionStats').executionStats.executionStages
{
  "stage": "COLLSCAN",
  "filter": {
    "nums.0": {
      "$gt": -Infinity
    }
  },
  "nReturned": 5,
  "executionTimeMillisEstimate": 0,
  "works": 2007,
  "advanced": 5,
  "needTime": 2001,
  "needFetch": 0,
  "saveState": 15,
  "restoreState": 15,
  "isEOF": 1,
  "invalidates": 0,
  "direction": "forward",
  "docsExamined": 2005
}

-

db.doc.find({'nums': { $gt: -Infinity }})étonnamment, cela fonctionne très bien! Il donne les bons résultats et c'est rapide, en avançant 5 documents de la phase de scan d'index.

MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $gt: -Infinity }}).explain('executionStats').executionStats.executionStages
{
  "stage": "FETCH",
  "nReturned": 5,
  "executionTimeMillisEstimate": 0,
  "works": 16,
  "advanced": 5,
  "needTime": 10,
  "needFetch": 0,
  "saveState": 0,
  "restoreState": 0,
  "isEOF": 1,
  "invalidates": 0,
  "docsExamined": 5,
  "alreadyHasObj": 0,
  "inputStage": {
    "stage": "IXSCAN",
    "nReturned": 5,
    "executionTimeMillisEstimate": 0,
    "works": 15,
    "advanced": 5,
    "needTime": 10,
    "needFetch": 0,
    "saveState": 0,
    "restoreState": 0,
    "isEOF": 1,
    "invalidates": 0,
    "keyPattern": {
      "nums": 1
    },
    "indexName": "nums_1",
    "isMultiKey": true,
    "direction": "forward",
    "indexBounds": {
      "nums": [
        "(-inf.0, inf.0]"
      ]
    },
    "keysExamined": 15,
    "dupsTested": 15,
    "dupsDropped": 10,
    "seenInvalidated": 0,
    "matchTested": 0
  }
}
wojcikstefan
la source
Merci pour votre réponse très détaillée @wojcikstefan. Malheureusement, votre solution suggérée ne semble pas fonctionner dans mon cas. J'ai une collection MongoDB 3.6.4 avec des documents de 2m, la plupart d'entre eux ayant un seen_eventstableau String, qui est également indexé. En recherchant avec { $gt: -Infinity }, j'obtiens immédiatement 0 document. En utilisant, { $exists: true, $ne: [] }j'obtiens les documents les plus probables de 1,2 m, avec beaucoup de temps perdu dans l'étape FETCH: gist.github.com/N-Coder/b9e89a925e895c605d84bfeed648d82c
NCode
Il semble que vous ayez raison @Ncode - cela ne fonctionne plus dans MongoDB v3.6 :( J'ai joué avec pendant quelques minutes et voici ce que j'ai trouvé: 1. db.test_collection.find({"seen_events.0": {$exists: true}})est mauvais car il utilise un scan de collection. 2. db.test_collection.find({seen_events: {$exists: true, $ne: []}})est mauvais parce que son IXSCAN correspond à tous les documents, puis le filtrage est effectué dans la phase lente FETCH 3. Idem pour db.test_collection.find({seen_events: {$exists: true, $not: {$size: 0}}})4. Toutes les autres requêtes renvoient des résultats invalides
wojcikstefan
1
@NCode a trouvé une solution! Si vous êtes certain que tous les non-vides seen_eventscontiennent des chaînes, vous pouvez utiliser ceci: db.test_collection.find({seen_events: {$gt: ''}}).count(). Pour confirmer qu'il fonctionne bien, consultez db.test_collection.find({seen_events: {$gt: ''}}).explain(true).executionStats. Vous pouvez probablement faire en sorte que les événements vus soient des chaînes via la validation de schéma: docs.mongodb.com/manual/core/schema-validation
wojcikstefan
Merci! Toutes les valeurs existantes sont des chaînes, je vais donc essayer cela. Il y a aussi un bug discutant de ce problème dans le bugtracker de MongoDB: jira.mongodb.org/browse/SERVER-26655
NCode
30

À partir de la version 2.6, une autre méthode consiste à comparer le champ à un tableau vide:

ME.find({pictures: {$gt: []}})

Le tester dans le shell:

> db.ME.insert([
{pictures: [1,2,3]},
{pictures: []},
{pictures: ['']},
{pictures: [0]},
{pictures: 1},
{foobar: 1}
])

> db.ME.find({pictures: {$gt: []}})
{ "_id": ObjectId("54d4d9ff96340090b6c1c4a7"), "pictures": [ 1, 2, 3 ] }
{ "_id": ObjectId("54d4d9ff96340090b6c1c4a9"), "pictures": [ "" ] }
{ "_id": ObjectId("54d4d9ff96340090b6c1c4aa"), "pictures": [ 0 ] }

Ainsi, il inclut correctement les documents où picturesa au moins un élément de tableau et exclut les documents où picturesest soit un tableau vide, pas un tableau, ou manquant.

JohnnyHK
la source
7
ATTENTION, cette réponse peut vous poser des problèmes si vous essayez d'utiliser des index. Faire db.ME.createIndex({ pictures: 1 })et ensuite db.ME.find({pictures: {$gt: []}})ne retournera aucun résultat, au moins dans MongoDB v3.0.14
wojcikstefan
@wojcikstefan Bonne prise. Besoin de jeter un regard neuf sur cela.
JohnnyHK
5

Vous pouvez utiliser l'une des méthodes suivantes pour y parvenir.
Les deux prennent également soin de ne pas retourner de résultat pour les objets qui ne contiennent pas la clé demandée:

db.video.find({pictures: {$exists: true, $gt: {$size: 0}}})
db.video.find({comments: {$exists: true, $not: {$size: 0}}})
Paul Imisi
la source
4

Récupérer tous et uniquement les documents où «images» est un tableau et n'est pas vide

ME.find({pictures: {$type: 'array', $ne: []}})

Si vous utilisez une version MongoDb antérieure à 3.2 , utilisez à la $type: 4place de $type: 'array'. Notez que cette solution n'utilise même pas $ size , donc il n'y a pas de problème avec les index ("Les requêtes ne peuvent pas utiliser d'index pour la partie $ size d'une requête")

Autres solutions, y compris celles-ci (réponse acceptée):

ME.find ({images: {$ existe: vrai, $ pas: {$ taille: 0}}}); ME.find ({images: {$ existe: vrai, $ ne: []}})

sont mal parce qu'ils renvoient des documents même si, par exemple, « images » est null, undefined, 0, etc.

SC1000
la source
2

Utiliser l' $elemMatchopérateur: selon la documentation

L'opérateur $ elemMatch fait correspondre les documents qui contiennent un champ de tableau avec au moins un élément qui correspond à tous les critères de requête spécifiés.

$elemMatchess'assure que la valeur est un tableau et qu'elle n'est pas vide. Ainsi, la requête serait quelque chose comme

ME.find({ pictures: { $elemMatch: {$exists: true }}})

PS Une variante de ce code se trouve dans le cours M121 de l'Université MongoDB.

Andres Moreno
la source
0

Vous pouvez également utiliser la méthode d'assistance Existe sur l'opérateur Mongo $ existe

ME.find()
    .exists('pictures')
    .where('pictures').ne([])
    .sort('-created')
    .limit(10)
    .exec(function(err, results){
        ...
    });
Manger chez Joes
la source
0
{ $where: "this.pictures.length > 1" }

utilisez le $ où et passez le this.field_name.length qui retourne la taille du champ du tableau et vérifiez-le en comparant avec le nombre. si un tableau a une valeur dont la taille du tableau doit être d'au moins 1. donc tous les champs du tableau ont une longueur supérieure à un, cela signifie qu'il contient des données dans ce tableau

Prabhat Yadav
la source
-8
ME.find({pictures: {$exists: true}}) 

Aussi simple que cela, cela a fonctionné pour moi.

Luis Fletes
la source