Mongoose 反向查找和删除
Mongoose reverse lookup and delete
我正在尝试对我的 collections
之一中的引用进行健康检查。所以要查看引用的对象是否仍然存在,如果不存在,我想删除 array
中的那个 _id
我还没有找到任何相关信息,所以我的想法是获得 $lookup
的 reversed
结果
是否可以在 MongoDB 中获得查找的反向结果?
这是一个集合示例,它的 taskList
引用了 tasks
集合。
现在我想删除 tasks
集合中没有现有结果的所有 ID。
我现在是如何解决这个问题的:
- 从
taskList
获取所有 ID
- 对它们中的每一个发送查询以查看是否与任务集合不匹配
- 发送查询以从数组中提取空引用
Afaik 除了您描述的以外没有其他方法可以达到预期的结果,但是您可以大大简化第二步以找到不匹配的项目。事实上,它是 taskList
-id 和现有 task
-id 之间的设置差异。
因此您可以使用 $setDifference
-operator 来计算差异:
db.tasks.aggregate([
{
$group: {
_id: "null",
ids: {
"$addToSet": "$_id"
}
}
},
{
$project: {
nonMatchingTaskIds: {
$setDifference: [
[
"taskId1",
"taskId2",
"taskId7",
"taskId8"
],
"$ids"
]
}
}
}
])
假设您的 tasks
集合包含 taskId1
、task2
(和其他文档),但不包含 taskId7
和 taskId8
,查询将导致nonMatchingTaskIds
包含 taskId7
和 taskId8
。
这是 mongoplayground 上的一个例子:https://mongoplayground.net/p/75BpiGBJi3Q
我认为这可以满足您的需求,即使您的 collection 很大也可以。
但这不是您可以在 $merge
阶段之后对任务列表执行的更新(如果匹配 _id 替换)(需要 MongoDB >= 4.4)或者您可以执行 $out
阶段到另一个 collection,并替换任务列表 collection。
数据在
db={
"tasklists": [
{
"_id": 1,
"tasklist": [
1,
2,
3,
4
]
},
{
"_id": 2,
"tasklist": [
5,
6,
7
]
}
],
"tasks": [
{
"_id": 1
},
{
"_id": 2
},
{
"_id": 3
},
{
"_id": 5
}
]
}
db.tasklists.aggregate([
{
"$lookup": {
"from": "tasks",
"let": {
"tasklist": "$tasklist"
},
"pipeline": [
{
"$match": {
"$expr": {
"$in": [
"$_id",
"$$tasklist"
]
}
}
}
],
"as": "valid"
}
},
{
"$addFields": {
"valid": {
"$map": {
"input": "$valid",
"as": "v",
"in": "$$v._id"
}
}
}
},
{
"$addFields": {
"tasklist": {
"$filter": {
"input": "$tasklist",
"as": "t",
"cond": {
"$in": [
"$$t",
"$valid"
]
}
}
}
}
},
{
"$unset": [
"valid"
]
}
])
结果(任务 4、6、7 未在任务 collection 中找到并已删除)
[
{
"_id": 1,
"tasklist": [
1,
2,
3
]
},
{
"_id": 2,
"tasklist": [
5
]
}
]
编辑
如果你想使用索引来做 $lookup
你可以试试这个
任务在 _id
上有索引,所以不需要做一个,如果你不加入 _id
就做一个。
db.tasklists.aggregate([
{
"$unwind": {
"path": "$tasklist"
}
},
{
"$lookup": {
"from": "tasks",
"localField": "tasklist",
"foreignField": "_id",
"as": "joined"
}
},
{
"$match": {
"$expr": {
"$gt": [
{
"$size": "$joined"
},
0
]
}
}
},
{
"$unset": [
"joined"
]
},
{
"$group": {
"_id": "$_id",
"tasklist": {
"$push": "$tasklist"
},
"afield": {
"$first": "$afield"
}
}
}
])
之后,您可以使用替换选项执行 $out
或 $merge
。
但是,如果发生这种情况,两者都会丢失更新的数据。
唯一的解决方案(如果这是一个问题)$merge
使用管道,
您还需要在带有初始任务列表的额外数组上方的管道中保留,因此您删除有效的,以拥有无效的,然后与管道合并以过滤数组,并删除那些无效的。 (这是安全的,不会丢失数据)
我认为最好的方法不是做所有这些,而是在任务列表上有一个索引(多键索引),当一个 _id 从任务中删除时,从 tasklist.With 索引它的数组中删除 _id会很快,所以你不需要检查无效的_ids。
所以我现在来做的是几个步骤的方法。
这是相当快的,但是从集合中收集的 taskIds 目前比我想象的集合的总量要小得多,一旦我得到那么多引用,使用 eol 提到的 $setDifference
运算符会更快。
let taskIdsInSets = []
// Get all referenced task ids
const result = await this.setSchema.aggregate([
{
'$project': {
'taskList': 1
}
}
])
// Map all elements in one row
result.forEach(set => taskIdsInSets.push(...set.taskList.map(x=> x.toString())))
// Delete duplicates of taskIds here
taskIdsInSets.filter((item, index) => taskIdsInSets.indexOf(item) != index)
// Get the existing task ids that are referenced in a Set
const result2 = await this.taskSchema.aggregate([
{
'$match': {
'_id': {
'$in': [...taskIdsInSets.map(x => Types.ObjectId(x.toString()))]
}
}
}, {
'$project': {
'_id': 1
}
}
])
let existingIdsInTasks = []
// Getting ids from result2 Object into
result2.forEach(set => existingIdsInTasks.push(set._id.toString()))
// Filtering out the ids that don't actually exist
let nonExistingTaskIds = taskIdsInSets.filter(x => existingIdsInTasks.indexOf(x) === -1);
// Deleting the ids that don't actually exist but are in Sets
const finalResult = await this.setSchema.updateMany(
{
$pullAll: {
taskList: [...nonExistingTaskIds.map(x => Types.ObjectId(x.toString()))]
}
})
console.log(finalResult)
return finalResult // returns the information how much got changed. unfortunately in mongoose there isn't the option to use findAndModify with `{new:true}` or atleast I didn't manage to make it work.
出于某种原因,数据库 returns 既不匹配 Mongo ObjectId 也不匹配字符串,所以我必须在那里做一些转换。
我正在尝试对我的 collections
之一中的引用进行健康检查。所以要查看引用的对象是否仍然存在,如果不存在,我想删除 array
_id
我还没有找到任何相关信息,所以我的想法是获得 $lookup
reversed
结果
是否可以在 MongoDB 中获得查找的反向结果?
这是一个集合示例,它的 taskList
引用了 tasks
集合。
现在我想删除 tasks
集合中没有现有结果的所有 ID。
我现在是如何解决这个问题的:
- 从
taskList
获取所有 ID
- 对它们中的每一个发送查询以查看是否与任务集合不匹配
- 发送查询以从数组中提取空引用
Afaik 除了您描述的以外没有其他方法可以达到预期的结果,但是您可以大大简化第二步以找到不匹配的项目。事实上,它是 taskList
-id 和现有 task
-id 之间的设置差异。
因此您可以使用 $setDifference
-operator 来计算差异:
db.tasks.aggregate([
{
$group: {
_id: "null",
ids: {
"$addToSet": "$_id"
}
}
},
{
$project: {
nonMatchingTaskIds: {
$setDifference: [
[
"taskId1",
"taskId2",
"taskId7",
"taskId8"
],
"$ids"
]
}
}
}
])
假设您的 tasks
集合包含 taskId1
、task2
(和其他文档),但不包含 taskId7
和 taskId8
,查询将导致nonMatchingTaskIds
包含 taskId7
和 taskId8
。
这是 mongoplayground 上的一个例子:https://mongoplayground.net/p/75BpiGBJi3Q
我认为这可以满足您的需求,即使您的 collection 很大也可以。
但这不是您可以在 $merge
阶段之后对任务列表执行的更新(如果匹配 _id 替换)(需要 MongoDB >= 4.4)或者您可以执行 $out
阶段到另一个 collection,并替换任务列表 collection。
数据在
db={
"tasklists": [
{
"_id": 1,
"tasklist": [
1,
2,
3,
4
]
},
{
"_id": 2,
"tasklist": [
5,
6,
7
]
}
],
"tasks": [
{
"_id": 1
},
{
"_id": 2
},
{
"_id": 3
},
{
"_id": 5
}
]
}
db.tasklists.aggregate([
{
"$lookup": {
"from": "tasks",
"let": {
"tasklist": "$tasklist"
},
"pipeline": [
{
"$match": {
"$expr": {
"$in": [
"$_id",
"$$tasklist"
]
}
}
}
],
"as": "valid"
}
},
{
"$addFields": {
"valid": {
"$map": {
"input": "$valid",
"as": "v",
"in": "$$v._id"
}
}
}
},
{
"$addFields": {
"tasklist": {
"$filter": {
"input": "$tasklist",
"as": "t",
"cond": {
"$in": [
"$$t",
"$valid"
]
}
}
}
}
},
{
"$unset": [
"valid"
]
}
])
结果(任务 4、6、7 未在任务 collection 中找到并已删除)
[
{
"_id": 1,
"tasklist": [
1,
2,
3
]
},
{
"_id": 2,
"tasklist": [
5
]
}
]
编辑
如果你想使用索引来做 $lookup
你可以试试这个
任务在 _id
上有索引,所以不需要做一个,如果你不加入 _id
就做一个。
db.tasklists.aggregate([
{
"$unwind": {
"path": "$tasklist"
}
},
{
"$lookup": {
"from": "tasks",
"localField": "tasklist",
"foreignField": "_id",
"as": "joined"
}
},
{
"$match": {
"$expr": {
"$gt": [
{
"$size": "$joined"
},
0
]
}
}
},
{
"$unset": [
"joined"
]
},
{
"$group": {
"_id": "$_id",
"tasklist": {
"$push": "$tasklist"
},
"afield": {
"$first": "$afield"
}
}
}
])
之后,您可以使用替换选项执行 $out
或 $merge
。
但是,如果发生这种情况,两者都会丢失更新的数据。
唯一的解决方案(如果这是一个问题)$merge
使用管道,
您还需要在带有初始任务列表的额外数组上方的管道中保留,因此您删除有效的,以拥有无效的,然后与管道合并以过滤数组,并删除那些无效的。 (这是安全的,不会丢失数据)
我认为最好的方法不是做所有这些,而是在任务列表上有一个索引(多键索引),当一个 _id 从任务中删除时,从 tasklist.With 索引它的数组中删除 _id会很快,所以你不需要检查无效的_ids。
所以我现在来做的是几个步骤的方法。
这是相当快的,但是从集合中收集的 taskIds 目前比我想象的集合的总量要小得多,一旦我得到那么多引用,使用 eol 提到的 $setDifference
运算符会更快。
let taskIdsInSets = []
// Get all referenced task ids
const result = await this.setSchema.aggregate([
{
'$project': {
'taskList': 1
}
}
])
// Map all elements in one row
result.forEach(set => taskIdsInSets.push(...set.taskList.map(x=> x.toString())))
// Delete duplicates of taskIds here
taskIdsInSets.filter((item, index) => taskIdsInSets.indexOf(item) != index)
// Get the existing task ids that are referenced in a Set
const result2 = await this.taskSchema.aggregate([
{
'$match': {
'_id': {
'$in': [...taskIdsInSets.map(x => Types.ObjectId(x.toString()))]
}
}
}, {
'$project': {
'_id': 1
}
}
])
let existingIdsInTasks = []
// Getting ids from result2 Object into
result2.forEach(set => existingIdsInTasks.push(set._id.toString()))
// Filtering out the ids that don't actually exist
let nonExistingTaskIds = taskIdsInSets.filter(x => existingIdsInTasks.indexOf(x) === -1);
// Deleting the ids that don't actually exist but are in Sets
const finalResult = await this.setSchema.updateMany(
{
$pullAll: {
taskList: [...nonExistingTaskIds.map(x => Types.ObjectId(x.toString()))]
}
})
console.log(finalResult)
return finalResult // returns the information how much got changed. unfortunately in mongoose there isn't the option to use findAndModify with `{new:true}` or atleast I didn't manage to make it work.
出于某种原因,数据库 returns 既不匹配 Mongo ObjectId 也不匹配字符串,所以我必须在那里做一些转换。