Abfrage nach Dokumenten, bei denen die Arraygröße größer als 1 ist


663

Ich habe eine MongoDB-Sammlung mit Dokumenten im folgenden Format:

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

Ich kann derzeit Dokumente abrufen, die einer bestimmten Arraygröße entsprechen:

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

Dadurch werden die Dokumente mit 2 Elementen im nameArray korrekt zurückgegeben . Ich kann jedoch keinen $gtBefehl ausführen, um alle Dokumente zurückzugeben, bei denen das nameFeld eine Arraygröße von mehr als 2 hat:

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

Wie kann ich alle Dokumente mit einem nameArray mit einer Größe größer als eins auswählen (vorzugsweise ohne die aktuelle Datenstruktur ändern zu müssen)?


3
Die neueren Versionen von MongoDB haben den Operator $ size. Sie sollten @ Tobias Antwort überprüfen
AlbertEngelB

4
Aktuelle Lösung: FooArray: {$ gt: {$ size: 'length'}} -> Länge kann eine beliebige Zahl sein
Sergi Nadal

Antworten:


489

Aktualisieren:

Für Mongodb-Versionen 2.2+ ist dies eine effizientere Methode, die von @JohnnyHK in einer anderen Antwort beschrieben wird .


1.Mit $ where

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

Aber...

Javascript wird langsamer ausgeführt als die auf dieser Seite aufgeführten nativen Operatoren, ist jedoch sehr flexibel. Weitere Informationen finden Sie auf der serverseitigen Verarbeitungsseite.

2.Erstellen Sie ein zusätzliches Feld NamesArrayLength, aktualisieren Sie es mit der Länge des Namensarrays und verwenden Sie es dann in Abfragen:

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

Es ist eine bessere Lösung und funktioniert viel schneller (Sie können einen Index dafür erstellen).


4
Großartig, das war perfekt, danke. Obwohl ich tatsächlich einige Dokumente habe, die keinen Namen haben, musste ich die Abfrage so ändern, dass sie lautet: db.accommodations.find ({$ where: "if (this.name && this.name.length> 1) {return this ;} "});
Emson

Sie sind willkommen, ja, Sie können jedes Javascript verwenden $where, es ist sehr flexibel.
Andrew Orsich

8
@emson Ich würde denken, es wäre schneller, so etwas wie {"name": {$ existiert: 1}, $ wobei: "this.name.lenght> 1"} ... den Teil in der langsameren Javascript-Abfrage zu minimieren. Ich gehe davon aus, dass funktioniert und dass das $ existiert eine höhere Priorität haben würde.
Nairbv

1
Ich hatte keine Ahnung, dass Sie Javascript in die Abfrage einbetten könnten, json kann umständlich sein. Viele dieser Abfragen werden nur einmal von Hand eingegeben, sodass keine Optimierung erforderlich ist. Ich werde diesen Trick oft
anwenden

3
Nach dem Hinzufügen / Entfernen von Elementen zum Array müssen wir die Anzahl der "NamesArrayLength" aktualisieren. Kann dies in einer einzigen Abfrage erfolgen? Oder sind zwei Abfragen erforderlich, eine zum Aktualisieren des Arrays und eine zum Aktualisieren der Anzahl?
WarLord

1325

In MongoDB 2.2+ gibt es jetzt eine effizientere Möglichkeit, numerische Array-Indizes in Abfrageobjektschlüsseln zu verwenden.

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

Sie können diese Abfrage mit einem Index unterstützen, der einen Teilfilterausdruck verwendet (erfordert 3.2+):

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

16
Könnte jemand bitte erklären, wie man dies indiziert.
Ben

26
Ich bin wirklich beeindruckt davon, wie effektiv dies ist und wie "out of the box" Sie darüber nachgedacht haben, diese Lösung zu finden. Dies funktioniert auch mit 2.6.
EarthmeLon

2
Funktioniert auch mit 3.0. Vielen Dank, dass Sie dies gefunden haben.
Pikanezi

1
@ Dim Kein Unterschied, wirklich : {'Name Field.1': {$exists: true}}.
JohnnyHK

9
@ JoseRicardoBustosM. Das würde die Dokumente namefinden, in denen mindestens 1 Element enthalten ist, aber das OP suchte nach mehr als 1.
JohnnyHK

127

Ich glaube, dies ist die schnellste Abfrage, die Ihre Frage beantwortet, da keine interpretierte $whereKlausel verwendet wird:

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

Es bedeutet "alle Dokumente außer denen ohne Namen (entweder nicht vorhanden oder leeres Array) oder mit nur einem Namen."

Prüfung:

