gpt4 book ai didi

node.js - Mongoose 聚合 "$sum"子文档中的行

转载 作者:搜寻专家 更新时间:2023-10-31 22:40:58 25 4
gpt4 key购买 nike

我对 sql 查询相当擅长,但我似乎无法理解分组和获取 mongo db 文档的总和,

this请记住,我有一个工作模型,其架构如下:

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

我想要一笔 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);
}
);

有人可以指导我朝着正确的方向前进。先感谢您

最佳答案

底漆

正如前面正确指出的,将聚合“管道”视为“管道”| 确实有帮助。来自 Unix 和其他系统 shell 的操作符。一个“阶段”将输入提供给“下一个”阶段,依此类推。

这里需要注意的是,您有“嵌套”数组,一个数组在另一个数组中,如果您不小心,这可能会对您的预期结果产生巨大影响。

您的文档由顶层的“all_service”数组组成。大概这里经常有“多个”条目,所有条目都包含您的“价格”属性以及“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 }
]
}
]
}

变成这样:

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

执行此操作的操作是 $unwind ,并且由于有多个数组,因此您需要 $unwind在继续任何处理之前:

db.collection.aggregate([
{ "$unwind": "$b" },
{ "$unwind": "$b.d" }
])

所以有来自“$b”的“管道”第一个数组,如下所示:

{ "a" : 1, "b" : { "c" : 1, "d" : [ { "e" : 1 }, { "e" : 2 } ] } }
{ "a" : 1, "b" : { "c" : 2, "d" : [ { "e" : 1 }, { "e" : 2 } ] } }

这留下了由“$b.d”引用的第二个数组,以进一步反规范化为“没有任何数组”的最终反规范化结果。这允许处理其他操作。

求解

对于几乎“每个”聚合管道,您要做的“第一件事”是将文档“过滤”为仅包含您的结果的文档。这是一个好主意,尤其是在执行诸如 $unwind 之类的操作时。 ,那么您不希望对甚至与您的目标数据不匹配的文档执行此操作。

所以你需要在数组深度匹配你的“user_id”。但这只是获得结果的一部分,因为您应该知道在查询文档以获取数组中的匹配值时会发生什么。

当然,仍然返回“整个”文档,因为这是您真正要求的。数据已经“连接”了,我们没有要求以任何方式“取消连接”它。你看这就像“第一个”文档选择一样,但是当“非规范化”时,每个数组元素现在实际上代表一个"file"本身。

所以你不是“唯一” $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) {

}
)

或者,自 2.6 以来的现代 MongoDB 版本也支持 $redact 运算符(operator)。在这种情况下,这可用于在使用 $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) {

}
)

这可以“递归地”遍历文档并测试条件,甚至在 $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) {

}
)

$redact 相比,该过程“大幅”减少了两个数组中项目的大小。 . $map 运算符将数组的每个元素处理为“in”中的给定语句。在这种情况下,每个“外部”数组元素都被发送到另一个 $map处理“内部”元素。

此处使用 $cond 执行逻辑测试因此,如果满足“条件”,则返回“内部”数组元素,否则返回 false值被返回。

$setDifference 用于过滤掉任何 false返回的值。或者在“外部”情况下,由所有 false 产生的任何“空白”数组从“内部”过滤的值,那里没有匹配项。这仅留下匹配的项目,包含在“双”数组中,例如:

[[{ "_id": 1, "price": 1, "owner": "b" },{..}],[{..},{..}]]

因为“所有”数组元素有一个 _id默认情况下使用 Mongoose (这是您保留它的一个很好的理由),那么除了删除不匹配的值之外,每个项目都是“不同的”并且不受“设置”运算符的影响。

进程 $unwind “两次”将这些转换为自己文档中的普通对象,适合聚合。

所以这些是你需要知道的事情。正如我之前所说,“注意”数据如何“非规范化”以及这对您的最终总数意味着什么。

关于node.js - Mongoose 聚合 "$sum"子文档中的行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31437026/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com