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

Ale*_*nik 5 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
Run Code Online (Sandbox Code Playgroud)

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

Nei*_*unn 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" }
Run Code Online (Sandbox Code Playgroud)

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

a|b
a|c
a|c
b|c
b|a
b|a
a|b
b|c
Run Code Online (Sandbox Code Playgroud)

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

鉴于此,采用 mongo 方法,我们可以查询与给定条件匹配的项目。因此,结果实际上相反:

a|b
a|c
a|c
b|c
b|a
b|a
a|b
b|c
Run Code Online (Sandbox Code Playgroud)

此操作将产生结果:

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"}}

])
Run Code Online (Sandbox Code Playgroud)

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

{ "name" : "f", "type" : "e" }
{ "name" : "z", "type" : "z" }
Run Code Online (Sandbox Code Playgroud)

最终匹配结果为:

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

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

它还因为在最终查找操作中使用负数而受到一些影响,这会强制扫描集合。但平心而论,使用相同否定前提的 SQL 查询也可以这样说。

编辑

当然,我没有提到的是,如果结果集相反,并且您在聚合中的排除项中匹配更多结果,那么只需反转逻辑即可获取您想要的键。只需更改 $match 如下:

{ "name" : "a", "type" : "b" }
{ "name" : "a", "type" : "c" }
{ "name" : "b", "type" : "c" }
{ "name" : "b", "type" : "a" }
{ "name" : "a", "type" : "b" }
{ "name" : "b", "type" : "c" }
Run Code Online (Sandbox Code Playgroud)

这就是结果,也许不是实际的文件,但它是一个结果。因此,您不需要另一个查询来匹配否定案例。

而且,最终这是我的错,因为我太专注于惯用翻译,以至于我没有阅读你问题中的最后一行,在哪里可以你正在寻找一份文档。

当然,目前如果结果大小大于 16MB,那么您就会陷入困境。至少在2.6版本之前,聚合操作的结果是一个游标,因此您可以像.find().

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

{$match: {$gt: 1}}
Run Code Online (Sandbox Code Playgroud)

如果您只是为了个人使用或开发/测试而执行此操作,则当前可以使用 MongoDB 2.6.0-rc0。


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

笔记

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

为了亚历山大

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

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"}}
])
Run Code Online (Sandbox Code Playgroud)

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

{
    "result" : [
            {
                    "name" : "f",
                    "type" : "e"
            },
            {
                    "name" : "z",
                    "type" : "z"
            }
    ],
    "ok" : 1
}
Run Code Online (Sandbox Code Playgroud)

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