【问题标题】:Finding two documents in MongoDB that share a key value在 MongoDB 中查找共享键值的两个文档
【发布时间】:2017-06-26 18:26:48
【问题描述】:

我在 MongoDB 中有大量文档,每个文档都有一个名为“name”的键和另一个名为“type”的键。我想找到两个具有同名不同类型的文档,一个简单的MongoDB副本

SELECT ...
FROM table AS t1, table AS t2
WHERE t1.name = t2.name AND t1.type <> t2.type

我可以想象使用聚合可以做到这一点:但是,集合非常大,处理它需要时间,我正在寻找一对这样的文档。

【问题讨论】:

  • 它们是两个不同的集合吗? - $or 是运算符 - docs.mongodb.org/manual/reference/operator/query/or - 您的问题 SQL 示例提示它们是 2 个集合,但您的开头行说“大量文档”意味着一个集合。
  • @RobSedgwick:这是一个集合;在 SQL 中,我只是使用相同的集合(表)两次好像这是两个集合(表)。 $or 似乎不起作用,因为我需要比较成对的文档而不是对值应用分离条件。
  • 好的,所以你需要类似 - db.collectionname.find({ $or: [ { name:"namevalue" }, { type:"typevalue" } ] } ?
  • @RobSedgwick:我不知道该名称应该具有哪个值,我只知道它应该由两个不同的文档共享。我可以以某种方式在“namevalue”中指示一个变量吗?如何声明类型值不同?
  • 确保您已在“类型”和“名称”db.collectionname.ensureIndex( { type: 1, name: 1 } ) - docs.mongodb.org/manual/tutorial/create-a-compound-index/… 上设置索引

标签: mongodb mongodb-query aggregation-framework


【解决方案1】:

虽然我支持 cmets,但我认为您提出问题的方式实际上与您遇到的特定问题无关,但我会以某种方式解释 MongoDB 类型的解决方案中惯用的 SQL 方式。我认为您的实际解决方案会有所不同,但您没有向我们提出这个问题,而只是向我们提出了 SQL。

因此,将以下文档视为样本集,为了清楚起见,删除了此列表中的 _id 字段:

{ "name" : "a", "type" : "b" }
{ "name" : "a", "type" : "c" }
{ "name" : "b", "type" : "c" }
{ "name" : "b", "type" : "a" }
{ "name" : "a", "type" : "b" }
{ "name" : "b", "type" : "c" }
{ "name" : "f", "type" : "e" }
{ "name" : "z", "type" : "z" }
{ "name" : "z", "type" : "z" }

如果我们对相同的数据运行 SQL,我们会得到以下结果:

a|b
a|c
a|c
b|c
b|a
b|a
a|b
b|c

我们可以看到2个文档不匹配,然后算出SQL操作的逻辑。因此,另一种说法是“哪些文档给定了“名称”键确实在键“类型”中具有多个一个可能的值。

鉴于此,采用 mongo 方法,我们可以查询 符合给定条件的项目。如此有效地反转结果:

db.sample.aggregate([

    // Store unique documents grouped by the "name"
    {$group: { 
        _id: "$name",
        comp: {
            $addToSet: { 
                name:"$name",
                type: "$type" 
            }
        } 
    }},

    // Unwind the "set" results
    {$unwind: "$comp"},

    // Push the results back to get the unique count
    // *note* you could not have done this with alongside $addtoSet
    {$group: {
        _id: "$_id",
        comp: {
            $push: { 
                name: "$comp.name",
                type: "$comp.type" 
            }
        },
        count: {$sum: 1} 
    }},

    // Match only what was counted once
    {$match: {count: 1}},

    // Unwind the array
    {$unwind: "$comp"},

    // Clean up to "name" and "type" only
    {$project: { _id: 0, name: "$comp.name", type: "$comp.type"}}

])

这个操作会产生结果:

{ "name" : "f", "type" : "e" }
{ "name" : "z", "type" : "z" }

现在为了获得与 SQL 查询相同的结果,我们将获取这些结果并将它们引导到另一个查询中:

db.sample.find({$nor: [{ name: "f", type: "e"},{ name: "z", type: "z"}] })

作为最终匹配结果到达:

{ "name" : "a", "type" : "b" }
{ "name" : "a", "type" : "c" }
{ "name" : "b", "type" : "c" }
{ "name" : "b", "type" : "a" }
{ "name" : "a", "type" : "b" }
{ "name" : "b", "type" : "c" }

所以这会起作用,但是可能使这不切实际的一件事是,当被比较的文档数量非常大时,我们在将这些结果压缩为数组时遇到了工作限制。

在最终的查找操作中使用 negative 也会带来一些影响,这会强制扫描集合。但平心而论,对于使用相同 否定 前提的 SQL 查询也是如此。

编辑

当然,我没有提到的是,如果结果集相反,并且您匹配 more 结果从聚合中排除项目,那么只需反转逻辑即可获得你想要的钥匙。只需将 $match 更改如下:

{$match: {$gt: 1}}

这将是结果,也许不是实际的文件,但它是结果。所以你不需要另一个查询来匹配否定的情况。

而且,归根结底,这是我的错,因为我太专注于惯用的翻译,以至于我没有阅读你问题的最后一行,在哪里说您正在寻找一个文档。

当然,目前如果结果大小大于 16MB,那么您将陷入困境。至少在 2.6 版本之前,聚合操作的结果是 cursor,因此您可以像 .find() 一样对其进行迭代。

2.6 中还引入了$size 运算符,用于查找文档中数组的大小。所以这将有助于删除第二个 $unwind$group 用于获取集合的长度。这会将查询更改为更快的形式:

db.sample.aggregate([
    {$group: { 
        _id: "$name",
        comp: {
            $addToSet: { 
                name:"$name",
                type: "$type"
            }
        } 
    }},
    {$project: { 
        comp: 1,
        count: {$size: "$comp"} 
    }},
    {$match: {count: {$gt: 1}}},
    {$unwind: "$comp"},
    {$project: { _id: 0, name: "$comp.name", type: "$comp.type"}}
])

如果您只是为了个人使用或开发/测试而这样做,那么目前可以使用 MongoDB 2.6.0-rc0。


故事的寓意。是的,你可以这样做,但是真的想要或需要那样做吗?那么可能不会,如果您针对特定业务案例提出不同的问题,您可能会得到不同的答案。但是话又说回来,这可能完全符合您的要求。

注意

值得一提的是,当您查看 SQL 的结果时,如果您没有为这些值使用 DISTINCT,由于其他可用的类型选项,它会错误地重复几个项目或本质上是另一个分组。但这就是这个过程使用 MongoDB 产生的结果。

给亚历山大

这是当前 2.4.x 版本的 shell 中聚合的输出:

{
    "result" : [
            {
                    "name" : "f",
                    "type" : "e"
            },
            {
                    "name" : "z",
                    "type" : "z"
            }
    ],
    "ok" : 1
}

所以这样做是为了让一个 var 作为参数传递给第二个查找中的 $nor 条件,如下所示:

var cond = db.sample.aggregate([ .....

db.sample.find({$nor: cond.result })

你应该得到相同的结果。否则请咨询您的司机。

【讨论】:

  • 是的,这就是我真正想要做的。我运行了查询的第一部分,得到了很多结果,太多了,无法将它们复制并粘贴到 $nor 中。我可以以某种方式将第一个查询嵌套在第二个查询中,还是应该将第一个查询的结果存储在一个文件中?
  • 使用.toArray() 并将结果分配给var,然后将其替换为第二个查询的$nor 值。基本上是外壳之外的语言中的方法。
  • @AlexanderSerebrenik 我对此不好。习惯于在 2.6 shell 中工作。 Aggregate 将为您提供 shell 的数组结果,但您需要稍微尝试一下才能获得结果值。应该在results 下作为键。
  • @AlexanderSerebrenik 添加了结尾部分来说明从聚合到从 shell 传递到下一个查询的响应。希望对您有所帮助。
  • @NeilLunn,印象深刻。那里有一些我可以学习的东西,比我对 MongoDB 的了解要深得多!你得到了问题的引擎盖下的工厂。已收藏。 (亚历山德拉,我现在不会再向你扔运算符了:)
【解决方案2】:

有一个非常简单的聚合可以让您获得不止一次出现的名称及其类型:

db.collection.aggregate([
      { $group: { _id : "$name", 
        count:{$sum:1},
        types:{$addToSet:"$type"}}},
      {$match:{"types.1":{$exists:true}}}
])

这适用于支持聚合框架的所有版本。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-02-21
    • 2017-05-19
    • 1970-01-01
    • 2021-07-01
    • 2023-02-09
    • 1970-01-01
    • 2019-12-30
    • 1970-01-01
    相关资源
    最近更新 更多