ค้นหาเอกสารที่มีขนาดอาร์เรย์มากกว่า 1


664

ฉันมีชุดรวบรวม MongoDB พร้อมเอกสารในรูปแบบต่อไปนี้:

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

ขณะนี้ฉันสามารถรับเอกสารที่ตรงกับขนาดอาร์เรย์ที่ระบุ:

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

ส่งคืนเอกสารที่ถูกต้องพร้อม 2 องค์ประกอบในnameอาร์เรย์อย่างถูกต้อง อย่างไรก็ตามฉันไม่สามารถทำ$gtคำสั่งเพื่อส่งคืนเอกสารทั้งหมดที่nameเขตข้อมูลมีขนาดอาร์เรย์ที่มากกว่า 2:

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

ฉันจะเลือกเอกสารทั้งหมดที่มีnameขนาดใหญ่กว่าหนึ่งเอกสารได้อย่างไร (โดยเฉพาะอย่างยิ่งโดยไม่ต้องแก้ไขโครงสร้างข้อมูลปัจจุบัน)


3
MongoDB รุ่นใหม่กว่ามีตัวดำเนินการ $ size; คุณควรตรวจสอบ @ คำตอบของ
tobia

4
วิธีการแก้ปัญหาจริง: FooArray: {$ gt: {$ size: 'length'}} -> ความยาวอาจเป็นตัวเลขใดก็ได้
Sergi Nadal

คำตอบ:


489

ปรับปรุง:

สำหรับ MongoDB เวอร์ชั่น2.2 +วิธีที่มีประสิทธิภาพมากขึ้นในการทำเช่นนี้อธิบายโดย@JohnnyHKอีกคำตอบ


1. ใช้$ ที่ไหน

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

แต่...

Javascript รันช้ากว่าโอเปอร์เรเตอร์ที่อยู่ในหน้านี้ แต่มีความยืดหยุ่นสูง ดูหน้าการประมวลผลฝั่งเซิร์ฟเวอร์สำหรับข้อมูลเพิ่มเติม

2. สร้างฟิลด์พิเศษNamesArrayLengthอัปเดตด้วยความยาวของชื่ออาร์เรย์จากนั้นใช้ในแบบสอบถาม:

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

มันจะเป็นทางออกที่ดีกว่าและจะทำงานได้เร็วขึ้นมาก (คุณสามารถสร้างดัชนีได้)


4
เยี่ยมมากขอบคุณมาก แม้ว่าจริง ๆ แล้วฉันมีเอกสารบางอย่างที่ไม่มีชื่อดังนั้นจำเป็นต้องปรับเปลี่ยนแบบสอบถามให้เป็น: db.accommodations.find ({$ where: "if (this.name && this.name.length> 1) นี้ {ส่งคืนสิ่งนี้ ;} "});
emson

คุณยินดีใช่คุณสามารถใช้จาวาสคริปต์ใด ๆ$whereมันยืดหยุ่นมาก
Andrew Orsich

8
@emson ฉันคิดว่ามันจะเร็วกว่าที่จะทำอะไรเช่น {"ชื่อ": {$ มีอยู่: 1}, $ โดย: "this.name.lenght> 1"} ... ย่อส่วนหนึ่งในการสืบค้นจาวาสคริปต์ที่ช้าลง ฉันคิดว่ามันใช้งานได้และเงินที่มีอยู่จะมีความสำคัญมากกว่า
nairbv

1
ฉันไม่รู้ว่าคุณสามารถฝังจาวาสคริปต์ในแบบสอบถามได้ json อาจจะยุ่งยาก แบบสอบถามจำนวนมากเหล่านี้เป็นเพียงครั้งเดียวที่ป้อนด้วยมือเท่านั้นจึงไม่จำเป็นต้องปรับให้เหมาะสม ฉันจะใช้เคล็ดลับนี้บ่อยครั้งที่ +1
pferrel

3
หลังจากเพิ่ม / ลบองค์ประกอบออกจาก Array เราจำเป็นต้องอัปเดตจำนวนของ "NamesArrayLength" สิ่งนี้สามารถทำได้ในแบบสอบถามเดียว? หรือต้องใช้ 2 แบบสอบถามหนึ่งรายการสำหรับการปรับปรุงอาร์เรย์และอีกชุดสำหรับการปรับปรุงการนับ?
ขุนศึก

1328

มีวิธีที่มีประสิทธิภาพมากขึ้นในการทำเช่นนี้ใน MongoDB 2.2+ ตอนนี้คุณสามารถใช้ดัชนีอาเรย์ตัวเลขในคีย์วัตถุคิวรีได้

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

