2013-08-16 10 views
9

私は次のクエリをエミュレートするために、DjangoのクエリセットのAPIを使用しようとしている:テーブルの計算要素をDjangoクエリーセットで取得するにはどうすればよいですか?

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を作成して残りのクエリを変更する必要がない場合があります。

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'}) 

しかし、私は「問題:ここで

は、私は本当に、私はextraと一緒に行った方法を適切にベースAPIを使用してヌルテストにより参照することができませんでした、私はこれをエミュレートで行った最初の刺しました2番目のクエリを実行すると、chosen_date列が存在しないというエラーが表示されます。私は後で計算された列(annotate()呼び出しのようなもの)を使用しようとしたときに同様のエラーが発生しましたが、計算された列が "ベース"の列とどのように異なるかについてドキュメントには何も見つかりませんでした。誰にもこれについての洞察はありますか?

(以前のバージョンは明らかに、論理上の欠陥があったので(他に支店を忘れて編集したPythonコード)。まだ動作しません)あなたの特定にはいくつかの回避策here'reまあ

+0

私は単純なままにして、生のクエリを使用します。彼らがそこにいるのはそれです。 –

答えて

6

短い答え: あなたがextra(select=...) を使用してエイリアス(または計算)のカラムを作成した場合、あなたはfilter()に、後続の呼び出しでエイリアスの列を使用することはできません。 また、発見したように、後で extra(select=...)またはextra(where=...)の呼び出しでエイリアス化された列を使用することはできません。

理由を説明しようとする試み:

:ような何かをしようと

SELECT (title) AS "alias_col", "myapp_mymodel"."title" 
FROM "myapp_mymodel" 
WHERE alias_col = "Camembert"; 

そしてextra_qs

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", (alias_col) AS "another_alias", 
     "myapp_mymodel"."title" 
FROM "myapp_mymodel"; 

いずれも有効なSQLではありません。一般に、クエリのSELECT句またはWHERE句で計算カラムの別名を複数回使用する場合は、毎回計算カラムの別名を計算する必要があります。このため、Roman Pekarの答えは、chosen_dateを一度計算してから、後で使用して、必要になるたびに計算します。


あなたの質問には注釈/集計があります。 annotate()で作成されたエイリアスにはfilter()を使用することができます(これは私が経験している同様のエラーを見るのに興味があります)。これは、注釈によって作成されたエイリアスをフィルタリングしようとすると、ORMはあなたが何をしているのかを認識し、そのエイリアスを作成した計算で置き換えます。だから、例として

:動作しないでしょう "HAVING MAXのalias_col> 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; 

qs = MyModel.objects.annotate(alias_col=Max('id')) 
qs = qs.filter(alias_col__gt=0) 

のようなものを作成します。


私は役に立つと思います。私がひどく説明したことがあれば私に知らせてください。私がそれを改善できるかどうかがわかります。

3

1.をあなたは余分な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を使用することも可能です。

012 SQLの世界で
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.新しいフィールドを計算のこのタイプは通常、サブクエリやcommon table expressionをuingによって行われます。私はそれが可読性のためにもっと気に入っています。

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 

はそうジャンゴにあなたのような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') 

、あなたが持っているだろう、それは次のように可能性があり追加のプロパティを持つ請求書オブジェクト。

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) 
1

うこの作品?少なくとも私は、彼らは生の形式でDjangoのテストで使用されているので、彼らは、ある仮定

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() 

私はそれはかなりWHEN両方CASEを保存し、NULLではないと思うが、不可知論かなりデシベル..です

1

あなたは可能性がありお使いのモデル定義にプロパティを追加してから実行します。

@property 
def chosen_date(self): 
    return self.due_date if self.due_date else self.date 

これは、あなたが常にあなたがDUE_DATE上のDoesNotExist例外をキャッチした後、第2 1を確認することができます好むdate.Ifにフォールバックすることができますを前提としています。

あなたは他のものと同じようにプロパティにアクセスします。他のクエリについては

、あなたが使用している場合、私は(ちょうどPythonの日付オブジェクトでなければなりません

model_instance.chosen_date.year 

chosen_dateを使用し、日付からY/M/Dを抽出するためにSQLを使用していないだろうORMのDateFieldとこのフィールドはモデル内にあります)

+0

実際に私がしている理由は、後で私は年と月に基づいて合計集計を行うためです。これを行うには、私がグループ化したい値で 'values'を呼び出す必要があります。そして、' values'の '__year'のようなフィールドルックアップを使うことはできません。 – rtpg

+0

もう一つは、 'selected_date'に使うのは文脈によって決まります。時には' due_date'と 'date'フォールバック、時には' date'だけを使うことがあります。 – rtpg

関連する問題