python - Django 中 `annotate` + `values` + `union` 的错误结果

标签 python django django-models django-orm

跳转到编辑查看更多真实代码示例,更改查询顺序后不起作用

这是我的模型:

class ModelA(models.Model):
    field_1a = models.CharField(max_length=32)
    field_2a = models.CharField(max_length=32)


class ModelB(models.Model):
    field_1b = models.CharField(max_length=32)
    field_2b = models.CharField(max_length=32)

现在,每个创建 2 个实例:

ModelA.objects.create(field_1a="1a1", field_2a="1a2")
ModelA.objects.create(field_1a="2a1", field_2a="2a2")
ModelB.objects.create(field_1b="1b1", field_2b="1b2")
ModelB.objects.create(field_1b="2b1", field_2b="2b2")

如果我只查询一个带注释的模型,我会得到类似的信息:

>>> ModelA.objects.all().annotate(field1=F("field_1a"), field2=F("field_2a")).values("field1", "field2")
[{"field1": "1a1", "field2": "1a2"}, {"field1": "2a1", "field2": "2a2"}]

这是正确的行为。问题开始了,当我想合并这两个模型时:

# model A first, with annotate
query = ModelA.objects.all().annotate(field1=F("field_1a"), field2=F("field_2a"))
# now union with model B, also annotated
query = query.union(ModelB.objects.all().annotate(field1=F("field_1b"), field2=F("field_2b")))
# get only field1 and field2
query = query.values("field1", "field2")

# the results are skewed:
assert list(query) == [
    {"field1": 1, "field2": "1a1"},
    {"field1": 1, "field2": "1b1"},
    {"field1": 2, "field2": "2a1"},
    {"field1": 2, "field2": "2b1"},
]

assert 正确通过,这意味着结果是错误的。好像是values()与变量名不匹配,它只是像元组一样遍历对象。 field1的值实际上是对象的ID,而field2field1 .

这在如此简单的模型中很容易解决,但我的真实模型非常复杂,而且它们有不同数量的字段。我如何正确地联合它们?

编辑

您可以在下面找到一个扩展示例,无论 union() 的顺序如何,它都失败了。和 values() - 现在模型稍微大了一些,似乎不同的字段计数以某种方式混淆了 Django:

# models

class ModelA(models.Model):
    field_1a = models.CharField(max_length=32)
    field_1aa = models.CharField(max_length=32, null=True)
    field_1aaa = models.CharField(max_length=32, null=True)
    field_2a = models.CharField(max_length=32)
    extra_a = models.CharField(max_length=32)


class ModelB(models.Model):
    extra = models.CharField(max_length=32)
    field_1b = models.CharField(max_length=32)
    field_2b = models.CharField(max_length=32)

# test

ModelA.objects.create(field_1a="1a1", field_2a="1a2", extra_a="1extra")
    ModelA.objects.create(field_1a="2a1", field_2a="2a2", extra_a="2extra")
    ModelB.objects.create(field_1b="1b1", field_2b="1b2", extra="3extra")
    ModelB.objects.create(field_1b="2b1", field_2b="2b2", extra="4extra")

    values = ("field1", "field2", "extra")

    query = (
        ModelA.objects.all()
        .annotate(
            field1=F("field_1a"), field2=F("field_2a"), extra=F("extra_a")
        )
        .values(*values)
    )
    query = query.union(
        ModelB.objects.all()
        .annotate(field1=F("field_1b"), field2=F("field_2b"))
        .values(*values)
    )

# outcome

assert list(query) == [
        {"field1": "1a1", "field2": "1a2", "extra": "1extra"},
        {"field1": "2a1", "field2": "2a2", "extra": "2extra"},
        {"field1": "3extra", "field2": "1b1", "extra": "1b2"},
        {"field1": "4extra", "field2": "2b1", "extra": "2b2"},
    ]

最佳答案

经过一些调试和查看源代码后,我知道为什么会发生这种情况。我要做的是尝试解释为什么要这样做annotate + values结果显示 id上面两种情况有什么区别。

