【问题标题】:Groupping and aggregating on django model related records对 django 模型相关记录进行分组和聚合
【发布时间】:2017-09-04 21:39:00
【问题描述】:

我正在学习 Django,但遇到了一个小问题:

我有一个具有相关行的模型,我想得到相关行的列的总和,按第二列的值分组。

我正在使用的模型如下:

class Integrante(models.Model):
    nombre = models.CharField(max_length=80, db_index=True)

class EstadoCuenta(models.Model):
    num_edc = models.PositiveIntegerField(validators=[MinValueValidator(1),])
    fecha_edc = models.DateField(null=True, db_index=True)

class Movimiento(models.Model):
    TIPOS_MOVIMIENTO = (
        # Choices for column 'tipo'
    )
    estado_cuenta = models.ForeignKey(EstadoCuenta, on_delete=models.CASCADE)
    integrante = models.ForeignKey(Integrante, on_delete=models.CASCADE)
    fecha = models.DateField(db_index=True)
    tipo = models.SmallIntegerField(db_index=True, choices=TIPOS_MOVIMIENTO)
    descripcion = models.CharField(max_length=200, blank=True)
    importe = models.DecimalField(max_digits=8, decimal_places=2)

    class Meta:
        ordering = ['integrante', 'fecha', 'tipo', 'pk']

我尝试了以下方法:

edc = EstadoCuenta.objects.filter(grupo__nombre_grupo='A group').latest()
edc.movimiento_set.values('integrante').annotate(Sum('importe'))

但是,我得到以下结果:

for x in edc.movimiento_set.values('integrante').annotate(Sum('importe')):
    print(x)

# {'integrante': 28, 'importe__sum': Decimal('-20.00')}
# {'integrante': 28, 'importe__sum': Decimal('23.00')}
# {'integrante': 28, 'importe__sum': Decimal('9.60')}
# {'integrante': 28, 'importe__sum': Decimal('20.00')}
# {'integrante': 28, 'importe__sum': Decimal('-0.60')}
# {'integrante': 24, 'importe__sum': Decimal('96.00')}
# {'integrante': 24, 'importe__sum': Decimal('28.80')}
# {'integrante': 24, 'importe__sum': Decimal('48.00')}
# {'integrante': 24, 'importe__sum': Decimal('28.80')}
# {'integrante': 24, 'importe__sum': Decimal('96.00')}
# {'integrante': 24, 'importe__sum': Decimal('48.00')}
# {'integrante': 24, 'importe__sum': Decimal('288.00')}
# {'integrante': 24, 'importe__sum': Decimal('144.00')}
# {'integrante': 24, 'importe__sum': Decimal('19.20')}
# {'integrante': 24, 'importe__sum': Decimal('-510.00')}
# {'integrante': 24, 'importe__sum': Decimal('48.00')}
# {'integrante': 24, 'importe__sum': Decimal('48.00')}
# {'integrante': 24, 'importe__sum': Decimal('48.00')}
# {'integrante': 24, 'importe__sum': Decimal('48.00')}
# {'integrante': 24, 'importe__sum': Decimal('48.00')}
# {'integrante': 24, 'importe__sum': Decimal('48.00')}
# {'integrante': 24, 'importe__sum': Decimal('35.00')}
# {'integrante': 24, 'importe__sum': Decimal('235.00')}
# {'integrante': 24, 'importe__sum': Decimal('-0.80')}
# ...

我想要得到的结果是这样的(直接在数据库服务器中完成):

select integrante_id, sum(importe) 
from core_movimiento 
where estado_cuenta_id=233 
group by integrante_id;

-- | integrante_id | sum(importe) |
-- +---------------+--------------+
-- |            24 |       844.00 |
-- |            25 |         0.00 |
-- |            26 |         0.00 |
-- |            27 |      -232.00 |
-- |            28 |        32.00 |
-- |            29 |         0.00 |
-- |            30 |        20.00 |

