V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
IurNusRay
V2EX  ›  Python

关于 django 的分组查询 annotation, 似乎只能按照具体数值来分组?

  •  
  •   IurNusRay · 2021-03-23 09:08:58 +08:00 · 1879 次点击
    这是一个创建于 1323 天前的主题,其中的信息可能已经有所发展或是发生改变。
    比如有个订单表,需要统计近一月总销售额的变化,那么笨办法是先获得近一个月的所有日期,然后遍历,进行 30 次这样的查询:Order.objects.filter(create_time__lte=date).aggregate(sum=Sum("amount")) ,但是用 annotation 又只能统计每一天单日的销售额,不能获得一个时间段内的总和,大佬们有没有啥更好的办法
    16 条回复    2021-03-23 11:43:58 +08:00
    ericls
        1
    ericls  
       2021-03-23 09:10:28 +08:00 via iPhone
    annotate 再 values 之后 再 annotate
    ericls
        2
    ericls  
       2021-03-23 09:10:59 +08:00 via iPhone
    values 其实相当于是 group by
    ericls
        3
    ericls  
       2021-03-23 09:31:03 +08:00   ❤️ 1
    Order.objects.filter(create_time__lte=date).annotate(day_of_month=...).values('day_of_month').annotate(sum=Sum("amount").values('day_of_month', 'sum')
    IurNusRay
        4
    IurNusRay  
    OP
       2021-03-23 09:40:16 +08:00
    @ericls 没太看懂。。假如每天的销售额都是 100,那么我想得到的数据类似: {"2021.3.1": 100, "2021.3.2": 200, "2021.3.3": 300}, 是累加的数值而不是单日的
    ericls
        5
    ericls  
       2021-03-23 09:43:14 +08:00 via iPhone
    @IurNusRay 如果是累加的话 可以用 subquery 或者 window?
    HelloViper
        6
    HelloViper  
       2021-03-23 09:50:19 +08:00
    还不如建个新表起个 job,每次结算下前一天的持久化
    IurNusRay
        7
    IurNusRay  
    OP
       2021-03-23 09:59:08 +08:00
    @ericls 没太用过,我去看看
    SjwNo1
        8
    SjwNo1  
       2021-03-23 09:59:13 +08:00
    没用过 django 的 orm 哈,不过应该有 convert date 的方法,然后 group_by 即可
    IurNusRay
        9
    IurNusRay  
    OP
       2021-03-23 10:00:54 +08:00
    @HelloViper 我这个只是用在首页的一个数据统计,所以只需要看看有没有啥办法优化一下查询语句
    SjwNo1
        10
    SjwNo1  
       2021-03-23 10:16:30 +08:00
    ```
    Order.objects.annotate(day=TruncDay('create_time')).values("day"). annotate(sum=Sum("amount")) .values("day", "sum")
    ```
    IurNusRay
        11
    IurNusRay  
    OP
       2021-03-23 10:21:05 +08:00
    @SjwNo1 这样还是统计的单日销售额,我需要查的是累加的值
    lvhuiyang
        12
    lvhuiyang  
       2021-03-23 10:41:40 +08:00
    如果 ORM 中没有针对于当前特定业务的实现的话,手动处理下也不繁琐。大概想到两种方式:

    - 1. 帖子开头描述的遍历一个月的所有日期,进行 30 次左右的 query 。
    - 2. 上面用 annotate 一个 query 语句按照 day of month 进行 group by 查询出每天的销售额,然后用 Python 进行逻辑上的累加。

    ```python
    In [9]: from collections import OrderedDict

    In [10]: od = OrderedDict([('2020-01-01', 10), ('2020-01-02', 12), ('2020-01-03', 15)])

    In [11]: pre_value = 0
    ...: result = OrderedDict()
    ...: for k, v in od.items():
    ...: pre_value += v
    ...: result[k] = pre_value
    ...:

    In [12]: result
    Out[12]: OrderedDict([('2020-01-01', 10), ('2020-01-02', 22), ('2020-01-03', 37)])
    ```

    对比第 1 种多次查询带来的开销,第 2 种的 Python 累加的开销可以忽略不计,要我我选第 2 种实现。
    IurNusRay
        13
    IurNusRay  
    OP
       2021-03-23 10:45:15 +08:00
    @lvhuiyang 恩,第二种方法的确也不错
    lvhuiyang
        14
    lvhuiyang  
       2021-03-23 10:47:40 +08:00
    #12 复制的代码缩进有点问题,发个截图:

    IurNusRay
        15
    IurNusRay  
    OP
       2021-03-23 11:24:25 +08:00
    @lvhuiyang 刚刚想了一下,这样好像也有个问题,首先根据日期分组的时候,如果某一天没有订单产生,则不会出现在 QuerySet 的结果里面,所以还要将缺失的数据比如 {"2020-01-01": 0} 插入到结果中。。
    lvhuiyang
        16
    lvhuiyang  
       2021-03-23 11:43:58 +08:00
    @IurNusRay 呃确实需要先将缺失的数据插入到结果,不过这些应该都是对 query 结果的逻辑操作,感觉逻辑上也不复杂。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5448 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 06:55 · PVG 14:55 · LAX 22:55 · JFK 01:55
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.