Mongodb 使用$lookup运算符的多个联接条件

Mongodb 使用$lookup运算符的多个联接条件,mongodb,join,mongodb-query,aggregation-framework,Mongodb,Join,Mongodb Query,Aggregation Framework,拥有以下两个集合: // 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 } { "_id" : Obje

拥有以下两个集合:

// 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
}
{
    "_id" : ObjectId("59e1ad7d36f42d8960c06022"),
    "user1" : 1,
    "user2" : 2,
    "percent" : 0.3,
    "percent1" : 0.56
}
{
    user1: 1,
    user2: 2,
    percent1: 0.56,
    percent2: 0.3
}

在3.6版及更新版本中,我们可以使用聚合管道操作符执行多个连接条件

我们需要使用
let
可选字段将字段的值分配给变量;然后在
管道
字段stage中访问这些变量,在该字段中指定要在集合上运行的管道

请注意,在
$match
阶段,我们使用求值查询操作符来比较字段的值

管道中的最后一个阶段是聚合管道阶段,在此阶段,我们只需使用操作符将
$$lookup
结果与
$$ROOT
文档的一部分合并

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"
                   }
                ]
             }
          }
       }
    ]
)
此管道产生的结果如下所示:

{
  user1: 1,
  user2: 2,
  percent1: 0.56,
  percent2: 0.3
}
{
    "_id" : ObjectId("59e1ad7d36f42d8960c06022"),
    "user1" : 1,
    "user2" : 2,
    "percent" : 0.3,
    "percent1" : 0.56
}
{
    user1: 1,
    user2: 2,
    percent1: 0.56,
    percent2: 0.3
}

如果您不在3.6+版本上,您可以首先使用一个字段(比如说“user1”)加入,然后使用聚合管道操作符从那里释放匹配文档的数组。管道中的下一个阶段是使用和系统变量筛选出“joined”集合中的“user2”值与输入文档不相等的文档的阶段。然后,您可以在阶段中重塑文档

产生:

{
    "_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”和“user2”编辑文档

这将产生:

{ "_id" : { "user1" : 1, "user2" : 2 }, "percent" : [ 0.56, 0.3 ] }

在3.6版及更新版本中,我们可以使用聚合管道操作符执行多个连接条件

我们需要使用
let
可选字段将字段的值分配给变量;然后在
管道
字段stage中访问这些变量,在该字段中指定要在集合上运行的管道

请注意,在
$match
阶段,我们使用求值查询操作符来比较字段的值

管道中的最后一个阶段是聚合管道阶段,在此阶段,我们只需使用操作符将
$$lookup
结果与
$$ROOT
文档的一部分合并

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"
                   }
                ]
             }
          }
       }
    ]
)
此管道产生的结果如下所示:

{
  user1: 1,
  user2: 2,
  percent1: 0.56,
  percent2: 0.3
}
{
    "_id" : ObjectId("59e1ad7d36f42d8960c06022"),
    "user1" : 1,
    "user2" : 2,
    "percent" : 0.3,
    "percent1" : 0.56
}
{
    user1: 1,
    user2: 2,
    percent1: 0.56,
    percent2: 0.3
}

如果您不在3.6+版本上,您可以首先使用一个字段(比如说“user1”)加入,然后使用聚合管道操作符从那里释放匹配文档的数组。管道中的下一个阶段是使用和系统变量筛选出“joined”集合中的“user2”值与输入文档不相等的文档的阶段。然后,您可以在阶段中重塑文档

产生:

{
    "_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”和“user2”编辑文档

这将产生:

{ "_id" : { "user1" : 1, "user2" : 2 }, "percent" : [ 0.56, 0.3 ] }

您可以使用$match$project管道进行多字段匹配。(参见此处的详细答案-)


您可以使用$match$project管道进行多字段匹配。(参见此处的详细答案-)


如果您试图对数据进行建模,并在决定之前来这里检查mongodb是否可以对多个字段执行联接,请继续阅读

虽然MongoDB可以执行连接,但您也可以根据应用程序访问模式自由地建模数据。如果数据如问题中所示那样简单,我们可以简单地维护一个如下所示的集合:

{
  user1: 1,
  user2: 2,
  percent1: 0.56,
  percent2: 0.3
}
{
    "_id" : ObjectId("59e1ad7d36f42d8960c06022"),
    "user1" : 1,
    "user2" : 2,
    "percent" : 0.3,
    "percent1" : 0.56
}
{
    user1: 1,
    user2: 2,
    percent1: 0.56,
    percent2: 0.3
}
现在,您可以在此集合上执行通过加入本应执行的所有操作。我们为什么要避免加入?因为切分集合()不支持它们,这将阻止您在需要时向外扩展。规范化数据(具有单独的表/集合)在SQL中工作得非常好,但在Mongo中,避免连接在大多数情况下都会带来好处而不会带来后果。只有在没有其他选择时,才在MongoDB中使用规范化。从:

通常,使用规范化数据模型:

  • 当嵌入会导致数据重复,但不会提供足够的读取性能优势来抵消重复的影响时
  • 表示更复杂的多对多关系
  • 为大型分层数据集建模

查看以了解更多关于嵌入的信息,以及为什么选择嵌入而不是标准化。

如果您试图对数据建模,并在决定之前来这里检查mongodb是否可以对多个字段执行联接,请继续阅读

虽然MongoDB可以执行连接,但您也可以根据应用程序访问模式自由地建模数据。如果数据如问题中所示那样简单,我们可以简单地维护一个如下所示的集合:

{
  user1: 1,
  user2: 2,
  percent1: 0.56,
  percent2: 0.3
}
{
    "_id" : ObjectId("59e1ad7d36f42d8960c06022"),
    "user1" : 1,
    "user2" : 2,
    "percent" : 0.3,
    "percent1" : 0.56
}
{
    user1: 1,
    user2: 2,
    percent1: 0.56,
    percent2: 0.3
}
现在,您可以在此集合上执行通过加入本应执行的所有操作。我们为什么要避免加入?因为切分集合()不支持它们,这将阻止您在需要时向外扩展。规范化数据(具有单独的表/集合)在SQL中工作得非常好,但在Mongo中,避免连接在大多数情况下都会带来好处而不会带来后果。只有在没有其他选择时,才在MongoDB中使用规范化。从:

通常,使用规范化数据模型:

  • 嵌入时会导致数据重复,但无法提供足够的读取