คุณสามารถรองรับการสืบค้นนี้ด้วยดัชนีที่ใช้นิพจน์ตัวกรองบางส่วน (ต้องการ 3.2+):

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

16
มีคนช่วยอธิบายวิธีทำดัชนีนี้ได้ไหม
Ben

26
ฉันประทับใจจริง ๆ กับประสิทธิภาพการทำงานนี้และวิธี 'ออกนอกกรอบ' ที่คุณคิดว่าจะหาวิธีแก้ปัญหานี้ ใช้งานได้กับ 2.6 เช่นกัน
earthmeLon

2
ทำงานบน 3.0 เช่นกัน ขอบคุณมากสำหรับการค้นหาสิ่งนี้
pikanezi

1
@Dims ไม่แตกต่างกันจริง ๆ : {'Name Field.1': {$exists: true}}.
JohnnyHK

9
@JoseRicardoBustosM ที่จะพบเอกสารที่nameมีอย่างน้อย 1 องค์ประกอบ แต่ OP ถูกมองหามากขึ้นกว่า 1
JohnnyHK

128

ฉันเชื่อว่านี่เป็นคำถามที่เร็วที่สุดที่ตอบคำถามของคุณเพราะไม่ได้ใช้$whereประโยคที่ตีความ:

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

มันหมายถึง "เอกสารทั้งหมดยกเว้นเอกสารที่ไม่มีชื่อ (อาเรย์ที่ไม่มีอยู่หรือว่างเปล่า) หรือมีเพียงชื่อเดียว"

ทดสอบ:

> 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 ฉันไม่รู้ นี่เป็นวิธีที่ดีกว่าโซลูชัน Javascript อย่างแน่นอน แต่สำหรับ MongoDB ที่ใหม่กว่าคุณควรใช้{'name.1': {$exists: true}}
Tobia

