Django-ORM:需要不同的。为什么?

Django-ORM: distinct is needed. Why?

我在玩 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)

我不明白第二个查询 which returns 1731.

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

尽管如此,1731 对我来说似乎是一个错误。

以下查询不是distinct/unique的意图是什么?

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

原始 MySQL 查询如下所示:

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

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

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

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

所以让你困惑的查询:

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

该查询可以描述为:

  • 访问 Group 模型管理器(Group.objects)。
  • 做一个QuerySet
    • Return 所有 Group 个实例(Group.objects.all())。
  • 访问 User 模型管理器(User.objects)。
  • 做一个Queryset
    • 加入 Group 模型,在 User.groups 外键上。
    • Return 每 (User + Group) 行有一个关联的 Group.

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

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


相反,您想要:

  • 访问 User 模型管理器(User.objects)。
  • 做一个QuerySet
    • Return 所有 groups 不为空的行。
User.objects.filter(groups__isnull=False)

请注意,这 - “所有具有一组非空关联组的用户” - 与您的另一个示例查询(“所有用户不在任何组中”)相反。

因为组是一个 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 table。 其中 auth_user_groupsManyToManyField table 而不是 Group 模型的 table。这样一个用户就会不止一次来。

您可能希望使用 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

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

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

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