django annotate and count: how to filter the ones to include in count

前端 未结 3 1121
遇见更好的自我
遇见更好的自我 2021-01-31 20:03

Given a queryset, I add the count of related objects (ModelA) with the following:

qs = User.objets.all()
qs.annotate(modela__count=models.Count(\'modela\'))


        
相关标签:
3条回答
  • 2021-01-31 20:57

    In a LEFT JOIN, every field of modela could be NULL because of the absence of corresponding row. So

    modela.deleted_at IS NULL
    

    ...is not only true for the matching rows, but also true for those users whose have no corresponding modela rows.

    I think the right SQL should be:

    SELECT COUNT(
        CASE
          WHEN
            `modela`.`user_id` IS NOT NULL  -- Make sure modela rows exist
            AND `modela`.`deleted_at` IS NULL
            THEN 1
          ELSE NULL
        END
      ) AS `modela__count`,
      `users`.*
    FROM `users`
    LEFT OUTER JOIN `modela`
      ON ( `users`.`id` = `modela`.`user_id` )
    GROUP BY `users`.`id`
    

    In Django 1.8 this should be:

    from django.db import models
    qs = User.objects.all().annotate(
        modela_count=models.Count(
            models.Case(
                models.When(
                    modela__user_id__isnull=False,
                    modela__deleted_at__isnull=True,
                    then=1,
                )
            )
        )
    )
    

    Notice:

    @YAmikep discovered that a bug in Django 1.8.0 makes the generated SQL have an INNER JOIN instead of a LEFT JOIN, so you will lose rows without corresponding foreign key relationship. Use Django 1.8.2 or above version to fix that.

    0 讨论(0)
  • 2021-01-31 20:59

    In Django 1.8 I believe this can be achieved with conditional aggregation . However for previous versions I would do it with .extra

    ModelA.objects.extra(select={
        'account_count': 'SELECT COUNT(*) FROM account WHERE modela.account_id = account.id AND account.some_prop IS NOT NULL'
    })
    
    0 讨论(0)
  • 2021-01-31 21:03

    You can simply filter before you annotate:

    from django.db.models import Q, Count
    
    qs = ModelA.objects.filter(account__prop1__isnull=False).annotate(account_count=Count('account'))
    
    0 讨论(0)
提交回复
热议问题