查询更改 Q 对象顺序的不同结果

Posted

技术标签:

【中文标题】查询更改 Q 对象顺序的不同结果【英文标题】:Different results on query changing Q object order 【发布时间】:2013-11-19 08:54:29 【问题描述】:

使用Q 对象创建查询集时遇到问题。我得到不同的结果取决于我如何订购一些Q 条件。我将稍微简化我的模型,以便以简洁的方式描述我的问题。

class D(models.Model):
    one_attr = models.BooleanField()
    s_attr = models.ManyToManyField(S, through='DRelatedToS')
    d_to_p = models.ForeignKey(P)


class S(models.Model):
    other_attr = models.BooleanField()
    s_to_p = models.ForeignKey(P)


class DRelatedToS(models.Model):
    to_s = models.ForeignKey(S)
    to_d = models.ForeignKey(D)
    date = models.DateField()


class P(models.Model):
    the_last_attr = models.PositiveIntegerField()

关系总结:

D <-- DRelatedToS --> S --> P
|                           ^
|                           |
-------->------->------>----^

使用这些模型和关系,我得到两个不同的结果,具体取决于我如何安排 Q 条件: 第一个查询,给出一个结果

D.objects.filter(
    Q(one_attr=True, s_attr__s_to_p__the_last_attr=5)
    |
    Q(one_attr=False, d_to_p__the_last_attr=10)
)

第二次查询,给出另一个结果,不同于第一次查询

D.objects.filter(
    Q(one_attr=False, d_to_p__the_last_attr=10)
    |
    Q(one_attr=True, s_attr__s_to_p__the_last_attr=5)
)

我的问题是:为什么会这样?我的查询方式有什么问题吗?

当我查看从这些查询派生的 SQL 语句时,我得到两个不同的语句:一个生成 LEFT OUTER JOIN 和很多 INNER JOINs,第二个生成所有 INNER JOINs。真正返回我想要的东西是制作LEFT OUTER JOIN 的那个。这让我觉得我的所有查询都可能返回糟糕的结果,具体取决于我如何安排它的条件。这是一个错误还是我做错了什么(或一切)?

【问题讨论】:

简化模型是Good Thing™ 【参考方案1】:

在您的示例中,不同的顺序应该返回相同的结果。

尽管如此,我测试了您的代码(使用我在问题代码中所做的更正),但无法生成您描述的错误。也许您在简化代码时引入了其他错误并错过了,您可以发布您使用的示例数据吗? (见下面我的数据)。

首先,您的示例代码有问题,我已编辑您的问题,建议进行以下更正以解决问题、简化和改进测试,但我没有看到更改已更新,因此我在此重复:

更正 1: diff 格式的模型更改:

3,4c6,10
<     s_attr = models.ManyToMany(S, through='DRelatedToS')
<     d_to_p = models.ForeignKey(P)
---
>     s_attr = models.ManyToManyField('S', through='DRelatedToS')
>     d_to_p = models.ForeignKey('P')
> 
>     def __unicode__(self):
>         return 'D:(%s,%s,%s)' % (self.id, self.one_attr, self.d_to_p.the_last_attr)
8,9c14
<     other_attr = models.BooleanField()
<     s_to_p = models.ForeignKey(P)
---
>     s_to_p = models.ForeignKey('P')
13d17
<     to_p = models.ForeignKey(P)
15c19
<     date = models.DateField()
---
>     to_s = models.ForeignKey(S)
19a24
> 

所以在应用校正后模型如下所示:

class D(models.Model):
    one_attr = models.BooleanField()
    s_attr = models.ManyToManyField('S', through='DRelatedToS')
    d_to_p = models.ForeignKey('P')

    def __unicode__(self):
        return 'D:(%s,%s,%s)' % (self.id, self.one_attr, self.d_to_p.the_last_attr)


class S(models.Model):
    s_to_p = models.ForeignKey('P')


class DRelatedToS(models.Model):
    to_d = models.ForeignKey(D)
    to_s = models.ForeignKey(S)


class P(models.Model):
    the_last_attr = models.PositiveIntegerField()

更正 2:您在查询中的查找字段错误(已修复)。

