我在MongoDB中有一个大的文档集合,每个文档都有一个叫做“name”的关键字和另一个叫做“type”的关键字。我想要查找两个具有相同名称但不同类型的文档,这是一个简单的MongoDB查询:
SELECT ...
FROM table AS t1, table AS t2
WHERE t1.name = t2.name AND t1.type <> t2.type
我可以想象使用聚合功能来实现此目的,但是集合非常大,处理它需要时间,而我只是寻找一对这样的文件。
我在MongoDB中有一个大的文档集合,每个文档都有一个叫做“name”的关键字和另一个叫做“type”的关键字。我想要查找两个具有相同名称但不同类型的文档,这是一个简单的MongoDB查询:
SELECT ...
FROM table AS t1, table AS t2
WHERE t1.name = t2.name AND t1.type <> t2.type
虽然我坚持认为你提出的问题并没有与你实际遇到的问题有关,但我会尝试解释SQL习惯用法在MongoDB解决方案中的应用。我坚信你实际的解决方案可能是不同的,但你目前只提供了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" }
a|b
a|c
a|c
b|c
b|a
b|a
a|b
b|c
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版本之前是这样的,聚合操作的结果是一个游标,所以您可以像使用.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
}
操作步骤如下,将变量作为参数传递给第二个 find 中的 $nor 条件:
var cond = db.sample.aggregate([ .....
db.sample.find({$nor: cond.result })
您应该获得相同的结果。否则,请咨询您的驱动程序。
results
下作为一个键。 - Neil Lunn有一个非常简单的聚合方法可以帮您获取出现多次的名称及其类型:
db.collection.aggregate([
{ $group: { _id : "$name",
count:{$sum:1},
types:{$addToSet:"$type"}}},
{$match:{"types.1":{$exists:true}}}
])
$or
是运算符 - http://docs.mongodb.org/manual/reference/operator/query/or/ - 你的问题中 SQL 的示例提示它们是 2 个集合,但你的开头说“一个大型文档集合”,意味着一个集合。 - Rob Sedgwickfind
查询中,您无法同时访问多个文档,因此必须使用aggregate
(可能)或mapReduce
来完成此操作。但是,这不会像简单或快速的Mongo一样适合此类查询。 - JohnnyHK