Dev*_*ium 5 mongoose mongodb node.js mongodb-query aggregation-framework
我对sql查询相当满意,但似乎无法集中精力进行mongo db文档的分组和求和,
考虑到这一点,我有一个具有以下模式的工作模型:
{
name: {
type: String,
required: true
},
info: String,
active: {
type: Boolean,
default: true
},
all_service: [
price: {
type: Number,
min: 0,
required: true
},
all_sub_item: [{
name: String,
price:{ // << -- this is the price I want to calculate
type: Number,
min: 0
},
owner: {
user_id: { // <<-- here is the filter I want to put
type: Schema.Types.ObjectId,
required: true
},
name: String,
...
}
}]
],
date_create: {
type: Date,
default : Date.now
},
date_update: {
type: Date,
default : Date.now
}
}
Run Code Online (Sandbox Code Playgroud)
我想要一price栏总和,在哪里owner,我在下面尝试过但是没有运气
Job.aggregate(
[
{
$group: {
_id: {}, // not sure what to put here
amount: { $sum: '$all_service.all_sub_item.price' }
},
$match: {'not sure how to limit the user': given_user_id}
}
],
//{ $project: { _id: 1, expense: 1 }}, // you can only project fields from 'group'
function(err, summary) {
console.log(err);
console.log(summary);
}
);
Run Code Online (Sandbox Code Playgroud)
有人可以指导我正确的方向。先感谢您
正如前面正确指出的那样,将聚合“管道”视为|Unix和其他系统shell中的“管道” 运算符确实有帮助。一个“阶段”将输入馈送到“下一个”阶段,依此类推。
您需要注意的是,您有“嵌套”数组,一个数组位于另一个数组中,如果不小心,这可能与预期结果产生巨大差异。
您的文档由顶层的“ all_service”数组组成。大概这里经常有“多个”条目,所有条目都包含“ price”属性以及“ all_sub_item”。那么当然“ all_sub_item”本身就是一个数组,也包含许多它自己的项。
您可以将这些数组视为SQL中表之间的“关系”,在每种情况下都是“一对多”。但是数据是“预联接”格式的,您可以在不执行联接的情况下一次获取所有数据。您应该已经熟悉了那么多。
但是,当您要“聚合”整个文档时,需要通过“定义”“联接”以与SQL中几乎相同的方式对“非规范化”。这是为了将数据“转换”为适合聚合的非标准化状态。
因此,同样的可视化适用。主文档的条目将通过子文档的数量进行复制,并且“联接”到“内部子文档”将相应地复制主文档和初始“子文档”。简而言之:
{
"a": 1,
"b": [
{
"c": 1,
"d": [
{ "e": 1 }, { "e": 2 }
]
},
{
"c": 2,
"d": [
{ "e": 1 }, { "e": 2 }
]
}
]
}
Run Code Online (Sandbox Code Playgroud)
变成这个:
{ "a" : 1, "b" : { "c" : 1, "d" : { "e" : 1 } } }
{ "a" : 1, "b" : { "c" : 1, "d" : { "e" : 2 } } }
{ "a" : 1, "b" : { "c" : 2, "d" : { "e" : 1 } } }
{ "a" : 1, "b" : { "c" : 2, "d" : { "e" : 2 } } }
Run Code Online (Sandbox Code Playgroud)
这样做的操作是$unwind,并且由于存在多个数组,$unwind因此在继续任何处理之前,您需要同时使用两个数组:
db.collection.aggregate([
{ "$unwind": "$b" },
{ "$unwind": "$b.d" }
])
Run Code Online (Sandbox Code Playgroud)
因此,“ $ b”中的“管道”第一个数组如下所示:
{ "a" : 1, "b" : { "c" : 1, "d" : [ { "e" : 1 }, { "e" : 2 } ] } }
{ "a" : 1, "b" : { "c" : 2, "d" : [ { "e" : 1 }, { "e" : 2 } ] } }
Run Code Online (Sandbox Code Playgroud)
这留下了由“ $ bd”引用的第二个数组,以进一步“归一化”为最终的“未归一化”结果。这允许其他操作进行处理。
对于几乎“每个”聚合管道,您要做的“第一件事”是将文档“过滤”为仅包含结果的文档。这是一个好主意,尤其是在执行诸如之类的操作时$unwind,那么您不想在甚至与您的目标数据都不匹配的文档上执行此操作。
因此,您需要在数组深度匹配您的“ user_id”。但这只是获得结果的一部分,因为您应该知道在查询文档以获取数组中的匹配值时会发生什么。
当然,仍然返回“整个”文档,因为这是您真正要求的。数据已经“联接”了,我们并没有要求以任何方式“取消联接”。就像选择“第一个”文档一样,但是当“去规范化”时,每个数组元素现在实际上本身就是一个“文档”。
因此$match,在“管道”的开头,您不仅要“唯一”,而且在处理完$match“所有” $unwind语句之后,您也要下降到希望匹配的元素级别。
Job.aggregate(
[
// Match to filter possible "documents"
{ "$match": {
"all_service.all_sub_item.owner": given_user_id
}},
// De-normalize arrays
{ "$unwind": "$all_service" },
{ "$unwind": "$all_service.all_subitem" },
// Match again to filter the array elements
{ "$match": {
"all_service.all_sub_item.owner": given_user_id
}},
// Group on the "_id" for the "key" you want, or "null" for all
{ "$group": {
"_id": null,
"total": { "$sum": "$all_service.all_sub_item.price" }
}}
],
function(err,results) {
}
)
Run Code Online (Sandbox Code Playgroud)
另外,自2.6以来的现代MongoDB版本也支持该$redact运算符。在这种情况下,可以使用它在处理以下内容之前对数组内容进行“预过滤” $unwind:
Job.aggregate(
[
// Match to filter possible "documents"
{ "$match": {
"all_service.all_sub_item.owner": given_user_id
}},
// Filter arrays for matches in document
{ "$redact": {
"$cond": {
"if": {
"$eq": [
{ "$ifNull": [ "$owner", given_user_id ] },
given_user_id
]
},
"then": "$$DESCEND",
"else": "$$PRUNE"
}
}},
// De-normalize arrays
{ "$unwind": "$all_service" },
{ "$unwind": "$all_service.all_subitem" },
// Group on the "_id" for the "key" you want, or "null" for all
{ "$group": {
"_id": null,
"total": { "$sum": "$all_service.all_sub_item.price" }
}}
],
function(err,results) {
}
)
Run Code Online (Sandbox Code Playgroud)
这样可以“递归”遍历文档并测试条件,从而有效地删除甚至“不匹配”的所有数组元素$unwind。由于不匹配的物品不需要“解开”,因此可以加快速度。但是,存在“捕获”的原因是,如果由于某种原因在数组元素上根本不存在“所有者”,则此处所需的逻辑会将其视为另一个“匹配”。您始终可以$match再次确定,但是还有一种更有效的方法可以做到这一点:
Job.aggregate(
[
// Match to filter possible "documents"
{ "$match": {
"all_service.all_sub_item.owner": given_user_id
}},
// Filter arrays for matches in document
{ "$project": {
"all_items": {
"$setDifference": [
{ "$map": {
"input": "$all_service",
"as": "A",
"in": {
"$setDifference": [
{ "$map": {
"input": "$$A.all_sub_item",
"as": "B",
"in": {
"$cond": {
"if": { "$eq": [ "$$B.owner", given_user_id ] },
"then": "$$B",
"else": false
}
}
}},
false
]
}
}},
[[]]
]
}
}},
// De-normalize the "two" level array. "Double" $unwind
{ "$unwind": "$all_items" },
{ "$unwind": "$all_items" },
// Group on the "_id" for the "key" you want, or "null" for all
{ "$group": {
"_id": null,
"total": { "$sum": "$all_items.price" }
}}
],
function(err,results) {
}
)
Run Code Online (Sandbox Code Playgroud)
与相比,该过程“大幅”减少了两个数组中项目的大小$redact。的$map操作者处理中“中的”阵列给定语句的每个elment。在这种情况下,每个“外部”数组$map元素将发送到另一个数组,以处理“内部”元素。
在此执行逻辑测试$cond,如果满足“条件”,则返回“内部”数组元素,否则false返回值。
本$setDifference是用来过滤下来的任何false返回的值。或者像在“外部”情况下一样,false从“内部”过滤掉所有值而导致的任何“空白”数组,那里没有匹配项。这样就只剩下匹配项,并封装在“双”数组中,例如:
[[{ "_id": 1, "price": 1, "owner": "b" },{..}],[{..},{..}]]
Run Code Online (Sandbox Code Playgroud)
由于“所有”数组元素_id默认情况下都带有猫鼬(这是您选择保留的一个很好的理由),因此每个项目都是“不同的”,并且不受“设置”运算符的影响,除了删除不匹配的值。
处理$unwind“两次”以将它们转换为适合自己聚合的普通对象。
这些就是您需要知道的事情。正如我之前所说,要“意识到”数据如何“去规范化”以及对您的最终目标意味着什么。
| 归档时间: |
|
| 查看次数: |
4059 次 |
| 最近记录: |