ตัวดำเนินการ $ คลี่คลายใน MongoDB คืออะไร?


103

นี่เป็นวันแรกของฉันกับ MongoDB ดังนั้นโปรดไปง่าย ๆ กับฉัน :)

ฉันไม่เข้าใจ$unwindโอเปอเรเตอร์อาจเป็นเพราะภาษาอังกฤษไม่ใช่ภาษาแม่ของฉัน

db.article.aggregate(
    { $project : {
        author : 1 ,
        title : 1 ,
        tags : 1
    }},
    { $unwind : "$tags" }
);

ผู้ดำเนินโครงการเป็นสิ่งที่ฉันเข้าใจฉันคิดว่า (มันก็เหมือนSELECTไม่ใช่เหรอ) แต่แล้ว$unwind(อ้าง) ผลตอบแทนเอกสารหนึ่งสำหรับสมาชิกของอาร์เรย์คลายภายในเอกสารแหล่งที่มาทุกคน

เป็นเช่นนี้JOINหรือไม่? ถ้าใช่วิธีที่ผลมาจาก$project(มี_id, author, titleและtagsสาขา) สามารถเทียบกับtagsอาร์เรย์?

หมายเหตุ : ฉันได้นำตัวอย่างมาจากเว็บไซต์ MongoDB ฉันไม่ทราบโครงสร้างของtagsอาร์เรย์ ฉันคิดว่ามันเป็นชื่อแท็กที่เรียบง่าย

คำตอบ:


240

ก่อนอื่นขอต้อนรับสู่ MongoDB!

สิ่งที่ต้องจำก็คือ MongoDB ใช้วิธีการจัดเก็บข้อมูลแบบ "NoSQL" ดังนั้นจงพินาศความคิดในการเลือกการเข้าร่วมและอื่น ๆ จากใจของคุณ วิธีการจัดเก็บข้อมูลของคุณอยู่ในรูปแบบของเอกสารและคอลเลคชันซึ่งช่วยให้สามารถเพิ่มและรับข้อมูลจากที่จัดเก็บข้อมูลของคุณได้แบบไดนามิก

ดังที่กล่าวมาเพื่อให้เข้าใจแนวคิดที่อยู่เบื้องหลังพารามิเตอร์ $ คลี่คลายก่อนอื่นคุณต้องเข้าใจว่ากรณีการใช้งานที่คุณพยายามอ้างนั้นพูดถึงอะไร ตัวอย่างเอกสารจากmongodb.orgมีดังนี้:

{
 title : "this is my title" ,
 author : "bob" ,
 posted : new Date () ,
 pageViews : 5 ,
 tags : [ "fun" , "good" , "fun" ] ,
 comments : [
             { author :"joe" , text : "this is cool" } ,
             { author :"sam" , text : "this is bad" }
 ],
 other : { foo : 5 }
}

สังเกตว่าแท็กเป็นอาร์เรย์ 3 รายการในกรณีนี้คือ "สนุก" "ดี" และ "สนุก"

สิ่งที่ $ คลี่คลายคืออนุญาตให้คุณลอกเอกสารสำหรับแต่ละองค์ประกอบและส่งคืนเอกสารผลลัพธ์นั้น หากต้องการคิดแบบนี้ในวิธีการแบบคลาสสิกมันจะเท่ากับ "สำหรับแต่ละรายการในอาร์เรย์แท็กให้ส่งคืนเอกสารที่มีเฉพาะรายการนั้น"

ดังนั้นผลลัพธ์ของการรันสิ่งต่อไปนี้:

db.article.aggregate(
    { $project : {
        author : 1 ,
        title : 1 ,
        tags : 1
    }},
    { $unwind : "$tags" }
);

จะส่งคืนเอกสารต่อไปนี้:

{
     "result" : [
             {
                     "_id" : ObjectId("4e6e4ef557b77501a49233f6"),
                     "title" : "this is my title",
                     "author" : "bob",
                     "tags" : "fun"
             },
             {
                     "_id" : ObjectId("4e6e4ef557b77501a49233f6"),
                     "title" : "this is my title",
                     "author" : "bob",
                     "tags" : "good"
             },
             {
                     "_id" : ObjectId("4e6e4ef557b77501a49233f6"),
                     "title" : "this is my title",
                     "author" : "bob",
                     "tags" : "fun"
             }
     ],
     "OK" : 1
}

