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

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

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

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

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

Tak satu pun dari yang di atas bekerja untuk saya. Yang ini jadi saya bagikan:

db.collection.find( {arrayName : {$exists:true}, $where:'this.arrayName.length>1'} )
lesolorzanov
sumber
javascript dijalankan lebih lambat dari operator asli yang disediakan oleh mongodb, tetapi sangat fleksibel. lihat: stackoverflow.com/a/7811259/2893073 , Jadi solusi terakhirnya adalah: stackoverflow.com/a/15224544/2893073
Eddy
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]}})
Sagar Veeram
sumber
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}}})
Yadvendar
sumber
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"
Barrard
sumber
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 } }})
Bhagvat Lande
sumber
-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}}})
Steffan Perry
sumber
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