Mic*_* K. 6 mongodb aggregation-framework
我想找到所有名为'Hans'的用户,并通过分组来汇总他们的'年龄'和'孩子'的数量.假设我在我的数据库'用户'中有以下内容.
{
"_id" : "01",
"user" : "Hans",
"age" : "50"
"childs" : "2"
}
{
"_id" : "02",
"user" : "Hans",
"age" : "40"
"childs" : "2"
}
{
"_id" : "03",
"user" : "Fritz",
"age" : "40"
"childs" : "2"
}
{
"_id" : "04",
"user" : "Hans",
"age" : "40"
"childs" : "1"
}
Run Code Online (Sandbox Code Playgroud)
结果应该是这样的:
"result" :
[
{
"age" :
[
{
"value" : "50",
"count" : "1"
},
{
"value" : "40",
"count" : "2"
}
]
},
{
"childs" :
[
{
"value" : "2",
"count" : "2"
},
{
"value" : "1",
"count" : "1"
}
]
}
]
Run Code Online (Sandbox Code Playgroud)
我怎样才能做到这一点?
小智 6
这几乎应该是一个MongoDB常见问题解答,主要是因为它是一个真实的例子,说明你应该如何改变你的SQL处理思路,并接受像MongoDB这样的引擎.
这里的基本原则是"MongoDB不做连接".任何"构想"如何构造SQL来执行此操作的方式基本上都需要"连接"操作.典型的形式是"UNION",实际上是"联合".
那么如何在不同的范例下做到这一点呢?首先,让我们来看看如何不去理解原因.即使它当然适用于您的小样本:
db.docs.aggregate([
{ "$group": {
"_id": null,
"age": { "$push": "$age" },
"childs": { "$push": "$childs" }
}},
{ "$unwind": "$age" },
{ "$group": {
"_id": "$age",
"count": { "$sum": 1 },
"childs": { "$first": "$childs" }
}},
{ "$sort": { "_id": -1 } },
{ "$group": {
"_id": null,
"age": { "$push": {
"value": "$_id",
"count": "$count"
}},
"childs": { "$first": "$childs" }
}},
{ "$unwind": "$childs" },
{ "$group": {
"_id": "$childs",
"count": { "$sum": 1 },
"age": { "$first": "$age" }
}},
{ "$sort": { "_id": -1 } },
{ "$group": {
"_id": null,
"age": { "$first": "$age" },
"childs": { "$push": {
"value": "$_id",
"count": "$count"
}}
}}
])
Run Code Online (Sandbox Code Playgroud)
这会给你一个像这样的结果:
{
"_id" : null,
"age" : [
{
"value" : "50",
"count" : 1
},
{
"value" : "40",
"count" : 3
}
],
"childs" : [
{
"value" : "2",
"count" : 3
},
{
"value" : "1",
"count" : 1
}
]
}
Run Code Online (Sandbox Code Playgroud)
那么为什么这么糟糕?在第一个流水线阶段,主要问题应该是显而易见的:
{ "$group": {
"_id": null,
"age": { "$push": "$age" },
"childs": { "$push": "$childs" }
}},
Run Code Online (Sandbox Code Playgroud)
我们要求在这里做的是将集合中的所有内容分组为我们想要的值,并将$push这些结果分组到一个数组中.如果事情很小,那么这是有效的,但真实世界的集合会导致管道中的这个"单个文档" 超过允许的16MB BSON限制.这就是坏事.
其余的逻辑遵循自然过程,通过使用每个数组.但当然,现实世界的场景几乎总会让这种情况变得难以为继.
您可以通过执行"复制"文档"类型""年龄或"孩子"以及按类型单独分组文档等方式来避免这种情况.但这有点"过于复杂"而不是一种坚实的方式做事.
自然的反应是"UNION怎么样?",但是由于MongoDB不进行"加入",那么如何处理呢?
在架构和性能方面,您最好的方法是通过客户端API以"并行"方式向服务器提交"两个"查询(是两个).收到结果后,您可以将它们"组合"成一个响应,然后将其作为数据源发送回最终的"客户端"应用程序.
不同的语言有不同的方法,但一般情况是寻找一个"异步处理"API,允许您串联执行此操作.
我的示例目的node.js在于使用"异步"方面基本上是"内置"并且相当直观地遵循.事物的"组合"方面可以是任何类型的"hash/map/dict"表实现,仅以简单的方式进行,例如:
var async = require('async'),
MongoClient = require('mongodb');
MongoClient.connect('mongodb://localhost/test',function(err,db) {
var collection = db.collection('docs');
async.parallel(
[
function(callback) {
collection.aggregate(
[
{ "$group": {
"_id": "$age",
"type": { "$first": { "$literal": "age" } },
"count": { "$sum": 1 }
}},
{ "$sort": { "_id": -1 } }
],
callback
);
},
function(callback) {
collection.aggregate(
[
{ "$group": {
"_id": "$childs",
"type": { "$first": { "$literal": "childs" } },
"count": { "$sum": 1 }
}},
{ "$sort": { "_id": -1 } }
],
callback
);
}
],
function(err,results) {
if (err) throw err;
var response = {};
results.forEach(function(res) {
res.forEach(function(doc) {
if ( !response.hasOwnProperty(doc.type) )
response[doc.type] = [];
response[doc.type].push({
"value": doc._id,
"count": doc.count
});
});
});
console.log( JSON.stringify( response, null, 2 ) );
}
);
});
Run Code Online (Sandbox Code Playgroud)
这给出了可爱的结果:
{
"age": [
{
"value": "50",
"count": 1
},
{
"value": "40",
"count": 3
}
],
"childs": [
{
"value": "2",
"count": 3
},
{
"value": "1",
"count": 1
}
]
}
Run Code Online (Sandbox Code Playgroud)
所以这里要注意的关键是"单独的"聚合语句本身实际上非常简单.你唯一面对的是将最终结果中的那些结合起来.有许多"组合"的方法,特别是处理来自每个查询的大结果,但这是执行模型的基本示例.
关键点在这里.
可以在聚合管道中混洗数据,但不适用于大型数据集.
使用支持"并行"和"异步"执行的语言实现和API,这样您就可以立即"加载"所有或"大部分"操作.
API应该支持某种"组合"方法,或者允许单独的"流"写入来处理接收到的一个结果集.
忘记SQL的方式.NoSQL方式将"连接"等事务的处理委托给您的"数据逻辑层",这是包含此处所示代码的内容.它是这样做的,因为它可以扩展到非常大的数据集.相反,您的"数据逻辑"处理大型应用程序中的节点需要将其提供给最终API.
与我可能描述的任何其他形式的"争吵"相比,这是快速的."NoSQL"思想的一部分是"忘掉你所学到的东西"并以不同的方式看待事物.如果这种方式表现不佳,那么坚持使用SQL方法进行存储和查询.
这就是替代品存在的原因.