Trouver des enregistrements MongoDB où le champ du tableau n'est pas vide
Tous mes dossiers ont un champ appelé "images". Ce domaine est un tableau de chaînes de caractères.
je veux maintenant les 10 enregistrements les plus récents où ce tableau n'est pas vide.
j'ai cherché sur Google, mais étrangement, je n'ai pas trouvé grand chose sur ça. J'ai lu dans l'option $where, mais je me demandais à quel point c'é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.pictures
sans le bit de longueur fonctionne, mais il retourne aussi des enregistrements vides, bien sûr.
10 réponses
si vous avez aussi des documents qui n'ont pas la clé, vous pouvez utiliser:
ME.find({ pictures: { $exists: true, $not: {$size: 0} } })
MongoDB n'utilisez pas d'index si $si la taille est impliquée, voici donc une meilleure solution:
ME.find({ pictures: { $exists: true, $ne: [] } })
depuis MongoDB 2.6 release, vous pouvez comparer avec l'opérateur $gt
mais pourrait conduire à des résultats inattendus (vous pouvez trouver une explication détaillée dans cette réponse ):
ME.find({ pictures: { $gt: [] } })
après un peu plus de recherche, en particulier dans les documents mongodb, et des morceaux de puzzle ensemble, c'était la réponse:
ME.find({pictures: {$not: {$size: 0}}})
cela pourrait aussi fonctionner pour vous:
ME.find({'pictures.0': {$exists: true}});
à partir de la version 2.6, une autre façon de procéder est de comparer le champ à un tableau vide:
ME.find({pictures: {$gt: []}})
la 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 ] }
donc il inclut correctement les docs où pictures
a au moins un élément de tableau, et exclut les docs où pictures
est soit un tableau vide, pas un tableau, ou manquant.
vous vous souciez de deux choses lorsque vous posez des questions - la précision et la performance. Avec cela à l'esprit, j'ai testé quelques approches différentes en MongoDB v3.0.14.
TL;DR db.doc.find({ nums: { $gt: -Infinity }})
est la manière la plus rapide et la plus fiable (au moins dans la MongoDB version que j'ai testé).
EDIT: Cela ne fonctionne plus dans MongoDB v3.6! Voir les commentaires sous ce post pour une solution potentielle.
Setup
j'ai inséré 1K docs avec un 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 ce n'est pas assez d'une échelle pour prendre la performance aussi au sérieux que je le suis dans les tests ci-dessous, mais c'est assez pour présenter la justesse de diverses requêtes et le comportement des plans de requête choisis.
Essais
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}})
retourne des résultats corrects, mais il est également lent en utilisant un balayage complet de collecte (avis COLLSCAN
étape 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'un scan d'index invalide qui n'avance aucun document. Il sera probablement exact mais lent sans l'indice.
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 }}})
retourne des résultats corrects, mais la performance est mauvaise. Il fait techniquement un scan index, mais ensuite il avance tous les docs et doit filtrer à travers eux).
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: [] }})
retourne des résultats corrects et est légèrement plus rapide, mais la performance n'est toujours pas idéale. Il utilise IXSCAN qui n'avance que docs avec un champ de liste existant, mais doit ensuite filtrer le champ vide Liste 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 CAR, SELON L'INDICE UTILISÉ, IL POURRAIT DONNER DES RÉSULTATS INATTENDUS. C'est à cause d'un scan d'index invalide qui n'avance 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 }})
retourne les résultats corrects, mais a de mauvaises performances (utilise un balayage complet 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 il est rapide, avance de 5 docs à partir de la phase de scan 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
}
}
vous pouvez utiliser l'un des suivants pour atteindre cet objectif.
Tous deux prennent également soin de ne pas retourner un résultat pour les objets qui n'ont pas la clé demandée en eux:
db.video.find({pictures: {$exists: true, $gt: {$size: 0}}})
db.video.find({comments: {$exists: true, $not: {$size: 0}}})
vous pouvez également utiliser la méthode helper existe sur l'opérateur Mongo $ existe
ME.find()
.exists('pictures')
.where('pictures').ne([])
.sort('-created')
.limit(10)
.exec(function(err, results){
...
});
{ $where: "this.pictures.length > 1" }
utilisez le $ où et passez le Ceci.nom_champ.longueur qui renvoie la taille du champ du tableau et le vérifie en le comparant avec le nombre. si une matrice a aucune valeur que la taille du tableau doit être d'au moins 1. donc tous les champs du tableau ont plus d'une longueur, cela signifie qu'il y a des données dans ce tableau
récupérez tous et seulement les documents où 'pictures' 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 $type: 4
au lieu de $type: 'array'
. Notez que cette solution n'utilise même pas $ size , donc il n'y a aucun problème avec les index ("les requêtes ne peuvent pas utiliser les index pour la partie $size d'une requête")
la réponse):
ME.find({ photos: { $existe: true, $pas: {$size: 0} } }); JE.find({ photos: { $existe: true, $ne: [] } })
sont erroné parce qu'ils renvoient des documents même si, par exemple, "Photos" est null
, undefined
, 0, etc.
ME.find({pictures: {$exists: true}})
aussi Simple que ça, ça a marché pour moi.