这是我的收藏:
collection1:
{
user1: 1,
user2: 2,
percent: 0.56
}
collection2:
{
user1: 1,
user2: 2,
percent: 0.3
}
我希望通过' user1'加入这两个系列和' user2'。
结果如下:
{
user1: 1,
user2: 2,
percent1: 0.56,
percent2: 0.3
}
我如何编写管道?
答案 0 :(得分:37)
我们可以在版本3.6及更高版本中使用$lookup
聚合管道运算符执行多个连接条件。
我们需要使用let
可选字段将字段的值分配给变量;然后,您可以在pipeline
字段阶段访问这些变量,您可以在其中指定要在集合上运行的管道。
请注意,在$match
阶段,我们使用$expr
评估查询运算符来比较字段的值。
管道中的最后一个阶段是$replaceRoot
聚合管道阶段,我们只需使用$mergeObjects
运算符将$lookup
结果与$$ROOT
文档的一部分合并。 / p>
db.collection2.aggregate([
{
$lookup: {
from: "collection1",
let: {
firstUser: "$user1",
secondUser: "$user2"
},
pipeline: [
{
$match: {
$expr: {
$and: [
{
$eq: [
"$user1",
"$$firstUser"
]
},
{
$eq: [
"$user2",
"$$secondUser"
]
}
]
}
}
}
],
as: "result"
}
},
{
$replaceRoot: {
newRoot: {
$mergeObjects:[
{
$arrayElemAt: [
"$result",
0
]
},
{
percent1: "$$ROOT.percent1"
}
]
}
}
}
]
)
此管道产生如下所示的内容:
{
"_id" : ObjectId("59e1ad7d36f42d8960c06022"),
"user1" : 1,
"user2" : 2,
"percent" : 0.3,
"percent1" : 0.56
}
如果您使用的是版本3.6+,则可以先使用其中一个字段加入,然后使用#34; user1"然后从那里使用$unwind
聚合管道运算符展开匹配文档的数组。管道中的下一个阶段是$redact
阶段,您可以在其中筛选出" user2"的值。来自"加入"使用$$KEEP
和$$PRUNE
系统变量,集合和输入文档不相等。然后,您可以在$project
阶段重塑您的文档。
db.collection1.aggregate([
{ "$lookup": {
"from": "collection2",
"localField": "user1",
"foreignField": "user1",
"as": "collection2_doc"
}},
{ "$unwind": "$collection2_doc" },
{ "$redact": {
"$cond": [
{ "$eq": [ "$user2", "$collection2_doc.user2" ] },
"$$KEEP",
"$$PRUNE"
]
}},
{ "$project": {
"user1": 1,
"user2": 1,
"percent1": "$percent",
"percent2": "$collection2_doc.percent"
}}
])
产生:
{
"_id" : ObjectId("572daa87cc52a841bb292beb"),
"user1" : 1,
"user2" : 2,
"percent1" : 0.56,
"percent2" : 0.3
}
如果集合中的文档具有相同的结构,并且您经常发现自己正在执行此操作,那么您应该考虑将这两个集合合并为一个集合,或者将这些集合中的文档插入到新集合中。
db.collection3.insertMany(
db.collection1.find({}, {"_id": 0})
.toArray()
.concat(db.collection2.find({}, {"_id": 0}).toArray())
)
然后通过" user1" $group
您的文件和" user2"
db.collection3.aggregate([
{ "$group": {
"_id": { "user1": "$user1", "user2": "$user2" },
"percent": { "$push": "$percent" }
}}
])
产生:
{ "_id" : { "user1" : 1, "user2" : 2 }, "percent" : [ 0.56, 0.3 ] }
答案 1 :(得分:4)
从Mongo 4.4
开始,我们可以通过新的$unionWith
聚合阶段以及经典的$group
阶段来实现这种“联接”:
// > db.collection1.find()
// { "user1" : 1, "user2" : 2, "percent" : 0.56 }
// { "user1" : 4, "user2" : 3, "percent" : 0.14 }
// > db.collection2.find()
// { "user1" : 1, "user2" : 2, "percent" : 0.3 }
// { "user1" : 2, "user2" : 3, "percent" : 0.25 }
db.collection1.aggregate([
{ $set: { percent1: "$percent" } },
{ $unionWith: {
coll: "collection2",
pipeline: [{ $set: { percent2: "$percent" } }]
}},
{ $group: {
_id: { user1: "$user1", user2: "$user2" },
percents: { $mergeObjects: { percent1: "$percent1", percent2: "$percent2" } }
}}
])
// { _id: { user1: 1, user2: 2 }, percents: { percent1: 0.56, percent2: 0.3 } }
// { _id: { user1: 2, user2: 3 }, percents: { percent2: 0.25 } }
// { _id: { user1: 4, user2: 3 }, percents: { percent1: 0.14 } }
此:
首先通过新的$unionWith
阶段将两个集合的并入管道:
percent
从collection1
重命名为percent1
(使用$set
阶段)$unionWith
阶段,我们在pipeline
上指定一个collection2
,以便这次也将percent
重命名为percent2
。继续执行$group
阶段:
user1
和user2
的记录分组$mergeObjects
操作累积百分比。使用$first: "$percent1"
和$first: "$percent2"
是行不通的,因为这可能首先需要null
(对于其他集合中的元素)。而$mergeObjects
丢弃null
值。如果需要其他输出格式,则可以添加下游$project
阶段。
答案 2 :(得分:2)
如果您正在尝试为数据建模,并且在决定这样做之前来检查mongodb是否可以在多个字段上执行连接,请继续阅读。
虽然MongoDB可以执行连接,但您也可以根据应用程序访问模式自由地建模数据。如果数据与问题中提供的数据一样简单,我们可以简单地维护一个如下所示的集合:
{
user1: 1,
user2: 2,
percent1: 0.56,
percent2: 0.3
}
现在,您可以通过加入对此集合执行所有操作。我们为什么要试图避免加入?因为分片集合(docs)不支持它们,这将阻止您在需要时扩展。规范化数据(具有单独的表/集合)在SQL中非常有效,但是当涉及到Mongo时,避免连接可以提供优势而在大多数情况下没有后果。只有在没有其他选择时才使用MongoDB中的规范化。来自docs:
通常,使用标准化数据模型:
- 嵌入会导致数据重复,但无法提供足够的读取性能优势,无法弥补重复的影响。
- 代表更复杂的多对多关系。
- 为大型分层数据集建模。
检查here以了解有关嵌入的更多信息以及为什么选择它而不是规范化。
答案 3 :(得分:1)
您可以使用 $ match 和 $ project 管道进行多场比赛。 (详见答案 - mongoDB Join on multiple fields)
db.collection1.aggregate([
{"$lookup": {
"from": "collection2",
"localField": "user1",
"foreignField": "user1",
"as": "c2"
}},
{"$unwind": "$c2"},
{"$project": {
"user2Eq": {"$eq": ["$user2", "$c2.user2"]},
"user1": 1, "user2": 1,
"percent1": "$percent", "percent2": "$c2.percent"
}},
{"$match": {
{"user2Eq": {"$eq": True}}
}},
{"$project": {
"user2Eq": 0
}}
])