我正在尝试使用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;
这个想法主要是在某些情况下,我宁愿在某些情况下使用date_due
列而不是date
列,但是,因为date_due
是可选的,我有时必须使用date
作为后备,并创建一个计算列chosen_date
,而不必更改其余的查询。
这是我在模仿时做的第一次尝试,我无法真正看到如何正确地使用基础api进行空测试,所以我选择了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'})
但我遇到的问题是当我运行第二个查询时,我得到chosen_date
列不存在的错误。我在以后尝试使用计算列时遇到过类似的错误(例如来自annotate()
调用内),但在文档中没有找到有关计算列与“基数”列有何不同的内容。有没有人对此有任何见解?
(编译的python代码因为以前的版本有明显的逻辑缺陷(忘记了else分支)。仍然无效)
答案 0 :(得分: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'})
filter_qs
会尝试生成如下查询:
SELECT (title) AS "alias_col", "myapp_mymodel"."title"
FROM "myapp_mymodel"
WHERE alias_col = "Camembert";
extra_qs
尝试类似:
SELECT (title) AS "alias_col", (alias_col) AS "another_alias",
"myapp_mymodel"."title"
FROM "myapp_mymodel";
这些都不是有效的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)
产生类似的东西:
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;
使用“HAVING MAX alias_col> 0”将无效。
我希望这有帮助。如果有什么我已经解释得很糟糕让我知道,我会看看我是否可以改进它。
答案 1 :(得分:3)
这里有一些解决方法
1。在您的特定情况下,您可以使用一个额外的内容:
if use_date_due:
sum_qs = sum_qs.extra(select={
'year': 'EXTRACT(year FROM coalesce(date_due, date))',
'month': 'EXTRACT(month FROM coalesce(date_due, date))',
'is_paid':'date_paid IS NOT NULL'
})
2. 也可以使用普通的python来获取所需的数据:
for x in sum_qs:
chosen_date = x.date_due if use_date_due and x.date_due else x.date
print chosen_date.year, chosen_date.month
或
[(y.year, y.month) for y in (x.date_due if use_date_due and x.date_due else x.date for x in sum_qs)]
3. 在SQL世界中,这种计算新字段的方式通常是通过子查询或common table expression来完成的。我更喜欢cte,因为它的可读性。它可能像:
with cte1 as (
select
*, coalesce(date_due, date) as chosen_date
from polls_invoice
)
select
*,
extract(year from chosen_date) as year,
extract(month from chosen_date) as month,
case when date_paid is not null then 1 else 0 end as is_paid
from cte1
您也可以根据需要链接尽可能多的cte:
with cte1 as (
select
*, coalesce(date_due, date) as chosen_date
from polls_invoice
), cte2 as (
select
extract(year from chosen_date) as year,
extract(month from chosen_date) as month,
case when date_paid is not null then 1 else 0 end as is_paid
from cte2
)
select
year, month, sum(is_paid) as paid_count
from cte2
group by year, month
所以在django中你可以使用raw query之类的:
Invoice.objects.raw('
with cte1 as (
select
*, coalesce(date_due, date) as chosen_date
from polls_invoice
)
select
*,
extract(year from chosen_date) as year,
extract(month from chosen_date) as month,
case when date_paid is not null then 1 else 0 end as is_paid
from cte1')
并且您将拥有具有一些其他属性的Invoice对象。
4. 或者您可以使用普通的python
替换查询中的字段if use_date_due:
chosen_date = 'coalesce(date_due, date)'
else:
chosen_date = 'date'
year = 'extract(year from {})'.format(chosen_date)
month = 'extract(month from {})'.format(chosen_date)
fields = {'year': year, 'month': month, 'is_paid':'date_paid is not null'}, 'chosen_date':chosen_date)
sum_qs = sum_qs.extra(select = fields)
答案 2 :(得分:1)
这会有用吗?:
from django.db import connection, transaction
cursor = connection.cursor()
sql = """
SELECT
%s AS year,
%s 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;
""" % (connection.ops.date_extract_sql('year', 'chosen_date'),
connection.ops.date_extract_sql('month', 'chosen_date'))
# Data retrieval operation - no commit required
cursor.execute(sql)
rows = cursor.fetchall()
我认为它很漂亮CASE WHEN和IS NOT NULL都是db不可知的,至少我认为它们是,因为它们用于原始格式的django测试..
答案 3 :(得分:1)
您可以在模型定义中添加属性,然后执行以下操作:
@property
def chosen_date(self):
return self.due_date if self.due_date else self.date
这假设您始终可以回退到日期。如果您愿意,可以在due_date上捕获DoesNotExist异常,然后检查第二个异常。
您可以像访问其他任何内容一样访问该媒体资源。
至于其他查询,我不会使用SQL从日期中提取y / m / d,只需使用
model_instance.chosen_date.year
chosen_date应该是一个python日期对象(如果你在ORM中使用DateField并且该字段在模型中)
答案 4 :(得分:1)
只需使用原始sql。 raw()管理器方法可用于执行返回模型实例的原始SQL查询。
https://docs.djangoproject.com/en/1.5/topics/db/sql/#performing-raw-sql-queries