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)?


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).


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}}}
);

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

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


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.


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


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

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}}})

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"

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 } }})

-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}}})

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
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.