สังเกตว่าสิ่งเดียวที่เปลี่ยนแปลงในอาร์เรย์ผลลัพธ์คือสิ่งที่จะถูกส่งกลับในค่าแท็ก หากคุณจำเป็นต้องอ้างอิงเพิ่มเติมเกี่ยวกับวิธีการทำงานนี้เราได้รวมการเชื่อมโยงที่นี่ หวังว่านี่จะช่วยได้และขอให้โชคดีกับการจู่โจมของคุณในระบบ NoSQL ที่ดีที่สุดที่ฉันเคยเจอมา


44

$unwind ทำซ้ำเอกสารแต่ละรายการในไปป์ไลน์หนึ่งครั้งต่อองค์ประกอบอาร์เรย์

ดังนั้นถ้าท่อป้อนข้อมูลของคุณมีเอกสารบทความหนึ่งที่มีสององค์ประกอบในtags, {$unwind: '$tags'}จะเปลี่ยนท่อจะเป็นสองเอกสารบทความที่เหมือนกันยกเว้นสำหรับtagsฟิลด์ ในเอกสารแรกtagsจะมีองค์ประกอบแรกจากอาร์เรย์ของเอกสารต้นฉบับและในเอกสารที่สองtagsจะมีองค์ประกอบที่สอง


22

มาทำความเข้าใจกับตัวอย่าง

นี่คือวิธีการที่บริษัทเอกสารดูเหมือนว่า:

เอกสารต้นฉบับ

$unwindช่วยให้เราสามารถใช้เอกสารเป็นอินพุทที่มีอาร์เรย์ฟิลด์มูลค่าและผลิตเอกสารการส่งออกดังกล่าวว่ามีเอกสารการส่งออกหนึ่งสำหรับองค์ประกอบในอาร์เรย์แต่ละ แหล่งที่มา

ขั้นตอน $ ผ่อนคลาย

ลองกลับไปที่ตัวอย่าง บริษัท ของเราและดูการใช้ขั้นตอนคลายเครียด คำถามนี้:


db.companies.aggregate([
    { $match: {"funding_rounds.investments.financial_org.permalink": "greylock" } },
    { $project: {
        _id: 0,
        name: 1,
        amount: "$funding_rounds.raised_amount",
        year: "$funding_rounds.funded_year"
    } }
])

สร้างเอกสารที่มีอาร์เรย์ทั้งจำนวนและปี

ผลผลิตของโครงการ

เนื่องจากเรากำลังเข้าถึงจำนวนเงินที่เพิ่มขึ้นและปีที่ได้รับการสนับสนุนสำหรับทุกองค์ประกอบภายในอาร์เรย์รอบการระดมทุน ในการแก้ไขปัญหานี้เราสามารถรวมขั้นตอนการผ่อนคลายก่อนขั้นตอนโครงการของเราในไปป์ไลน์การรวมนี้และกำหนดพารามิเตอร์โดยบอกว่าเราต้องการให้unwindอาร์เรย์รอบการระดมทุน:


db.companies.aggregate([
    { $match: {"funding_rounds.investments.financial_org.permalink": "greylock" } },
    { $unwind: "$funding_rounds" },
    { $project: {
        _id: 0,
        name: 1,
        amount: "$funding_rounds.raised_amount",
        year: "$funding_rounds.funded_year"
    } }
])

การคลายมีผลในการส่งออกเอกสารไปยังขั้นถัดไปมากกว่าที่จะได้รับเป็นอินพุต