> 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 Ich weiß es nicht. Dies war sicherlich besser als Javascript-Lösungen, aber für neuere MongoDB sollten Sie wahrscheinlich verwenden{'name.1': {$exists: true}}
Tobia

@Tobia meine erste Verwendung war $ existiert nur, aber es verwendet tatsächlich den gesamten Tabellenscan so sehr langsam. db.test.find ({"name": "abc", "d.5": {$ existiert: wahr}, "d.6": {$ existiert: wahr}}) "nReturned": 46525, "executeTimeMillis ": 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 ] "]}} Wenn Sie sehen, dass die gesamte Tabelle gescannt wurde.

Es wäre schön, die Antwort zu aktualisieren, um andere Alternativen zu empfehlen (wie 'name.1': {$exists: true}}und auch, weil dies für "1" fest
codiert ist

1
Dies mag schnell sein, fällt aber auseinander, wenn Sie nach Listen> N suchen, wobei N nicht klein ist.
Brandon Hill

62

Sie können auch Aggregat verwenden:

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

// Sie fügen dem Transitdokument "size_of_name" hinzu und filtern damit die Größe des Namens


Diese Lösung ist zusammen mit @ JohnnyHKs die allgemeinste, da sie für jede Arraygröße verwendet werden kann.
Arun

Wenn ich "size_of_name" in der Projektion verwenden möchte, wie kann ich das tun? Eigentlich möchte ich $ Slice innerhalb der Projektion verwenden, wobei sein Wert gleich $ Slice ist: [0, "size_of_name" - überspringen] ??
Sudhanshu Gaur

44

Versuchen Sie so etwas zu tun:

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

1 ist die Nummer. Wenn Sie einen Datensatz größer als 50 abrufen möchten, führen Sie ArrayName.50 aus. Danke.


2
Die gleiche Antwort wurde drei Jahre zuvor gegeben .
Dan Dascalescu

Ich komme aus der Zukunft und hätte dies geschätzt: Diese Lösung überprüft, ob an dieser Position ein Element vorhanden ist. Daher muss die Sammlung größer als diese Zahl sein.
MarAvFe

Können wir eine dynamische Zahl wie "ArrayName. <some_num>" in die Abfrage einfügen?
Sahil Mahajan

Ja, Sie können eine beliebige Nummer verwenden. Wenn Sie einen Datensatz abrufen möchten, der größer als N ist, übergeben Sie n.
Aman Goel


26

Sie können verwenden $ expr (3.6 Mongo-Versionsoperator) verwenden, um Aggregationsfunktionen in regulären Abfragen zu verwenden.

Vergleichen query operatorsvs aggregation comparison operators.

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

Wie würden Sie anstelle $nameeines Arrays übergeben, das ein Unterdokument ist, beispielsweise in einem "Personen" -Datensatz passport.stamps? Ich habe verschiedene Zitatkombinationen ausprobiert, aber ich verstehe "The argument to $size must be an array, but was of type: string/missing".
Dan Dascalescu

3
@DanDascalescu Es sieht so aus, als ob Briefmarken nicht in allen Dokumenten vorhanden sind. Sie können ifNull verwenden leeres Array ausgeben, wenn die Stempel nicht vorhanden sind. So etwas wiedb.col.find({$expr:{$gt:[{$size:{$ifNull:["$passport.stamps", []]}}, 1]}})
Sagar Veeram



13

Ich habe diese Lösung gefunden, um Elemente mit einem Array-Feld zu finden, das größer als eine bestimmte Länge ist

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

Das erste $ match-Aggregat verwendet ein Argument, das für alle Dokumente gilt. Wenn leer, würde ich bekommen

"errmsg" : "exception: The argument to $size must be an Array, but was of type: EOO"

Dies ist im Wesentlichen die gleiche Antwort wie diese , die 2 Jahre zuvor gegeben wurde.
Dan Dascalescu

1

Ich kenne die alte Frage, aber ich versuche dies mit $ gte und $ size in find. Ich denke zu finden () ist schneller.

db.getCollection('collectionName').find({ name : { $gte : {  $size : 1 } }})

-5

Obwohl die obigen Antworten alle funktionieren, war das, was Sie ursprünglich versucht haben, der richtige Weg, aber Sie haben nur die Syntax rückwärts (wechseln Sie "$ size" und "$ gt").

Richtig:

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

Falsch:

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

1
Ich verstehe nicht, warum so viele Abstimmungen - das funktioniert perfekt für mich!
Jake Stokes

Ich habe nicht abgelehnt, aber es funktioniert nicht (v4.2).
Evgeni Nabokov

Funktioniert einwandfrei, v 4.2.5
jperl
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.