866 votes

Recherche de documents dont la taille du tableau est supérieure à 1

Je dispose d'une collection MongoDB contenant 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 fichier name réseau. Cependant, je ne peux pas faire un $gt pour retourner tous les documents dans lesquels le name 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 name d'une taille supérieure à un (de préférence sans avoir à modifier la structure de données actuelle) ?

3 votes

Les versions plus récentes de MongoDB disposent de l'opérateur $size ; vous devriez consulter la réponse de @tobia.

8 votes

La solution actuelle : FooArray:{$gt:{$size : 'length'}} --> la longueur peut être un nombre quelconque

1 votes

@SergiNadal : Je ne pense pas que cela FooArray:{$gt:{$size:'length'}} fonctionne ! Au moins sur l'objet imbriqué qui est un tableau. person:{ids:[123,456]}

1661voto

JohnnyHK Points 61191

Il existe un moyen plus efficace de le faire dans MongoDB 2.2+ maintenant que vous pouvez utiliser des index de tableaux numériques (basés sur 0) dans les clés des 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 partiel (nécessite 3.2+) :

// index for at least two name array elements
db.accommodations.createIndex(
    {'name.1': 1},
    {partialFilterExpression: {'name.1': {$exists: true}}}
);

20 votes

Quelqu'un pourrait-il m'expliquer comment indexer ceci ?

50 votes

Je suis vraiment impressionné par l'efficacité de cette solution et par le fait que vous avez su sortir des sentiers battus pour trouver cette solution. Cela fonctionne également sur la version 2.6.

2 votes

Cela fonctionne aussi sur la version 3.0. Merci beaucoup d'avoir trouvé cette solution.

592voto

Andrew Orsich Points 24503

Mise à jour :

Pour les versions de mongodb 2.2+ Une façon plus efficace de le faire est décrite par @JohnnyHK dans un autre réponse .


1.Utilisation

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

Mais...

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

2.Créer extra champ NamesArrayLength et le mettre à jour avec la longueur du tableau des noms, puis l'utiliser 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).

5 votes

Super, c'était parfait, merci. Bien que j'aie en fait quelques documents qui n'ont pas de nom, j'ai dû modifier la requête pour qu'elle soit : db.accommodations.find( { $where : "if (this.name && this.name.length > 1) {return this ; }". "} ) ;

0 votes

Vous êtes le bienvenu, oui, vous pouvez utiliser n'importe quel javascript en $where il est très flexible.

8 votes

@emson Je pense qu'il serait plus rapide de faire quelque chose comme {"name" : {$existe:1}, $where : "this.name.lenght > 1"} ... en minimisant la partie de la requête javascript plus lente. Je suppose que cela fonctionne et que $exists aurait une plus grande priorité.

169voto

Tobia Points 2978

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

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

Cela signifie "tous les documents sauf ceux qui n'ont pas de nom (soit inexistants, soit vides) ou qui n'ont qu'un seul nom".

Test :

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

13 votes

@viren Je ne sais pas. C'était certainement mieux que les solutions Javascript, mais pour les MongoDB plus récents, vous devriez probablement utiliser {'name.1': {$exists: true}}

0 votes

@Tobia ma première utilisation était $exists seulement mais elle utilise en fait le scan de la table entière donc très lent. db.test.find({"name" : "abc", "d.5":{$exists:true}, "d.6 ":{$exists:true}}) "nReturned" : 46525, "executionTimeMillis" : 167289, "totalKeysExamined" : 10990840, "totalDocsExamined" : 10990840, "inputStage" : { "stage" : "IXSCAN", "keyPattern" : { "name" : 1, "d" : 1 }, "indexName" : "name_1_d_1", "direction" : "forward", "indexBounds" : { "name" : [ "[\"abc\", \"abc\"]" ], "d" : [ "[MinKey, MaxKey]" ] } } Si vous voyez qu'il a scanné toute la table.

1 votes

Il serait bon de mettre à jour la réponse pour recommander d'autres alternatives (comme par ex. 'name.1': {$exists: true}} et aussi parce qu'il est codé en dur pour "1" et ne s'adapte pas à une longueur de tableau minimale arbitraire ou paramétrique.

85voto

one_cent_thought Points 769

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.

0 votes

Cette solution est la plus générale, avec celle de @JohnnyHK puisqu'elle peut être utilisée pour toute taille de tableau.

0 votes

Si je veux utiliser "size_of_name" en projection, comment puis-je le faire ? En fait, je veux utiliser $slice dans la projection où sa valeur est égale à $slice : [0, "size_of_name" - skip] ? ??

69voto

Veeram Points 36230

Vous pouvez utiliser $expr ( 3.6 mongo version operator ) pour utiliser les fonctions d'agrégation dans les requêtes régulières.

Comparez query operators vs aggregation comparison operators .

db.accommodations.find({$expr:{$gt:[{$size:"$name"}, 1]}})

1 votes

Comment passeriez-vous au lieu de $name un tableau qui est un sous-document, par exemple dans un enregistrement "personne", passport.stamps ? J'ai essayé plusieurs combinaisons de citations mais j'obtiens "The argument to $size must be an array, but was of type: string/missing" .

3 votes

@DanDascalescu Il semble que les timbres ne soient pas présents dans tous les documents. Vous pouvez utiliser ifNull pour produire un tableau vide lorsque les timbres ne sont pas présents. Quelque chose comme db.col.find({$expr:{$gt:[{$size:{$ifNull:["$passport.stamps"‌​, []]}}, 1]}})

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X