Bạn hiện đang sử dụng phiên bản phát triển của MongoDB có một số tính năng được kích hoạt dự kiến sẽ được phát hành cùng với MongoDB 4.0 dưới dạng bản phát hành chính thức. Lưu ý rằng một số tính năng có thể thay đổi trước khi phát hành cuối cùng, vì vậy mã sản xuất nên biết điều này trước khi bạn cam kết thực hiện.
Tại sao $ convert không thành công ở đây
Có lẽ cách tốt nhất để giải thích điều này là xem mẫu đã thay đổi của bạn nhưng thay thế bằng ObjectId
giá trị cho _id
và "chuỗi" cho những người dưới các mảng:
{
"_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" } ]
}
Điều đó sẽ đưa ra một mô phỏng chung về những gì bạn đang cố gắng làm việc.
Những gì bạn đã cố gắng chuyển đổi _id
giá trị thành một "chuỗi" qua $project
trước khi nhập $graphLookup
sân khấu. Lý do mà điều này không thành công là trong khi bạn thực hiện $project
ban đầu "trong" đường dẫn này, vấn đề là nguồn cho $graphLookup
trong "from"
tùy chọn vẫn là bộ sưu tập không thay đổi và do đó bạn không nhận được chi tiết chính xác trong các lần lặp lại "tra cứu" tiếp theo.
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"
}}
])
Do đó, không khớp trên "tra cứu":
{
"_id" : "5afe5763419503c46544e277",
"name" : "três",
"ANCESTORS_FROM_BEGINNING" : [ ]
}
"Khắc phục" sự cố
Tuy nhiên, đó là vấn đề cốt lõi và không phải là lỗi của $convert
hoặc chính nó là bí danh. Để làm cho điều này thực sự hoạt động, thay vào đó, chúng ta có thể tạo một "chế độ xem" tự hiển thị dưới dạng một bộ sưu tập nhằm mục đích đầu vào.
Tôi sẽ làm điều này theo cách khác và chuyển đổi "chuỗi" thành ObjectId
qua $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"
]
}
}}
])
Tuy nhiên, sử dụng "chế độ xem" có nghĩa là dữ liệu được nhìn thấy nhất quán với các giá trị được chuyển đổi. Vì vậy, tổng hợp sau đây bằng cách sử dụng chế độ xem:
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"
}}
])
Trả về kết quả mong đợi:
{
"_id" : ObjectId("5afe5763419503c46544e277"),
"name" : "três",
"ANCESTORS_FROM_BEGINNING" : [
ObjectId("5afe5763419503c46544e275"),
ObjectId("5afe5763419503c46544e273"),
ObjectId("5afe5763419503c46544e274"),
ObjectId("5afe5763419503c46544e276"),
ObjectId("5afe5763419503c46544e272")
]
}
Khắc phục sự cố
Với tất cả những điều đã nói, vấn đề thực sự ở đây là bạn có một số dữ liệu "trông giống như" một ObjectId
giá trị và trên thực tế có giá trị như một ObjectId
, tuy nhiên nó đã được ghi lại là một "chuỗi". Vấn đề cơ bản để mọi thứ hoạt động như bình thường là hai "loại" không giống nhau và điều này dẫn đến sự không khớp bình đẳng khi các "liên kết" được cố gắng thực hiện.
Vì vậy, cách khắc phục thực sự vẫn giống như mọi khi, đó là thay vào đó đi qua dữ liệu và sửa nó để các "chuỗi" thực sự cũng là ObjectId
các giá trị. Sau đó, chúng sẽ khớp với _id
khóa mà chúng được dùng để tham chiếu đến và bạn đang tiết kiệm được một lượng lớn dung lượng lưu trữ kể từ một ObjectId
chiếm ít không gian hơn để lưu trữ so với việc biểu diễn chuỗi bằng ký tự thập lục phân.
Sử dụng các phương pháp MongoDB 4.0, bạn "có thể" thực sự sử dụng "$toObjectId"
để viết một bộ sưu tập mới, cũng giống như vấn đề mà chúng tôi đã tạo "chế độ xem" trước đó:
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" }
])
Hoặc tất nhiên khi bạn "cần" giữ nguyên bộ sưu tập, thì "vòng lặp và cập nhật" truyền thống vẫn giống như những gì luôn được yêu cầu:
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 = [];
}
Đây thực sự là một chút "búa tạ" do thực sự ghi đè lên toàn bộ mảng trong một lần. Không phải là một ý tưởng tuyệt vời cho môi trường sản xuất, nhưng đủ để minh chứng cho các mục đích của bài tập này.
Kết luận
Vì vậy, mặc dù MongoDB 4.0 sẽ thêm các tính năng "đúc" này thực sự có thể rất hữu ích, mục đích thực tế của chúng không thực sự dành cho những trường hợp như thế này. Trên thực tế, chúng hữu ích hơn nhiều như đã được chứng minh trong việc "chuyển đổi" sang một bộ sưu tập mới bằng cách sử dụng một đường ống tổng hợp so với hầu hết các cách sử dụng có thể có khác.
Trong khi chúng tôi "có thể" tạo một "chế độ xem" biến đổi các kiểu dữ liệu để cho phép những thứ như $lookup
và $graphLookup
để hoạt động khi dữ liệu thu thập thực tế khác nhau, đây thực sự chỉ là một "dải hỗ trợ" về vấn đề thực tế vì các kiểu dữ liệu thực sự không được khác nhau và trên thực tế phải được chuyển đổi vĩnh viễn.
Sử dụng "chế độ xem" trên thực tế có nghĩa là đường ống tổng hợp để xây dựng cần chạy mọi thời gian "bộ sưu tập" (thực ra là "chế độ xem") được truy cập, điều này tạo ra chi phí thực sự.
Tránh chi phí cao thường là một mục tiêu thiết kế, do đó, việc khắc phục những lỗi lưu trữ dữ liệu như vậy là bắt buộc để mang lại hiệu suất thực sự cho ứng dụng của bạn, thay vì chỉ làm việc với "bạo lực" sẽ chỉ làm chậm mọi thứ.
Tập lệnh "chuyển đổi" an toàn hơn nhiều áp dụng các cập nhật "phù hợp" cho từng phần tử mảng. Mã ở đây yêu cầu NodeJS v10.x và trình điều khiển nút MongoDB phiên bản mới nhất 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()
}
})()
Tạo và thực hiện các hoạt động hàng loạt như thế này cho bảy tài liệu:
{
"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"
}
]
}
}