Toán tử $ unwind trong MongoDB là gì?


103

Đây là ngày đầu tiên của tôi với MongoDB vì vậy hãy thoải mái với tôi :)

Tôi không thể hiểu người $unwindđiều hành, có thể vì tiếng Anh không phải là ngôn ngữ mẹ đẻ của tôi.

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

Tôi cho rằng người điều hành dự án là điều mà tôi có thể hiểu được (nó giống như vậy SELECT, phải không?). Nhưng sau đó, $unwind(trích dẫn) trả về một tài liệu cho mọi thành viên của mảng chưa liên kết trong mọi tài liệu nguồn .

Đây có phải là một JOIN ? Nếu có, làm thế nào kết quả của $project(với _id, author, titletagscác lĩnh vực) có thể được so sánh với tagsmảng?

LƯU Ý : Tôi đã lấy ví dụ từ trang web MongoDB, tôi không biết cấu trúc của tagsmảng. Tôi nghĩ đó là một dãy tên thẻ đơn giản.

Câu trả lời:


235

Trước hết, chào mừng bạn đến với MongoDB!

Điều cần nhớ là MongoDB sử dụng cách tiếp cận "NoSQL" để lưu trữ dữ liệu, vì vậy hãy loại bỏ những suy nghĩ về lựa chọn, kết hợp, v.v. khỏi tâm trí của bạn. Cách nó lưu trữ dữ liệu của bạn dưới dạng tài liệu và bộ sưu tập, cho phép một phương tiện động để thêm và lấy dữ liệu từ các vị trí lưu trữ của bạn.

Nói như vậy, để hiểu khái niệm đằng sau tham số $ unwind, trước tiên bạn phải hiểu trường hợp sử dụng mà bạn đang cố gắng trích dẫn đang nói gì. Tài liệu ví dụ từ mongodb.org như sau:

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

Lưu ý rằng các thẻ thực sự là một mảng gồm 3 mục, trong trường hợp này là "vui", "tốt" và "vui".

Những gì $ unwind làm là cho phép bạn bóc một tài liệu cho từng phần tử và trả về tài liệu kết quả đó. Để nghĩ về điều này theo cách tiếp cận cổ điển, nó sẽ tương đương với "đối với mỗi mục trong mảng thẻ, trả về một tài liệu chỉ có mục đó".

Do đó, kết quả của việc chạy như sau:

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

sẽ trả lại các tài liệu sau:

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

Lưu ý rằng điều duy nhất thay đổi trong mảng kết quả là những gì được trả về trong giá trị thẻ. Nếu bạn cần tham khảo thêm về cách thức hoạt động của nó, tôi đã bao gồm một liên kết ở đây . Hy vọng rằng điều này sẽ hữu ích và chúc bạn may mắn khi bước vào một trong những hệ thống NoSQL tốt nhất mà tôi đã xem xét cho đến nay.


44

$unwind sao chép mỗi tài liệu trong đường ống, một lần cho mỗi phần tử mảng.

Vì vậy, nếu đường dẫn đầu vào của bạn chứa một tài liệu bài viết với hai phần tử trong tags, {$unwind: '$tags'}sẽ chuyển đổi đường ống thành hai tài liệu bài viết giống nhau ngoại trừ tagstrường. Trong tài liệu đầu tiên, tagssẽ chứa phần tử đầu tiên từ mảng của tài liệu gốc và trong tài liệu thứ hai, tagssẽ chứa phần tử thứ hai.


22

Hãy hiểu nó bằng một ví dụ

Đây là cách tài liệu công ty trông như thế này:

tài liệu gốc

Các $unwindcho phép chúng tôi để có tài liệu như là đầu vào có một trường mảng có giá trị và tạo ra các tài liệu đầu ra, như vậy mà có một tài liệu đầu ra cho mỗi phần tử trong mảng. nguồn

Giai đoạn thư giãn $

Vì vậy, hãy quay lại các ví dụ về công ty của chúng tôi và xem xét việc sử dụng các giai đoạn thư giãn. Truy vấn này:


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

tạo ra các tài liệu có các mảng cho cả số lượng và năm.

đầu ra dự án

Bởi vì chúng tôi đang truy cập số tiền huy động được và năm tài trợ cho mọi phần tử trong mảng vòng tài trợ. Để khắc phục điều này, chúng tôi có thể bao gồm một giai đoạn thư giãn trước giai đoạn dự án của chúng tôi trong quy trình tổng hợp này và tham số hóa điều này bằng cách nói rằng chúng tôi muốn unwindmảng vòng quay tài trợ:


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

unwind có tác dụng xuất sang giai đoạn tiếp theo nhiều tài liệu hơn số tài liệu nhận được dưới dạng đầu vào

Nếu chúng ta nhìn vào funding_roundsmảng, chúng ta biết rằng với mỗi mảng funding_rounds, có một raised_amountvà một funded_yeartrường. Vì vậy, unwindđối với mỗi một trong các tài liệu là phần tử của funding_roundsmảng sẽ tạo ra một tài liệu đầu ra. Bây giờ, trong ví dụ này, giá trị của chúng ta là strings. Tuy nhiên, bất kể loại giá trị nào cho các phần tử trong một mảng, unwindsẽ tạo ra một tài liệu đầu ra cho mỗi một trong các giá trị này, sao cho trường được đề cập sẽ chỉ có phần tử đó. Trong trường hợp của funding_rounds, phần tử đó sẽ là một trong những tài liệu này làm giá trị funding_roundscho mọi tài liệu được chuyển đến projectgiai đoạn của chúng tôi . Kết quả sau khi chạy nó là bây giờ chúng ta nhận được một amountvà mộtyear . Một cho mỗi vòng tài trợ cho mọi công tytrong bộ sưu tập của chúng tôi. Điều này có nghĩa là trận đấu của chúng tôi tạo ra nhiều tài liệu công ty và mỗi tài liệu công ty đó dẫn đến nhiều tài liệu. Một cho mỗi vòng tài trợ trong mỗi tài liệu của công ty. unwindthực hiện thao tác này bằng cách sử dụng các tài liệu được trao cho nó từ matchgiai đoạn. Và tất cả các tài liệu này cho mọi công ty sau đó được chuyển đến projectgiai đoạn.

