Zapytanie o dokumenty, których rozmiar tablicy jest większy niż 1

664

Mam kolekcję MongoDB z dokumentami w następującym formacie:

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

Obecnie mogę uzyskać dokumenty pasujące do określonego rozmiaru tablicy:

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

To poprawnie zwraca dokumenty z 2 elementami w nametablicy. Nie mogę jednak wykonać $gtpolecenia, aby zwrócić wszystkie dokumenty, w których namepole ma rozmiar tablicy większy niż 2:

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

Jak mogę wybrać wszystkie dokumenty z nametablicą o rozmiarze większym niż jeden (najlepiej bez konieczności modyfikowania bieżącej struktury danych)?

Emson
źródło
3
Nowsze wersje MongoDB mają operator wielkości $; powinieneś sprawdzić odpowiedź @ tobia
AlbertEngelB
4
Rzeczywiste rozwiązanie: FooArray: {$ gt: {$ size: 'length'}} -> długość może być dowolną liczbą
Sergi Nadal

Odpowiedzi:

489

Aktualizacja:

Dla wersji MongoDB 2.2+ bardziej efektywny sposób to zrobić opisane przez @JohnnyHK w innej odpowiedzi .


1. Za pomocą $ gdzie

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

Ale...

JavaScript działa wolniej niż natywne operatory wymienione na tej stronie, ale jest bardzo elastyczny. Aby uzyskać więcej informacji, zobacz stronę przetwarzania po stronie serwera.

2. Utwórz dodatkowe pole NamesArrayLength, zaktualizuj je o długość tablicy nazw, a następnie użyj w zapytaniach:

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

Będzie to lepsze rozwiązanie i będzie działało znacznie szybciej (możesz na nim utworzyć indeks).

Andrew Orsich
źródło
4
Świetnie, to było idealne, dziękuję. Chociaż faktycznie mam kilka dokumentów, które nie mają nazwy, musiałem zmodyfikować zapytanie: db.accommodations.find ({$ where: "if (this.name && this.name.length> 1) {zwróć to ;} "});
emson,
jesteś mile widziany, tak, możesz użyć dowolnego javascript $where, jest bardzo elastyczny.
Andrew Orsich,
8
@emson Myślę, że szybsze byłoby zrobienie czegoś takiego jak {"nazwa": {$ istnieje: 1}, $ gdzie: "this.name.lenght> 1"} ... minimalizując część wolniejszego zapytania javascript. Zakładam, że działa i że $ istnieje ma wyższy priorytet.
nairbv
1
Nie miałem pojęcia, że ​​możesz umieścić javascript w zapytaniu, json może być kłopotliwy. Wiele z tych zapytań jest wprowadzanych tylko raz ręcznie, więc optymalizacja nie jest wymagana. Często używam tej sztuczki +1
pferrel
3
Po dodaniu / usunięciu elementów z tablicy musimy zaktualizować liczbę „NamesArrayLength”. Czy można to zrobić za pomocą jednego zapytania? A może wymaga 2 zapytań, jednego do aktualizacji tablicy, a drugiego do aktualizacji liczby?
WarLord,
1327

Istnieje bardziej wydajny sposób na wykonanie tego w MongoDB 2.2+, ponieważ można używać indeksów tablic numerycznych w kluczach obiektów zapytań.

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

Możesz wesprzeć to zapytanie za pomocą indeksu, który używa częściowego wyrażenia filtrującego (wymaga wersji 3.2+):

// index for at least two name array elements
db.accommodations.createIndex(
    {'name.1': 1},
    {partialFilterExpression: {'name.1': {$exists: true}}}
);
JohnnyHK
źródło
16
Czy ktoś mógłby wyjaśnić, jak to zindeksować.
Ben
26
Jestem pod wielkim wrażeniem skuteczności tego rozwiązania, a także tego, jak „po wyjęciu z pudełka” zastanawiałeś się nad znalezieniem tego rozwiązania. Działa to również w wersji 2.6.
earthmeLon
2
Działa również na 3.0. Dziękuję bardzo za znalezienie tego.
pikanezi
1
@Dims Żadnej różnicy, naprawdę: {'Name Field.1': {$exists: true}}.
JohnnyHK
9
@JoseRicardoBustosM. To znalazłoby dokumenty, w których namezawiera co najmniej 1 element, ale OP szukał więcej niż 1.
JohnnyHK
128

Uważam, że jest to najszybsze zapytanie, które odpowiada na twoje pytanie, ponieważ nie używa interpretowanej $whereklauzuli:

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

Oznacza to „wszystkie dokumenty oprócz tych bez nazwy (nieistniejącej lub pustej tablicy) lub z tylko jedną nazwą”.

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" ] }
>
Tobia
źródło
9
@viren Nie wiem. Było to z pewnością lepsze niż rozwiązania Javascript, ale w nowszej wersji MongoDB powinieneś prawdopodobnie użyć{'name.1': {$exists: true}}
Tobia
@Tobia, moje pierwsze użycie było $ istnieje tylko, ale tak naprawdę używa tak bardzo całej tabeli tak wolno. db.test.find ({"name": "abc", "d.5": {$ istnieje: true}, "d.6": {$ istnieje: true}}) "nReturned": 46525, "wykonanieTimeMillis „: 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 ] "]}} Jeśli zobaczysz, że skanował cały stół.
Byłoby miło zaktualizować odpowiedź, aby polecić inne alternatywy (na przykład 'name.1': {$exists: true}}, a także dlatego, że jest ona zakodowana na „1” i nie skaluje się do arbitralnej lub parametrycznej minimalnej długości tablicy.
Dan Dascalescu,
1
Może to być szybkie, ale rozpada się, jeśli szukasz list> N, gdzie N nie jest małe.
Brandon Hill,
62

