Ada banyak hal dalam hal ini, terutama jika Anda relatif baru menggunakan agregat , tetapi bisa dilakukan. Saya akan menjelaskan tahapannya setelah daftar:
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
]}}
}}
])
Ringkasan
Ini mengambil asumsi bahwa "pemain" di setiap larik "menang" dan "kalah" semuanya unik untuk memulai. Tampaknya masuk akal untuk apa yang tampaknya dimodelkan di sini:
-
Lepaskan kedua array. Ini akan membuat duplikat tetapi akan dihapus nanti.
-
Saat memproyeksikan ada beberapa penggunaan $cond operator (terner) untuk mendapatkan beberapa nilai string literal. Dan penggunaan terakhir adalah khusus, karena dan array sedang ditambahkan. Jadi setelah memproyeksikan array itu akan dibatalkan lagi. Lebih banyak duplikat, tapi itulah intinya. Satu rekor "menang", satu "kalah" untuk masing-masing.
-
Lebih banyak proyeksi dengan $cond operator dan penggunaan $eq operatornya juga. Kali ini kami menggabungkan dua bidang menjadi satu. Jadi menggunakan ini, ketika "jenis" bidang cocok dengan nilai dalam "skor" maka "bidang kunci" itu digunakan untuk nilai bidang "hasil". Hasilnya adalah dua bidang "menang" dan "kalah" yang berbeda sekarang memiliki nama yang sama, diidentifikasi dengan "jenis".
-
Menyingkirkan duplikat dalam setiap dokumen. Cukup mengelompokkan berdasarkan dokumen
_id
dan bidang "hasil" sebagai kunci. Sekarang seharusnya ada catatan "menang" dan "kalah" yang sama seperti yang ada di dokumen asli, hanya dalam bentuk yang berbeda karena dihapus dari larik. -
Terakhir, kelompokkan semua dokumen untuk mendapatkan total per "pemain". Lebih banyak penggunaan $cond dan $eq tapi kali ini untuk menentukan apakah dokumen saat ini "menang" atau "kalah". Jadi di mana ini cocok kami mengembalikan 1 dan di mana salah kami mengembalikan 0. Nilai-nilai itu diteruskan ke
Dan itu menjelaskan cara mendapatkan hasilnya.
Pelajari lebih lanjut tentang operator agregasi dari dokumentasi. Beberapa penggunaan "lucu" untuk $cond dalam daftar itu harus dapat diganti dengan $ harfiah operator. Tapi itu tidak akan tersedia sampai versi 2.6 dan yang lebih tinggi dirilis.
Kasing "Sederhana" untuk MongoDB 2.6 dan yang lebih baru
Tentu saja ada operator set baru dalam rilis apa yang akan datang pada saat penulisan, yang akan membantu menyederhanakan ini:
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
]}}
}}
])
Tapi "disederhanakan" masih bisa diperdebatkan. Bagi saya, itu hanya "terasa" seperti "berlari" dan melakukan lebih banyak pekerjaan. Ini tentu lebih tradisional, karena hanya mengandalkan $ setUnion untuk menggabungkan hasil larik.
Tetapi "pekerjaan" itu akan dibatalkan dengan sedikit mengubah skema Anda, seperti yang ditunjukkan di sini:
{
"_id" : ObjectId("531ea2b1fcc997d5cc5cbbc9"),
"win": [
{
"player" : "Player2",
"type" : "win"
},
{
"player" : "Player4",
"type" : "win"
}
],
"loss" : [
{
"player" : "Player6",
"type" : "loss"
},
{
"player" : "Player5",
"type" : "loss"
},
]
}
Dan ini menghilangkan kebutuhan untuk memproyeksikan isi array dengan menambahkan atribut "type" seperti yang telah kita lakukan, dan mengurangi kueri, dan pekerjaan yang dilakukan:
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
]}}
}}
])
Dan tentu saja hanya mengubah skema Anda sebagai berikut:
{
"_id" : ObjectId("531ea2b1fcc997d5cc5cbbc9"),
"results" : [
{
"player" : "Player6",
"type" : "loss"
},
{
"player" : "Player5",
"type" : "loss"
},
{
"player" : "Player2",
"type" : "win"
},
{
"player" : "Player4",
"type" : "win"
}
]
}
Itu membuat segalanya sangat mudah. Dan ini bisa dilakukan di versi sebelum 2.6. Jadi Anda bisa melakukannya sekarang:
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
]}}
}}
])
Jadi bagi saya, jika itu adalah aplikasi saya, saya ingin skema dalam bentuk terakhir yang ditunjukkan di atas daripada yang Anda miliki. Semua pekerjaan yang dilakukan dalam operasi agregasi yang disediakan (dengan pengecualian pernyataan terakhir) ditujukan untuk mengambil bentuk skema yang ada dan memanipulasinya menjadi ini form, sehingga mudah untuk menjalankan pernyataan agregasi sederhana seperti yang ditunjukkan di atas.
Karena setiap pemain "ditandai" dengan atribut "menang/kalah", bagaimanapun Anda selalu dapat mengakses "pemenang/kalah" Anda secara diam-diam.
Sebagai hal terakhir. Tanggal Your Anda adalah string. Saya tidak suka itu.
Mungkin ada alasan untuk melakukannya tetapi saya tidak melihatnya. Jika Anda perlu mengelompokkan berdasarkan hari yang mudah dilakukan secara agregasi hanya dengan menggunakan tanggal BSON yang tepat. Anda juga akan dapat dengan mudah bekerja dengan interval waktu lain.
Jadi, jika Anda memperbaiki tanggalnya, dan menjadikannya tanggal_mulai , dan mengganti "durasi" dengan end_time , maka Anda dapat menyimpan sesuatu yang "durasinya" dapat diperoleh dengan matematika sederhana + Anda mendapatkan banyak tambahan manfaat dengan menjadikannya sebagai nilai tanggal.
Sehingga dapat memberi Anda beberapa bahan untuk dipikirkan tentang skema Anda.
Bagi mereka yang tertarik, berikut adalah beberapa kode yang saya gunakan untuk menghasilkan kumpulan data yang berfungsi:
// 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);
}