【问题标题】:Django-ORM: distinct is needed. Why?Django-ORM:需要不同的。为什么?
【发布时间】:2018-12-02 02:29:44
【问题描述】:

我在玩 django ORM

import django
django.setup()
from django.contrib.auth.models import User, Group
from django.db.models import Count

# All users
print(User.objects.all().count())
# --> 742

# Should be: All users which are in a group.
# But the result is different. I don't understand this.
print(User.objects.filter(groups__in=Group.objects.all()).count())
# --> 1731

# All users which are in a group.
# distinct needed
print(User.objects.filter(groups__in=Group.objects.all()).distinct().count())
# --> 543

# All users which are in a group. Without distinct, annotate seems to do this.
print(User.objects.filter(groups__in=Group.objects.all()).annotate(Count('pk')).count())
# --> 543

# All users which are in no group
print(User.objects.filter(groups__isnull=True).count())
# --> 199

# 199 + 543 = 742  (nice)

我不明白返回 1731 的第二个查询。

我知道我可以使用 distinct()。

尽管如此,1731 对我来说还是个 bug。

为什么下面的查询不是不同/唯一的意图是什么?

User.objects.filter(groups__in=Group.objects.all())

【问题讨论】:

    标签: django django-orm


    【解决方案1】:

    原始 MySQL 查询如下所示:

    SELECT user.id, group.id FROM user LEFT JOIN group ON user.group_id = group.id
    

    结果将包含用户和组的所有可能组合,我猜有些用户属于多个组。

    【讨论】:

    • 是的,结果是左连接。我正在寻找意图为什么事情会像现在这样。
    • Django 总是使用 LEFT JOIN,因为可以添加 User.objects.filter(groups=None) 甚至 User.objects.filter(groups__deparment__manager=None) 之类的条件
    • 但原因不是LEFT JOIN,而是每个用户有多个组。
    • 此外,您只是在查询错误的内容。如果您想知道一个组中有多少用户,User.objects.exclude(groups__isnull=True).count() 提供了答案。正如 Raz 所说,您正在查询用户和组的总组合。这是要查询的两个不同的东西。
    【解决方案2】:

    您正在尝试从所有组中获取所有用户,但一个用户可以出现在多个组中,这就是需要 distinct 的原因。如果您希望特定组中的用户而不是执行 all,请尝试 filter 查询。

    【讨论】:

      【解决方案3】:

      我假设User.groups 是一个ForeignKey 或将每个User 与零到多个Group 实例相关联的其他关系。

      所以让你困惑的查询:

      User.objects.filter(groups__in=Group.objects.all())
      

      该查询可以描述为:

      • 访问 Group 模型管理器 (Group.objects)。
      • QuerySet:
        • 返回所有 Group 实例 (Group.objects.all())。
      • 访问 User 模型管理器 (User.objects)。
      • Queryset:
        • User.groups 外键上加入Group 模型。
        • 返回每个 (User + Group) 具有关联 Group 的行。

      这不是“一个组中的所有用户”;相反,它是“该组所在的所有用户-组对”。

      通过查询每个多值User.groups 字段,您暗示查询必须包含从UserGroup 行的连接 .


      相反,你想要:

      • 访问 User 模型管理器 (User.objects)。
      • QuerySet:
        • 返回所有groups 不为空的行。
      User.objects.filter(groups__isnull=False)
      

      请注意,“所有具有非空关联组集的用户”是您拥有的另一个示例查询(“不属于任何组的所有用户”)的反面。

      【讨论】:

        【解决方案4】:

        由于组是 ManyToManyField,因此查询转换为 INNER JOIN 语句。

        如果您打印以下内容,您将看到QuerySet 生成的查询:

        >>> print(User.objects.filter(groups__in=Group.objects.all()).query)
        SELECT `auth_user`.`id`,  .... , `auth_user`.`date_joined` FROM `auth_user` INNER JOIN `auth_user_groups` ON (`auth_user`.`id` = `auth_user_groups`.`user_id`) WHERE `auth_user_groups`.`group_id` IN (SELECT `auth_group`.`id` FROM `auth_group`)
        

        如您所见,查询连接了auth_userauth_user_groups 表。 其中auth_user_groupsManyToManyField 表而不是Group 模型的表。因此,用户将不止一次来。

        您可能希望使用 annotate 让用户感到恶心,在我的情况下,数字如下:

        $ ./manage.py shell
        >>> 
        >>> from django.contrib.auth.models import User, Group
        >>> from django.db.models import Count
        >>>
        # All users
        >>> print(User.objects.all().count())
        556
        >>>
        # All users which are not in a group.
        >>> print(User.objects.annotate(group_count=Count('groups')).filter(group_count=0).count())
        44
        >>>
        # All users which are in a group.
        >>> print(User.objects.annotate(group_count=Count('groups')).filter(group_count__gt=0).count())
        512
        >>>
        

        Annotate 在行为上类似于 distinct。它创建一个group by 查询。您可以按如下方式查看和检查查询。

        >>> print(User.objects.annotate(group_count=Count('groups')).filter(group_count__gt=0).query)
        SELECT `auth_user`.`id`, `auth_user`.`password`, `auth_user`.`last_login`, `auth_user`.`is_superuser`, `auth_user`.`username`, `auth_user`.`first_name`, `auth_user`.`last_name`, `auth_user`.`email`, `auth_user`.`is_staff`, `auth_user`.`is_active`, `auth_user`.`date_joined`, COUNT(`auth_user_groups`.`group_id`) AS `group_count` FROM `auth_user` LEFT OUTER JOIN `auth_user_groups` ON (`auth_user`.`id` = `auth_user_groups`.`user_id`) GROUP BY `auth_user`.`id` HAVING COUNT(`auth_user_groups`.`group_id`) > 0 ORDER BY NULL
        

        【讨论】:

          【解决方案5】:

          当您对数据库运行“DISTINCT”查询时,您最终会得到数据结果中每个不同行的列表。在 Django 结果中有更多“DISTINCT”行的原因是组合交叉乘法正在进行,从而产生了额外的结果。

          其他答案都提到了所有这些,但既然你问为什么: 这个连接中的 ORM 可能允许您从查询中提取附加到组的字段。因此,例如,如果您想要所有这些用户和所有群组以及群组联系人进行某种大规模的奇怪邮件合并,您可以得到它们。

          DISTINCT 带来的后处理是根据您提取的字段而不是查询中的行来缩小结果范围。如果您要使用 PyCharm 调试器或其他工具,您可能会发现使用不同的 ORM 语法访问组并不像没有不同时那样容易。

          【讨论】:

            猜你喜欢
            • 2020-05-26
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2022-01-13
            • 2012-03-03
            • 2020-05-03
            • 2022-01-13
            相关资源
            最近更新 更多