ถ้าเราดูfunding_roundsอาร์เรย์เราจะรู้ว่าสำหรับแต่ละอาร์เรย์funding_roundsมีraised_amountและfunded_yearฟิลด์ ดังนั้นunwindเอกสารแต่ละรายการที่เป็นองค์ประกอบของfunding_roundsอาร์เรย์จะสร้างเอกสารเอาต์พุต ตอนนี้ในตัวอย่างนี้ค่าของเราคือstrings แต่โดยไม่คำนึงถึงประเภทของค่าสำหรับองค์ประกอบในอาร์เรย์unwindจะสร้างเอกสารเอาต์พุตสำหรับแต่ละค่าเหล่านี้ดังนั้นฟิลด์ที่เป็นปัญหาจะมีองค์ประกอบนั้นเพียงอย่างเดียว ในกรณีของfunding_roundsองค์ประกอบที่จะเป็นหนึ่งในเอกสารเหล่านี้เป็นค่าสำหรับfunding_roundsเอกสารที่ได้รับการส่งต่อไปของเราทุกprojectขั้นตอน ผลลัพธ์จากการรันสิ่งนี้คือตอนนี้เราได้รับamountและ a year. หนึ่งในแต่ละรอบการระดมทุนสำหรับทุก บริษัทในคอลเลกชันของเรา สิ่งนี้หมายความว่าการจับคู่ของเราสร้างเอกสารของ บริษัท จำนวนมากและเอกสารของ บริษัท แต่ละฉบับส่งผลให้มีเอกสารจำนวนมาก หนึ่งสำหรับแต่ละรอบการระดมทุนภายในเอกสารของ บริษัท ทุกฉบับ unwindดำเนินการนี้โดยใช้เอกสารที่ส่งมาจากmatchเวที และเอกสารทั้งหมดนี้สำหรับทุก บริษัท จะถูกส่งไปยังprojectขั้นตอน

คลายเอาต์พุต

ดังนั้นเอกสารทั้งหมดที่ funder เป็นGreylock (เช่นในตัวอย่างแบบสอบถาม) จะถูกแบ่งออกเป็นจำนวนของเอกสารเท่ากับจำนวนรอบการระดมทุนสำหรับ บริษัท $match: {"funding_rounds.investments.financial_org.permalink": "greylock" }ที่ตรงกับตัวกรองทุก projectและแต่ละคนเอกสารที่ส่งผลให้ผู้นั้นจะผ่านไปพร้อมกับเรา ตอนนี้unwindสร้างสำเนาที่ถูกต้องสำหรับทุกเอกสารที่ได้รับเป็นอินพุต ฟิลด์ทั้งหมดมีคีย์และค่าเหมือนกันโดยมีข้อยกเว้นอย่างหนึ่งและนั่นคือfunding_roundsฟิลด์แทนที่จะเป็นอาร์เรย์ของfunding_roundsเอกสารจะมีค่าเป็นเอกสารเดียวแทนซึ่งเป็นรอบการระดมทุนของแต่ละคน ดังนั้น บริษัท ที่มีการระดมทุน4รอบจะทำให้unwindเกิด4เอกสาร. โดยที่ทุกฟิลด์เป็นสำเนาที่ถูกต้องยกเว้นfunding_roundsฟิลด์ซึ่งแทนที่จะเป็นอาร์เรย์สำหรับแต่ละสำเนาจะเป็นองค์ประกอบแต่ละรายการจากfunding_roundsอาร์เรย์จากเอกสารของ บริษัท ที่unwindกำลังดำเนินการอยู่ ดังนั้นจึงunwindมีผลในการส่งออกเอกสารไปยังขั้นถัดไปมากกว่าที่จะได้รับเป็นอินพุต หมายความว่าprojectตอนนี้สเตจของเราได้รับfunding_roundsฟิลด์นั้นอีกครั้งไม่ใช่อาร์เรย์ แต่เป็นเอกสารซ้อนที่มีraised_amountและfunded_yearฟิลด์แทน ดังนั้นprojectจะได้รับเอกสารหลายฉบับสำหรับแต่ละ บริษัทmatchในตัวกรองดังนั้นจึงสามารถประมวลผลเอกสารแต่ละฉบับแยกกันและระบุจำนวนเงินและปีสำหรับแต่ละรอบการระดมทุนสำหรับแต่ละ บริษัท.


2
ใช้เอกสารเดียวกันจะดีกว่า
Jeb50

1
ในกรณีการใช้งานครั้งแรกสำหรับ $ คลายฉันมีชุดที่ซ้อนกันค่อนข้างซับซ้อน ระหว่าง mongo docs และ stackowerflow ในที่สุดคำตอบของคุณก็ช่วยให้ฉันเข้าใจ $ project และ $ คลี่คลายได้ดีขึ้น ขอบคุณ @Zameer!
7

3

ตามเอกสารอย่างเป็นทางการของ mongodb:

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

คำอธิบายผ่านตัวอย่างพื้นฐาน:

สินค้าคงคลังคอลเลกชันมีเอกสารดังต่อไปนี้:

