首页   注册   登录
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐工具
RoboMongo
推荐书目
50 Tips and Tricks for MongoDB Developers
Related Blogs
Snail in a Turtleneck
V2EX  ›  MongoDB

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

  •  
  •   lqzhgood · 119 天前 · 6223 次点击
    这是一个创建于 119 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

    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);
    
    
    第 1 条附言  ·  119 天前
    result 的 total 好像会引起误解~ 重写下~

    const result = {
    list: [{ a: 1 }, { a: 2 }, { a: 3 }],
    total: 10,
    };
    6 回复  |  直到 2019-08-11 21:18:38 +08:00
        1
    menyakun   119 天前
    所以 list 和 total 来自两个 collection ?那可不是得查两次
        2
    lqzhgood   119 天前
    @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/
        3
    tankeco   119 天前   ♥ 1
    大概就这样?
    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']
        4
    menyakun   119 天前   ♥ 1
    @lqzhgood aggregate 的返回值一定是数组的吧,#3 正解
        5
    lqzhgood   119 天前
    @menyakun 3Q~
    aggregate 只能返回 Array 那就没办法了~ 不过我感觉从语义上来说 facet 以后应该要返回 Object 的。
    因为 facet 以后只剩下一个 DIY 后重组的对象了,还不如直接返回这个 Object。

    P.S
    奇了怪了 我下午也是按 3L 这么写, $count 那里一直报错。 估计是哪里秀逗了~~
        6
    lqzhgood   119 天前
    贴一下最终代码留给后人~

    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;


    整体下来感觉补丁打的挺多的 没有酣畅淋漓的感觉~ 如果有更好的写法 欢迎下面回帖补充
    关于   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   785 人在线   最高记录 5043   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.3 · 18ms · UTC 20:25 · PVG 04:25 · LAX 12:25 · JFK 15:25
    ♥ Do have faith in what you're doing.