如何按不同领域分组
How to group by different fields
我想找到所有名为 'Hans' 的用户,并通过对他们进行分组来汇总他们的 'age' 和 'childs' 的数量。
假设我的数据库中有以下 'users'.
{
"_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"
}
结果应该是这样的:
"result" :
[
{
"age" :
[
{
"value" : "50",
"count" : "1"
},
{
"value" : "40",
"count" : "2"
}
]
},
{
"childs" :
[
{
"value" : "2",
"count" : "2"
},
{
"value" : "1",
"count" : "1"
}
]
}
]
我怎样才能做到这一点?
那个是一个艰难的过程!
首先,最简单的解决方案:
db.test.aggregate([
{ "$match": { "user": "Hans" } },
// duplicate each document: one for "age", the other for "childs"
{ $project: { age: "$age", childs: "$childs",
data: {$literal: ["age", "childs"]}}},
{ $unwind: "$data" },
// pivot data to something like { data: "age", value: "40" }
{ $project: { data: "$data",
value: {$cond: [{$eq: ["$data", "age"]},
"$age",
"$childs"]} }},
// Group by data type, and count
{ $group: { _id: {data: "$data", value: "$value" },
count: { $sum: 1 },
value: {$first: "$value"} }},
// aggregate values in an array for each independant (type,value) pair
{ $group: { _id: "$_id.data", values: { $push: { count: "$count", value: "$value" }} }} ,
// project value to the correctly name field
{ $project: { result: {$cond: [{$eq: ["$_id", "age"]},
{age: "$values" },
{childs: "$values"}]} }},
// group all data in the result array, and remove unneeded `_id` field
{ $group: { _id: null, result: { $push: "$result" }}},
{ $project: { _id: 0, result: 1}}
])
制作中:
{
"result" : [
{
"age" : [
{
"count" : 3,
"value" : "40"
},
{
"count" : 1,
"value" : "50"
}
]
},
{
"childs" : [
{
"count" : 1,
"value" : "1"
},
{
"count" : 3,
"value" : "2"
}
]
}
]
}
现在,为了一些解释:
这里的主要问题之一是每个传入文档都必须是两个 不同总和的一部分。我通过向您的文档添加文字数组 ["age", "childs"]
解决了这个问题,然后通过该数组展开它们。这样一来,每个文档在后期都会呈现两次。
完成后,为了简化处理,我将数据表示更改为更易于管理的形式,例如 { data: "age", value: "40" }
以下步骤本身将执行数据聚合。直到第三个 $project
步骤,它将值字段映射到相应的 age
或 childs
字段。
最后两个步骤只是将两个文档合二为一,删除不需要的 _id
字段。
噗!
这几乎应该是一个 MongoDB 常见问题解答,主要是因为它是一个真实的示例概念,说明你应该如何改变你的想法,从 SQL 处理和拥抱像 MongoDB 的引擎做。
这里的基本原则是"MongoDB does not do joins"。 "envisioning" 你将如何构造 SQL 来做到这一点的任何方式本质上都需要一个 "join" 操作。典型的形式是 "UNION" 实际上是 "join".
那么在不同的范式下如何做呢?那么首先,让我们了解如何而不是 并了解原因。即使它当然适用于您的非常小的样本:
艰难的道路
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"
}}
}}
])
这会给你这样的结果:
{
"_id" : null,
"age" : [
{
"value" : "50",
"count" : 1
},
{
"value" : "40",
"count" : 3
}
],
"childs" : [
{
"value" : "2",
"count" : 3
},
{
"value" : "1",
"count" : 1
}
]
}
那么为什么会这样呢?主要问题应该在管道的第一个阶段就很明显:
{ "$group": {
"_id": null,
"age": { "$push": "$age" },
"childs": { "$push": "$childs" }
}},
我们在这里要求做的是将集合中的所有内容 分组,以获得我们想要的值,并将这些结果$push
分组到一个数组中。当事情很小时,这会起作用,但现实世界的集合会导致管道中的这个 "single document" 超过 允许的 16MB BSON 限制。这就是不好的地方。
其余逻辑通过处理每个数组来遵循自然过程。但当然,现实世界的场景几乎总是会让这变得站不住脚。
您可以通过执行 "duplicating" 文档属于 "type" "age or "child 之类的操作并按类型单独对文档进行分组来避免这种情况。但这有点"over complex" 而不是可靠的做事方式。
自然的反应是 "what about a UNION?",但由于 MongoDB 不做 "join" 那么如何处理呢?
更好的方法(又名新希望)
您在架构和性能方面的最佳方法是简单地通过您的客户端 API 向服务器提交 "parallel" 中的 "both" 个查询(是两个)。收到结果后,您 "combine" 将它们合并为一个响应,然后您可以将其作为数据源发送回最终的 "client" 应用程序。
不同的语言对此有不同的方法,但一般情况下是寻找一个 "asynchronous processing" API 允许您串联执行此操作。
我这里的示例目的是使用 node.js
作为 "asynchronous" 方面基本上是 "built in" 并且遵循起来相当直观。 "combination" 方面可以是任何类型的 "hash/map/dict" table 实现,仅以简单的方式进行操作,例如:
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 ) );
}
);
});
给出可爱的结果:
{
"age": [
{
"value": "50",
"count": 1
},
{
"value": "40",
"count": 3
}
],
"childs": [
{
"value": "2",
"count": 3
},
{
"value": "1",
"count": 1
}
]
}
所以这里要注意的关键是 "separate" 聚合语句本身其实很简单。您唯一面临的就是将这些组合到您的最终结果中。 "combining" 有很多方法,特别是处理每个查询的大量结果,但这是执行模型的基本示例。
关键点在这里。
在聚合管道中混洗数据是可能的,但对于大型数据集来说性能不佳。
使用支持 "parallel" 和 "asynchronous" 执行的语言实现和 API,因此您可以 "load up" 全部或 "most"您的操作一次。
API 应该支持 "combination" 的某些方法,或者允许单独的 "stream" 写入来处理每个接收到的结果集。
忘记 SQL 方式。 NoSQL 方式将 "joins" 等事情的处理委托给您的 "data logic layer",它包含此处所示的代码。它这样做是因为它可以扩展到非常大的数据集。您的 "data logic" 处理大型应用程序中的节点的工作是将其交付到最后 API.
与我可能描述的任何其他形式的 "wrangling" 相比,这是 快速 。 "NoSQL" 思考的一部分是 "Unlearn what you have learned" 并以不同的方式看待事物。如果这种方式效果不佳,请坚持使用 SQL 存储和查询方法。
这就是存在替代方案的原因。
我想找到所有名为 'Hans' 的用户,并通过对他们进行分组来汇总他们的 'age' 和 'childs' 的数量。 假设我的数据库中有以下 'users'.
{
"_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"
}
结果应该是这样的:
"result" :
[
{
"age" :
[
{
"value" : "50",
"count" : "1"
},
{
"value" : "40",
"count" : "2"
}
]
},
{
"childs" :
[
{
"value" : "2",
"count" : "2"
},
{
"value" : "1",
"count" : "1"
}
]
}
]
我怎样才能做到这一点?
那个是一个艰难的过程!
首先,最简单的解决方案:
db.test.aggregate([
{ "$match": { "user": "Hans" } },
// duplicate each document: one for "age", the other for "childs"
{ $project: { age: "$age", childs: "$childs",
data: {$literal: ["age", "childs"]}}},
{ $unwind: "$data" },
// pivot data to something like { data: "age", value: "40" }
{ $project: { data: "$data",
value: {$cond: [{$eq: ["$data", "age"]},
"$age",
"$childs"]} }},
// Group by data type, and count
{ $group: { _id: {data: "$data", value: "$value" },
count: { $sum: 1 },
value: {$first: "$value"} }},
// aggregate values in an array for each independant (type,value) pair
{ $group: { _id: "$_id.data", values: { $push: { count: "$count", value: "$value" }} }} ,
// project value to the correctly name field
{ $project: { result: {$cond: [{$eq: ["$_id", "age"]},
{age: "$values" },
{childs: "$values"}]} }},
// group all data in the result array, and remove unneeded `_id` field
{ $group: { _id: null, result: { $push: "$result" }}},
{ $project: { _id: 0, result: 1}}
])
制作中:
{
"result" : [
{
"age" : [
{
"count" : 3,
"value" : "40"
},
{
"count" : 1,
"value" : "50"
}
]
},
{
"childs" : [
{
"count" : 1,
"value" : "1"
},
{
"count" : 3,
"value" : "2"
}
]
}
]
}
现在,为了一些解释:
这里的主要问题之一是每个传入文档都必须是两个 不同总和的一部分。我通过向您的文档添加文字数组 ["age", "childs"]
解决了这个问题,然后通过该数组展开它们。这样一来,每个文档在后期都会呈现两次。
完成后,为了简化处理,我将数据表示更改为更易于管理的形式,例如 { data: "age", value: "40" }
以下步骤本身将执行数据聚合。直到第三个 $project
步骤,它将值字段映射到相应的 age
或 childs
字段。
最后两个步骤只是将两个文档合二为一,删除不需要的 _id
字段。
噗!
这几乎应该是一个 MongoDB 常见问题解答,主要是因为它是一个真实的示例概念,说明你应该如何改变你的想法,从 SQL 处理和拥抱像 MongoDB 的引擎做。
这里的基本原则是"MongoDB does not do joins"。 "envisioning" 你将如何构造 SQL 来做到这一点的任何方式本质上都需要一个 "join" 操作。典型的形式是 "UNION" 实际上是 "join".
那么在不同的范式下如何做呢?那么首先,让我们了解如何而不是 并了解原因。即使它当然适用于您的非常小的样本:
艰难的道路
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"
}}
}}
])
这会给你这样的结果:
{
"_id" : null,
"age" : [
{
"value" : "50",
"count" : 1
},
{
"value" : "40",
"count" : 3
}
],
"childs" : [
{
"value" : "2",
"count" : 3
},
{
"value" : "1",
"count" : 1
}
]
}
那么为什么会这样呢?主要问题应该在管道的第一个阶段就很明显:
{ "$group": {
"_id": null,
"age": { "$push": "$age" },
"childs": { "$push": "$childs" }
}},
我们在这里要求做的是将集合中的所有内容 分组,以获得我们想要的值,并将这些结果$push
分组到一个数组中。当事情很小时,这会起作用,但现实世界的集合会导致管道中的这个 "single document" 超过 允许的 16MB BSON 限制。这就是不好的地方。
其余逻辑通过处理每个数组来遵循自然过程。但当然,现实世界的场景几乎总是会让这变得站不住脚。
您可以通过执行 "duplicating" 文档属于 "type" "age or "child 之类的操作并按类型单独对文档进行分组来避免这种情况。但这有点"over complex" 而不是可靠的做事方式。
自然的反应是 "what about a UNION?",但由于 MongoDB 不做 "join" 那么如何处理呢?
更好的方法(又名新希望)
您在架构和性能方面的最佳方法是简单地通过您的客户端 API 向服务器提交 "parallel" 中的 "both" 个查询(是两个)。收到结果后,您 "combine" 将它们合并为一个响应,然后您可以将其作为数据源发送回最终的 "client" 应用程序。
不同的语言对此有不同的方法,但一般情况下是寻找一个 "asynchronous processing" API 允许您串联执行此操作。
我这里的示例目的是使用 node.js
作为 "asynchronous" 方面基本上是 "built in" 并且遵循起来相当直观。 "combination" 方面可以是任何类型的 "hash/map/dict" table 实现,仅以简单的方式进行操作,例如:
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 ) );
}
);
});
给出可爱的结果:
{
"age": [
{
"value": "50",
"count": 1
},
{
"value": "40",
"count": 3
}
],
"childs": [
{
"value": "2",
"count": 3
},
{
"value": "1",
"count": 1
}
]
}
所以这里要注意的关键是 "separate" 聚合语句本身其实很简单。您唯一面临的就是将这些组合到您的最终结果中。 "combining" 有很多方法,特别是处理每个查询的大量结果,但这是执行模型的基本示例。
关键点在这里。
在聚合管道中混洗数据是可能的,但对于大型数据集来说性能不佳。
使用支持 "parallel" 和 "asynchronous" 执行的语言实现和 API,因此您可以 "load up" 全部或 "most"您的操作一次。
API 应该支持 "combination" 的某些方法,或者允许单独的 "stream" 写入来处理每个接收到的结果集。
忘记 SQL 方式。 NoSQL 方式将 "joins" 等事情的处理委托给您的 "data logic layer",它包含此处所示的代码。它这样做是因为它可以扩展到非常大的数据集。您的 "data logic" 处理大型应用程序中的节点的工作是将其交付到最后 API.
与我可能描述的任何其他形式的 "wrangling" 相比,这是 快速 。 "NoSQL" 思考的一部分是 "Unlearn what you have learned" 并以不同的方式看待事物。如果这种方式效果不佳,请坚持使用 SQL 存储和查询方法。
这就是存在替代方案的原因。