你能指出正确的方向吗?

我正在使用 Python 3.6 和 Django 1.11

奖金

我还想得到importe 的总和,这取决于列tipo 的值。在 SQL 中,我可以这样得到:

select integrante_id
     , sum(case when tipo=1 then importe else 0 end) as pagos
     , sum(case when tipo in (11, 12) then importe else 0 end) as ventas
     , sum(case when tipo in (70, 71) then importe else 0 end) as paquetes
     , sum(case when tipo=99 then importe else 0 end) as ajustes 
     , sum(importe) as total
from core_movimiento 
where estado_cuenta_id = 233 
group by integrante_id;

-- | integrante_id | pagos   | ventas  | paquetes | ajustes | total   |
-- +---------------+---------+---------+----------+---------+---------+
-- |            24 | -510.00 | 1084.80 |   270.00 |   -0.80 |  844.00 |
-- |            25 |  -35.00 |    0.00 |    35.00 |    0.00 |    0.00 |
-- |            26 |  -20.00 |    0.00 |    20.00 |    0.00 |    0.00 |
-- |            27 | -422.00 |  190.00 |     0.00 |    0.00 | -232.00 |
-- |            28 |  -20.00 |   32.60 |    20.00 |   -0.60 |   32.00 |
-- |            29 | -200.00 |    0.00 |   200.00 |    0.00 |    0.00 |
-- |            30 |    0.00 |    0.00 |    20.00 |    0.00 |   20.00 |

有什么想法吗?

【问题讨论】:

  • 你的模型中有ordering meta 吗?
  • 这是一个很好的问题。特别是奖金部分!一个好的答案可以做到这一点,而无需使用 django ORM 对数据库进行多次往返。你认为劫持 django ORM 是一个合理的答案吗?就像通过 django 进行原始 sql 查询:docs.djangoproject.com/en/1.11/topics/db/sql
  • @BearBrown 刚刚将Meta 类添加到Movimiento。我应该在订单中只留下'integrante 吗?
  • @Tico 我会检查的。我想使用 ORM(仅用于学习目的),但如果最简单的方法是发送原始查询,我会使用它

标签: django django-models group-by aggregate


【解决方案1】:

您的第一个只需要添加空订单:

edc.movimiento_set.values('integrante').annotate(Sum('importe')).order_by()
#                                                              ^^^^^^^^^^

默认情况下,django 为排序字段添加 group by ordering-or-order-by

我认为对于奖金,稍后会更新答案

【讨论】:

    【解决方案2】:

    为您的第二个问题添加新答案以提高可读性,因为 django 1.8 有 conditional-expressions

    from django.db.models import Sum, When, Case, IntegerField, F
    
    edc.movimiento_set.values('integrante'
        ).annotate(total=Sum('importe')
        ).annotate(pagos=Sum(
            Case(
                When(tipo=1, then=F('importe')),
                default=0,
                output_field=IntegerField()
                )
            )
        ).annotate(ventas=Sum(
            Case(
                When(tipo__in=(11, 12), then=F('importe')),
                default=0,
                output_field=IntegerField()
                )
            )
        ).annotate(paquetes=Sum(
            Case(
                When(tipo__in=(70, 71), then=F('importe')),
                default=0,
                output_field=IntegerField()
                )
            )
        ).annotate(ajustes=Sum(
            Case(
                When(tipo=99, then=F('importe')),
                default=0,
                output_field=IntegerField()
                )
            )
        ).order_by()
    

    希望一切顺利。

    【讨论】:

    • 令人印象深刻!我愿意接受这两个答案!谢谢!
    猜你喜欢
    • 2020-11-09
    • 2017-04-21
    • 1970-01-01
    • 2012-11-04
    • 2017-02-14
    • 1970-01-01
    • 1970-01-01
    • 2021-10-19
    • 1970-01-01
    相关资源
    最近更新 更多