更新一个,如果不存在则插入,仅当条件为真时才修改MongoDB
Update one, insert if not exists and modify only when a condition is true MongoDB
我正在使用 MongoDB 在 Node.js 中编写程序,我想在某些文件被修改时更新数据库。在数据库中,我存储了每个数据库条目的 mtime,我想将其与文件的 mtime 进行比较,以查看数据库条目是否已过时。这是我的代码:
function updateArticles(articles) {
const dir = "articles";
const files = fs.readdirSync(dir);
files.forEach(
(file) => {
const fpath = path.join(dir, file);
const fmtime = fs.statSync(fpath).mtimeMs;
articles.updateOne(
//refresh content and mtime if the text file has been modified
{
_id: file,
mtime: {$lte: fmtime}
},
{
$set: {content: htmlifySync(fpath), mtime: Date.now(), title: titleSync(fpath)},
//btime is only set once
$setOnInsert: {btime: Date.now()}
},
{upsert: true}
)
});
}
当我设置 {upsert: true}
时,出现重复键错误。当我设置 {upsert: false}
时,不会添加新条目。如果我删除查询中的第二个条件,它会起作用,但是对于每个昂贵且不必要的条目,它是 运行 htmlifySync
和 titleSync
。
我认为问题在于 mongoDB 正在尝试插入满足两个查询条件的新条目,这意味着它会尝试插入具有相同 ID 的新条目。我希望 mtime: {$lte: fmtime}
成为更新的过滤器,但我不希望更新使它成为现实。
澄清一下:
- 如果文件有数据库条目:
- 如果文件自上次数据库更新以来已被修改,我想更新它
- 如果自上次数据库更新以来文件没有被修改,我想保持不变
- 如果没有我想创建的文件的数据库条目
如果没有匹配项,更新操作将尝试插入新文档。如果文件没有更新,您将没有匹配项,它会尝试插入一个新文件,从而触发错误。
可能的解决方案:
- 捕获错误并忽略它
重复键错误表示给定文件已存在于数据库中,但文件时间不大于存储时间,因此无需采取任何措施。
- 在更新中使用条件赋值
如果你使用的是MongoDB 4.2,可以在更新中使用聚合表达式。
articles.updateOne(
{
_id: file
},
[{
$set: {
content: htmlifySync(fpath),
mtime: {$cond:[{$lt:["$mtime",fmtime]},Date.now(),"$mtime"]}
title: titleSync(fpath),
btime: {$cond:[{$eq:[{$type:"$btime"},"missing"]},Date.now(),"$btime"]}
}
}],
{upsert: true}
)
这使用$cond
仅在不存在时设置btime
,并且仅在存储值小于fmtime
时设置mtime
,并且利用这样一个事实,即如果更新设置已存在的相同值,MongoDB 将不会实际处理写入。
我意识到我真正想避免的是评估 htmlifySync
不需要更新的文档。我认为唯一的方法是首先 findOne
然后 insertOne
或 updateOne
作为停止每次评估 htmlifySync
和 titleSync
的唯一方法就是把它们放在if语句中。
articles.findOne({_id: file}, {mtime: 1}).then(
entry => {
if (!entry) {
articles.insertOne(
{
_id: file,
mtime: Date.now(),
title: titleSync(fpath),
content: htmlifySync(fpath),
btime: Date.now()
}
).then((res) => {
console.log(res.res)
})
} else if (entry.mtime < fmtime) {
console.log(file);
articles.updateOne(
{_id: file},
{
$set: {
mtime: Date.now(),
title: titleSync(fpath),
content: htmlifySync(fpath)
}
}
).then((res) => {
console.log(res.res)
})
}
}
)
我正在使用 MongoDB 在 Node.js 中编写程序,我想在某些文件被修改时更新数据库。在数据库中,我存储了每个数据库条目的 mtime,我想将其与文件的 mtime 进行比较,以查看数据库条目是否已过时。这是我的代码:
function updateArticles(articles) {
const dir = "articles";
const files = fs.readdirSync(dir);
files.forEach(
(file) => {
const fpath = path.join(dir, file);
const fmtime = fs.statSync(fpath).mtimeMs;
articles.updateOne(
//refresh content and mtime if the text file has been modified
{
_id: file,
mtime: {$lte: fmtime}
},
{
$set: {content: htmlifySync(fpath), mtime: Date.now(), title: titleSync(fpath)},
//btime is only set once
$setOnInsert: {btime: Date.now()}
},
{upsert: true}
)
});
}
当我设置 {upsert: true}
时,出现重复键错误。当我设置 {upsert: false}
时,不会添加新条目。如果我删除查询中的第二个条件,它会起作用,但是对于每个昂贵且不必要的条目,它是 运行 htmlifySync
和 titleSync
。
我认为问题在于 mongoDB 正在尝试插入满足两个查询条件的新条目,这意味着它会尝试插入具有相同 ID 的新条目。我希望 mtime: {$lte: fmtime}
成为更新的过滤器,但我不希望更新使它成为现实。
澄清一下:
- 如果文件有数据库条目:
- 如果文件自上次数据库更新以来已被修改,我想更新它
- 如果自上次数据库更新以来文件没有被修改,我想保持不变
- 如果没有我想创建的文件的数据库条目
如果没有匹配项,更新操作将尝试插入新文档。如果文件没有更新,您将没有匹配项,它会尝试插入一个新文件,从而触发错误。
可能的解决方案:
- 捕获错误并忽略它
重复键错误表示给定文件已存在于数据库中,但文件时间不大于存储时间,因此无需采取任何措施。 - 在更新中使用条件赋值 如果你使用的是MongoDB 4.2,可以在更新中使用聚合表达式。
articles.updateOne(
{
_id: file
},
[{
$set: {
content: htmlifySync(fpath),
mtime: {$cond:[{$lt:["$mtime",fmtime]},Date.now(),"$mtime"]}
title: titleSync(fpath),
btime: {$cond:[{$eq:[{$type:"$btime"},"missing"]},Date.now(),"$btime"]}
}
}],
{upsert: true}
)
这使用$cond
仅在不存在时设置btime
,并且仅在存储值小于fmtime
时设置mtime
,并且利用这样一个事实,即如果更新设置已存在的相同值,MongoDB 将不会实际处理写入。
我意识到我真正想避免的是评估 htmlifySync
不需要更新的文档。我认为唯一的方法是首先 findOne
然后 insertOne
或 updateOne
作为停止每次评估 htmlifySync
和 titleSync
的唯一方法就是把它们放在if语句中。
articles.findOne({_id: file}, {mtime: 1}).then(
entry => {
if (!entry) {
articles.insertOne(
{
_id: file,
mtime: Date.now(),
title: titleSync(fpath),
content: htmlifySync(fpath),
btime: Date.now()
}
).then((res) => {
console.log(res.res)
})
} else if (entry.mtime < fmtime) {
console.log(file);
articles.updateOne(
{_id: file},
{
$set: {
mtime: Date.now(),
title: titleSync(fpath),
content: htmlifySync(fpath)
}
}
).then((res) => {
console.log(res.res)
})
}
}
)