Django:如何基于它覆盖字段';聚合查询中的s值(在多个列上联接)

Django:如何基于它覆盖字段';聚合查询中的s值(在多个列上联接),django,postgresql,django-orm,Django,Postgresql,Django Orm,我已经在互联网的不同角落和Django项目文档上寻找了一段时间,但没有成功,因此我的问题是: 作为后台,我正在用一个外部API同步模型,所以我不希望改变模型结构 我有以下模型(为了简洁起见,只留下相关字段): 时间输入: 类时间条目(models.Model): id=CharField(主键=True,最大长度=255) 持续时间\u DecimalField=DecimalField(最大数字=5,小数位数=2,详细名称=dec duration') start=DateTimeField

我已经在互联网的不同角落和Django项目文档上寻找了一段时间,但没有成功,因此我的问题是:

作为后台,我正在用一个外部API同步模型,所以我不希望改变模型结构

我有以下模型(为了简洁起见,只留下相关字段):

  • 时间输入:

    类时间条目(models.Model):
    id=CharField(主键=True,最大长度=255)
    持续时间\u DecimalField=DecimalField(最大数字=5,小数位数=2,详细名称=dec duration')
    start=DateTimeField()
    project=ForeignKey(project,models.SET_NULL,blank=True,NULL=True)
    user=ForeignKey(user,models.SET_NULL,blank=True,NULL=True)
    
  • 会员资格

    类成员资格(models.Model):
    user=ForeignKey(user,models.SET_NULL,blank=True,NULL=True)
    project=ForeignKey(project,models.SET_NULL,blank=True,NULL=True)
    小时费率=外键(HourlyRate,models.SET\u NULL,blank=True,NULL=True)
    活动=布尔字段(默认值=真)
    
  • 使用者

    类用户(models.Model):
    小时费率=小数字段(最大位数=7,小数位数=2,空白=True,空=True)
    
  • 钟楼

    class HourlyRate(models.Model):
    金额=货币字段(最大数字=10,小数位数=2,空白=True,空=True,默认货币=USD)
    
小时费率可以在用户级别设置,也可以在成员级别覆盖,因此,我试图创建一个Django ORM查询,以获取覆盖的小时费率或用户小时费率的值(如果未定义成员级别的值),并将其乘以十进制持续时间,然后每月汇总所有内容,生成每月消耗小时数及其货币值的报告

这里是我目前查询的用户级别的小时费率:

  • 用户级查询

    TimeEntry.objects.filter(项目id='xxxxxx')
    .annotate(年=提取年('start'),月=提取月('start'))\
    .值('年'、'月')\
    .annotate(sum=ExpressionWrapper(sum(F('user\u hourly\u rate'))*F('duration\u DecimalField'))、DecimalField()、hours\u sum=sum('duration\u decimal')、count=count('id'))\
    .值('year'、'month'、'count'、'sum')。排序依据('year'、'month')
    
  • 输出:

    +----------------------------------------------+
    |                    Results                   |
    +----+------+-------+------+-----------+-------+
    | No | year | month | sum  | hours_sum | count |
    +----+------+-------+------+-----------+-------+
    | 1  | 2020 |     1 | 1000 |        95 |    15 |
    +----+------+-------+------+-----------+-------+
    | 2  | 2019 |    12 |  990 |        87 |     9 |
    +----+------+-------+------+-----------+-------+
    | 3  | 2019 |    11 | 1250 |       113 |    21 |
    +----+------+-------+------+-----------+-------+
    |    | ...  |       |      |           |       |
    +----+------+-------+------+-----------+-------+
    
这是我试图从成员资格模型中获取重写值的尝试

  • 成员级别查询
    TimeEntry.objects.filter(项目id='xxxxxx')
    .annotate(年=提取年('start'),月=提取月('start'))\
    .值('年'、'月')\
    .annotate(sum=ExpressionWrapper(sum(F('project\u membership\u hourly\u rate\u amount'))*F('duration\u DecimalField'))、DecimalField()、count=count('id'))\
    .order_by('-year','-month')。值('year','month','count','sum'))
    
下面是为该查询生成SQL的步骤:

  • 成员级别生成SQL
    选择提取(“时间输入”中的“年”。“欧洲/苏黎世”时区的“开始”)作为“年”,
    将“时间输入”中的“月”、“时区“欧洲/苏黎世”中的“开始”提取为“月”,
    总和((“hourlyrate”,“amount”*“timeentry”,“duration\u decimal”))作为“总和”,
    计数(“时间项”。“id”)为“计数”
    从“时间输入”
    “时间项”“项目id”“项目”“id”上的内部联接“项目”
    “项目”“id”“clockify_会员”“项目id”上的左侧外部加入“会员资格”
    左外联接“hourlyrate”ON(“会员”,“小时费率”\u id“=“hourlyrate”,“id”)
    其中“时间项”。“项目id”=“xxxxxx”
    按摘录分组(“年”来自“时间输入”,“开始”来自“欧洲/苏黎世”时区),摘录(“月”来自“时间输入”,“开始”来自“欧洲/苏黎世”时区)
    按“年”说明、“月”说明订购
    
生成的SQL的问题在于,前两个联接被连接在一起,而不是合并为一个联接(这会导致太多行),下面是给出正确结果的SQL:

  • 成员级别生成SQL[已更正]
    选择提取(“时间输入”中的“年”。“欧洲/苏黎世”时区的“开始”)作为“年”,
    将“时间输入”中的“月”、“时区“欧洲/苏黎世”中的“开始”提取为“月”,
    总和((“hourlyrate”,“amount”*“timeentry”,“duration\u decimal”))作为“总和”,
    计数(“时间项”。“id”)为“计数”
    从“时间输入”
    --开始>这里是合并前两个分离联接的两列上的联接
    左外部加入“成员资格”在(“时间项”。“用户id”=“成员资格”。“用户id”和“成员资格”。“项目id”=“时间项”。“项目id”)
    
    --我建议您在
    TimeEntry
    模型上定义一个指向
    成员身份的
    外来对象

    来自django.db.models.fields.related导入外来对象的
    
    类时间条目(models.Model):
    ...
    project=ForeignKey(project,models.SET_NULL,blank=True,NULL=True)
    user=ForeignKey(user,models.SET_NULL,blank=True,NULL=True)
    成员资格=外国对象(
    会员
    from_fields=('project','user'),
    to_字段=(“项目”、“用户”)
    )
    

    定义了此关系后,您的查询集将能够使用
    Coalesce('membership\uu hourly\u rate'、'membership\uu user\uu hourly\u rate')
    我建议您在
    TimeEntry
    模型上定义一个指向
    成员身份的
    外来对象

    来自django.db.models.fields.related导入外来对象的
    
    类时间条目(models.Model):
    ...
    project=ForeignKey(project,models.SET_NULL,blank=True,NULL=True)
    user=ForeignKey(user,models.SET_NULL,blank=True,NULL=True)
    成员资格=外国对象(
    会员
    from_fields=('project','user'),
    to_字段=(“项目”,