Có rất nhiều điều trong điều này, đặc biệt nếu bạn tương đối mới sử dụng tổng hợp , nhưng nó có thể được thực hiện. Tôi sẽ giải thích các giai đoạn sau khi liệt kê:
db.collection.aggregate([
// 1. Unwind both arrays
{"$unwind": "$win"},
{"$unwind": "$loss"},
// 2. Cast each field with a type and the array on the end
{"$project":{
"win.player": "$win.player",
"win.type": {"$cond":[1,"win",0]},
"loss.player": "$loss.player",
"loss.type": {"$cond": [1,"loss",0]},
"score": {"$cond":[1,["win", "loss"],0]}
}},
// Unwind the "score" array
{"$unwind": "$score"},
// 3. Reshape to "result" based on the value of "score"
{"$project": {
"result.player": {"$cond": [
{"$eq": ["$win.type","$score"]},
"$win.player",
"$loss.player"
] },
"result.type": {"$cond": [
{"$eq":["$win.type", "$score"]},
"$win.type",
"$loss.type"
]}
}},
// 4. Get all unique result within each document
{"$group": { "_id": { "_id":"$_id", "result": "$result" } }},
// 5. Sum wins and losses across documents
{"$group": {
"_id": "$_id.result.player",
"wins": {"$sum": {"$cond": [
{"$eq":["$_id.result.type","win"]},1,0
]}},
"losses": {"$sum":{"$cond": [
{"$eq":["$_id.result.type","loss"]},1,0
]}}
}}
])
Tóm tắt
Điều này có giả định rằng các "người chơi" trong mỗi mảng "thắng" và "thua" đều là duy nhất để bắt đầu. Điều đó có vẻ hợp lý cho những gì dường như được mô hình hóa ở đây:
-
Mở rộng cả hai mảng. Điều này tạo ra các bản sao nhưng chúng sẽ bị xóa sau đó.
-
Khi chiếu, có một số cách sử dụng $ cond toán tử (một bậc ba) để nhận một số giá trị chuỗi theo nghĩa đen. Và cách sử dụng cuối cùng là đặc biệt, bởi vì và mảng đang được thêm vào. Vì vậy, sau khi chiếu mảng đó sẽ không được gắn lại nữa. Nhiều bản sao hơn, nhưng đó là điểm chính. Mỗi bản ghi một "thắng", một "thua".
-
Dự báo nhiều hơn với $ cond toán tử và việc sử dụng $ eq cả nhà điều hành. Lần này chúng tôi hợp nhất hai trường thành một. Vì vậy, bằng cách sử dụng này, khi "loại" của trường khớp với giá trị trong "điểm" thì "trường khóa" đó được sử dụng cho giá trị trường "kết quả". Kết quả là hai trường "thắng" và "thua" hiện có cùng tên, được xác định bằng "loại".
-
Loại bỏ các bản sao trong mỗi tài liệu. Chỉ cần nhóm theo tài liệu
_id
và các trường "kết quả" làm khóa. Bây giờ sẽ có các bản ghi "thắng" và "thua" giống như trong tài liệu gốc, chỉ ở một dạng khác khi chúng bị xóa khỏi mảng. -
Cuối cùng, nhóm trên tất cả các tài liệu để nhận tổng số cho mỗi "người chơi". Sử dụng thêm $ cond và $ eq nhưng lần này để xác định xem tài liệu hiện tại là "thắng" hay "thua". Vì vậy, nơi điều này khớp với chúng tôi trả về 1 và nơi sai chúng tôi trả về 0. Những giá trị đó được chuyển đến $ sum để có được tổng số cho "thắng" và "thua".
Và điều đó giải thích cách đi đến kết quả.
Tìm hiểu thêm về toán tử tổng hợp từ tài liệu. Một số cách sử dụng "vui nhộn" cho $ cond trong danh sách đó có thể được thay thế bằng $ chữ nhà điều hành. Nhưng điều đó sẽ không khả dụng cho đến khi phiên bản 2.6 trở lên được phát hành.
Trường hợp "đơn giản hóa" cho MongoDB 2.6 trở lên
Tất nhiên có toán tử bộ mới trong bản phát hành sắp tới là gì tại thời điểm viết bài, điều này sẽ giúp đơn giản hóa phần nào điều này:
db.collection.aggregate([
{ "$unwind": "$win" },
{ "$project": {
"win.player": "$win.player",
"win.type": { "$literal": "win" },
"loss": 1,
}},
{ "$group": {
"_id" : {
"_id": "$_id",
"loss": "$loss"
},
"win": { "$push": "$win" }
}},
{ "$unwind": "$_id.loss" },
{ "$project": {
"loss.player": "$_id.loss.player",
"loss.type": { "$literal": "loss" },
"win": 1,
}},
{ "$group": {
"_id" : {
"_id": "$_id._id",
"win": "$win"
},
"loss": { "$push": "$loss" }
}},
{ "$project": {
"_id": "$_id._id",
"results": { "$setUnion": [ "$_id.win", "$loss" ] }
}},
{ "$unwind": "$results" },
{ "$group": {
"_id": "$results.player",
"wins": {"$sum": {"$cond": [
{"$eq":["$results.type","win"]},1,0
]}},
"losses": {"$sum":{"$cond": [
{"$eq":["$results.type","loss"]},1,0
]}}
}}
])
Nhưng "đơn giản hóa" là điều gây tranh cãi. Đối với tôi, điều đó chỉ "cảm thấy" giống như nó đang "chu du xung quanh" và làm nhiều việc hơn. Nó chắc chắn là truyền thống hơn, vì nó chỉ dựa vào $ setUnion để hợp nhất kết quả mảng.
Nhưng "tác phẩm" đó sẽ bị vô hiệu hóa bằng cách thay đổi lược đồ của bạn một chút, như được hiển thị ở đây:
{
"_id" : ObjectId("531ea2b1fcc997d5cc5cbbc9"),
"win": [
{
"player" : "Player2",
"type" : "win"
},
{
"player" : "Player4",
"type" : "win"
}
],
"loss" : [
{
"player" : "Player6",
"type" : "loss"
},
{
"player" : "Player5",
"type" : "loss"
},
]
}
Và điều này loại bỏ sự cần thiết phải chiếu nội dung mảng bằng cách thêm thuộc tính "type" như chúng ta đã làm, đồng thời giảm bớt truy vấn và công việc đã hoàn thành:
db.collection.aggregate([
{ "$project": {
"results": { "$setUnion": [ "$win", "$loss" ] }
}},
{ "$unwind": "$results" },
{ "$group": {
"_id": "$results.player",
"wins": {"$sum": {"$cond": [
{"$eq":["$results.type","win"]},1,0
]}},
"losses": {"$sum":{"$cond": [
{"$eq":["$results.type","loss"]},1,0
]}}
}}
])
Và tất nhiên chỉ cần thay đổi lược đồ của bạn như sau:
{
"_id" : ObjectId("531ea2b1fcc997d5cc5cbbc9"),
"results" : [
{
"player" : "Player6",
"type" : "loss"
},
{
"player" : "Player5",
"type" : "loss"
},
{
"player" : "Player2",
"type" : "win"
},
{
"player" : "Player4",
"type" : "win"
}
]
}
Điều đó làm cho mọi thứ rất dễ. Và điều này có thể được thực hiện trong các phiên bản trước 2.6. Vì vậy, bạn có thể làm điều đó ngay bây giờ:
db.collection.aggregate([
{ "$unwind": "$results" },
{ "$group": {
"_id": "$results.player",
"wins": {"$sum": {"$cond": [
{"$eq":["$results.type","win"]},1,0
]}},
"losses": {"$sum":{"$cond": [
{"$eq":["$results.type","loss"]},1,0
]}}
}}
])
Vì vậy, đối với tôi, nếu đó là ứng dụng của tôi, tôi sẽ muốn giản đồ ở dạng cuối cùng được hiển thị ở trên hơn là những gì bạn có. Tất cả công việc được thực hiện trong các hoạt động tổng hợp được cung cấp (ngoại trừ câu lệnh cuối cùng) nhằm mục đích lấy biểu mẫu giản đồ hiện có và thao tác nó thành this , do đó, thật dễ dàng để chạy câu lệnh tổng hợp đơn giản như được hiển thị ở trên.
Vì mỗi người chơi được "gắn thẻ" với thuộc tính "thắng / thua", bạn luôn có thể truy cập riêng biệt vào "người thắng / người thua" của mình.
Như một điều cuối cùng. Ngày tháng của bạn là một chuỗi. Tôi không thích điều đó.
Có thể có một lý do để làm như vậy nhưng tôi không thấy nó. Nếu bạn cần nhóm theo ngày điều đó dễ dàng thực hiện trong việc tổng hợp chỉ bằng cách sử dụng ngày BSON thích hợp. Sau đó, bạn cũng sẽ có thể dễ dàng làm việc với các khoảng thời gian khác.
Vì vậy, nếu bạn đã sửa ngày và đặt ngày đó là ngày_ bắt_đầu và thay thế "thời lượng" bằng end_time , sau đó bạn có thể giữ một cái gì đó mà bạn có thể lấy "thời lượng" từ phép toán đơn giản + Bạn nhận được rất nhiều thêm lợi ích bằng cách sử dụng chúng dưới dạng giá trị ngày thay thế.
Vì vậy, điều đó có thể cung cấp cho bạn một số thực phẩm để suy nghĩ về lược đồ của bạn.
Đối với những người quan tâm, đây là một số mã tôi đã sử dụng để tạo một tập hợp dữ liệu hoạt động:
// Ye-olde array shuffle
function shuffle(array) {
var m = array.length, t, i;
while (m) {
i = Math.floor(Math.random() * m--);
t = array[m];
array[m] = array[i];
array[i] = t;
}
return array;
}
for ( var l=0; l<10000; l++ ) {
var players = ["Player1","Player2","Player3","Player4"];
var playlist = shuffle(players);
for ( var x=0; x<playlist.length; x++ ) {
var obj = {
player: playlist[x],
score: Math.floor(Math.random() * (100000 - 50 + 1)) +50
};
playlist[x] = obj;
}
var rec = {
duration: Math.floor(Math.random() * (50000 - 15000 +1)) +15000,
date: new Date(),
win: playlist.slice(0,2),
loss: playlist.slice(2)
};
db.game.insert(rec);
}