Pregunta

Is it possible to compute an aggregation (ex.: sum) of an expression (ex.: subtraction) of fields from a related table in a Django ORM query?

For a full example of what I'm trying to achieve, see this working SQL Fiddle. I broke it down into two questions (other one here). In this case, I have a model Sequence that represents a sequence of numbers, and a model Part that represent a "link" in this sequence:

Sequence   Sequence   Sequence   Sequence   ...
0          5          20         15         ...
|-- Part --|-- Part --|-- Part --|-- ...

Each Part thus represents a delta, which is the difference between the two values in the sequence. I have a set of (non-contiguous) parts and want to calculate the sum of those deltas. Something like this:

sum([p.after.value - p.before.value for p in Part.objects.filter(...)])

But in a single query (the reason is that I want to use this as a subquery for a more complex one). It's easy to do it in SQL:

select sum(a.value - b.value)
  from part p
    join sequence a on p.after_id = a.id
    join sequence b on p.before_id = b.id
  where condition
  group by p.other_field;

I don't know how to do it using Django ORM. I can do it for a single value:

Part.objects.filter(condition).aggregate(Sum('before__value')).values()

But not for an expression involving multiple values. Using F() is not supported yet, so I'm looking for an alternative way. I also looked at this question, which is also about expressions in aggregates, but the accepted answer (using extra) is not applicable to my case AFAIK, since the fields I'm interested in are not in the same table, but in a related one.

>>> Part.objects.filter(condition).extra(select={
...     'delta':'sum(after__value - before__value)'
... })

DatabaseError: no such column: after__value

Here's a SSCCE, in case anyone want to experiment with it: Download or Browse. It has the same models and data as the SQL fiddle linked above.

¿Fue útil?

Solución

Now that 1.8 implemented support for F expressions in aggregate, the answer became straightforward:

Part.objects.filter(condition).aggregate(sum=Sum(F('after__value')-F('before__value'))).values()
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top