连接功能不正确的 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()) 预计会返回 filter(~Q()) 和 exclude(Q( ))。在code.djangoproject.com/ticket/16893code.djangoproject.com/ticket/19672code.djangoproject.com/ticket/24705 报告了几个相关的错误,但它们都与崩溃相关,而不是像您的情况那样“错误的过滤器”。 FWIW,我在 Django 1.8.7 上遇到了类似的问题 【参考方案1】:

找出这些差异发生原因的最佳方法是调查生成的查询: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。漏洞?的主要内容,如果未能解决你的问题,请参考以下文章

使用 AND 和 OR 动态连接 Django Q 对象

Django GIS SQL注入漏洞

如何在django中上传csv文件的函数中使用外键连接模型?

如何使用自定义连接器正确终止 Django 中的 MySQL/MariaDB 连接

Django orm字符串与数字比较导致数据不一致

Django-MySQL数据库使用01