web-dev-qa-db-fra.com

Rechercher des enregistrements MongoDB où le champ de tableau n'est pas vide

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 IS N'EST PAS vide.

J'ai fait des recherches sur Google, mais assez étrangement, je n'ai pas trouvé grand chose à ce sujet… .. J'ai lu dans l'option $ where, mais je me demandais à quel point c'est lent pour les fonctions natives et s'il existe une meilleure solution .

Et même alors, ça ne marche pas:

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

Ne renvoie rien. Le fait de laisser this.pictures sans le bit de longueur fonctionne, mais il renvoie également des enregistrements vides, bien sûr.

375
skerit

Si vous avez également des documents qui ne possèdent 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 MongoDB 2.6, vous pouvez comparer avec l'opérateur $gt mais vous risquez d'obtenir des résultats inattendus (vous pouvez trouver une explication détaillée dans cette réponse ):

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

Après un peu plus de recherches, en particulier dans les documents mongodb, et des morceaux énigmatiques ensemble, voici la réponse:

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

Cela pourrait aussi fonctionner pour vous:

ME.find({'pictures.0': {$exists: true}});
92
tenbatsu

À partir de la version 2.6, vous pouvez également comparer le champ à un tableau vide:

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

Test 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 ] }

Donc, il inclut correctement les documents où pictures a au moins un élément de tableau et exclut les documents où pictures est soit un tableau vide, pas un tableau, soit manquant.

28
JohnnyHK

Lorsque vous interrogez, deux choses vous importent: l'exactitude et la performance. Dans cet esprit, j'ai testé différentes approches dans MongoDB v3.0.14.

TL; DR db.doc.find({ nums: { $gt: -Infinity }}) est le plus rapide et le plus fiable (du 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é 1k docs sans champ de liste, 1k docs avec une liste vide et 5 docs 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 cette échelle n'est pas suffisante pour prendre les performances aussi au sérieux que dans les tests ci-dessous, mais elle suffit pour présenter la correction des requêtes et le comportement des plans de requête choisis.

Des 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 à utiliser une analyse complète de la collection (notez le stade COLLSCAN 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. Cela est dû à une analyse d'index non valide ne faisant 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 une analyse d’index, mais il continue de faire avancer 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 optimales. Il utilise IXSCAN qui avance uniquement les documents 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: [] }}) IS DANGEREUX PARCE EN FONCTION DE L’INDICE UTILISÉ, IL POURRAIT DONNER DES RÉSULTATS INATTENDUS. Cela est dû à un balayage 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 présente de mauvaises performances (utilise une analyse de collection complète).

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 est rapide, faisant progresser 5 documents de la phase d’analyse 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
  }
}
27
wojcikstefan

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

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

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

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

utilisez le $ where et passez le this.field_name.length qui renvoie la taille du champ array et vérifiez-le en comparant avec number. si un tableau a une valeur supérieure à la taille du tableau doit être au moins égal à 1. Pour que tous les champs du tableau aient une longueur supérieure à un, cela signifie qu'il contient des données dans ce tableau.

0
Prabhat Yadav

Utilisez l'opérateur $elemMatch: selon la documentation

L'opérateur $ elemMatch établit une correspondance entre les documents contenant un champ de tableau et au moins un élément correspondant à tous les critères de requête spécifiés.

$elemMatches s'assure que la valeur est un tableau et qu'elle n'est pas vide. Donc, 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.

0
Andres Moreno

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 de MongoDb antérieure à 3.2 , utilisez $type: 4 au lieu de $type: 'array'. Notez que cette solution n'utilise même pas $ size , il n'y a donc aucun problème avec les index ("Les requêtes ne peuvent pas utiliser les index pour la portion $ size d'une requête")

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

ME.find ({pictures: {$ existe: true, $ not: {$ size: 0}}}); ME.find ({images: {$ existe: true, $ ne: []}})

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

0
SC1000