Câu hỏi của bạn có hai khả năng đối với tôi, nhưng có lẽ một số giải thích để giúp bạn bắt đầu.
Trước hết, tôi cần giải thích cho bạn rằng bạn hiểu sai ý định của $elemMatch
và nó được sử dụng sai trong trường hợp này.
Ý tưởng về $elemMatch
là tạo một "tài liệu truy vấn" thực sự được áp dụng cho các phần tử của mảng. Mục đích là nơi bạn có "nhiều điều kiện" trên một tài liệu trong mảng để đối sánh nó một cách riêng biệt trong tài liệu thành viên chứ không phải trong toàn bộ mảng của tài liệu bên ngoài. tức là:
{
"data": [
{ "a": 1, "b": 3 },
{ "a": 2, "b": 2 }
]
}
Và truy vấn sau sẽ hoạt động, mặc dù không có phần tử đơn lẻ thực tế nào trong mảng đó khớp, nhưng toàn bộ tài liệu thì có:
db.collection.find({ "data.a": 1, "data.b": 2 })
Nhưng để kiểm tra xem một phần tử thực tế có khớp với cả hai điều kiện đó hay không, đây là nơi bạn sử dụng $elemMatch
:
db.collection.find({ "data": { "a": 1, "b": 2 } })
Vì vậy, không có kết quả phù hợp nào trong mẫu đó và nó sẽ chỉ khớp khi một phần tử mảng cụ thể có cả hai phần tử đó.
Bây giờ chúng ta có $elemMatch
đã giải thích, đây là truy vấn đơn giản của bạn:
db.collection.find({ "tracks.artist": { "$in": arr } })
Đơn giản hơn nhiều và nó hoạt động bằng cách xem xét tất cả các thành viên mảng theo một trường và trả về nơi bất kỳ phần tử nào trong tài liệu chứa ít nhất một trong những kết quả có thể có đó.
Nhưng không phải những gì bạn đang hỏi, như vậy với câu hỏi của bạn. Nếu bạn đọc qua tuyên bố cuối cùng đó, bạn sẽ nhận ra rằng $in
thực sự là một $or
tình trạng. Đây chỉ là một dạng rút gọn để hỏi "hoặc" trên cùng một phần tử trong tài liệu.
Với suy nghĩ đó, trọng tâm của điều bạn đang yêu cầu là "và" hoạt động trong đó tất cả các giá trị "ba" được chứa. Giả sử rằng bạn chỉ gửi "ba" mục trong thử nghiệm thì bạn có thể sử dụng dạng $and
ở dạng rút gọn của $all
:
db.collection.find({ "tracks.artist": { "$all": arr } })
Điều đó sẽ chỉ trả lại cho bạn các tài liệu có phần tử nằm trong các thành viên của mảng đó khớp với "tất cả" các phần tử được chỉ định trong điều kiện thử nghiệm. Đó có thể là những gì bạn muốn, nhưng có trường hợp tất nhiên bạn muốn chỉ định một danh sách giả sử, "bốn nghệ sĩ trở lên" để thử nghiệm và chỉ muốn "ba" hoặc một số ít hơn trong số đó, trong trường hợp đó một $all
toán tử quá ngắn.
Nhưng có một cách hợp lý để giải quyết vấn đề này, chỉ cần xử lý thêm một chút với các toán tử không có sẵn cho các truy vấn cơ bản nhưng có sẵn cho khung tổng hợp :
var arr = ["A","B","C","D"]; // List for testing
db.collection.aggregate([
// Match conditions for documents to narrow down
{ "$match": {
"tracks.artist": { "$in": arr },
"tracks.2": { "$exists": true } // you would construct in code
}},
// Test the array conditions
{ "$project": {
"user": 1,
"tracks": 1, // any fields you want to keep
"matched": {
"$gte": [
{ "$size": {
"$setIntersection": [
{ "$map": {
"input": "$tracks",
"as": "t",
"in": { "$$t.artist" }
}},
arr
]
}},
3
]
}
}},
// Filter out anything that did not match
{ "$match": { "matched": true } }
])
Giai đoạn đầu tiên triển khai truy vấn chuẩn $match
điều kiện để chỉ lọc các tài liệu có "khả năng" phù hợp với các điều kiện. Trường hợp hợp lý ở đây là sử dụng $in
như trước đây, nó sẽ tìm thấy các tài liệu đó trong đó ít nhất một trong các phần tử có trong mảng "thử nghiệm" của bạn hiện diện trong ít nhất một trong các trường thành viên trong mảng riêng của tài liệu.
Mệnh đề tiếp theo là thứ lý tưởng bạn nên xây dựng trong mã vì nó liên quan đến "độ dài" của mảng. Ý tưởng ở đây là nơi bạn muốn có ít nhất "ba" đối sánh thì mảng bạn đang kiểm tra trong tài liệu phải có ít nhất "ba" phần tử để đáp ứng điều đó, vì vậy không ích gì khi truy xuất tài liệu có "hai" hoặc ít hơn các phần tử mảng. vì chúng không bao giờ có thể khớp với "ba".
Vì tất cả các truy vấn MongoDB về cơ bản chỉ là một biểu diễn của cấu trúc dữ liệu, nên điều này rất dễ xây dựng. tức là đối với JavaScript:
var matchCount = 3; // how many matches we want
var match1 = { "$match": { "tracks.artist": { "$in": arr } } };
match1["$match"]["tracks."+ (matchCount-1)] = { "$exits": true };
Logic ở đó là dạng "ký hiệu dấu chấm" với $exists
kiểm tra sự hiện diện của một phần tử tại chỉ mục được chỉ định (n-1) và nó cần phải ở đó để mảng ít nhất phải có độ dài đó.
Phần còn lại của việc thu hẹp lý tưởng là sử dụng $setIntersection
để trả về các phần tử phù hợp giữa mảng thực và mảng được kiểm tra. Vì mảng trong tài liệu không khớp với cấu trúc cho "mảng thử nghiệm" nên nó cần được chuyển đổi qua $map
hoạt động được đặt để chỉ trả về trường "nghệ sĩ" từ mỗi phần tử mảng.
Khi "giao điểm" của hai mảng đó được tạo ra, cuối cùng nó đã được kiểm tra cho $size
trong danh sách kết quả của các phần tử chung, nơi thử nghiệm được áp dụng để thấy rằng "ít nhất ba" trong số các phần tử đó là điểm chung.
Cuối cùng, bạn chỉ cần "lọc ra" bất kỳ điều gì không đúng sự thật bằng cách sử dụng $match
điều kiện.
Lý tưởng nhất là bạn đang sử dụng MongoDB 2.6 hoặc cao hơn để có sẵn các toán tử đó. Đối với các phiên bản 2.2.x và 2.4.x trước đó, vẫn có thể thực hiện được, nhưng chỉ cần thêm một chút công việc và xử lý chi phí:
db.collection.aggregate([
// Match conditions for documents to narrow down
{ "$match": {
"tracks.artist": { "$in": arr },
"tracks.2": { "$exists": true } // you would construct in code
}},
// Unwind the document array
{ "$unwind": "$tracks" },
// Filter the content
{ "$match": { "tracks.artist": { "$in": arr } }},
// Group for distinct values
{ "$group": {
"_id": {
"_id": "$_id",
"artist": "$tracks.artist"
}
}},
// Make arrays with length
{ "$group": {
"_id": "$_id._id",
"artist": { "$push": "$_id.artist" },
"length": { "$sum": 1 }
}},
// Filter out the sizes
{ "$match": { "length": { "$gte": 3 } }}
])