连接功能不正确的 Django ~Q。漏洞?
Posted
技术标签:
【中文标题】连接功能不正确的 Django ~Q。漏洞?【英文标题】:Django ~Q with Joins Functioning Incorrectly. Bug? 【发布时间】:2012-08-01 20:02:39 【问题描述】:我们在 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 次达阵。
我想要拥有一位名叫 Joe 且至少有 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。
【问题讨论】:
我当然认为这是一个错误,因为 filter(Q(找出这些差异发生原因的最佳方法是调查生成的查询:django-debug-toolbar
带有一个debugsqlshell
命令,该命令在使用 Django 查询集 API 后打印发送到数据库的实际查询。对于这些测试,我使用了 User
模型和 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,然后由于 ~
,选择 with INNER JOIN 被否定。 exclude
的情况,否定与 INNER JOIN 并行发生。
【讨论】:
在检查查询时,这是一个真正的问题:当使用 .filter(~Q(child__stuff, child__otherstuff)) 时,我们会按照“初步要求 AND NOT (team.id IN (SELECT U1.player_id FROM data_player U1 WHERE (U1.touchdowns>=3 )) AND team.id IN (SELECT U1.player_id FROM data_player U1 WHERE (U1.name='JOE' ))" 如您所见,问题在于事实上它执行两个内部查询,每个需求一个。但是,如果我手动将 JOE 部分移动到 >=3 部分旁边并在控制台中运行,它就可以工作。长话短说,我该如何制作〜Q()或还有其他类似的行为吗? 重写Q
,向 Django 提交拉取请求,并希望它被合并,基本上。这就是Q
的作用方式,所以没有办法回避它。您可以尝试调整您的逻辑,使其产生您认为应该的结果,但Q
生成的查询是Q
生成的查询。
好吧,现在以你的诚实观点来看,Q 目前的运作方式是“正确的方式”,还是我认为的方式是“正确”的方式来构建 SQL?
就是这样。我认为不一定有“正确”和“不正确”的方式。 Q
以特定方式构建查询,原因我无法完全证明。它在 99.99% 的情况下都能正常工作。在这种情况下,它似乎失败了。然而,这并不意味着它是错误的,它只是意味着它没有涵盖所有可能的使用场景,如果我们老实说,无论你怎么削减它,这都是一项非常不可能完成的任务。
没错,我可以同意。我们找到了一种解决方法,但我仍然想看看个人修改 Django。如果您想写一个解释这是 Q 函数的方式(每个选项单独的内部查询)的答案,我会接受它作为答案。感谢您的意见!【参考方案2】:
С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
【讨论】:
我们实际上是在构建一个动态查询构建器,所以在我的应用程序中,我必须将每个 Q(inputtedquery) 和 &= 它们放在一起以允许动态查询。我们尝试了多种组合定位 ~ 并编写手动查询以进行测试,包括上述内容均无济于事。只有成功的尝试是从 .query 手动移动 SQL。请参阅我对 Chris 帖子的回复。以上是关于连接功能不正确的 Django ~Q。漏洞?的主要内容,如果未能解决你的问题,请参考以下文章
如何在django中上传csv文件的函数中使用外键连接模型?