Saat ini Anda menggunakan versi pengembangan MongoDB yang memiliki beberapa fitur yang diaktifkan yang diharapkan akan dirilis dengan MongoDB 4.0 sebagai rilis resmi. Perhatikan bahwa beberapa fitur dapat berubah sebelum rilis final, jadi kode produksi harus mengetahui hal ini sebelum Anda berkomitmen.
Mengapa $convert gagal di sini
Mungkin cara terbaik untuk menjelaskan ini adalah dengan melihat sampel Anda yang diubah tetapi mengganti dengan ObjectId
nilai untuk _id
dan "string" untuk mereka yang berada di bawah array:
{
"_id" : ObjectId("5afe5763419503c46544e272"),
"name" : "cinco",
"children" : [ { "_id" : "5afe5763419503c46544e273" } ]
},
{
"_id" : ObjectId("5afe5763419503c46544e273"),
"name" : "quatro",
"ancestors" : [ { "_id" : "5afe5763419503c46544e272" } ],
"children" : [ { "_id" : "5afe5763419503c46544e277" } ]
},
{
"_id" : ObjectId("5afe5763419503c46544e274"),
"name" : "seis",
"children" : [ { "_id" : "5afe5763419503c46544e277" } ]
},
{
"_id" : ObjectId("5afe5763419503c46544e275"),
"name" : "um",
"children" : [ { "_id" : "5afe5763419503c46544e276" } ]
}
{
"_id" : ObjectId("5afe5763419503c46544e276"),
"name" : "dois",
"ancestors" : [ { "_id" : "5afe5763419503c46544e275" } ],
"children" : [ { "_id" : "5afe5763419503c46544e277" } ]
},
{
"_id" : ObjectId("5afe5763419503c46544e277"),
"name" : "três",
"ancestors" : [
{ "_id" : "5afe5763419503c46544e273" },
{ "_id" : "5afe5763419503c46544e274" },
{ "_id" : "5afe5763419503c46544e276" }
]
},
{
"_id" : ObjectId("5afe5764419503c46544e278"),
"name" : "sete",
"children" : [ { "_id" : "5afe5763419503c46544e272" } ]
}
Itu akan memberikan simulasi umum tentang apa yang Anda coba kerjakan.
Apa yang Anda coba adalah mengonversi _id
nilai menjadi "string" melalui $project
sebelum memasukkan $graphLookup
panggung. Alasan kegagalan ini adalah saat Anda melakukan $project
awal "di dalam" pipeline ini, masalahnya adalah sumber untuk $graphLookup
di "from"
opsi masih merupakan koleksi yang tidak berubah dan oleh karena itu Anda tidak mendapatkan detail yang benar pada iterasi "pencarian" berikutnya.
db.strcoll.aggregate([
{ "$match": { "name": "três" } },
{ "$addFields": {
"_id": { "$toString": "$_id" }
}},
{ "$graphLookup": {
"from": "strcoll",
"startWith": "$ancestors._id",
"connectFromField": "ancestors._id",
"connectToField": "_id",
"as": "ANCESTORS_FROM_BEGINNING"
}},
{ "$project": {
"name": 1,
"ANCESTORS_FROM_BEGINNING": "$ANCESTORS_FROM_BEGINNING._id"
}}
])
Tidak cocok dengan "pencarian" oleh karena itu:
{
"_id" : "5afe5763419503c46544e277",
"name" : "três",
"ANCESTORS_FROM_BEGINNING" : [ ]
}
"Menambal" masalah
Namun itu adalah masalah inti dan bukan kegagalan $convert
atau itu alias itu sendiri. Agar ini benar-benar berfungsi, kita dapat membuat "tampilan" yang menampilkan dirinya sebagai kumpulan untuk masukan.
Saya akan melakukan ini sebaliknya dan mengonversi "string" menjadi ObjectId
melalui $toObjectId
:
db.createView("idview","strcoll",[
{ "$addFields": {
"ancestors": {
"$ifNull": [
{ "$map": {
"input": "$ancestors",
"in": { "_id": { "$toObjectId": "$$this._id" } }
}},
"$$REMOVE"
]
},
"children": {
"$ifNull": [
{ "$map": {
"input": "$children",
"in": { "_id": { "$toObjectId": "$$this._id" } }
}},
"$$REMOVE"
]
}
}}
])
Namun, menggunakan "tampilan" berarti bahwa data dilihat secara konsisten dengan nilai yang dikonversi. Jadi agregasi berikut menggunakan tampilan:
db.idview.aggregate([
{ "$match": { "name": "três" } },
{ "$graphLookup": {
"from": "idview",
"startWith": "$ancestors._id",
"connectFromField": "ancestors._id",
"connectToField": "_id",
"as": "ANCESTORS_FROM_BEGINNING"
}},
{ "$project": {
"name": 1,
"ANCESTORS_FROM_BEGINNING": "$ANCESTORS_FROM_BEGINNING._id"
}}
])
Mengembalikan output yang diharapkan:
{
"_id" : ObjectId("5afe5763419503c46544e277"),
"name" : "três",
"ANCESTORS_FROM_BEGINNING" : [
ObjectId("5afe5763419503c46544e275"),
ObjectId("5afe5763419503c46544e273"),
ObjectId("5afe5763419503c46544e274"),
ObjectId("5afe5763419503c46544e276"),
ObjectId("5afe5763419503c46544e272")
]
}
Memperbaiki masalah
Dengan semua itu, masalah sebenarnya di sini adalah Anda memiliki beberapa data yang "terlihat seperti" sebuah ObjectId
nilai dan sebenarnya valid sebagai ObjectId
, namun telah dicatat sebagai "string". Masalah dasar untuk semua yang berfungsi sebagaimana mestinya adalah bahwa kedua "jenis" tidak sama dan ini menghasilkan ketidakcocokan kesetaraan saat "penggabungan" dicoba.
Jadi perbaikan sebenarnya masih sama seperti dulu, yaitu melalui data dan memperbaikinya sehingga "string" sebenarnya juga ObjectId
nilai-nilai. Ini kemudian akan cocok dengan _id
kunci yang dimaksudkan untuk dirujuk, dan Anda menghemat banyak ruang penyimpanan karena ObjectId
membutuhkan lebih sedikit ruang untuk disimpan daripada representasi string dalam karakter heksadesimal.
Menggunakan metode MongoDB 4.0, Anda "bisa" sebenarnya menggunakan "$toObjectId"
untuk menulis koleksi baru, hampir sama dengan yang kita buat "tampilan" sebelumnya:
db.strcoll.aggregate([
{ "$addFields": {
"ancestors": {
"$ifNull": [
{ "$map": {
"input": "$ancestors",
"in": { "_id": { "$toObjectId": "$$this._id" } }
}},
"$$REMOVE"
]
},
"children": {
"$ifNull": [
{ "$map": {
"input": "$children",
"in": { "_id": { "$toObjectId": "$$this._id" } }
}},
"$$REMOVE"
]
}
}}
{ "$out": "fixedcol" }
])
Atau tentu saja di mana Anda "perlu" untuk menyimpan koleksi yang sama, maka "loop and update" tradisional tetap sama seperti yang selalu diperlukan:
var updates = [];
db.strcoll.find().forEach(doc => {
var update = { '$set': {} };
if ( doc.hasOwnProperty('children') )
update.$set.children = doc.children.map(e => ({ _id: new ObjectId(e._id) }));
if ( doc.hasOwnProperty('ancestors') )
update.$set.ancestors = doc.ancestors.map(e => ({ _id: new ObjectId(e._id) }));
updates.push({
"updateOne": {
"filter": { "_id": doc._id },
update
}
});
if ( updates.length > 1000 ) {
db.strcoll.bulkWrite(updates);
updates = [];
}
})
if ( updates.length > 0 ) {
db.strcoll.bulkWrite(updates);
updates = [];
}
Yang sebenarnya sedikit "palu godam" karena benar-benar menimpa seluruh array dalam sekali jalan. Bukan ide bagus untuk lingkungan produksi, tetapi cukup sebagai demonstrasi untuk tujuan latihan ini.
Kesimpulan
Jadi sementara MongoDB 4.0 akan menambahkan fitur "casting" ini yang memang bisa sangat berguna, maksud sebenarnya mereka tidak benar-benar untuk kasus seperti ini. Mereka sebenarnya jauh lebih berguna seperti yang ditunjukkan dalam "konversi" ke koleksi baru menggunakan pipa agregasi daripada kebanyakan kemungkinan penggunaan lainnya.
Sementara kita "bisa" buat "tampilan" yang mengubah tipe data untuk mengaktifkan hal-hal seperti $lookup
dan $graphLookup
untuk bekerja di mana data pengumpulan yang sebenarnya berbeda, ini sebenarnya hanya "pembalut" pada masalah sebenarnya karena tipe data seharusnya tidak berbeda, dan sebenarnya harus dikonversi secara permanen.
Menggunakan "tampilan" sebenarnya berarti bahwa alur agregasi untuk konstruksi perlu dijalankan secara efektif setiap waktu "koleksi" ( sebenarnya "tampilan" ) diakses, yang menciptakan overhead nyata.
Menghindari overhead biasanya merupakan tujuan desain, oleh karena itu memperbaiki kesalahan penyimpanan data tersebut sangat penting untuk mendapatkan kinerja nyata dari aplikasi Anda, daripada hanya bekerja dengan "kekuatan kasar" yang hanya akan memperlambat segalanya.
Skrip "konversi" yang jauh lebih aman yang menerapkan pembaruan "cocok" ke setiap elemen larik. Kode di sini memerlukan NodeJS v10.x dan rilis terbaru driver node MongoDB 3.1.x:
const { MongoClient, ObjectID: ObjectId } = require('mongodb');
const EJSON = require('mongodb-extended-json');
const uri = 'mongodb://localhost/';
const log = data => console.log(EJSON.stringify(data, undefined, 2));
(async function() {
try {
const client = await MongoClient.connect(uri);
let db = client.db('test');
let coll = db.collection('strcoll');
let fields = ["ancestors", "children"];
let cursor = coll.find({
$or: fields.map(f => ({ [`${f}._id`]: { "$type": "string" } }))
}).project(fields.reduce((o,f) => ({ ...o, [f]: 1 }),{}));
let batch = [];
for await ( let { _id, ...doc } of cursor ) {
let $set = {};
let arrayFilters = [];
for ( const f of fields ) {
if ( doc.hasOwnProperty(f) ) {
$set = { ...$set,
...doc[f].reduce((o,{ _id },i) =>
({ ...o, [`${f}.$[${f.substr(0,1)}${i}]._id`]: ObjectId(_id) }),
{})
};
arrayFilters = [ ...arrayFilters,
...doc[f].map(({ _id },i) =>
({ [`${f.substr(0,1)}${i}._id`]: _id }))
];
}
}
if (arrayFilters.length > 0)
batch = [ ...batch,
{ updateOne: { filter: { _id }, update: { $set }, arrayFilters } }
];
if ( batch.length > 1000 ) {
let result = await coll.bulkWrite(batch);
batch = [];
}
}
if ( batch.length > 0 ) {
log({ batch });
let result = await coll.bulkWrite(batch);
log({ result });
}
await client.close();
} catch(e) {
console.error(e)
} finally {
process.exit()
}
})()
Menghasilkan dan menjalankan operasi massal seperti ini untuk tujuh dokumen:
{
"updateOne": {
"filter": {
"_id": {
"$oid": "5afe5763419503c46544e272"
}
},
"update": {
"$set": {
"children.$[c0]._id": {
"$oid": "5afe5763419503c46544e273"
}
}
},
"arrayFilters": [
{
"c0._id": "5afe5763419503c46544e273"
}
]
}
},
{
"updateOne": {
"filter": {
"_id": {
"$oid": "5afe5763419503c46544e273"
}
},
"update": {
"$set": {
"ancestors.$[a0]._id": {
"$oid": "5afe5763419503c46544e272"
},
"children.$[c0]._id": {
"$oid": "5afe5763419503c46544e277"
}
}
},
"arrayFilters": [
{
"a0._id": "5afe5763419503c46544e272"
},
{
"c0._id": "5afe5763419503c46544e277"
}
]
}
},
{
"updateOne": {
"filter": {
"_id": {
"$oid": "5afe5763419503c46544e274"
}
},
"update": {
"$set": {
"children.$[c0]._id": {
"$oid": "5afe5763419503c46544e277"
}
}
},
"arrayFilters": [
{
"c0._id": "5afe5763419503c46544e277"
}
]
}
},
{
"updateOne": {
"filter": {
"_id": {
"$oid": "5afe5763419503c46544e275"
}
},
"update": {
"$set": {
"children.$[c0]._id": {
"$oid": "5afe5763419503c46544e276"
}
}
},
"arrayFilters": [
{
"c0._id": "5afe5763419503c46544e276"
}
]
}
},
{
"updateOne": {
"filter": {
"_id": {
"$oid": "5afe5763419503c46544e276"
}
},
"update": {
"$set": {
"ancestors.$[a0]._id": {
"$oid": "5afe5763419503c46544e275"
},
"children.$[c0]._id": {
"$oid": "5afe5763419503c46544e277"
}
}
},
"arrayFilters": [
{
"a0._id": "5afe5763419503c46544e275"
},
{
"c0._id": "5afe5763419503c46544e277"
}
]
}
},
{
"updateOne": {
"filter": {
"_id": {
"$oid": "5afe5763419503c46544e277"
}
},
"update": {
"$set": {
"ancestors.$[a0]._id": {
"$oid": "5afe5763419503c46544e273"
},
"ancestors.$[a1]._id": {
"$oid": "5afe5763419503c46544e274"
},
"ancestors.$[a2]._id": {
"$oid": "5afe5763419503c46544e276"
}
}
},
"arrayFilters": [
{
"a0._id": "5afe5763419503c46544e273"
},
{
"a1._id": "5afe5763419503c46544e274"
},
{
"a2._id": "5afe5763419503c46544e276"
}
]
}
},
{
"updateOne": {
"filter": {
"_id": {
"$oid": "5afe5764419503c46544e278"
}
},
"update": {
"$set": {
"children.$[c0]._id": {
"$oid": "5afe5763419503c46544e272"
}
}
},
"arrayFilters": [
{
"c0._id": "5afe5763419503c46544e272"
}
]
}
}