Możesz także użyć agregacji:

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

// dodajesz „rozmiar_nazwy” do dokumentu transportowego i używasz go do filtrowania rozmiaru nazwy

one_cent_ thinkt
źródło
To rozwiązanie jest najbardziej ogólne wraz z @ JohnnyHK, ponieważ może być używane dla dowolnej wielkości tablicy.
arun
jeśli chcę użyć „size_of_name” wewnątrz projekcji, to jak mogę to zrobić? Właściwie chcę użyć $ slice wewnątrz projekcji, gdzie jego wartość jest równa $ slice: [0, "nazwa_rozmiaru" - pomiń] ??
Sudhanshu Gaur
44

Spróbuj zrobić coś takiego:

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

1 jest liczbą, jeśli chcesz pobrać rekord większy niż 50, to zrób ArrayName.50 Dzięki.

Aman Goel
źródło
2
Ta sama odpowiedź została udzielona trzy lata wcześniej .
Dan Dascalescu
Jestem z przyszłości i doceniłbym to: To rozwiązanie działa poprzez sprawdzenie, czy element istnieje na wymienionej pozycji. Dlatego kolekcja musi być większa | równa tej liczbie.
MarAvFe
czy możemy umieścić w zapytaniu jakąś liczbę dynamiczną, np. „ArrayName. <some_num>”?
Sahil Mahajan
Tak, możesz użyć dowolnego numeru. Jeśli chcesz pobrać rekord większy niż N, przekaż n.
Aman Goel
36

Żadne z powyższych nie działało dla mnie. Ten zrobił, więc dzielę się tym:

db.collection.find( {arrayName : {$exists:true}, $where:'this.arrayName.length>1'} )
lesolorzanov
źródło
javascript działa wolniej niż natywne operatory dostarczane przez mongodb, ale jest bardzo elastyczny. patrz: stackoverflow.com/a/7811259/2893073 , więc ostatecznym rozwiązaniem jest: stackoverflow.com/a/15224544/2893073
Eddy
26

Możesz użyć $ expr (operator wersji mongo 3.6), aby użyć funkcji agregujących w regularnym zapytaniu.

Porównaj query operatorsvs aggregation comparison operators.

db.accommodations.find({$expr:{$gt:[{$size:"$name"}, 1]}})
Sagar Veeram
źródło
W jaki sposób można przekazać zamiast $nametablicy, która jest dokument podrzędny, na przykład w „osoba” rekordu passport.stamps? Próbowałem różnych kombinacji cytatów, ale dostaję "The argument to $size must be an array, but was of type: string/missing".
Dan Dascalescu,
3
@DanDascalescu Wygląda na to, że znaczki nie są obecne we wszystkich dokumentach. Możesz użyć ifNull do wyprowadzenia pustej tablicy, gdy znaczki nie są obecne. Coś w styludb.col.find({$expr:{$gt:[{$size:{$ifNull:["$passport.stamps", []]}}, 1]}})
Sagar Veeram,
22
db.accommodations.find({"name":{"$exists":true, "$ne":[], "$not":{"$size":1}}})
Yadvendar
źródło
1
Nie skaluje się to dobrze do innych minimalnych rozmiarów (powiedzmy 10).
Dan Dascalescu,
taka sama jak pierwsza odpowiedź
arianpress
13

Znalazłem to rozwiązanie, aby znaleźć przedmioty o polu tablicy większym niż pewna długość

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

W agregacji pierwszego dopasowania $ użyto argumentu, który jest prawdziwy dla wszystkich dokumentów. Jeśli puste, dostanę

"errmsg" : "exception: The argument to $size must be an Array, but was of type: EOO"
Bariera
źródło
To jest w zasadzie taka sama odpowiedź jak ten , przewidziany 2 lata wcześniej.
Dan Dascalescu
1

Znam jego stare pytanie, ale próbuję tego z $ gte i $ size w znalezieniu. Myślę, że znalezienie () jest szybsze.

db.getCollection('collectionName').find({ name : { $gte : {  $size : 1 } }})
Bhagvat Lande
źródło
-5

Chociaż powyższe odpowiedzi działają poprawnie, to, co pierwotnie próbowałeś zrobić, było poprawne, ale masz tylko składnię wstecz (przełącz „$ size” i „$ gt”) ..

Poprawny:

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

Błędny:

db.collection.find({items: {$size: {$gt: 1}}})
Steffan Perry
źródło
1
Nie rozumiem, dlaczego tak wiele głosów negatywnych - to działa dla mnie idealnie!
Jake Stokes
Nie głosowałem, ale to nie działa (v4.2).
Evgeni Nabokov
Działa idealnie dobrze, v 4.2.5
wersja