thư giãn đầu ra

Vì vậy, tất cả các tài liệu mà nhà tài trợ là Greylock (như trong ví dụ truy vấn) sẽ được chia thành một số tài liệu, bằng số vòng tài trợ cho mọi công ty phù hợp với bộ lọc $match: {"funding_rounds.investments.financial_org.permalink": "greylock" }. Và mỗi tài liệu kết quả đó sau đó sẽ được chuyển cho của chúng tôi project. Bây giờ, hãy unwindtạo một bản sao chính xác cho mọi tài liệu mà nó nhận được làm đầu vào. Tất cả các trường đều có cùng một khóa và giá trị, với một ngoại lệ, đó là funding_roundstrường thay vì là một mảng funding_roundstài liệu, thay vào đó có giá trị là một tài liệu duy nhất, là một vòng tài trợ riêng lẻ. Vì vậy, một công ty có 4 vòng tài trợ sẽ có kết quả làunwind việc tạo ra 4các tài liệu. Trong đó mọi trường là một bản sao chính xác, ngoại trừ funding_roundstrường, thay vì là một mảng cho mỗi bản sao đó sẽ là một phần tử riêng lẻ từ funding_roundsmảng từ tài liệu công ty unwindhiện đang xử lý. Vì vậy, unwindcó tác dụng xuất cho giai đoạn tiếp theo nhiều tài liệu hơn so với đầu vào mà nó nhận được. Điều đó có nghĩa là projectgiai đoạn của chúng ta bây giờ nhận được một funding_roundstrường, không phải là một mảng, mà thay vào đó là một tài liệu lồng nhau có một raised_amountvà một funded_yeartrường. Vì vậy, projectsẽ nhận được nhiều tài liệu cho mỗi công ty matchtrong bộ lọc và do đó có thể xử lý từng tài liệu riêng lẻ và xác định số tiền và năm riêng lẻ cho mỗi vòng tài trợ cho mỗi công ty.


2
sử dụng cùng một tài liệu sẽ tốt hơn.
Jeb50

1
Là trường hợp sử dụng đầu tiên cho $ unwind, tôi có một tập hợp các tập hợp lồng nhau khá phức tạp. Giữa mongo docs và stackowerflow, câu trả lời của bạn cuối cùng đã giúp tôi hiểu $ project và $ unwind tốt hơn. Cảm ơn @Zameer!
bảy

3

Theo tài liệu chính thức mongodb:

$ unwind Giải cấu trúc một trường mảng từ các tài liệu đầu vào để xuất ra một tài liệu cho mỗi phần tử. Mỗi tài liệu đầu ra là tài liệu đầu vào với giá trị của trường mảng được thay thế bằng phần tử.

Giải thích thông qua ví dụ cơ bản:

Một kho thu gom có ​​các tài liệu sau:

{ "_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 }

Các thao tác $ unwind sau đây là tương đương và trả về một tài liệu cho mỗi phần tử trong trường kích thước . Nếu trường kích thước không phân giải thành một mảng nhưng không thiếu, null hoặc một mảng trống, $ unwind sẽ coi toán hạng không phải là mảng như một mảng phần tử.

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

hoặc là

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

Kết quả truy vấn phía trên:

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

Tại sao nó cần thiết?

$ unwind rất hữu ích khi thực hiện tổng hợp. nó phá vỡ tài liệu phức tạp / lồng nhau thành tài liệu đơn giản trước khi thực hiện các hoạt động khác nhau như sắp xếp, tìm kiếm, v.v.

Để biết thêm về $ unwind:

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

Để biết thêm về tổng hợp:

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


2

xem xét ví dụ dưới đây để hiểu Dữ liệu này trong một bộ sưu tập

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

Truy vấn - db.test1.aggregate ([{$ unwind: "$ size"}]);

đầu ra

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

1

Hãy để tôi giải thích một cách cốt lõi với cách RDBMS. Đây là tuyên bố:

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

để áp dụng cho tài liệu / hồ sơ :

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

Các $ dự án / Chọn đơn giản trả về những lĩnh vực / cột như

CHỌN tác giả, tiêu đề, thẻ TỪ bài viết

Tiếp theo là phần thú vị của Mongo, hãy coi mảng này tags : [ "fun" , "good" , "fun" ]như một bảng liên quan khác (không thể là một bảng tra cứu / tham chiếu vì các giá trị có một số trùng lặp) có tên là "tags". Hãy nhớ SELECT thường tạo ra những thứ theo chiều dọc, vì vậy việc rút "thẻ" là tách () theo chiều dọc thành "thẻ" của bảng.

Kết quả cuối cùng của $ project + $ unwind: nhập mô tả hình ảnh ở đây

Dịch đầu ra sang 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"}

Vì chúng tôi đã không yêu cầu Mongo bỏ qua trường "_id" nên nó sẽ tự động được thêm vào.

Điều quan trọng là làm cho nó giống như bảng để thực hiện tổng hợp.


Hoặc một cách khác để nghĩ về nó là UNION ALL
Jeb50
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.