Có một số cách để thực hiện việc này bằng cách sử dụng khung tổng hợp
Chỉ là một tập hợp dữ liệu đơn giản, ví dụ:
{
"_id" : ObjectId("538181738d6bd23253654690"),
"movies": [
{ "_id": 1, "rating": 5 },
{ "_id": 2, "rating": 6 },
{ "_id": 3, "rating": 7 }
]
},
{
"_id" : ObjectId("538181738d6bd23253654691"),
"movies": [
{ "_id": 1, "rating": 5 },
{ "_id": 4, "rating": 6 },
{ "_id": 2, "rating": 7 }
]
},
{
"_id" : ObjectId("538181738d6bd23253654692"),
"movies": [
{ "_id": 2, "rating": 5 },
{ "_id": 5, "rating": 6 },
{ "_id": 6, "rating": 7 }
]
}
Sử dụng "người dùng" đầu tiên làm ví dụ, bây giờ bạn muốn tìm xem có ai trong số hai người dùng còn lại có ít nhất hai bộ phim giống nhau hay không.
Đối với MongoDB 2.6 trở lên, bạn có thể chỉ cần sử dụng $setIntersection
toán tử cùng với $size
nhà điều hành:
db.users.aggregate([
// Match the possible documents to reduce the working set
{ "$match": {
"_id": { "$ne": ObjectId("538181738d6bd23253654690") },
"movies._id": { "$in": [ 1, 2, 3 ] },
"$and": [
{ "movies": { "$not": { "$size": 1 } } }
]
}},
// Project a copy of the document if you want to keep more than `_id`
{ "$project": {
"_id": {
"_id": "$_id",
"movies": "$movies"
},
"movies": 1,
}},
// Unwind the array
{ "$unwind": "$movies" },
// Build the array back with just `_id` values
{ "$group": {
"_id": "$_id",
"movies": { "$push": "$movies._id" }
}},
// Find the "set intersection" of the two arrays
{ "$project": {
"movies": {
"$size": {
"$setIntersection": [
[ 1, 2, 3 ],
"$movies"
]
}
}
}},
// Filter the results to those that actually match
{ "$match": { "movies": { "$gte": 2 } } }
])
Điều này vẫn có thể xảy ra trong các phiên bản trước của MongoDB không có các toán tử đó, chỉ cần sử dụng thêm một vài bước:
db.users.aggregate([
// Match the possible documents to reduce the working set
{ "$match": {
"_id": { "$ne": ObjectId("538181738d6bd23253654690") },
"movies._id": { "$in": [ 1, 2, 3 ] },
"$and": [
{ "movies": { "$not": { "$size": 1 } } }
]
}},
// Project a copy of the document along with the "set" to match
{ "$project": {
"_id": {
"_id": "$_id",
"movies": "$movies"
},
"movies": 1,
"set": { "$cond": [ 1, [ 1, 2, 3 ], 0 ] }
}},
// Unwind both those arrays
{ "$unwind": "$movies" },
{ "$unwind": "$set" },
// Group back the count where both `_id` values are equal
{ "$group": {
"_id": "$_id",
"movies": {
"$sum": {
"$cond":[
{ "$eq": [ "$movies._id", "$set" ] },
1,
0
]
}
}
}},
// Filter the results to those that actually match
{ "$match": { "movies": { "$gte": 2 } } }
])
Chi tiết
Điều đó có thể hơi khó khăn một chút, vì vậy chúng ta có thể xem xét từng giai đoạn và chia nhỏ các giai đoạn đó để xem họ đang làm gì.
$ trận đấu :Bạn không muốn thao tác trên mọi tài liệu trong bộ sưu tập, vì vậy đây là cơ hội để xóa các mục không thể khớp ngay cả khi vẫn còn nhiều việc phải làm để tìm chính xác những cái. Vì vậy, điều hiển nhiên là loại trừ cùng một "người dùng" và sau đó chỉ đối sánh các tài liệu có ít nhất một trong những bộ phim giống như đã được tìm thấy cho "người dùng" đó.
Điều có ý nghĩa tiếp theo là cân nhắc điều đó khi bạn muốn đối sánh n
sau đó chỉ các mục nhập tài liệu có mảng "phim" lớn hơn n-1
có thể thực sự chứa các kết quả phù hợp. Việc sử dụng $and
ở đây trông buồn cười và không bắt buộc cụ thể, nhưng nếu các kết quả phù hợp bắt buộc là 4
thì phần thực tế đó của câu lệnh sẽ giống như sau:
"$and": [
{ "movies": { "$not": { "$size": 1 } } },
{ "movies": { "$not": { "$size": 2 } } },
{ "movies": { "$not": { "$size": 3 } } }
]
Vì vậy, về cơ bản bạn "loại trừ" các mảng có thể không đủ dài để có n
diêm. Lưu ý ở đây rằng $size
này
toán tử trong biểu mẫu truy vấn khác với $size
cho khung tổng hợp. Chẳng hạn, không có cách nào để sử dụng điều này với toán tử bất đẳng thức chẳng hạn như $gt
mục đích của nó là để khớp cụ thể với "kích thước" được yêu cầu. Do đó, biểu mẫu truy vấn này để chỉ định tất cả các kích thước có thể có nhỏ hơn.
$ dự án :Có một số mục đích trong câu lệnh này, trong đó một số mục đích khác nhau tùy thuộc vào phiên bản MongoDB mà bạn có. Thứ nhất, và theo tùy chọn, một bản sao tài liệu đang được giữ dưới _id
giá trị để các trường này không bị sửa đổi bởi các bước còn lại. Phần khác ở đây là giữ mảng "phim" ở đầu tài liệu như một bản sao cho giai đoạn tiếp theo.
Điều gì cũng đang xảy ra trong phiên bản được trình bày cho các phiên bản trước 2.6 là có một mảng bổ sung đại diện cho _id
giá trị cho "phim" phù hợp. Việc sử dụng $cond
toán tử ở đây chỉ là một cách tạo ra một biểu diễn "theo nghĩa đen" của mảng. Thật hài hước, MongoDB 2.6 giới thiệu một toán tử được gọi là $literal
để thực hiện chính xác điều này mà không theo cách hài hước mà chúng tôi đang sử dụng $cond
ngay tại đây.
$ thư giãn :Để làm bất cứ điều gì xa hơn, mảng phim cần được mở ra vì trong cả hai trường hợp đó là cách duy nhất để cô lập _id
hiện có giá trị cho các mục nhập cần được so khớp với "tập hợp". Vì vậy, đối với phiên bản trước 2.6, bạn cần "giải phóng" cả hai mảng hiện có.
$ nhóm :Đối với MongoDB 2.6 trở lên, bạn chỉ đang nhóm lại thành một mảng chỉ chứa _id
giá trị của phim bị xóa "xếp hạng".
Trước 2.6 vì tất cả các giá trị được trình bày "cạnh nhau" (và có nhiều sự trùng lặp) nên bạn đang thực hiện so sánh hai giá trị để xem chúng có giống nhau hay không. Đó là true
, điều này cho $cond
câu lệnh toán tử để trả về giá trị 1
hoặc 0
trong đó điều kiện là false
. Điều này được chuyển trực tiếp qua $sum
để tổng số phần tử phù hợp trong mảng vào "bộ" bắt buộc.
$ dự án :Đây là phần khác biệt đối với MongoDB 2.6 và lớn hơn là vì bạn đã đẩy lùi một mảng "phim" _id
các giá trị mà bạn đang sử dụng sau đó $setIntersection
để so sánh trực tiếp các mảng đó. Kết quả của việc này là một mảng chứa các phần tử giống nhau, mảng này sau đó được bao bọc trong một $size
để xác định có bao nhiêu phần tử được trả về trong tập hợp đó.
$ trận đấu :Là giai đoạn cuối cùng đã được thực hiện ở đây, bước rõ ràng là chỉ đối sánh những tài liệu có số lượng phần tử giao nhau lớn hơn hoặc bằng số được yêu cầu.
Chung kết
Về cơ bản đó là cách bạn làm điều đó. Trước 2.6 thì phức tạp hơn một chút và sẽ yêu cầu nhiều bộ nhớ hơn một chút do việc mở rộng được thực hiện bằng cách sao chép từng thành viên mảng được tìm thấy bởi tất cả các giá trị có thể của tập hợp, nhưng đây vẫn là một cách hợp lệ để thực hiện điều này.
Tất cả những gì bạn cần làm là áp dụng điều này với n
lớn hơn các giá trị phù hợp để đáp ứng các điều kiện của bạn và tất nhiên đảm bảo rằng đối sánh người dùng ban đầu của bạn có n
bắt buộc khả năng. Nếu không, chỉ cần tạo mã này trên n-1
từ độ dài của mảng "phim" của "người dùng".