Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

django - annotate() - Sum() of a column with filter on another column

I have the following two models.

class Product(models.Model):
    product_group=models.ForeignKey('productgroup.ProductGroup', null=False,blank=False)
    manufacturer=models.ForeignKey(Manufacturer, null=False,blank=False)
    opening_stock=models.PositiveIntegerField(default=0)

    class Meta:
        unique_together = ('product_group', 'manufacturer')

and

TRANSACTION_TYPE=(('I','Stock In'),('O','Stock Out'))
class Stock(models.Model):
    product=models.ForeignKey('product.Product', blank=False,null=False)
    date=models.DateField(blank=False, null=False,)
    quantity=models.PositiveIntegerField(blank=False, null=False)
    ttype=models.CharField(max_length=1,verbose_name="Transaction type",choices=TRANSACTION_TYPE, blank=False)

I need to list all products with stock_in_sum=Sum(of all stock ins) , stock_out_sum=Sum(of all stock outs) and blance_stock=opening_stock+stock_in_sum - stock_out_sum

This is what I've achieved so far.

class ProductList(ListView):
    model=Product

    def get_queryset(self):
        queryset = super(ProductList, self).get_queryset()
        queryset = queryset.prefetch_related('product_group','product_group__category','manufacturer')
        queryset = queryset.annotate(stock_in_sum = Sum('stock__quantity'))
        queryset = queryset.annotate(stock_out_sum = Sum('stock__quantity'))

I need to get

  1. stock_in_sum as the sum(quantity) where ttype='I'
  2. stock_out_sum as the sum(quantity) where ttype='O'
  3. blance_stock as product.opening_stock + stock_in_sum - stock_out_sum

along with each Product object.

How do I achieve this?

Thanks.

like image 495
art Avatar asked Aug 04 '17 12:08

art


People also ask

What is the difference between aggregate and annotate in Django?

Unlike aggregate() , annotate() is not a terminal clause. The output of the annotate() clause is a QuerySet ; this QuerySet can be modified using any other QuerySet operation, including filter() , order_by() , or even additional calls to annotate() .

What is the purpose of filter () method in Django?

The filter() method is used to filter you search, and allows you to return only the rows that matches the search term.

How does annotate work in Django?

In the Django framework, both annotate and aggregate are responsible for identifying a given value set summary. Among these, annotate identifies the summary from each of the items in the queryset. Whereas in the case of aggregate, the summary is calculated for the entire queryset.


1 Answers

You could use conditional aggregation

queryset = queryset.annotate(
    stock_in_sum = Sum(Case(When(stock__ttype='I', then=F('stock__quantity')), output_field=DecimalField(), default=0)),
    stock_out_sum = Sum(Case(When(stock__ttype='O', then=F('stock__quantity')), output_field=DecimalField(), default=0)))
)

To make the sums, and after that compute the balance with F() expression

queryset = queryset.annotate(balance_stock=F('opening_stock') + F('stock_in_sum') - F('stock_out_sum'))

You can also chain the different operation instead of multiple assignations:

queryset = queryset.prefetch_related(...).annotate(...).annotate(...)
like image 159
Nadège Avatar answered Oct 09 '22 09:10

Nadège