为了简单起见,我还将为每个语句编写可能的结果 sql 查询。

1. annotate首先但得到values关于联合查询

qs1 = ModelA.objects.all().annotate(field1=F("field_1a"), field2=F("field_2a"))

在写这样的东西时,django 会得到所有的字段 + 带注释的字段,所以生成的 sql 查询看起来像:
select id, field_1a, field_2a, field_1a as field1, field_2a as field2 from ModelA

所以,如果我们有一个 query这是以下结果:
qs = qs1.union(qs2)

django 生成的 sql 如下所示:
(select id, field_1a, field_2a, field_1a as field1, field_2a as field2 from ModelA)
UNION
(select id, field_1b, field_2b, field_1b as field1, field_2b as field2 from ModelB)

下面我们深入了解一下这个sql是如何生成的。当我们做 union , combinatorcombined_queries设置在 qs.query生成的sql由combining the sql生成个人查询。所以,总结一下:
qs.sql == qs1.sql UNION qs2.sql # in abstract sense

什么时候,我们做qs.values('field1', 'field2') , col_count in 编译器设置为 2,这是字段数。如您所见,上面的联合查询返回 5 列,但在编译器的最终返回结果中,每行是 sliced using col_count .现在,这个 results只有 2 列被传回 ValuesIterable它在哪里maps所选字段中的每个名称以及结果列。这就是它如何导致不正确的结果。

2. annotate + values单个查询,然后执行 union
现在,让我们看看 annotate 会发生什么与 values 一起使用直接地
qs1 = ModelA.objects.all().annotate(field1=F("field_1a"), field2=F("field_2a")).values('field1', 'field2')

生成的sql是:
select field_1a as field1, field_2a as field2 from ModelA

现在,当我们做联合时:
qs = qs1.union(qs2)

sql是:
(select field_1a as field1, field_2a as field2 from ModelA)
UNION
(select field_1b as field1, field_2b as field2 from ModelB)

现在,当 qs.values('field1', 'field2')执行时,联合查询返回的列数有 2 列,与 col_count 相同这是 2 并且每个字段都与产生预期结果的各个列匹配。

3.不同的字段注释计数和字段排序

在OP中,甚至使用.values时也有一个场景。之前 union不会产生正确的结果。原因是在ModelB , extra 没有注释 field 。

因此,让我们看看为每个模型生成的查询:
ModelA.objects.all()
        .annotate(
            field1=F("field_1a"), field2=F("field_2a"), extra=F("extra_a")
        )
        .values(*values)

SQL 变为:
select field_1a as field1, field_2a as field2, extra_a as extra from ModelA

对于 B 型:
ModelB.objects.all()
        .annotate(field1=F("field_1b"), field2=F("field_2b"))
        .values(*values)

查询语句:
select extra, field_1b as field1, field_2b as field2 from ModelB

工会是:
(select field_1a as field1, field_2a as field2, extra_a as extra from ModelA)
UNION
(select extra, field_1b as field1, field_2b as field2 from ModelB)

因为带注释的字段列在真​​实的 db 字段之后,extraModelBfield1 混合的 ModelB .为了确保您得到正确的结果,请确保生成的 SQL 中的字段顺序始终正确 - 带或不带注释。在这种情况下,我会建议注释 extraModelB以及。

关于python - Django 中 `annotate` + `values` + `union` 的错误结果,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60562759/

相关文章:

python - 如何将文本/x-python 转换为纯文本格式?

python 正则表达式,字典未插入mysql

javascript - AngularJS 注销和重新登录

python - Django 迁移损坏,不会忘记已删除的列

python - 属性错误: 'ModelFormOptions' object has no attribute 'private_fields' error message

python - tf.data 内存泄漏

python - CNTK Python - one_hot 编码无原型(prototype)

django - 'many = True' 在 Django Rest FrameWork 中做什么?

Django:支持带引号 (>1.5) 或不带引号的 URL 标记(较旧的 Django 版本)

python - 如何使用 Django 模型字段定义保持 DRY