Permintaan dokumen dengan ukuran array lebih besar dari 1


664

Saya memiliki koleksi MongoDB dengan dokumen dalam format berikut:

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

Saat ini saya bisa mendapatkan dokumen yang cocok dengan ukuran array tertentu:

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

Ini dengan benar mengembalikan dokumen dengan 2 elemen dalam namearray. Namun, saya tidak bisa melakukan $gtperintah untuk mengembalikan semua dokumen yang namebidangnya memiliki ukuran array lebih dari 2:

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

Bagaimana saya bisa memilih semua dokumen dengan namearray ukuran lebih besar dari satu (lebih disukai tanpa harus mengubah struktur data saat ini)?


3
Versi MongoDB yang lebih baru memiliki operator $ size; Anda harus memeriksa jawaban @ tobia
AlbertEngelB

4
Solusi aktual: FooArray: {$ gt: {$ size: 'length'}} -> panjangnya bisa berapa saja
Sergi Nadal

Jawaban:


489

Memperbarui:

Untuk mongodb versi 2.2 + cara yang lebih efisien untuk melakukan hal ini dijelaskan oleh @JohnnyHK di lain jawabannya .


1.Menggunakan $ di mana

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

Tapi...

Javascript dijalankan lebih lambat dari operator asli yang tercantum di halaman ini, tetapi sangat fleksibel. Lihat halaman pemrosesan sisi server untuk informasi lebih lanjut.

2.Buat bidang tambahanNamesArrayLength , perbarui dengan panjang array nama dan kemudian gunakan dalam permintaan:

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

Ini akan menjadi solusi yang lebih baik, dan akan bekerja lebih cepat (Anda dapat membuat indeks di atasnya).


4
Hebat, itu sempurna terima kasih. Meskipun saya sebenarnya memiliki beberapa dokumen yang tidak memiliki nama, maka saya harus memodifikasi kueri menjadi: db.accommodations.find ({$ where: "if (this.name && this.name.length> 1) {kembalikan ini ;} "});
emson

Anda dipersilakan, ya Anda dapat menggunakan javascript apa pun $where, sangat fleksibel.
Andrew Orsich

8
@emson Saya pikir akan lebih cepat untuk melakukan sesuatu seperti {"name": {$ exist: 1}, $ where: "this.name.lenght> 1"} ... meminimalkan bagian dalam permintaan javascript yang lebih lambat. Saya berasumsi bahwa berfungsi dan $ ada akan memiliki prioritas lebih tinggi.
nairbv

1
Saya tidak tahu Anda bisa menanamkan javascript dalam permintaan, json bisa rumit. Banyak dari pertanyaan ini hanya satu kali dimasukkan dengan tangan sehingga optimasi tidak diperlukan. Saya akan sering menggunakan trik ini +1
pferrel

3
Setelah menambahkan / menghapus elemen dari Array, kita perlu memperbarui hitungan "NamesArrayLength". Bisakah ini dilakukan dalam satu permintaan? Atau memerlukan 2 kueri, satu untuk memperbarui array dan satu lagi untuk memperbarui hitungan?
WarLord

1329

Ada cara yang lebih efisien untuk melakukan ini di MongoDB 2.2+ sekarang Anda dapat menggunakan indeks array numerik dalam kunci objek kueri.

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

Anda dapat mendukung permintaan ini dengan indeks yang menggunakan ekspresi filter parsial (membutuhkan 3,2+):

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

16
Bisakah seseorang tolong jelaskan bagaimana cara mengindeks ini.
Ben

26
Saya benar-benar terkesan dengan betapa efektifnya ini dan juga seberapa 'out of the box' Anda berpikir untuk menemukan solusi ini. Ini bekerja pada 2.6, juga.
earthmeLon

2
Bekerja di 3.0 juga. Terima kasih banyak telah menemukan ini.
pikanezi

1
@Dims ada perbedaan, benar-benar: {'Name Field.1': {$exists: true}}.
JohnnyHK

9
@JoseRicardoBustosM. Itu akan menemukan dokumen di mana nameberisi setidaknya 1 elemen, tetapi OP mencari lebih dari 1.
JohnnyHK

128

Saya percaya ini adalah permintaan tercepat yang menjawab pertanyaan Anda, karena itu tidak menggunakan $whereklausa yang ditafsirkan :

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

Ini berarti "semua dokumen kecuali yang tanpa nama (baik array yang tidak ada atau kosong) atau hanya dengan satu nama."

Uji:

> 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 saya tidak tahu. Ini tentu saja lebih baik daripada solusi Javascript, tetapi untuk MongoDB yang lebih baru Anda mungkin harus menggunakan{'name.1': {$exists: true}}
Tobia

