Requête pour les documents dont la taille du tableau est supérieure à 1

664

J'ai une collection MongoDB avec des documents au format suivant:

{
  "_id" : ObjectId("4e8ae86d08101908e1000001"),
  "name" : ["Name"],
  "zipcode" : ["2223"]
}
{
  "_id" : ObjectId("4e8ae86d08101908e1000002"),
  "name" : ["Another ", "Name"],
  "zipcode" : ["2224"]
}

Je peux actuellement obtenir des documents qui correspondent à une taille de tableau spécifique:

db.accommodations.find({ name : { $size : 2 }})

Cela renvoie correctement les documents avec 2 éléments dans le nametableau. Cependant, je ne peux pas exécuter de $gtcommande pour renvoyer tous les documents dont le namechamp a une taille de tableau supérieure à 2:

db.accommodations.find({ name : { $size: { $gt : 1 } }})

Comment puis-je sélectionner tous les documents avec un nametableau d'une taille supérieure à un (de préférence sans avoir à modifier la structure de données actuelle)?

emson
la source
3
Les nouvelles versions de MongoDB ont l'opérateur $ size; vous devriez vérifier la réponse de @ tobia
AlbertEngelB
4
Solution réelle: FooArray: {$ gt: {$ size: 'length'}} -> la longueur peut être n'importe quel nombre
Sergi Nadal

Réponses:

489

Mise à jour:

Pour les versions mongodb 2.2+, un moyen plus efficace de le faire décrit par @JohnnyHK dans une autre réponse .


1.Utilisation de $ where

db.accommodations.find( { $where: "this.name.length > 1" } );

Mais...

Javascript s'exécute plus lentement que les opérateurs natifs répertoriés sur cette page, mais est très flexible. Voir la page de traitement côté serveur pour plus d'informations.

2.Créez un champ supplémentaireNamesArrayLength , mettez-le à jour avec la longueur du tableau de noms, puis utilisez-le dans les requêtes:

db.accommodations.find({"NamesArrayLength": {$gt: 1} });

Ce sera une meilleure solution et fonctionnera beaucoup plus rapidement (vous pouvez créer un index dessus).

Andrew Orsich
la source
4
Super, c'était parfait merci. Bien que j'aie en fait certains documents qui n'ont pas de nom, j'ai donc dû modifier la requête pour qu'elle soit: db.accommodations.find ({$ where: "if (this.name && this.name.length> 1) {return this ;} "});
emson
vous êtes les bienvenus, oui vous pouvez utiliser n'importe quel javascript $where, c'est très flexible.
Andrew Orsich
8
@emson Je pense qu'il serait plus rapide de faire quelque chose comme {"nom": {$ existe: 1}, $ où: "this.name.lenght> 1"} ... en minimisant la partie dans la requête javascript plus lente. Je suppose que cela fonctionne et que le $ existe aurait une priorité plus élevée.
nairbv
1
Je ne savais pas que vous pouviez intégrer javascript dans la requête, json peut être lourd. Beaucoup de ces requêtes ne sont saisies qu'une seule fois à la main, une optimisation n'est donc pas requise. Je vais souvent utiliser cette astuce +1
pferrel
3
Après avoir ajouté / supprimé des éléments du tableau, nous devons mettre à jour le nombre de "NamesArrayLength". Cela peut-il se faire en une seule requête? Ou cela nécessite 2 requêtes, une pour mettre à jour le tableau et une autre pour mettre à jour le nombre?
WarLord
1329

Il existe un moyen plus efficace de le faire dans MongoDB 2.2+ maintenant que vous pouvez utiliser des index de tableaux numériques dans les clés d'objets de requête.

// Find all docs that have at least two name array elements.
db.accommodations.find({'name.1': {$exists: true}})

Vous pouvez prendre en charge cette requête avec un index qui utilise une expression de filtre partielle (nécessite 3.2+):