{ "_id" : 1, "item" : "ABC", "sizes": [ "S", "M", "L"] }
{ "_id" : 2, "item" : "EFG", "sizes" : [ ] }
{ "_id" : 3, "item" : "IJK", "sizes": "M" }
{ "_id" : 4, "item" : "LMN" }
{ "_id" : 5, "item" : "XYZ", "sizes" : null }

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

db.inventory.aggregate( [ { $unwind: "$sizes" } ] )

หรือ

db.inventory.aggregate( [ { $unwind: { path: "$sizes" } } ] 

ผลลัพธ์การค้นหาด้านบน:

{ "_id" : 1, "item" : "ABC", "sizes" : "S" }
{ "_id" : 1, "item" : "ABC", "sizes" : "M" }
{ "_id" : 1, "item" : "ABC", "sizes" : "L" }
{ "_id" : 3, "item" : "IJK", "sizes" : "M" }

ทำไมจึงจำเป็น?

$ คลี่คลายมีประโยชน์มากในขณะที่ดำเนินการรวบรวม มันแบ่งเอกสารที่ซับซ้อน / ซ้อนกันเป็นเอกสารง่ายๆก่อนที่จะดำเนินการต่างๆเช่นการเรียงลำดับการค้นหา ฯลฯ

หากต้องการทราบข้อมูลเพิ่มเติมเกี่ยวกับ $ คลาย:

https://docs.mongodb.com/manual/reference/operator/aggregation/unwind/

หากต้องการทราบข้อมูลเพิ่มเติมเกี่ยวกับการรวม:

https://docs.mongodb.com/manual/reference/operator/aggregation-pipeline/


2

พิจารณาตัวอย่างด้านล่างเพื่อทำความเข้าใจข้อมูลนี้ในคอลเล็กชัน

{
        "_id" : 1,
        "shirt" : "Half Sleeve",
        "sizes" : [
                "medium",
                "XL",
                "free"
        ]
}

แบบสอบถาม - db.test1.aggregate ([{$ คลาย: "$ sizes"}]);

เอาท์พุท

{ "_id" : 1, "shirt" : "Half Sleeve", "sizes" : "medium" }
{ "_id" : 1, "shirt" : "Half Sleeve", "sizes" : "XL" }
{ "_id" : 1, "shirt" : "Half Sleeve", "sizes" : "free" }

1

ให้ฉันอธิบายด้วยวิธี corelated กับ RDBMS นี่คือคำสั่ง:

db.article.aggregate(
    { $project : {
        author : 1 ,
        title : 1 ,
        tags : 1
    }},
    { $unwind : "$tags" }
);

เพื่อใช้กับเอกสาร / บันทึก :

{
 title : "this is my title" ,
 author : "bob" ,
 posted : new Date () ,
 pageViews : 5 ,
 tags : [ "fun" , "good" , "fun" ] ,
 comments : [
             { author :"joe" , text : "this is cool" } ,
             { author :"sam" , text : "this is bad" }
 ],
 other : { foo : 5 }
}

$ / โครงการเลือกเพียงแค่ส่งกลับข้อมูลเหล่านี้ / คอลัมน์เป็น

เลือกผู้เขียนชื่อแท็กจากบทความ

ต่อไปเป็นส่วนที่น่าสนุกของ Mongo ให้พิจารณาอาร์เรย์นี้tags : [ "fun" , "good" , "fun" ]เป็นตารางอื่นที่เกี่ยวข้อง (ไม่สามารถเป็นตารางการค้นหา / อ้างอิงได้เนื่องจากค่ามีการทำซ้ำบางส่วน) ชื่อ "แท็ก" โปรดจำไว้ว่า SELECT มักจะสร้างสิ่งต่างๆในแนวตั้งดังนั้นคลาย "แท็ก" คือการแบ่ง () ในแนวตั้งเป็น "แท็ก" ของตาราง

ผลลัพธ์สุดท้ายของ $ project + $ คลาย: ป้อนคำอธิบายภาพที่นี่

แปลผลลัพธ์เป็น JSON:

{ "author": "bob", "title": "this is my title", "tags": "fun"},
{ "author": "bob", "title": "this is my title", "tags": "good"},
{ "author": "bob", "title": "this is my title", "tags": "fun"}

เนื่องจากเราไม่ได้บอกให้ Mongo ละเว้นช่อง "_id" จึงมีการเพิ่มอัตโนมัติ

กุญแจสำคัญคือการทำให้เหมือนตารางเพื่อดำเนินการรวม


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