Khái niệm cơ bản ở đây là bạn cần khung tổng hợp để áp dụng các điều kiện để "lọc" các phần tử mảng theo các điều kiện. Tùy thuộc vào phiên bản có sẵn, có các kỹ thuật khác nhau có thể được áp dụng.
Trong mọi trường hợp, đây là kết quả:
{
"_id" : ObjectId("593921425ccc8150f35e7664"),
"user1" : 1,
"user2" : 4,
"messages" : {
"sender" : 1,
"datetime" : ISODate("2017-06-09T10:04:50Z"),
"body" : "hiii 1"
}
}
{
"_id" : ObjectId("593921425ccc8150f35e7663"),
"user1" : 1,
"user2" : 3,
"messages" : {
"sender" : 1,
"datetime" : ISODate("2017-06-10T10:04:50Z"),
"body" : "hiii 2"
}
}
{
"_id" : ObjectId("593921425ccc8150f35e7662"),
"user1" : 1,
"user2" : 2,
"messages" : {
"sender" : 1,
"datetime" : ISODate("2017-06-08T10:04:50Z"),
"body" : "hiii 0"
}
}
MongoDB 3.4 trở lên
db.chat.aggregate([
{ "$match": { "messages.sender": 1 } },
{ "$replaceRoot": {
"newRoot": {
"$let": {
"vars": {
"messages": {
"$filter": {
"input": "$messages",
"as": "m",
"cond": { "$eq": [ "$$m.sender", 1 ] }
}
},
"maxDate": {
"$max": {
"$map": {
"input": {
"$filter": {
"input": "$messages",
"as": "m",
"cond": { "$eq": [ "$$m.sender", 1 ] }
}
},
"as": "m",
"in": "$$m.datetime"
}
}
}
},
"in": {
"_id": "$_id",
"user1": "$user1",
"user2": "$user2",
"messages": {
"$arrayElemAt": [
{ "$filter": {
"input": "$$messages",
"as": "m",
"cond": { "$eq": [ "$$m.datetime", "$$maxDate" ] }
}},
0
]
}
}
}
}
}}
])
Đây là cách hiệu quả nhất tận dụng lợi thế của $ ReplaceRoot
cho phép chúng tôi khai báo các biến để sử dụng trong cấu trúc "được thay thế" bằng cách sử dụng $ let
. Ưu điểm chính ở đây là điều này chỉ yêu cầu "hai" giai đoạn đường ống.
Để khớp với nội dung mảng, bạn sử dụng $ filter
nơi bạn áp dụng $ eq
hoạt động logic để kiểm tra giá trị của "sender"
. Trong trường hợp điều kiện khớp, thì chỉ các mục nhập mảng phù hợp mới được trả về.
Sử dụng cùng một $ filter
để chỉ các mục nhập "người gửi" phù hợp mới được xem xét, sau đó chúng tôi muốn áp dụng $ max
qua danh sách "đã lọc" đến các giá trị trong "datetime"
. $ max
] 5
giá trị là ngày "mới nhất" theo các điều kiện.
Chúng tôi muốn giá trị này để sau này chúng tôi có thể so sánh kết quả trả về từ mảng "đã lọc" với "ngày tối đa" này. Đó là những gì xảy ra bên trong "in"
khối $ let
trong đó hai "biến" được khai báo trước đó cho nội dung được lọc và "maxDate" lại được áp dụng cho $ filter
để trả về giá trị phải là giá trị duy nhất đáp ứng cả hai điều kiện có "ngày mới nhất".
Vì bạn chỉ muốn "một" kết quả, chúng tôi sử dụng $ arrayElemAt
để sử dụng giá trị thay vì mảng.
MongoDB 3.2
db.chat.aggregate([
{ "$match": { "messages.sender": 1 } },
{ "$project": {
"user1": 1,
"user2": 1,
"messages": {
"$filter": {
"input": "$messages",
"as": "m",
"cond": { "$eq": [ "$$m.sender", 1 ] }
}
},
"maxDate": {
"$max": {
"$map": {
"input": {
"$filter": {
"input": "$messages",
"as": "m",
"cond": { "$eq": [ "$$m.sender", 1 ] }
}
},
"as": "m",
"in": "$$m.datetime"
}
}
}
}},
{ "$project": {
"user1": 1,
"user2": 1,
"messages": {
"$arrayElemAt":[
{ "$filter": {
"input": "$messages",
"as": "m",
"cond": { "$eq": [ "$$m.datetime", "$maxDate" ] }
}},
0
]
}
}}
])
Về cơ bản đây là quá trình giống như được mô tả, nhưng không có $ ReplaceRoot
giai đoạn đường ống, chúng tôi cần áp dụng trong hai $ project
các giai đoạn. Lý do cho điều này là chúng tôi cần "giá trị được tính toán" từ "maxDate" để thực hiện điều đó cuối cùng $ filter
và nó không có sẵn để thực hiện trong một câu lệnh ghép, vì vậy thay vào đó chúng tôi chia các đường ống dẫn. Điều này có tác động nhỏ đến chi phí chung của hoạt động.
Trong MongoDB 2.6 đến 3.0, chúng tôi có thể sử dụng hầu hết các kỹ thuật ở đây ngoại trừ $ arrayElemAt
và chấp nhận kết quả "mảng" với một mục nhập duy nhất hoặc đưa vào một $ thư giãn
giai đoạn để đối phó với những gì bây giờ phải là một mục duy nhất.
MongoDB phiên bản trước
db.chat.aggregate([
{ "$match": { "messages.sender": 1 } },
{ "$unwind": "$messages" },
{ "$match": { "messages.sender": 1 } },
{ "$sort": { "_id": 1, "messages.datetime": -1 } },
{ "$group": {
"_id": "$_id",
"user1": { "$first": "$user1" },
"user2": { "$first": "$user2" },
"messages": { "$first": "$messages" }
}}
])
Mặc dù có vẻ ngắn gọn, nhưng đây là hoạt động tốn kém nhất cho đến nay. Tại đây, bạn phải sử dụng $ unwind
để áp dụng các điều kiện cho các phần tử của mảng. Đây là một quá trình rất tốn kém vì nó tạo ra một bản sao của mỗi tài liệu cho mỗi mục nhập mảng và về cơ bản được thay thế bằng các toán tử hiện đại để tránh điều này trong trường hợp "lọc".
$ khớp
thứ hai
ở đây giai đoạn loại bỏ bất kỳ phần tử nào (bây giờ là "tài liệu") không phù hợp với điều kiện "người gửi". Sau đó, chúng tôi áp dụng $ sort
để đặt ngày "mới nhất" lên đầu mỗi tài liệu bằng _id
, do đó có hai phím "sắp xếp".
Cuối cùng, chúng tôi áp dụng $ group
để chỉ tham khảo tài liệu gốc, sử dụng $ 1
làm bộ tích lũy để lấy phần tử "ở trên cùng".