On Friday, December 4, 2015 at 5:47:43 PM UTC+2, Paulo Maciel wrote:
>
> Combining multiple aggregations with annotate() will yield the wrong 
> results <https://code.djangoproject.com/ticket/10060>, as multiple tables 
> are cross joined. Due to the use of LEFT OUTER JOIN, duplicate records 
> will be generated if some of the joined tables contain more records than 
> the others
>

This has nothing to do with cross joins, usage of LEFT JOINS or having more 
records in one table than the others. The reason is that we should use 
subqueries where we currently use JOINs.

As a complex example, (one) correct SQL query for 
Book.objects.annotate(chapter_cnt=Count('chapter'), 
author_cnt=Count('author')) is:

  SELECT subq1.chapter_cnt, subq2.author_cnt, book_id
   FROM book
   LEFT JOIN (SELECT count(*) as chapter_cnt FROM chapter GROUP BY book_id) 
as subq1 ON subq1.book_id = book.book_id
   LEFT JOIN (SELECT count(*) as author_cnt FROM author GROUP BY book_id) 
as subq2 ON subq2.book_id = book.book_id
   GROUP BY book.book_id

This is still an easy case for the ORM. To add a bit more complexity, 
consider 
Book.objects.filter(Q(chapter__pages__lt=100)|Q(author__age__gt=50)).annotate(chapter_cnt=Count('chapter'),
 
author_cnt=Count('author')). I *think* we need a query like:

  SELECT subq1.chapter_cnt, subq2.author_cnt, book_id
   FROM book
   LEFT JOIN (SELECT count(*) as chapter_cnt FROM chapter WHERE 
chapter.pages < 100 GROUP BY book_id) as subq1 ON subq1.book_id = 
book.book_id
   LEFT JOIN (SELECT count(*) as author_cnt FROM author WHERE author.age > 
50 GROUP BY book_id) as subq2 ON subq2.book_id = book.book_id
  WHERE subq1.book_id IS NOT NULL OR subq2.book_id IS NOT NULL
   GROUP BY book.book_id

There are even more complex cases. For example you might need a subquery 
inside the generated subqueries in some cases.

I have some ideas of how to fix this (though in cases like this it is hard 
to know if the ideas actually work before trying). I believe fixing this 
takes at least a couple of months of development time. At least my 
development budget seems to be around one day a month currently.

An alternate approach is to error out when the ORM detects a complex enough 
query, and add explicit ways for users to add subqueries, subselects and 
stuff like that to ORM queries. This might make more sense than trying to 
generate complex queries automatically, as there are multiple ways to write 
the above query, and the different forms perform vastly differently 
depending on the data in the tables and the used database backend. Still, 
we can have it both ways - automatically generate a correct query, but also 
offer a way to explicitly add subqueries to ORM queries.

 - Anssi

>
>
> The Count 
> <https://docs.djangoproject.com/en/1.9/ref/models/querysets/#django.db.models.Count>
>  aggregate 
> has a distinct parameter that may help:
>
> q = Book.objects.annotate(Count('authors', distinct=True), Count('chapters', 
> distinct=True))
>
> Why not "distinct=True" to use in Sum?
>
> q = Book.objects.annotate(Sum('val_a', distinct=True), Sum('val_b', 
> distinct=True))
>
>

-- 
You received this message because you are subscribed to the Google Groups 
"Django developers  (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
Visit this group at http://groups.google.com/group/django-developers.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-developers/e32741dc-097e-4fba-b6ed-e831dc545c9a%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to