@Tobia penggunaan pertama saya adalah $ ada saja tetapi sebenarnya menggunakan pemindaian seluruh tabel sangat lambat. db.test.find ({"name": "abc", "d.5": {$ exist: true}, "d.6": {$ exist: true}}) "nReturned": 46525, "eksekusiTimeMillis ": 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 ] "]}} Jika Anda melihatnya memindai seluruh tabel.

Akan menyenangkan untuk memperbarui jawaban untuk merekomendasikan alternatif lain (seperti 'name.1': {$exists: true}}, dan juga karena ini adalah hardcoded untuk "1" dan tidak skala ke panjang array minimum sewenang-wenang atau parametrik.
Dan Dascalescu

1
Ini mungkin cepat tetapi berantakan jika Anda mencari daftar> N, di mana N tidak kecil.
Brandon Hill

62

Anda juga dapat menggunakan agregat:

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

// Anda menambahkan "size_of_name" ke transit dokumen dan menggunakannya untuk menyaring ukuran nama


Solusi ini adalah yang paling umum, bersama dengan @ JohnnyHK karena dapat digunakan untuk berbagai ukuran array.
arun

jika saya ingin menggunakan "size_of_name" di dalam proyeksi lalu bagaimana saya bisa melakukan itu ?? Sebenarnya saya ingin menggunakan $ slice di dalam proyeksi di mana nilainya sama dengan $ slice: [0, "size_of_name" - skip] ??
Sudhanshu Gaur

44

Coba lakukan sesuatu seperti ini:

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

1 adalah angka, jika Anda ingin mengambil catatan yang lebih besar dari 50 maka lakukan ArrayName.50 Terima kasih.


2
Jawaban yang sama diberikan tiga tahun sebelumnya .
Dan Dascalescu

Saya dari masa depan dan akan menghargai ini: Solusi ini bekerja dengan memeriksa apakah ada elemen pada posisi tersebut. Karena itu, koleksinya harus lebih besar | sama dengan angka itu.
MarAvFe

bisakah kita memasukkan beberapa nomor dinamis seperti "ArrayName. <some_num>" di dalam kueri?
Sahil Mahajan

Ya, Anda dapat menggunakan nomor apa pun. Jika Anda ingin mengambil rekaman yang lebih besar dari N maka lulus n.
Aman Goel


26

Anda dapat menggunakan $ expr (operator versi 3,6 mongo) untuk menggunakan fungsi agregasi dalam kueri reguler.

Bandingkan query operatorsvs aggregation comparison operators.

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

Bagaimana Anda meneruskan alih-alih $namearray yang merupakan subdokumen, misalnya dalam catatan "orang" passport.stamps,? Saya mencoba berbagai kombinasi kutipan tetapi saya dapat "The argument to $size must be an array, but was of type: string/missing".
Dan Dascalescu

3
@DanDascalescu Sepertinya perangko tidak ada di semua dokumen. Anda dapat menggunakan ifNull untuk menampilkan array kosong ketika prangko tidak ada. Sesuatu sepertidb.col.find({$expr:{$gt:[{$size:{$ifNull:["$passport.stamps", []]}}, 1]}})
Sagar Veeram

22
db.accommodations.find({"name":{"$exists":true, "$ne":[], "$not":{"$size":1}}})

1
Ini tidak skala dengan baik ke ukuran minimum lainnya (katakanlah, 10).
Dan Dascalescu

sama dengan jawaban pertama
arianpress


13

Saya menemukan solusi ini, untuk menemukan item dengan bidang array yang lebih besar dari panjang tertentu

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

Agregat $ pertandingan pertama menggunakan argumen yang benar untuk semua dokumen. Jika kosong, saya akan mendapatkannya

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

Ini pada dasarnya jawaban yang sama dengan yang ini , asalkan 2 tahun sebelumnya.
Dan Dascalescu

1

Saya tahu pertanyaan lamanya, tetapi saya mencoba ini dengan $ gte dan $ size. Saya pikir untuk menemukan () lebih cepat.

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

-5

Meskipun jawaban di atas semuanya berfungsi, Apa yang awalnya Anda coba lakukan adalah cara yang benar, namun Anda hanya memiliki sintaks mundur (beralih "$ size" dan "$ gt") ..

Benar:

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

Salah:

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

1
Saya tidak mengerti mengapa begitu banyak downvotes - ini sangat cocok untuk saya!
Jake Stokes

Saya tidak downvote, tetapi tidak berhasil (v4.2).
Evgeni Nabokov

Bekerja dengan sangat baik, v 4.2.5
jperl
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.