Django,如何在单个查询集中进行多个注释?

时间:2015-04-28 22:31:43

标签: python django python-3.x django-queryset

使用Django 1.7,Python 3.4和PostgreSQL 9.1我在查询集上注释时遇到了困难。 这是我的模特:

class Payment(models.Model):
    TYPE_CHOICES= (
        ('C', 'CREDIT'),
        ('D', 'DEBIT')
    )
    amount = models.DecimalField(max_digits=8, decimal_places=2, default=0.0)
    customer = models.ForeignKey(Customer, null=False)
    type=models.CharField(max_length=1, null=True, choices=TYPE_CHOICES)

class Customer(models.Model):
    name = models.CharField(max_length=100, unique=True)
    available_funds = models.DecimalField(max_digits=8, decimal_places=2, null=True, default=0.0)
    total_funds = models.DecimalField(max_digits=8, decimal_places=2, null=True, default=0.0)

我想要的是:

Customers:
Name    | Total in  | Total out | available funds   | total funds
-----------------------------------------------------------------
cust 1  | 255       | 220       | 5                 | 35
cust 2  | 100       | 120       | 0                 | -20
cust 3  | 50        | 20        | 15                | 30

和一些数据:

Payments:
amount  | customer  | type 
-------------------------- 
20      | cust 1    | D 
10      | cust 1    | c 
70      | cust 2    | D 
20      | cust 2    | C 
10      | cust 2    | D 
25      | cust 1    | C 
200     | cust 3    | D 
10      | cust 3    | C 
20      | cust 1    | D 

我正在尝试此查询集:

Customer.objects.select_related().filter(Q(payment__isnull=False)& Q(payment__type='D')).values('name').annotate(Sum('payment__amount'))

但我只得到借记。 我不知道如何创建包含客户,总计,总计,总资金,可用资金的列表。

任何人都可以帮我吗?

1 个答案:

答案 0 :(得分:0)

我认为您对使用单个查询集可以执行的操作有限制。我这样说的原因是你要求在不同的Payment条记录集上进行数据库聚合。

让我们看一下您当前的查询集:

Customer.objects.select_related().filter(Q(payment__isnull=False)& Q(payment__type='D')).values('name').annotate(Sum('payment__amount'))

忽略无关的Q()来电,过滤电话payment__type='D'表示payment_amount将始终仅与借记相关。如果您将其更改为“C”,它将始终仅与学分相关。此查询演示了Django的查询集语言强加给您的基本约束 - 您无法真正生成两个不同的聚合并将它们注释到单个记录中。

绕过原始的SQL土地来看看我如何编写这个查询是另一种证明这一点的方法。当然,您会注意到我仍在运行两个不同的Payment聚合!一个用于学分,一个用于借记。

SELECT
    *
FROM
    customer
        INNER JOIN
            (
                SELECT SUM(amount) as total FROM Payment WHERE type='C' GROUP BY customer_id, type
            ) AS credits
            ON credits.customer_id=customer.id
        INNER JOIN
            (
                SELECT SUM(amount) as total FROM Payment WHERE type='D' GROUP BY customer_id, type
            ) AS debits
            ON debits.customer_id=customer.id

该查询将返回大致形式的数据:

customer.id | customer.name | ... | credits.total | debits.total
----------------------------------------------------------------
          1 |       foo bar |     | 20            | 30
          2 |       baz qux |     | 30            | 20

如果您尝试仅使用一个内部联接/聚合,则您必须按付款类型和客户分组,从而产生如下表格:

customer_id | type | sum(amount)
--------------------------------
          1 | C    | 20
          1 | D    | 30
          2 | C    | 30
          2 | D    | 20

当您将这个中间结果与您的客户联系起来时,应该立即明确说明借方和贷方仍未统一到单个记录中。

因为你不能在Django中使用内连接进行这种选择(据我所知),你不能真正做你想在单个查询中做的事情。但是,您的问题有解决方案。

按照可取性的降序排列(我认为当然 - 并且基于我认为结果代码的显而易见性/可维护性),首先是只做多个查询并手动统一结果。

您还可以将信用/借记作为Customer记录的一部分进行跟踪。您已经以这种方式跟踪可用资金(您在查询中使用F个对象来更新/维护这些记录,对吧?),因此维护类似的信用卡/借记卡摘要并不是真的太繁重了时尚也是如此。

最后,我认为你不应该这样做,因为我认为不需要这样做,你可以执行原始的SQL查询来一次性获得所需的结果。