我创建了一个函数来拼接来自不同集合的2条记录:
选择1记录:
{
_id: objectId(1231242331233),
acc: '12390',
val2: 'asdasdas'
}
收集2记录:
{
_id: objectId(989232382302308),
isValid: '1',
tf: '098789928',
acc: '12390'
}
为此,我提出了以下聚合函数和$ lookup。
Collection2.aggregate([
{
$lookup:
{
from: "Collection1",
localField: "acc",
foreignField: "acc",
as: "acc_record"
}
}
{
$out: 'Collection3'
}
]);
这将生成一个Collection3,其记录具有以下结构:
{
_id: objectId(989232382302308),
isValid: '1',
tf: '098789928',
acc: '12390',
acc_record:[
{
_id: objectId(1231242331233),
acc: '12390',
val2: 'asdasdas'
}
]
}
合并这两条记录的聚合函数是什么,但不是将Collection1记录放在json对象的更深层,而只放置非相等元素并合并具有相同名称的元素?
所以最终的记录结果是:
{
_id: objectId(989232382302308),
isValid: '1',
tf: '098789928',
acc: '12390',
val2: 'asdasdas'
}
答案 0 :(得分:2)
添加$project
:
Collection2.aggregate([
{ "$lookup":{
"from": "Collection1",
"localField": "acc",
"foreignField": "acc",
"as": "acc_record"
}},
{ "$project": {
"isValid": 1,
"tf": 1,
"acc": { "$arrayElemAt": ["$acc_record.acc",0] },
"val2": { "$arrayElemAt": ["$acc_record.val2", 0] }
}},
{ "$out": "Collection3" }
]);
使用$arrayElemAt
引用数组中的值,并将它们作为顶级对象中的值进行提升。
如果你总是知道结果是“一对一的”并且可以简单地从返回的第一个数组元素中获取值,那就没关系。如果他们是“一对多”,那么您也应用$unwind
:
Collection2.aggregate([
{ "$lookup":{
"from": "Collection1",
"localField": "acc",
"foreignField": "acc",
"as": "acc_record"
}},
{ "$unwind": "$acc_record" },
{ "$project": {
"_id": 0,
"isValid": 1,
"tf": 1,
"acc": "$acc_record.acc",
"val2": "$acc_record.val2"
}},
{ "$out": "Collection3" }
]);
对于“一对一”也是完全有效的,但您应该注意_id
在这里被“故意”删除。原因是,对于“很多”结果,$unwind
为$lookup
结果中返回的每个数组成员生成父文档的“多个”副本。由于_id
是“主键”,因此您无法将该值保持为“多个文档”中的相同值。
因此,丢弃主键的意义在于$out
可以在写入时创建新值,而不会因“重复键错误”而失败。或者,如果您想将此作为“参考”,则只需将"$_id"
值重命名为$project
中的其他字段。
对于更大的输出,我们可以使用一些技巧来“合并”MongoDB支持这些功能的地方。在当前版本中,这些是来自MongoDB 3.4.4及更高版本的$arrayToObject
和$objectToArray
:
Collection2.aggregate([
{ "$lookup":{
"from": "Collection1",
"localField": "acc",
"foreignField": "acc",
"as": "acc_record"
}},
{ "$unwind": "$acc_record" },
{ "$replaceRoot": {
"newRoot": {
"$arrayToObject": {
"$concatArrays": [
{ "$filter": {
"input": { "$objectToArray": "$$ROOT" },
"cond": { "$not": { "$in": ["$$this.k", ["_id", "acc_record"] } }
}},
{ "$filter": {
"input": { "$objectToArray": "$acc_record" },
"cond": { "$ne": ["$$this.k", "acc"] }
}}
]
}
}
}},
{ "$out": "Collection3" }
])
诀窍是将“ROOT”文档和子数组内容转换为单独的数组,过滤掉重叠的键并应用$concatArrays
将它们组合成一个数组。然后,您可以对“已加入”结果应用$arrayToObject
,并通过$replaceRoot
将其转换为根文档。
MongoDB 3.6使它更容易并引入$mergeObjects
,所以你可以做一些简单的事情:
Collection2.aggregate([
{ "$lookup":{
"from": "Collection1",
"localField": "acc",
"foreignField": "acc",
"as": "acc_record"
}},
{ "$unwind": "$acc_record" },
{ "$replaceRoot": {
"newRoot": {
"$mergeObjects": [
{ "$arrayToObject": {
"$filter": {
"input": { "$objectToArray": "$$ROOT" },
"cond": { "$not": { "$in": ["$$this.k", ["_id", "acc_record","acc"] } },
}
},
"$acc_record"
]
}
}},
{ "$out": "Collection3" }
]);
一般情况下,您仍然需要$filter
您不想要的密钥,例如$lookup
的目标字段名称,最有可能是"localField"
或{{1} }值也是。因此,除非您准备添加另一个聚合阶段以完全删除该子密钥,否则您不能真正使用"foreignField"
与子密钥的内容合并。
所以一般来说$mergeObjects
并没有真正增加太多,除了操作符的命名使得代码的意图清晰明了。