gpt4 book ai didi

mongodb - Upsert Document和/或添加子文档

转载 作者:可可西里 更新时间:2023-11-01 09:43:17 26 4
gpt4 key购买 nike

我一直在努力解决mongodb、mongoose和javascript的异步特性,以及如何最好地对一个集合进行多次更新。
我有一张客户和联系资料的excel表格。有些客户机有多个联系人,每行一个,而且客户机数据是相同的(因此客户机名称可以用作唯一的键-实际上在用unique: true定义的模式中)。
我想达到的逻辑是:
在客户端集合中搜索以clientName为键的客户端
如果找不到匹配的clientname,则为该客户机创建一个新文档(不是upsert,如果客户机文档已经在数据库中,则我不想更改任何内容)
使用firstNamelastName键检查联系人是否已存在于客户端文档中的联系人数组中
如果找不到联系人,则$push该联系人将显示在数组中
当然,我们很容易遇到这样一种情况:客户机不存在(因此被创建),然后立即,工作表的下一行,是同一客户机的另一个联系人,所以我希望找到现有(刚刚创建)客户机,并将第二个新联系人$push放入数组中。
我试过但没用:

Client.findOneAndUpdate(
{clientName: obj.client.clientname},
{$set: obj.client, $push: {contacts: obj.contact}},
{upsert: true, new: true},
function(err, client){
console.log(client)
}
)

我仔细研究了其他问题,例如:
create mongodb document with subdocuments atomically?
https://stackoverflow.com/questions/28026197/upserting-complex-collections-through-mongoose-via-express
但没办法解决…我的结论是,也许我必须使用一些应用程序逻辑来完成查找,然后在我的代码中做出决定,然后编写,而不是使用一个mongoose/mongo语句,但是异步性的问题在他们丑陋的脑袋后面。
有什么建议吗?

最佳答案

处理这个问题的方法并不简单,因为混合使用“upserts”和向“array”添加项很容易导致不希望的结果。这还取决于您是否希望逻辑设置其他字段,例如“计数器”,指示数组中有多少个联系人,您只希望在分别添加或删除项时增加/减少这些联系人。
然而,在最简单的情况下,如果“contacts”只包含一个单一值,例如链接到另一个集合的ObjectId,那么只要不涉及“counters”,那么$addToSet修饰符就可以正常工作:

Client.findOneAndUpdate(
{ "clientName": clientName },
{ "$addToSet": { "contacts": contact } },
{ "upsert": true, "new": true },
function(err,client) {
// handle here
}
);

这一切都很好,因为您只是在测试doucment是否与“clientname”匹配,如果不是upsert的话。无论是否匹配, $addToSet操作符都会处理唯一的“奇异”值,即任何真正唯一的“对象”。
当你遇到以下困难时:
{ "firstName": "John", "lastName": "Smith", "age": 37 }

已经在“联系人”数组中,然后您要执行以下操作:
{ "firstName": "John", "lastName": "Smith", "age": 38 }

你的真实意图是这是“相同的”约翰·史密斯,只是“年龄”没有不同。理想情况下,您只需在创建新数组或新文档后“更新”该数组条目。
在希望更新的文档返回的位置使用 .findOneAndUpdate()可能很困难。因此,如果您真的不希望修改后的文档响应,那么mongodb的 Bulk Operations API和核心驱动程序在这里是最有帮助的。
考虑到这些声明:
var bulk = Client.collection.initializeOrderedBulkOP();

// First try the upsert and set the array
bulk.find({ "clientName": clientName }).upsert().updateOne({
"$setOnInsert": {
// other valid client info in here
"contacts": [contact]
}
});

// Try to set the array where it exists
bulk.find({
"clientName": clientName,
"contacts": {
"$elemMatch": {
"firstName": contact.firstName,
"lastName": contact.lastName
}
}
}).updateOne({
"$set": { "contacts.$": contact }
});

// Try to "push" the array where it does not exist
bulk.find({
"clientName": clientName,
"contacts": {
"$not": { "$elemMatch": {
"firstName": contact.firstName,
"lastName": contact.lastName
}}
}
}).updateOne({
"$push": { "contacts": contact }
});

bulk.execute(function(err,response) {
// handle in here
});

这很好,因为这里的批量操作意味着这里的所有语句都一次发送到服务器,并且只有一个响应。还要注意,这里的逻辑意味着最多只有两个操作可以修改任何内容。
在第一种情况下, $setOnInsert修饰符确保当文档只是一个匹配项时没有任何更改。由于这里唯一的修改都在该块中,所以这只影响发生“upsert”的文档。
另外,请注意在接下来的两个语句中,不要再次尝试“upsert”。这就认为,第一个陈述在必须的地方可能是成功的,否则无关紧要。
不存在“upsert”的另一个原因是,测试数组中元素的存在所需的条件将导致在不满足条件时“upsert”新文档。这是不需要的,因此没有“upsert”。
它们实际上分别检查数组元素是否存在,并更新现有元素或创建新元素。因此,总的来说,所有操作意味着您要么修改“一次”,要么在发生upsert的情况下最多修改“两次”。可能的“两次”只会产生很少的开销,而且不会产生真正的问题。
另外,在第三条语句中, $not运算符会反转 $elemMatch的逻辑,以确定不存在具有查询条件的数组元素。
.findOneAndUpdate()来翻译这个就成了一个更大的问题。现在重要的不仅是“成功”,还决定了最终内容的返回方式。
因此,这里最好的方法是在“series”中运行事件,然后对结果使用一点魔法,以便返回最终的“updated”表单。
我们将在这里使用 async.waterfalllodash库的帮助:
var _ = require('lodash');   // letting you know where _ is coming from

async.waterfall(
[
function(callback) {
Client.findOneAndUpdate(
{ "clientName": clientName },
{
"$setOnInsert": {
// other valid client info in here
"contacts": [contact]
}
},
{ "upsert": true, "new": true },
callback
);
},
function(client,callback) {
Client.findOneAndUpdate(
{
"clientName": clientName,
"contacts": {
"$elemMatch": {
"firstName": contact.firstName,
"lastName": contact.lastName
}
}
},
{ "$set": { "contacts.$": contact } },
{ "new": true },
function(err,newClient) {
client = client || {};
newClient = newClient || {};
client = _.merge(client,newClient);
callback(err,client);
}
);
},
function(client,callback) {
Client.findOneAndUpdate(
{
"clientName": clientName,
"contacts": {
"$not": { "$elemMatch": {
"firstName": contact.firstName,
"lastName": contact.lastName
}}
}
},
{ "$push": { "contacts": contact } },
{ "new": true },
function(err,newClient) {
newClient = newClient || {};
client = _.merge(client,newClient);
callback(err,client);
}
);
}
],
function(err,client) {
if (err) throw err;
console.log(client);
}
);

这遵循了与之前相同的逻辑,因为这些语句中只有两个或一个实际执行任何操作,可能返回的“new”文档是 null。这里的“瀑布”将结果从每个阶段传递到下一个阶段,包括任何错误都将立即分支到的结尾。
在这种情况下, null将被替换为空对象, {}方法将在每个后期将这两个对象合并为一个对象。这将给出最终的结果,即修改后的对象,而不管前面的操作实际上做了什么。
当然, _.merge()需要不同的操作,而且您的问题本身也有作为对象表单的输入数据。但这些实际上是答案本身。
这至少可以让您开始了解如何处理更新模式。

关于mongodb - Upsert Document和/或添加子文档,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31869971/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com