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

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

Andrew Orsich
quelle
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}}}
);
JohnnyHK
quelle
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" ] }
>
Tobia
quelle
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

one_cent_thought
quelle
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.

Aman Goel
quelle
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
36

Keines der oben genannten hat bei mir funktioniert. Dieser hat es getan, also teile ich es:

db.collection.find( {arrayName : {$exists:true}, $where:'this.arrayName.length>1'} )
lesolorzanov
quelle
Javascript wird langsamer ausgeführt als die von mongodb bereitgestellten nativen Operatoren, ist jedoch sehr flexibel. siehe: stackoverflow.com/a/7811259/2893073 , Die endgültige Lösung lautet also: stackoverflow.com/a/15224544/2893073
Eddy
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]}})
Sagar Veeram
quelle
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
22
db.accommodations.find({"name":{"$exists":true, "$ne":[], "$not":{"$size":1}}})
Yadvendar
quelle
1
Dies lässt sich nicht gut auf andere Mindestgrößen skalieren (z. B. 10).
Dan Dascalescu
wie erste Antwort
arianpress vor
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"
Barrard
quelle
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 } }})
Bhagvat Lande
quelle
-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}}})
Steffan Perry
quelle
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