【问题标题】:How to write union queries in mongoDB如何在 mongoDB 中编写联合查询
【发布时间】:2020-10-27 17:31:17
【问题描述】:

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

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

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

【问题讨论】:

  • 欢迎来到使用文档数据库的世界。您可能有 4 个集合,因为每个集合都包含具有不同结构的文档。在这种情况下,解决方案是将所有文档放在“一个”集合中,因为这是使用没有严格模式的基于文档的数据库的用途。如果您只是想以与使用 RDBMS 相同的方式做所有事情,那么切换引擎根本没有意义。做引擎做的事情,而不是你习惯做的事情。
  • @NeilLunn :- 在这 1 个集合中是父集合,其他 3 个是参考集合。但是,在父集合中没有为它们提供参考。数据库映射就是这样设计的,不太可能改变。所有这 4 个集合都具有相同的唯一标识符 - vTMId。
  • 评论不是解释的地方。你不能做一个“联合查询”,因为这是一个 SQL 的事情,它不适用于这里。您的一般解决方案是进行不同的建模。如果您有需要在 MongoDB 中建模以解决问题的数据,请提出您的问题。但简单地问“我如何在 MongoDB 中执行此 SQL 事情?” 没有提供您真正需要做什么的示例,这只是题外话,无法回答。这些是您在此处发布问题时的期望。

标签: mongodb union spring-mongo


【解决方案1】:

在单个查询中使用聚合和查找,可以在 MongoDB 中以“SQL UNION”方式进行联合。

类似这样的:

    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,其中至少包含一个文档。如果您不能保证您的数据库的任何集合都不会为空,您可以通过在数据库中创建某种包含单个空文档的“虚拟”集合来解决此问题,该集合将专门用于执行联合查询。

  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()。几乎任何你想要的东西。

【讨论】:

  • 它在性能方面的表现如何?你认为它已经准备好生产了吗?
  • 我们没有对其进行分析,但我们在生产环境中使用了这种方法,并且性能看起来不是问题。
  • 在我的项目中这是一个问题,因为 $lookup 超出了它的最大容量。因此,对于使用它的人来说,如果您希望在您的收藏中写入更多文档,请考虑未来的问题。
【解决方案2】:

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 的文档
  • 然后使用新的$unionWith 阶段将来自collection2 的文档包含到管道中。 pipeline 参数是一个可选的聚合管道,在合并发生之前应用于正在合并的集合中的文档。
  • 还将来自collection3 的文档包含到具有相同$unionWith 阶段的管道中。

【讨论】:

  • 对于那些询问为什么有人想在 MongoDB 中这样做的人;考虑按月存储在集合中的时间序列数据(例如 MyLogDataYYYYMM)。这允许轻松删除旧日志数据,而无需处理昂贵的删除操作。但是,您可能需要跨多个月的数据进行查询。这就是 $unionWith 超级方便的地方。
【解决方案3】:

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

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

【讨论】:

  • 我没有重新设计数据库模型的选项。我们可以在 4 个不同的查询中实现这一点,即每次查询不同的集合,然后我们可以组合所有 4 个操作的结果,但这似乎不是一个好方法。我想在单个操作/查询中完成。
  • 也许你可以使用 DBRefs,但是你可能想要修改你的文档。请看:docs.mongodb.com/manual/reference/database-references/#dbrefs
  • 注意:即使在最初发布的时候,mongodb v3.2 已经发布,它支持$lookup,本质上是 JOIN。截至 2020 年夏季,支持过滤和非等值连接 $lookup 以及 $graphLookup 和 v4.4 中的 $unionWith
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-10-07
  • 2019-08-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多