如何在mongoDB中编写联合查询

时间:2017-09-07 07:45:33

标签: mongodb spring-mongo

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

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

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

3 个答案:

答案 0 :(得分: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过滤文档
  • 然后将collection2中的文档从$unionWith阶段引入到管道中。 pipeline参数是一个可选的聚合管道,应用于合并发生之前要合并的集合中的文档。
  • 并且还将collection3$unionWith相同阶段的文档从管道中引入。

答案 1 :(得分:1)

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

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

答案 2 :(得分:1)

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

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

  3. 使用$project阶段对剩余文档的所有字段进行剥离:

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

    { $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. Voilà。您知道有一个结果集,其中包含要合并在一起的集合。然后,您可以添加更多阶段以对其进行进一步过滤,排序,应用skip()和limit()。您想要的几乎任何东西。