以下是我所做的:

    创建名为testso的项目和应用程序:

    django-admin.py startproject marianobianchi
    cd marianobianchi
    python manage.py startapp testso
    

    添加您的模型并调整项目设置(数据库设置,将testso 添加到INSTALLED_APPS

    添加示例数据:

    mkdir testso/fixtures
    cat > testso/fixtures/initial_data.json
    [
        "pk": 1, "model": "testso.d", "fields": "one_attr": true, "d_to_p": 3,
        "pk": 2, "model": "testso.d", "fields": "one_attr": true, "d_to_p": 4,
        "pk": 3, "model": "testso.d", "fields": "one_attr": false, "d_to_p": 5,
        "pk": 4, "model": "testso.d", "fields": "one_attr": false, "d_to_p": 5,
    
        "pk": 1, "model": "testso.s", "fields": "s_to_p": 1,
        "pk": 2, "model": "testso.s", "fields": "s_to_p": 2,
        "pk": 3, "model": "testso.s", "fields": "s_to_p": 3,
    
        "pk": 1, "model": "testso.drelatedtos", "fields": "to_d": 2, "to_s": 1,
        "pk": 2, "model": "testso.drelatedtos", "fields": "to_d": 1, "to_s": 2,
        "pk": 3, "model": "testso.drelatedtos", "fields": "to_d": 1, "to_s": 3,
    
        "pk": 1, "model": "testso.p", "fields": "the_last_attr": 5,
        "pk": 2, "model": "testso.p", "fields": "the_last_attr": 5,
        "pk": 3, "model": "testso.p", "fields": "the_last_attr": 3,
        "pk": 4, "model": "testso.p", "fields": "the_last_attr": 4,
        "pk": 5, "model": "testso.p", "fields": "the_last_attr": 10
    ]
    

    python manage.py syncdb

    python manage.py shell

    在外壳中:

    >>> from testso.models import *
    >>> from django.db.models import Q
    >>> D.objects.filter(Q(one_attr=True, s_attr__s_to_p__the_last_attr=5) | Q(one_attr=False, d_to_p__the_last_attr=10))
    [<D: D:(1,True,3)>, <D: D:(2,True,4)>, <D: D:(3,False,10)>, <D: D:(4,False,10)>]
    >>> D.objects.filter(Q(one_attr=False, d_to_p__the_last_attr=10) | Q(one_attr=True, s_attr__s_to_p__the_last_attr=5))
    [<D: D:(1,True,3)>, <D: D:(2,True,4)>, <D: D:(3,False,10)>, <D: D:(4,False,10)>]
    

同样的结果! ...正如预期的那样。

【讨论】:

感谢您的回答!我稍后会尝试这个,看看它是否有效。关于您的更正,我编辑了我的问题以修复我的错误,但其中一些不是错误(例如“P”而不是 P)......如果您想编辑您的答案以使其更短。我想接受您的编辑,但我认为我没有所需的声誉...... 引用一个你之前没有定义过的模型会引发一个异常,所以这就是为什么你需要'P' insted of P ...我会再次提出更正,我认为你不需要接受此提案的声誉;) 提案更改完成,在我看到您的问题更新后,我将从我的答案中删除 好的...我明白您的意思,但您知道这不是问题...关于您的提议,我看到了有关提议的版本的通知,但是当我点击那里时,我可以看到您的更改没有任何按钮可以接受它们.... 您应该会看到批准/拒绝编辑建议的按钮,也许this 和this 可以帮助您。您应该认为它不能解决您的问题,但可以帮助人们运行您的测试示例,如果它没有运行,如何尝试解决它?,我修复了它并建议进行更改以使不仅由我更容易找到解决方案。 【参考方案2】:

我无法直接回答您的问题,但可能有另一种方法可以做您想做的事情,这可能会产生更一致的结果:

subset_a = D.objects.filter(one_attr=False, d_to_p__the_last_attr=10)
subset_b = D.objects.filter(one_attr=True, s_attr__p__the_last_attr=5)
union_set = subset_a | subset_b
union_set = union_set.distinct()

该 |两个查询集上的运算符执行联合,并且 distinct 将确保您不会得到重复行。我很想知道这里的安排是否也很重要。

【讨论】:

感谢您的回答!但我认为这行不通。虽然子集_a 和子集_b 给了我 25 和 0 个结果,但当我执行 subset_a | subset_bsubset_b | subset_a 时,我得到相同的结果:0(零......空集)。除了这些结果之外,我在 django 项目页面上没有找到关于两个查询集之间该运算符的任何官方评论......你确定它像你说的那样工作吗? 我用过 | QuerySets 上的操作员多次,它对我有用。我还没有找到关于此的官方 python 文档,但其他 *** 答案证实了我的建议:***.com/questions/4411049/…。你能看到str(subset_a.query)str(subset_b.query)str(union_set.query)的结果是什么吗? 文档 | docs.djangoproject.com/en/dev/topics/db/queries/…【参考方案3】:

Django 似乎有一个weak ORM implementation。我建议使用“过滤器”或其他方式来查询数据库,看看是否有相同的不一致。

或者也许您应该寻找替代的 ORM 实现,例如 peewee。

【讨论】:

以上是关于查询更改 Q 对象顺序的不同结果的主要内容,如果未能解决你的问题,请参考以下文章

Lucene查询中的顺序是否会影响结果?

MySQL:返回结果及其顺序

SPARQL 查询根据语句的顺序返回不同的结果

重新排序单元格后如何更改获取结果控制器数组中对象的顺序

自动化手动步骤的顺序

sql 查询以顺序更改wp用户表中的用户电子邮件,以确保所有内容都不同