如何在django查询集中获取表的计算元素?

rtp*_*tpg 9 django postgresql django-queryset

我正在尝试使用django的queryset API来模拟以下查询:

SELECT EXTRACT(year FROM chosen_date) AS year, 
EXTRACT(month FROM chosen_date) AS month,
 date_paid IS NOT NULL as is_paid FROM 
    (SELECT (CASE WHEN date_due IS NULL THEN date_due ELSE date END) AS chosen_date,* FROM invoice_invoice) as t1;
Run Code Online (Sandbox Code Playgroud)

这个想法主要是在某些情况下,我宁愿在某些情况下使用date_due列而不是date列,但是,因为date_due是可选的,我有时必须使用date作为后备,并创建一个计算列chosen_date,而不必更改其余查询.

这是我在模仿这个时做的第一次尝试,我无法真正看到如何正确地使用基础api进行null测试,所以我选择了extra:

if(use_date_due):
    sum_qs = sum_qs.extra(select={'chosen_date': 'CASE WHEN date_due IS NULL THEN date ELSE date_due END'})
else: 
    sum_qs = sum_qs.extra(select={'chosen_date':'date'})
sum_qs = sum_qs.extra(select={'year': 'EXTRACT(year FROM chosen_date)',
                              'month': 'EXTRACT(month FROM chosen_date)',
                              'is_paid':'date_paid IS NOT NULL'})
Run Code Online (Sandbox Code Playgroud)

但我遇到的问题是当我运行第二个查询时,我得到一个关于chosen_date列不存在的错误.我在以后尝试使用计算列时遇到过类似的错误(比如在annotate()调用中),但是在文档中没有找到有关计算列与"基数"列有何不同的内容.有没有人对此有任何见解?

(编辑python代码,因为以前的版本有一个明显的逻辑缺陷(忘了else分支).仍然无法工作)

Kev*_*vin 7

简短回答: 如果使用extra(select=...) 则创建别名(或计算)列,则无法在后续调用中使用别名列filter().此外,正如您所发现的,您不能在以后调用extra(select=...)或使用别名列 extra(where=...).

试图解释原因:

例如:

qs = MyModel.objects.extra(select={'alias_col': 'title'})

#FieldError: Cannot resolve keyword 'alias_col' into field...
filter_qs = qs.filter(alias_col='Camembert')

#DatabaseError: column "alias_col" does not exist
extra_qs = qs.extra(select={'another_alias': 'alias_col'})
Run Code Online (Sandbox Code Playgroud)

filter_qs 将尝试生成如下查询:

SELECT (title) AS "alias_col", "myapp_mymodel"."title"
FROM "myapp_mymodel"
WHERE alias_col = "Camembert";
Run Code Online (Sandbox Code Playgroud)

extra_qs尝试是这样的:

SELECT (title) AS "alias_col", (alias_col) AS "another_alias",
        "myapp_mymodel"."title"
FROM "myapp_mymodel";
Run Code Online (Sandbox Code Playgroud)

这些都不是有效的SQL.通常,如果要在查询的SELECT或WHERE子句中多次使用计算列的别名,则实际上每次都需要计算它.这就是为什么Roman Pekar的答案可以解决您的具体问题 - 而不是尝试计算chosen_date一次然后再次使用它,他每次需要时都会计算它.


您在提问中提到了注释/聚合.你可以使用filter()创建的别名annotate()(所以我有兴趣看到你正在谈论的类似错误,在我的经验中它是相当强大的).这是因为当您尝试过滤注释创建的别名时,ORM会识别您正在执行的操作,并使用创建它的计算替换别名.

举个例子:

qs = MyModel.objects.annotate(alias_col=Max('id'))
qs = qs.filter(alias_col__gt=0)
Run Code Online (Sandbox Code Playgroud)

产生类似的东西:

SELECT "myapp_mymodel"."id", "myapp_mymodel"."title",
        MAX("myapp_mymodel"."id") AS "alias_col"
FROM "myapp_mymodel"
GROUP BY "myapp_mymodel"."id", "myapp_mymodel"."title"
HAVING MAX("myapp_mymodel"."id") > 0;
Run Code Online (Sandbox Code Playgroud)

使用"HAVING MAX alias_col> 0"将不起作用.


我希望这很有帮助.如果有什么我已经解释得很糟糕让我知道,我会看看我是否可以改进它.