1

我们遇到了一个关于 Django 中 Q 对象求反的非常奇怪的问题。让我们以足球为例:

class Team(models.Model):
    id = UUIDField(primary_key=True)

class Player(models.Model):
    id = UUIDField(primary_key=True)
    name = models.CharField(max_length=128)
    team = models.ForeignKey(Team)
    touchdowns = models.IntegerField()

有10个团队。

有100名球员,每队10名。每支球队都有一名名叫“乔”的球员。一支球队中有一名“乔”获得了 5 次达阵。所有其他乔的得分为 1 次达阵。有 8 支球队,每名球员只得分 1 次达阵。

我想要拥有一个名叫乔的球员至少得分 3 次达阵的球队。

models.Team.objects.filter(Q(player__name="Joe", player__touchdowns__gte=3)).count()

应该返回 One。否定应该返回 9(其他 9 支没有名为 Joe 且至少有 3 次达阵的球队):

models.Team.objects.filter(~Q(player__name="Joe", player__touchdowns__gte=3)).count()

而是返回该团队中每个人的达阵次数少于 3 (8) 的任何团队。

我哪里错了?请注意,我们对此的实际应用要复杂得多,因此我们需要使用带否定的 Q 对象,我们不能使用 Exclude。

4

2 回答 2

2

找出这些差异发生原因的最佳方法是调查生成的查询:django-debug-toolbar附带一个debugsqlshell命令,该命令在使用 Django 查询集 API 后打印发送到数据库的实际查询。对于这些测试,我使用了User带有 join on 的模型Group。我也注意到所选对象的计数不同,因此从表面上看,它似乎与您的用例有很好的相关性。

User.objects.filter(~Q(username='jdoe', groups__name='Awesome Group'))

SELECT "auth_user"."id",
       "auth_user"."username",
       "auth_user"."first_name",
       "auth_user"."last_name",
       "auth_user"."email",
       "auth_user"."password",
       "auth_user"."is_staff",
       "auth_user"."is_active",
       "auth_user"."is_superuser",
       "auth_user"."last_login",
       "auth_user"."date_joined"
FROM "auth_user"
WHERE NOT ("auth_user"."username" = 'jdoe'
           AND "auth_user"."id" IN
             (SELECT U1."user_id"
              FROM "auth_user_groups" U1
              INNER JOIN "auth_group" U2 ON (U1."group_id" = U2."id")
              WHERE (U2."name" = 'Awesome Group'
                     AND U1."user_id" IS NOT NULL))) LIMIT 21

User.objects.exclude(Q(username='jdoe', groups__name='Awesome Group'))

SELECT "auth_user"."id",
       "auth_user"."username",
       "auth_user"."first_name",
       "auth_user"."last_name",
       "auth_user"."email",
       "auth_user"."password",
       "auth_user"."is_staff",
       "auth_user"."is_active",
       "auth_user"."is_superuser",
       "auth_user"."last_login",
       "auth_user"."date_joined"
FROM "auth_user"
INNER JOIN "auth_user_groups" ON ("auth_user"."id" = "auth_user_groups"."user_id")
INNER JOIN "auth_group" ON ("auth_user_groups"."group_id" = "auth_group"."id")
WHERE NOT (("auth_user"."username" = 'jdoe'
            AND "auth_group"."name" = 'Awesome Group')) LIMIT 21

这里的区别在于 INNER JOIN 发生的位置。该Q对象在第一个示例中导致 INNER JOIN,然后由于~. 的情况下exclude,否定与 INNER JOIN 并行发生。

于 2012-08-03T19:31:30.367 回答
0

Сheck 使用 Q 和 ~Q 在数据库中查询:

>>> print models.Team.objects.filter(Q(player__name="Joe", player__touchdowns__gte=3)).query
>>> print models.Team.objects.filter(~Q(player__name="Joe", player__touchdowns__gte=3)).query

并用两个 Q 对象对其进行测试:

>>> print models.Team.objects.filter(Q(player__name="Joe") & Q(player__touchdowns__gte=3)).query
>>> print models.Team.objects.filter(~Q(player__name="Joe") & ~Q(player__touchdowns__gte=3)).query
于 2012-08-03T19:33:13.497 回答