在MongoDB中查找共享密钥值的两个文档

时间:2014-03-01 13:56:38

标签: mongodb mongodb-query aggregation-framework

我在MongoDB中有大量文档,其中每个文档都有一个名为“name”的键,另一个键叫做“type”。我想找到两个文件,其中同名不同类型,一个简单的MongoDB对应

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

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

2 个答案:

答案 0 :(得分:3)

虽然我不赞成我不认为你的问题表达方式实际上与你遇到的具体问题有关,但我将在某种程度上解释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操作的逻辑。所以另一种说法是“给出”name“键的哪些文件在键”type“中具有超过一个可能的值。

鉴于此,采用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" }

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

在最终的查找操作中使用否定也会受到一些影响,这会强制扫描集合。但公平地说,使用相同的否定前提的SQL查询也是如此。

修改

当然我没有提到的是,如果结果集反过来并且你匹配更多会导致聚合中的排除项,那么只需反转逻辑以获得你想要的钥匙。只需按如下方式更改$ 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作为参数传递给第二个find中的$ nor条件,如下所示:

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

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

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

答案 1 :(得分:2)

有一个非常简单的聚合可以帮助您获取多次出现的名称及其类型:

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

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