// index for at least two name array elements
db.accommodations.createIndex(
    {'name.1': 1},
    {partialFilterExpression: {'name.1': {$exists: true}}}
);
JohnnyHK
la source
16
Quelqu'un pourrait-il expliquer comment l'indexer?
Ben
26
Je suis vraiment impressionné par son efficacité et aussi par le fait que vous pensiez «sortir des sentiers battus» pour trouver cette solution. Cela fonctionne également sur 2.6.
earthmeLon
2
Fonctionne également sur 3.0. Merci beaucoup d'avoir trouvé cela.
pikanezi
1
@Dims Pas de différence, vraiment: {'Name Field.1': {$exists: true}}.
JohnnyHK
9
@JoseRicardoBustosM. Cela trouverait les documents namecontenant au moins 1 élément, mais l'OP recherchait plus de 1.
JohnnyHK
128

Je pense que c'est la requête la plus rapide qui répond à votre question, car elle n'utilise pas de $whereclause interprétée :

{$nor: [
    {name: {$exists: false}},
    {name: {$size: 0}},
    {name: {$size: 1}}
]}

Cela signifie «tous les documents sauf ceux sans nom (tableau inexistant ou vide) ou avec un seul nom».

Tester:

> db.test.save({})
> db.test.save({name: []})
> db.test.save({name: ['George']})
> db.test.save({name: ['George', 'Raymond']})
> db.test.save({name: ['George', 'Raymond', 'Richard']})
> db.test.save({name: ['George', 'Raymond', 'Richard', 'Martin']})
> db.test.find({$nor: [{name: {$exists: false}}, {name: {$size: 0}}, {name: {$size: 1}}]})
{ "_id" : ObjectId("511907e3fb13145a3d2e225b"), "name" : [ "George", "Raymond" ] }
{ "_id" : ObjectId("511907e3fb13145a3d2e225c"), "name" : [ "George", "Raymond", "Richard" ] }
{ "_id" : ObjectId("511907e3fb13145a3d2e225d"), "name" : [ "George", "Raymond", "Richard", "Martin" ] }
>
Tobia
la source
9
@viren je ne sais pas. C'était certainement mieux que les solutions Javascript, mais pour les nouveaux MongoDB, vous devriez probablement utiliser{'name.1': {$exists: true}}
Tobia
@Tobia ma première utilisation était $ existe seulement mais il utilise en fait le scan de toute la table donc très lentement. db.test.find ({"nom": "abc", "d.5": {$ existe: vrai}, "d.6": {$ existe: vrai}}) "nReturned": 46525, "executionTimeMillis ": 167289," totalKeysExamined ": 10990840," totalDocsExamined ": 10990840," inputStage ": {" stage ":" IXSCAN "," keyPattern ": {" name ": 1," d ": 1}," indexName " : "nom_1_d_1", "direction": "avant", "indexBounds": {"nom": ["[\" abc \ ", \" abc \ "]"], "d": ["[MinKey, MaxKey ] "]}} Si vous le voyez, il a numérisé toute la table.
Ce serait bien de mettre à jour la réponse pour recommander d'autres alternatives (comme 'name.1': {$exists: true}}, et aussi parce que cela est codé en dur pour "1" et ne s'adapte pas à une longueur minimale de tableau arbitraire ou paramétrique.
Dan Dascalescu
1
Cela peut être rapide mais s'effondre si vous recherchez des listes> N, où N n'est pas petit.
Brandon Hill
62

Vous pouvez également utiliser l'agrégat:

db.accommodations.aggregate(
[
     {$project: {_id:1, name:1, zipcode:1, 
                 size_of_name: {$size: "$name"}
                }
     },
     {$match: {"size_of_name": {$gt: 1}}}
])

// vous ajoutez "size_of_name" au document de transit et l'utilisez pour filtrer la taille du nom

one_cent_thought
la source
Cette solution est la plus générale, avec @ JohnnyHK car elle peut être utilisée pour n'importe quelle taille de tableau.
arun
si je veux utiliser "size_of_name" à l'intérieur de la projection, comment puis-je faire cela ?? En fait, je veux utiliser $ slice dans la projection où sa valeur est égale à $ slice: [0, "size_of_name" - sauter] ??
Sudhanshu Gaur
44

Essayez de faire quelque chose comme ceci:

db.getCollection('collectionName').find({'ArrayName.1': {$exists: true}})

1 est un nombre, si vous voulez récupérer un enregistrement supérieur à 50, faites ArrayName.50 Merci.

Aman Goel
la source
2
La même réponse avait été donnée trois ans plus tôt .
Dan Dascalescu
Je viens du futur et j'aurais apprécié ceci: Cette solution fonctionne en vérifiant si un élément existe sur ladite position. Par conséquent, la collection doit être supérieure | égale à ce nombre.
MarAvFe
pouvons-nous mettre un certain nombre dynamique comme "ArrayName. <some_num>" dans la requête?
Sahil Mahajan
Oui, vous pouvez utiliser n'importe quel numéro. Si vous souhaitez récupérer un enregistrement supérieur à N, passez n.
Aman Goel
36

Rien de ce qui précède n'a fonctionné pour moi. Celui-ci l'a fait alors je le partage:

db.collection.find( {arrayName : {$exists:true}, $where:'this.arrayName.length>1'} )
lesolorzanov
la source
javascript s'exécute plus lentement que les opérateurs natifs fournis par mongodb, mais il est très flexible. voir: stackoverflow.com/a/7811259/2893073 , donc la solution finale est: stackoverflow.com/a/15224544/2893073
Eddy
26

Vous pouvez utiliser $ expr (opérateur de version 3,6 mongo) pour utiliser les fonctions d'agrégation dans une requête régulière.

Comparez query operatorsvs aggregation comparison operators.

db.accommodations.find({$expr:{$gt:[{$size:"$name"}, 1]}})
Sagar Veeram
la source
Comment voulez - vous passer au lieu d' $nameun tableau qui est un sous - document, par exemple dans un dossier « personne », passport.stamps? J'ai essayé différentes combinaisons de devis, mais j'obtiens "The argument to $size must be an array, but was of type: string/missing".
Dan Dascalescu
3
@DanDascalescu Il semble que les tampons ne soient pas présents dans tous les documents. Vous pouvez utiliser ifNull pour générer un tableau vide lorsque les tampons ne sont pas présents. Quelque chose commedb.col.find({$expr:{$gt:[{$size:{$ifNull:["$passport.stamps", []]}}, 1]}})
Sagar Veeram
22
db.accommodations.find({"name":{"$exists":true, "$ne":[], "$not":{"$size":1}}})
Yadvendar
la source
1
Cela ne s'adapte pas bien à d'autres tailles minimales (disons, 10).
Dan Dascalescu
même que la première réponse
arianpress
13

J'ai trouvé cette solution, pour trouver des éléments avec un champ de tableau supérieur à une certaine longueur

db.allusers.aggregate([
  {$match:{username:{$exists:true}}},
  {$project: { count: { $size:"$locations.lat" }}},
  {$match:{count:{$gt:20}}}
])

Le premier agrégat $ match utilise un argument qui est vrai pour tous les documents. Si vide, j'obtiendrais

"errmsg" : "exception: The argument to $size must be an Array, but was of type: EOO"
Barrard
la source
C'est essentiellement la même réponse que celle-ci , fournie 2 ans plus tôt.
Dan Dascalescu
1

Je connais sa vieille question, mais j'essaye ceci avec $ gte et $ size dans find. Je pense que trouver () est plus rapide.

db.getCollection('collectionName').find({ name : { $gte : {  $size : 1 } }})
Bhagvat Lande
la source
-5

Bien que les réponses ci-dessus fonctionnent toutes, ce que vous avez essayé de faire à l'origine était la bonne façon, mais vous avez juste la syntaxe à l'envers (changez "$ size" et "$ gt") ..

Correct:

db.collection.find({items: {$gt: {$size: 1}}})

Incorrect:

db.collection.find({items: {$size: {$gt: 1}}})
Steffan Perry
la source
1
Je ne vois pas pourquoi tant de votes négatifs - cela fonctionne parfaitement pour moi!
Jake Stokes
Je n'ai pas downvote, mais cela ne fonctionne pas (v4.2).
Evgeni Nabokov
Fonctionne parfaitement bien, v 4.2.5
jperl