如何在 mongoDB 中编写联合查询

How to write union queries in mongoDB

是否可以使用 2 个或更多类似于 SQL 查询的集合在 Mongo 数据库中编写联合查询?

我正在使用 spring mongo 模板,在我的用例中,我需要根据某些条件从 3-4 个集合中获取数据。我们可以在一次操作中实现吗?

例如,我有一个名为 "circuitId" 的字段,它存在于所有 4 个集合中。我需要从所有 4 个集合中获取该字段与给定值匹配的所有记录。

不幸的是,基于文档的 MongoDB 不支持 JOINS/Unions,就像在关系数据库引擎中一样。 MongoDB 的关键设计原则之一是根据应用程序的数据获取模式防止使用嵌入式文档进行联接。 话虽如此,如果您确实需要使用 4 个集合,则需要在应用程序端管理逻辑,或者您可以根据 MongoDB 最佳实践重新设计您的数据库设计。

更多信息:https://docs.mongodb.com/master/core/data-model-design/

可以在单个查询中使用聚合和查找,以 'SQL UNION' 的方式在 MongoDB 中执行联合。

像这样:

    db.getCollection("AnyCollectionThatContainsAtLeastOneDocument").aggregate(
    [
      { $limit: 1 }, // Reduce the result set to a single document.
      { $project: { _id: 1 } }, // Strip all fields except the Id.
      { $project: { _id: 0 } }, // Strip the id. The document is now empty.

      // Lookup all collections to union together.
      { $lookup: { from: 'collectionToUnion1', pipeline: [...], as: 'Collection1' } },
      { $lookup: { from: 'collectionToUnion2', pipeline: [...], as: 'Collection2' } },
      { $lookup: { from: 'collectionToUnion3', pipeline: [...], as: 'Collection3' } },

      // Merge the collections together.
      {
        $project:
        {
          Union: { $concatArrays: ["$Collection1", "$Collection2", "$Collection3"] }
        }
      },

      { $unwind: "$Union" }, // Unwind the union collection into a result set.
      { $replaceRoot: { newRoot: "$Union" } } // Replace the root to cleanup the resulting documents.
    ]);

以下是其工作原理的解释:

  1. 从数据库的 任何 集合中实例化一个 aggregate,其中至少有一个文档。如果您不能保证数据库的任何集合都不会为空,则可以通过在数据库中创建某种 'dummy' 集合来解决此问题,其中包含一个空文档,该文档专门用于执行合并查询。

  2. 将管道的第一阶段设置为 { $limit: 1 }。这将删除集合中除第一个文档之外的所有文档。

  3. 使用 $project 个阶段删除剩余文档的所有字段:

    { $project: { _id: 1 } },
    { $project: { _id: 0 } }
    
  4. 您的聚合现在包含一个空文档。是时候为要合并的每个集合添加查找了。您可以使用 pipeline 字段进行一些特定的过滤,或者将 localFieldforeignField 保留为 null 以匹配整个集合。

    { $lookup: { from: 'collectionToUnion1', pipeline: [...], as: 'Collection1' } },
    { $lookup: { from: 'collectionToUnion2', pipeline: [...], as: 'Collection2' } },
    { $lookup: { from: 'collectionToUnion3', pipeline: [...], as: 'Collection3' } }
    
  5. 您现在有一个包含单个文档的聚合,该文档包含 3 个数组,如下所示:

    {
        Collection1: [...],
        Collection2: [...],
        Collection3: [...]
    }
    

    然后您可以使用 $project 阶段和 $concatArrays 聚合运算符将它们合并到一个数组中:

    {
      "$project" :
      {
        "Union" : { $concatArrays: ["$Collection1", "$Collection2", "$Collection3"] }
      }
    }
    
  6. 您现在有一个包含单个文档的聚合,其中包含一个包含您的集合并集的数组。剩下要做的是添加一个 $unwind 和一个 $replaceRoot 阶段以将您的数组拆分为单独的文档:

    { $unwind: "$Union" },
    { $replaceRoot: { newRoot: "$Union" } }
    
  7. 瞧瞧。您知道有一个结果集包含您想要联合在一起的集合。然后,您可以添加更多阶段以进一步过滤它、对其进行排序、应用 skip() 和 limit()。几乎任何你想要的东西。

Mongo 4.4开始,聚合框架提供了一个新的$unionWith阶段,执行两个集合的并集(将两个集合的管道结果合并到一个结果集中)。

因此,为了合并来自 3 个集合的文档:

// > db.collection1.find()
//   { "circuitId" : 12, "a" : "1" }
//   { "circuitId" : 17, "a" : "2" }
//   { "circuitId" : 12, "a" : "5" }
// > db.collection2.find()
//   { "circuitId" : 12, "b" : "x" }
//   { "circuitId" : 12, "b" : "y" }
// > db.collection3.find()
//   { "circuitId" : 12, "c" : "i" }
//   { "circuitId" : 32, "c" : "j" }
db.collection1.aggregate([
  { $match: { circuitId: 12 } },
  { $unionWith: { coll: "collection2", pipeline: [{ $match: { circuitId: 12 } }] } },
  { $unionWith: { coll: "collection3", pipeline: [{ $match: { circuitId: 12 } }] } }
])
// { "circuitId" : 12, "a" : "1" }
// { "circuitId" : 12, "a" : "5" }
// { "circuitId" : 12, "b" : "x" }
// { "circuitId" : 12, "b" : "y" }
// { "circuitId" : 12, "c" : "i" }

这个:

  • 首先过滤来自 collection1
  • 的文档
  • 然后将来自 collection2 的文档包含到新的 $unionWith 阶段的管道中。 pipeline 参数是一个可选的聚合管道,在合并发生之前应用于正在合并的集合中的文档。
  • 并且还将 collection3 中的文档包含到具有相同 $unionWith 阶段的管道中。