@Tobia การใช้งานครั้งแรกของฉันคือ $ มีอยู่เพียงอย่างเดียว แต่จริงๆแล้วมันใช้การสแกนทั้งตารางช้ามาก db.test.find ({"ชื่อ": "abc", "d.5": {$ มีอยู่แล้ว: จริง}, "d.6": {$ มีอยู่: จริง}}) "nReturned": 46525, "executionTimeMillis ": 167289," totalKeysExamined ": 10990840," totalDocsExamined ": 10990840," inputStage ": {" stage ":" IXSCAN "," keyPattern ": {" ชื่อ ": 1," d ": 1}," index "" : "name_1_d_1", "ทิศทาง": "ไปข้างหน้า", "ดัชนีขอบเขต": {"ชื่อ": ["[\" abc \ ", \" abc \ "]"], "d": ["[MinKey, MaxKey ] "]}} หากคุณเห็นว่ามันสแกนทั้งตาราง

จะดีที่จะปรับปรุงคำตอบเพื่อแนะนำทางเลือกอื่น ๆ (เช่น'name.1': {$exists: true}}และเพราะนี่คือ hardcoded สำหรับ "1" และไม่ได้ปรับขนาดความยาวอาร์เรย์ขั้นต่ำโดยพลการหรือพารามิเตอร์
Dan Dascalescu

1
สิ่งนี้อาจเร็ว แต่แยกออกหากคุณกำลังมองหารายการ> N โดยที่ N ไม่เล็ก
Brandon Hill

62

คุณสามารถใช้การรวมได้เช่นกัน:

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

// คุณเพิ่ม "size_of_name" ไปยังเอกสารการขนส่งและใช้เพื่อกรองขนาดของชื่อ


วิธีนี้เป็นวิธีที่ใช้กันทั่วไปมากที่สุดพร้อมกับ @ JohnnyHK's เนื่องจากสามารถใช้กับอาเรย์ทุกขนาดได้
อรุณ

ถ้าฉันต้องการใช้ "size_of_name" ในการฉายภาพแล้วฉันจะทำอย่างไร ?? ที่จริงฉันต้องการใช้ $ slice ในการฉายโดยที่ค่าเท่ากับ $ slice: [0, "size_of_name" - skip] ??
Sudhanshu Gaur

44

ลองทำสิ่งนี้:

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

1 คือตัวเลขถ้าคุณต้องการดึงข้อมูลเรคคอร์ดมากกว่า 50 ให้ทำ ArrayName.50 ขอบคุณ


2
คำตอบเดียวที่ได้รับเมื่อสามปีก่อน
Dan Dascalescu

ฉันมาจากอนาคตและจะได้ชื่นชมสิ่งนี้: วิธีนี้ใช้งานได้โดยการตรวจสอบว่ามีองค์ประกอบอยู่ในตำแหน่งดังกล่าวหรือไม่ ดังนั้นการรวบรวมจะต้องมากกว่า | เท่ากับจำนวนนั้น
MarAvFe

เราสามารถใส่จำนวนไดนามิกเช่น "ArrayName. <some_num>" ภายในแบบสอบถามได้ไหม
Sahil Mahajan

ใช่คุณสามารถใช้หมายเลขใด ๆ หากคุณต้องการดึงบันทึกที่มากกว่า N ให้ผ่าน n
Aman Goel

36

ไม่มีข้อใดถูกเลยสำหรับฉัน อันนี้ฉันก็เลยแบ่งปันมัน:

db.collection.find( {arrayName : {$exists:true}, $where:'this.arrayName.length>1'} )

จาวาสคริปต์รันช้ากว่าตัวดำเนินการดั้งเดิมที่จัดเตรียมโดย mongodb แต่มีความยืดหยุ่นสูง ดู: stackoverflow.com/a/7811259/2893073ดังนั้นทางออกสุดท้ายคือ: stackoverflow.com/a/15224544/2893073
Eddy

26

คุณสามารถใช้$ expr (ตัวดำเนินการรุ่น 3.6 mongo) เพื่อใช้ฟังก์ชันการรวมในแบบสอบถามปกติ

เปรียบเทียบVSquery operatorsaggregation comparison operators

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

วิธีที่คุณจะผ่านแทน$nameอาร์เรย์ที่เป็นเอกสารย่อยเช่นใน "บุคคล" บันทึกการpassport.stamps? ฉันพยายามอยู่รวมกัน quoting ต่างๆ "The argument to $size must be an array, but was of type: string/missing"แต่ฉันได้รับ
Dan Dascalescu

3
@DanDascalescu ดูเหมือนว่าแสตมป์จะไม่ปรากฏในเอกสารทั้งหมด คุณสามารถใช้ifNullเพื่อแสดงอาเรย์ที่ว่างเปล่าเมื่อไม่มีตราประทับ บางอย่างเช่นdb.col.find({$expr:{$gt:[{$size:{$ifNull:["$passport.stamps", []]}}, 1]}})
Sagar Veeram

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

1
ขนาดนี้ไม่ได้ปรับขนาดได้อย่างเหมาะสมกับขนาดต่ำสุดอื่น ๆ (พูด, 10)
Dan Dascalescu

เช่นเดียวกับคำตอบแรก
arianpress

22

MongoDB 3.6 รวม $ expr https://docs.mongodb.com/manual/reference/operator/query/expr/

คุณสามารถใช้ $ expr เพื่อประเมินค่านิพจน์ภายในการจับคู่ $ หรือค้นหา

{ $match: {
           $expr: {$gt: [{$size: "$yourArrayField"}, 0]}
         }
}

หรือค้นหา

collection.find({$expr: {$gte: [{$size: "$yourArrayField"}, 0]}});

1
ในขณะที่ถูกต้องนี่เป็นคำตอบที่ซ้ำกัน ดูstackoverflow.com/a/48410837/2424641โดย @ user2683814
SteveB

13

ฉันพบโซลูชันนี้เพื่อค้นหารายการที่มีเขตข้อมูลอาร์เรย์มากกว่าความยาวที่แน่นอน

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

การรวมการจับคู่ $ แรกใช้อาร์กิวเมนต์ที่เป็นจริงสำหรับเอกสารทั้งหมด ถ้าว่างฉันจะได้รับ

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

นี่เป็นคำตอบเดียวกับที่ให้ไว้เมื่อ 2 ปีก่อน
Dan Dascalescu

1

ฉันรู้คำถามเก่า แต่ฉันลองกับ $ gte และ $ size ในการค้นหา ฉันคิดว่าจะหา () เร็วขึ้น

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

-5

แม้ว่าคำตอบข้างต้นจะทำงานทั้งหมด แต่สิ่งที่คุณพยายามทำในตอนแรกนั้นเป็นวิธีที่ถูกต้อง แต่คุณเพียงแค่ใช้ไวยากรณ์ข้างหลัง (สลับ "$ size" และ "$ gt") ..

แก้ไข:

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

ไม่ถูกต้อง:

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

1
ฉันไม่เห็นว่าทำไม downvotes มากมาย - มันใช้ได้อย่างสมบูรณ์แบบสำหรับฉัน!
Jake Stokes

ฉันไม่ได้ลงคะแนน แต่มันไม่ทำงาน (v4.2)
Evgeni Nabokov

ทำงานได้ดีอย่างสมบูรณ์ v 4.2.5
jperl
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.