Ada pendekatan yang berbeda tergantung pada versi yang tersedia, tetapi semuanya pada dasarnya memecah untuk mengubah bidang dokumen Anda menjadi dokumen terpisah dalam "array", lalu "melepas" array itu dengan $unwind
dan melakukan $group
berturut-turut
tahapan untuk mengakumulasi total output dan larik.
MongoDB 3.4.4 dan yang lebih baru
Rilis terbaru memiliki operator khusus seperti $arrayToObject
dan $objectToArray
yang dapat membuat transfer ke "array" awal dari dokumen sumber lebih dinamis daripada rilis sebelumnya:
db.profile.aggregate([
{ "$project": {
"_id": 0,
"data": {
"$filter": {
"input": { "$objectToArray": "$$ROOT" },
"cond": { "$in": [ "$$this.k", ["gender","caste","education"] ] }
}
}
}},
{ "$unwind": "$data" },
{ "$group": {
"_id": "$data",
"total": { "$sum": 1 }
}},
{ "$group": {
"_id": "$_id.k",
"v": {
"$push": { "name": "$_id.v", "total": "$total" }
}
}},
{ "$group": {
"_id": null,
"data": { "$push": { "k": "$_id", "v": "$v" } }
}},
{ "$replaceRoot": {
"newRoot": {
"$arrayToObject": "$data"
}
}}
])
Jadi gunakan $objectToArray
Anda membuat dokumen awal menjadi larik dari kunci dan nilainya sebagai "k"
dan "v"
kunci dalam array objek yang dihasilkan. Kami menerapkan $filter
di sini untuk memilih dengan "kunci". Di sini menggunakan $in
dengan daftar kunci yang kita inginkan, tapi ini bisa lebih dinamis digunakan sebagai daftar kunci untuk "mengecualikan" di mana yang lebih pendek. Ini hanya menggunakan operator logika untuk mengevaluasi kondisinya.
Tahap akhir di sini menggunakan $replaceRoot
dan karena semua manipulasi dan "pengelompokan" kami di antaranya masih menyimpan "k"
dan "v"
formulir, kami kemudian menggunakan $arrayToObject
di sini untuk mempromosikan "array objek" kami sebagai "kunci" dari dokumen tingkat atas dalam output.
MongoDB 3.6 $mergeObjects
Sebagai tambahan kerutan di sini, MongoDB 3.6 menyertakan $mergeObjects
yang dapat digunakan sebagai "akumulator "
dalam $group
tahap pipeline juga, sehingga menggantikan $push
dan membuat $replaceRoot
final
cukup dengan menggeser "data"
kunci ke "root" dari dokumen yang dikembalikan sebagai gantinya:
db.profile.aggregate([
{ "$project": {
"_id": 0,
"data": {
"$filter": {
"input": { "$objectToArray": "$$ROOT" },
"cond": { "$in": [ "$$this.k", ["gender","caste","education"] ] }
}
}
}},
{ "$unwind": "$data" },
{ "$group": { "_id": "$data", "total": { "$sum": 1 } }},
{ "$group": {
"_id": "$_id.k",
"v": {
"$push": { "name": "$_id.v", "total": "$total" }
}
}},
{ "$group": {
"_id": null,
"data": {
"$mergeObjects": {
"$arrayToObject": [
[{ "k": "$_id", "v": "$v" }]
]
}
}
}},
{ "$replaceRoot": { "newRoot": "$data" } }
])
Ini tidak terlalu berbeda dengan apa yang ditunjukkan secara keseluruhan, tetapi hanya menunjukkan bagaimana $mergeObjects
dapat digunakan dengan cara ini dan mungkin berguna dalam kasus di mana kunci pengelompokan adalah sesuatu yang berbeda dan kami tidak ingin "penggabungan" terakhir itu ke ruang akar objek.
Perhatikan bahwa $arrayToObject
masih diperlukan untuk mengubah "nilai" kembali menjadi nama "kunci", tetapi kami hanya melakukannya selama akumulasi daripada setelah pengelompokan, karena akumulasi baru memungkinkan "penggabungan" kunci.
MongoDB 3.2
Mengambil kembali versi atau bahkan jika Anda memiliki MongoDB 3.4.x yang kurang dari rilis 3.4.4, kami masih dapat menggunakan sebagian besar dari ini tetapi sebaliknya kami menangani pembuatan array dengan cara yang lebih statis, juga karena menangani "transformasi" akhir pada output secara berbeda karena operator agregasi yang tidak kita miliki:
db.profile.aggregate([
{ "$project": {
"data": [
{ "k": "gender", "v": "$gender" },
{ "k": "caste", "v": "$caste" },
{ "k": "education", "v": "$education" }
]
}},
{ "$unwind": "$data" },
{ "$group": {
"_id": "$data",
"total": { "$sum": 1 }
}},
{ "$group": {
"_id": "$_id.k",
"v": {
"$push": { "name": "$_id.v", "total": "$total" }
}
}},
{ "$group": {
"_id": null,
"data": { "$push": { "k": "$_id", "v": "$v" } }
}},
/*
{ "$replaceRoot": {
"newRoot": {
"$arrayToObject": "$data"
}
}}
*/
]).map( d =>
d.data.map( e => ({ [e.k]: e.v }) )
.reduce((acc,curr) => Object.assign(acc,curr),{})
)
Ini adalah hal yang persis sama, kecuali alih-alih memiliki transformasi dinamis dari dokumen ke dalam array, kami sebenarnya "secara eksplisit" menetapkan setiap anggota array dengan "k"
yang sama dan "v"
notasi. Benar-benar hanya menyimpan nama-nama kunci untuk konvensi pada saat ini karena tidak ada operator agregasi di sini yang bergantung pada itu sama sekali.
Juga daripada menggunakan $replaceRoot
, kami hanya melakukan hal yang persis sama seperti yang dilakukan oleh implementasi tahap pipeline sebelumnya di sana tetapi dalam kode klien sebagai gantinya. Semua driver MongoDB memiliki beberapa implementasi cursor.map()
untuk mengaktifkan "transformasi kursor". Di sini dengan shell kami menggunakan fungsi JavaScript dasar Array.map()
dan Array.reduce()
untuk mengambil output itu dan sekali lagi mempromosikan konten array menjadi kunci dari dokumen tingkat atas yang dikembalikan.
MongoDB 2.6
Dan kembali ke MongoDB 2.6 untuk menutupi versi di antaranya, satu-satunya hal yang berubah di sini adalah penggunaan $map
dan $literal
untuk input dengan deklarasi array:
db.profile.aggregate([
{ "$project": {
"data": {
"$map": {
"input": { "$literal": ["gender","caste", "education"] },
"as": "k",
"in": {
"k": "$$k",
"v": {
"$cond": {
"if": { "$eq": [ "$$k", "gender" ] },
"then": "$gender",
"else": {
"$cond": {
"if": { "$eq": [ "$$k", "caste" ] },
"then": "$caste",
"else": "$education"
}
}
}
}
}
}
}
}},
{ "$unwind": "$data" },
{ "$group": {
"_id": "$data",
"total": { "$sum": 1 }
}},
{ "$group": {
"_id": "$_id.k",
"v": {
"$push": { "name": "$_id.v", "total": "$total" }
}
}},
{ "$group": {
"_id": null,
"data": { "$push": { "k": "$_id", "v": "$v" } }
}},
/*
{ "$replaceRoot": {
"newRoot": {
"$arrayToObject": "$data"
}
}}
*/
])
.map( d =>
d.data.map( e => ({ [e.k]: e.v }) )
.reduce((acc,curr) => Object.assign(acc,curr),{})
)
Karena ide dasarnya di sini adalah untuk "mengulangi" array yang disediakan dari nama bidang, penetapan nilai yang sebenarnya datang dengan "menyarangkan" $cond
pernyataan. Untuk tiga kemungkinan hasil, ini berarti hanya satu sarang untuk "bercabang" untuk setiap hasil.
MongoDB modern dari 3.4 memiliki $switch
yang membuat percabangan ini lebih sederhana, namun ini menunjukkan bahwa logika selalu memungkinkan dan $cond
operator telah ada sejak kerangka kerja agregasi diperkenalkan di MongoDB 2.2.
Sekali lagi, transformasi yang sama pada hasil kursor berlaku karena tidak ada yang baru di sana dan sebagian besar bahasa pemrograman memiliki kemampuan untuk melakukan ini selama bertahun-tahun, jika tidak sejak awal.
Tentu saja proses dasar bahkan dapat dilakukan kembali ke MongoDB 2.2, tetapi hanya menerapkan pembuatan array dan $unwind
dengan cara yang berbeda. Tetapi tidak seorang pun boleh menjalankan MongoDB di bawah 2.8 pada saat ini, dan dukungan resmi bahkan dari 3.0 bahkan cepat habis.
Keluaran
Untuk visualisasi, output dari semua pipeline yang didemonstrasikan di sini memiliki bentuk berikut sebelum "transform" terakhir dilakukan:
/* 1 */
{
"_id" : null,
"data" : [
{
"k" : "gender",
"v" : [
{
"name" : "Male",
"total" : 3.0
},
{
"name" : "Female",
"total" : 2.0
}
]
},
{
"k" : "education",
"v" : [
{
"name" : "M.C.A",
"total" : 1.0
},
{
"name" : "B.E",
"total" : 3.0
},
{
"name" : "B.Com",
"total" : 1.0
}
]
},
{
"k" : "caste",
"v" : [
{
"name" : "Lingayath",
"total" : 3.0
},
{
"name" : "Vokkaliga",
"total" : 2.0
}
]
}
]
}
Dan kemudian dengan $replaceRoot
atau kursor berubah seperti yang ditunjukkan hasilnya menjadi:
/* 1 */
{
"gender" : [
{
"name" : "Male",
"total" : 3.0
},
{
"name" : "Female",
"total" : 2.0
}
],
"education" : [
{
"name" : "M.C.A",
"total" : 1.0
},
{
"name" : "B.E",
"total" : 3.0
},
{
"name" : "B.Com",
"total" : 1.0
}
],
"caste" : [
{
"name" : "Lingayath",
"total" : 3.0
},
{
"name" : "Vokkaliga",
"total" : 2.0
}
]
}
Jadi sementara kami dapat menempatkan beberapa operator baru dan mewah ke dalam pipa agregasi di mana kami memilikinya, kasus penggunaan yang paling umum adalah dalam "transformasi akhir pipa" ini di mana kami mungkin juga melakukan transformasi yang sama pada setiap dokumen di hasil kursor kembali sebagai gantinya.