mongodb aggregate 如何返回 { list:[{...},{...},{...}], total:3 } 这样格式的数据?

2019-08-11 17:13:27 +08:00
 lqzhgood

这样格式的数据应该是很常见的。

const result = {
    list: [{ a: 1 }, { a: 2 }, { a: 3 }],
    total: 3,
};

之前我的做法是查 2 次
一次 list, 一次 total。 我想问问有没有办法查一次就出结果的? 求教~

单独的 list 我是这么写的。

const { query = {}, queryPopulate = {}, limit = 10, skip = 0 } = parmas;
const list = await this.ctx.model.Monitor.Check
    .aggregate()
    .match(query)
    .sort({ "meta.createdAt": -1 })
    .lookup({
        from: 'Project',
        localField: 'Project',
        foreignField: '_id',
        as: 'Project',
    })
    .addFields({ Project: { $arrayElemAt: ['$Project', 0] } })
    .match(queryPopulate)
    .skip(limit * skip)
    .limit(limit);

17131 次点击
所在节点    MongoDB
7 条回复
menyakun
2019-08-11 19:57:18 +08:00
所以 list 和 total 来自两个 collection ?那可不是得查两次
lqzhgood
2019-08-11 20:08:32 +08:00
@menyakun 同一个 collection
例如有一组数据 [{a:1},{a:2},{a:3},{a:4},{a:5},{a:6},{a:7},{a:8},{a:9},{a:10}.........,{a:98},{a:99},{a:100}]

前端传过来一个查询条件 a>50 ,需要返回 分页为每页 5 个 第 2 页的数据, 也就是需要返回的数据为
const result = {
list: [{a:56},{a:57},{a:58},{a:59},{a:60}],
total: 50
}

我之前的做法是
const result = {
list: await Model.find( {a:{"$gt":50}} ).skip(1),limit(5),
total: await Model.find( {a:{"$gt":50}} ).count()
}
这里 find 了两次 query。实际上是查了 2 次数据库。


我想在 find 第一次的时候就计算出 total。然后再 skip limit 返回 list,然后这个很常见的需求, MongoDB 怎么做呢?


P.S
我查了 MongoDB 文档, aggregation 里面的 facet 应该是可以做这个需求的。但是我写了一下午也没搞出来。result 始终返回的是一个 Array 不是一个 Object。求教

#api
https://docs.mongodb.com/manual/reference/operator/aggregation/facet/
tankeco
2019-08-11 20:25:13 +08:00
大概就这样?
ret = aggregate(
{查询条件},
{
facet: {
"list": [
{$sort: {...}},
{$skip: xxx},
{$limit: xxx}
],
"total": [
{$count: "total"}
]
}
}
)
返回的是 array,你把第一个元素拿出来就行了。
ret = ret[0]
if len(ret['total']) == 0:
ret['total'] = 0
else:
ret['total'] = ret['total'][0]['total']
menyakun
2019-08-11 20:56:04 +08:00
@lqzhgood aggregate 的返回值一定是数组的吧,#3 正解
lqzhgood
2019-08-11 21:14:16 +08:00
@menyakun 3Q~
aggregate 只能返回 Array 那就没办法了~ 不过我感觉从语义上来说 facet 以后应该要返回 Object 的。
因为 facet 以后只剩下一个 DIY 后重组的对象了,还不如直接返回这个 Object。

P.S
奇了怪了 我下午也是按 3L 这么写, $count 那里一直报错。 估计是哪里秀逗了~~
lqzhgood
2019-08-11 21:18:38 +08:00
贴一下最终代码留给后人~

let resp = await this.ctx.model.Monitor.Check
.aggregate()
.match(query)
.lookup({
from: 'Project',
localField: 'Project',
foreignField: '_id',
as: 'Project',
})
.addFields({ Project: { $arrayElemAt: ['$Project', 0] } })
.match(queryPopulate)
.facet({
list: [{ $sort: { "meta.createdAt": -1 } }, { $skip: (page - 1) * limit }, { $limit: limit }],
total: [{ $count: "total" }],
})
.addFields({ total: { $arrayElemAt: ['$total', 0] } })
.project({ list: 1, total: "$total.total" });

最后两行是重组 Object 结构的。
如果查询条件为空 total 会返回空数组,最终还要处理 query 到 空数组的 临界情况

resp = resp[0];
if (!resp.total) resp.total = 0;


整体下来感觉补丁打的挺多的 没有酣畅淋漓的感觉~ 如果有更好的写法 欢迎下面回帖补充
ljpCN
2019-12-24 10:09:59 +08:00
可以用 group 然后用 push 操作符来生成你需要的 list

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/590957

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX