Link 文档中数组的每个元素到另一个文档数组中的对应元素 MongoDB
Link each element of array in a document to the corresponding element in an array of another document with MongoDB
使用 MongoDB 4.2 和 MongoDB Atlas 测试聚合管道。
我有这个 products 集合,其中包含具有以下架构的文档:
{
"name": "TestProduct",
"relatedList": [
{id:ObjectId("someId")},
{id:ObjectId("anotherId")}
]
}
然后是这个 cities 集合,包含具有以下架构的文档:
{
"name": "TestCity",
"instructionList": [
{ related_id: ObjectId("anotherId"), foo: bar},
{ related_id: ObjectId("someId"), foo: bar}
{ related_id: ObjectId("notUsefulId"), foo: bar}
...
]
}
我的objective是把两个集合join起来输出这样的东西(操作是从city文档的instructionList中挑选出每一个相关的对象放到product文档的relatedList中):
{
"name": "TestProduct",
"relatedList": [
{ related_id: ObjectId("someId"), foo: bar},
{ related_id: ObjectId("anotherId"), foo: bar},
]
}
我尝试使用 $lookup 运算符进行聚合,例如 :
$lookup:{
from: 'cities',
let: {rId:'$relatedList._id'},
pipeline: [
{
$match: {
$expr: {
$eq: ["$instructionList.related_id", "$$rId"]
}
}
},
]
}
但它不起作用,我对这种复杂的管道语法有点迷茫。
编辑
通过在两个数组上使用 unwind :
{
{$unwind: "$relatedList"},
{$lookup:{
from: "cities",
let: { "rId": "$relatedList.id" },
pipeline: [
{$unwind:"$instructionList"},
{$match:{$expr:{$eq:["$instructionList.related_id","$$rId"]}}},
],
as:"instructionList",
}},
{$group: {
_id: "$_id",
instructionList: {$addToSet:"$instructionList"}
}}
}
我能够实现我想要的,但是,
我根本没有得到干净的结果:
{
"name": "TestProduct",
instructionList: [
[
{
"name": "TestCity",
"instructionList": {
"related_id":ObjectId("someId")
}
}
],
[
{
"name": "TestCity",
"instructionList": {
"related_id":ObjectId("anotherId")
}
}
]
]
}
我怎样才能把所有的东西都分组到像我原来的问题所说的那样干净?
再一次,我完全迷失了聚合框架。
我相信您只需要 $unwind 数组来查找关系,然后 $group 来重新收集它们。也许是这样的:
.aggregeate([
{$unwind:"relatedList"},
{$lookup:{
from:"cities",
let:{rId:"$relatedList.id"}
pipeline:[
{$match:{$expr:{$eq:["$instructionList.related_id", "$$rId"]}}},
{$unwind:"$instructionList"},
{$match:{$expr:{$eq:["$instructionList.related_id", "$$rId"]}}},
{$project:{_id:0, instruction:"$instructionList"}}
],
as: "lookedup"
}},
{$addFields: {"relatedList.foo":"$lookedup.0.instruction.foo"}},
{$group: {
_id:"$_id",
root: {$first:"$$ROOT"},
relatedList:{$push:"$relatedList"}
}},
{$addFields:{"root.relatedList":"$relatedList"}},
{$replaceRoot:{newRoot:"$root"}}
])
关于每个阶段的一些信息:
- $unwind 为数组的每个元素复制整个文档,
用单个元素替换数组
- $lookup 然后可以分别考虑每个元素。 $lookup.pipeline:
中的阶段
一种。 $match 所以我们只展开具有匹配 ID
的文档
b. $展开数组,以便我们可以考虑单个元素
C。重复 $match 所以我们只剩下匹配的元素(希望只有 1 个)
- $addFields 将从查找中检索到的
foo
字段分配给 relatedList
中的对象
- $group 收集所有具有相同 _id 的文档(即从单个原始文档展开),将第一个存储为 'root',并将所有 relatedList 元素推回数组
- $addFields 将 relatedList 移动到根目录
- $replaceRoot returns
root
,现在应该是原始文档,每个 relatedList
元素 添加了匹配的 foo
the operation is picking each related object from the instructionList in the city document to put it into the relatedList of the product document)
给出了关于 cities
集合的示例文档:
{"_id": ObjectId("5e4a22a08c54c8e2380b853b"),
"name": "TestCity",
"instructionList": [
{"related_id": "a", "foo": "x"},
{"related_id": "b", "foo": "y"},
{"related_id": "c", "foo": "z"}
]}
以及关于 products
集合的示例文档:
{"_id": ObjectId("5e45cdd8e8d44a31a432a981"),
"name": "TestProduct",
"relatedList": [
{"id": "a"},
{"id": "b"}
]}
您可以尝试使用以下聚合管道:
db.products.aggregate([
{"$lookup":{
"from": "cities",
"let": { "rId": "$relatedList.id" },
"pipeline": [
{"$unwind":"$instructionList"},
{"$match":{
"$expr":{
"$in":["$instructionList.related_id", "$$rId"]
}
}
}],
"as":"relatedList",
}},
{"$project":{
"name":"$name",
"relatedList":{
"$map":{
"input":"$relatedList",
"as":"x",
"in":{
"related_id":"$$x.instructionList.related_id",
"foo":"$$x.instructionList.foo"
}
}
}
}}
]);
得到如下结果:
{ "_id": ObjectId("5e45cdd8e8d44a31a432a981"),
"name": "TestProduct",
"relatedList": [
{"related_id": "a", "foo": "x"},
{"related_id": "b", "foo": "y"}
]}
以上内容在MongoDB v4.2.x.
中测试
But it's not working, I'm a bit lost with this complex pipeline syntax.
这里稍微复杂的原因是因为您有一个数组 relatedList
和一个子文档数组 instructionList
。当您使用 $eq
运算符引用 instructionList.related_id
(这可能意味着多个值)时,管道不知道要匹配哪一个。
在上面的管道中,我添加了 $unwind stage to turn instructionList
into multiple single documents. Afterward, using $in 来表示数组 relatedList
中单个值 instructionList.related_id
的匹配。
使用 MongoDB 4.2 和 MongoDB Atlas 测试聚合管道。
我有这个 products 集合,其中包含具有以下架构的文档:
{
"name": "TestProduct",
"relatedList": [
{id:ObjectId("someId")},
{id:ObjectId("anotherId")}
]
}
然后是这个 cities 集合,包含具有以下架构的文档:
{
"name": "TestCity",
"instructionList": [
{ related_id: ObjectId("anotherId"), foo: bar},
{ related_id: ObjectId("someId"), foo: bar}
{ related_id: ObjectId("notUsefulId"), foo: bar}
...
]
}
我的objective是把两个集合join起来输出这样的东西(操作是从city文档的instructionList中挑选出每一个相关的对象放到product文档的relatedList中):
{
"name": "TestProduct",
"relatedList": [
{ related_id: ObjectId("someId"), foo: bar},
{ related_id: ObjectId("anotherId"), foo: bar},
]
}
我尝试使用 $lookup 运算符进行聚合,例如
$lookup:{
from: 'cities',
let: {rId:'$relatedList._id'},
pipeline: [
{
$match: {
$expr: {
$eq: ["$instructionList.related_id", "$$rId"]
}
}
},
]
}
但它不起作用,我对这种复杂的管道语法有点迷茫。
编辑
通过在两个数组上使用 unwind :
{
{$unwind: "$relatedList"},
{$lookup:{
from: "cities",
let: { "rId": "$relatedList.id" },
pipeline: [
{$unwind:"$instructionList"},
{$match:{$expr:{$eq:["$instructionList.related_id","$$rId"]}}},
],
as:"instructionList",
}},
{$group: {
_id: "$_id",
instructionList: {$addToSet:"$instructionList"}
}}
}
我能够实现我想要的,但是, 我根本没有得到干净的结果:
{
"name": "TestProduct",
instructionList: [
[
{
"name": "TestCity",
"instructionList": {
"related_id":ObjectId("someId")
}
}
],
[
{
"name": "TestCity",
"instructionList": {
"related_id":ObjectId("anotherId")
}
}
]
]
}
我怎样才能把所有的东西都分组到像我原来的问题所说的那样干净? 再一次,我完全迷失了聚合框架。
我相信您只需要 $unwind 数组来查找关系,然后 $group 来重新收集它们。也许是这样的:
.aggregeate([
{$unwind:"relatedList"},
{$lookup:{
from:"cities",
let:{rId:"$relatedList.id"}
pipeline:[
{$match:{$expr:{$eq:["$instructionList.related_id", "$$rId"]}}},
{$unwind:"$instructionList"},
{$match:{$expr:{$eq:["$instructionList.related_id", "$$rId"]}}},
{$project:{_id:0, instruction:"$instructionList"}}
],
as: "lookedup"
}},
{$addFields: {"relatedList.foo":"$lookedup.0.instruction.foo"}},
{$group: {
_id:"$_id",
root: {$first:"$$ROOT"},
relatedList:{$push:"$relatedList"}
}},
{$addFields:{"root.relatedList":"$relatedList"}},
{$replaceRoot:{newRoot:"$root"}}
])
关于每个阶段的一些信息:
- $unwind 为数组的每个元素复制整个文档, 用单个元素替换数组
- $lookup 然后可以分别考虑每个元素。 $lookup.pipeline:
中的阶段 一种。 $match 所以我们只展开具有匹配 ID
的文档 b. $展开数组,以便我们可以考虑单个元素
C。重复 $match 所以我们只剩下匹配的元素(希望只有 1 个) - $addFields 将从查找中检索到的
foo
字段分配给relatedList
中的对象
- $group 收集所有具有相同 _id 的文档(即从单个原始文档展开),将第一个存储为 'root',并将所有 relatedList 元素推回数组
- $addFields 将 relatedList 移动到根目录
- $replaceRoot returns
root
,现在应该是原始文档,每个relatedList
元素 添加了匹配的
foo
the operation is picking each related object from the instructionList in the city document to put it into the relatedList of the product document)
给出了关于 cities
集合的示例文档:
{"_id": ObjectId("5e4a22a08c54c8e2380b853b"),
"name": "TestCity",
"instructionList": [
{"related_id": "a", "foo": "x"},
{"related_id": "b", "foo": "y"},
{"related_id": "c", "foo": "z"}
]}
以及关于 products
集合的示例文档:
{"_id": ObjectId("5e45cdd8e8d44a31a432a981"),
"name": "TestProduct",
"relatedList": [
{"id": "a"},
{"id": "b"}
]}
您可以尝试使用以下聚合管道:
db.products.aggregate([
{"$lookup":{
"from": "cities",
"let": { "rId": "$relatedList.id" },
"pipeline": [
{"$unwind":"$instructionList"},
{"$match":{
"$expr":{
"$in":["$instructionList.related_id", "$$rId"]
}
}
}],
"as":"relatedList",
}},
{"$project":{
"name":"$name",
"relatedList":{
"$map":{
"input":"$relatedList",
"as":"x",
"in":{
"related_id":"$$x.instructionList.related_id",
"foo":"$$x.instructionList.foo"
}
}
}
}}
]);
得到如下结果:
{ "_id": ObjectId("5e45cdd8e8d44a31a432a981"),
"name": "TestProduct",
"relatedList": [
{"related_id": "a", "foo": "x"},
{"related_id": "b", "foo": "y"}
]}
以上内容在MongoDB v4.2.x.
中测试But it's not working, I'm a bit lost with this complex pipeline syntax.
这里稍微复杂的原因是因为您有一个数组 relatedList
和一个子文档数组 instructionList
。当您使用 $eq
运算符引用 instructionList.related_id
(这可能意味着多个值)时,管道不知道要匹配哪一个。
在上面的管道中,我添加了 $unwind stage to turn instructionList
into multiple single documents. Afterward, using $in 来表示数组 relatedList
中单个值 instructionList.related_id
的匹配。