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


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


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

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

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


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.


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


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

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

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"

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

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

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.