2013-08-16 28 views
9

Tôi đang cố gắng để sử dụng API queryset django để bắt chước các truy vấn sau đây:Làm thế nào tôi có thể nhận được các yếu tố tính toán của một bảng trong một queryset 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; 

Ý tưởng là chủ yếu mà trong những tình huống nhất định, tôi muốn sử dụng các cột date_due chứ không phải là date cột trong một số tình huống, nhưng điều đó, vì date_due là tùy chọn, đôi khi tôi phải sử dụng date làm dự phòng và tạo cột được tính chosen_date để không phải thay đổi phần còn lại của truy vấn.

Dưới đây là một đâm đầu tiên tôi đã làm tại bắt chước này, tôi đã không thể thực sự thấy làm thế nào để đúng cách do các kiểm tra null với các api cơ sở vì vậy tôi đã đi với 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'}) 

Nhưng vấn đề tôi m có khi tôi chạy truy vấn thứ hai, tôi gặp lỗi về cách cột chosen_date không tồn tại. Tôi đã có lỗi tương tự sau này khi cố gắng sử dụng cột được tính toán (như từ trong số annotate() cuộc gọi), nhưng không tìm thấy bất kỳ điều gì trong tài liệu về cách cột được tính khác với cột "cơ sở". Có ai có bất cứ cái nhìn sâu sắc về điều này?

(mã python thay đổi nội dung bởi vì phiên bản trước đã có một lỗ hổng luận rõ ràng (quên chi nhánh khác). Vẫn không hoạt động)

+0

Tôi sẽ đơn giản và sử dụng truy vấn thô. Đó là những gì họ đang có cho. –

Trả lời

6

Câu trả lời ngắn: Nếu bạn tạo một cột aliased (hoặc tính) sử dụng extra(select=...) thì bạn không thể sử dụng cột aliased trong một cuộc gọi tiếp theo để filter(). Ngoài ra, như bạn đã khám phá, bạn không thể sử dụng cột bí danh trong các cuộc gọi sau này đến extra(select=...) hoặc extra(where=...).

Một cố gắng để giải thích tại sao:

Ví dụ:

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 sẽ cố gắng để tạo ra một truy vấn như:

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

extra_qs cố gắng một cái gì đó như:

SELECT (title) AS "alias_col", (alias_col) AS "another_alias", 
     "myapp_mymodel"."title" 
FROM "myapp_mymodel"; 

Không ai trong số đó là SQL hợp lệ. Nói chung, nếu bạn muốn sử dụng bí danh của cột được tính toán nhiều lần trong mệnh đề SELECT hoặc WHERE của truy vấn bạn thực sự cần phải tính toán nó mỗi lần. Đây là lý do tại sao câu trả lời của Roman Pekar giải quyết vấn đề cụ thể của bạn - thay vì cố gắng tính toán chosen_date một lần và sau đó sử dụng lại sau này, ông tính toán nó mỗi khi cần.


Bạn đề cập đến chú thích/tập hợp trong câu hỏi của bạn. Bạn có thể sử dụng filter() trên các bí danh được tạo bởi annotate() (vì vậy, tôi muốn thấy các lỗi tương tự mà bạn đang nói đến, nó khá mạnh mẽ trong kinh nghiệm của tôi). Đó là bởi vì khi bạn cố gắng lọc một bí danh được tạo bởi chú thích, ORM nhận ra những gì bạn đang làm và thay thế bí danh bằng tính toán đã tạo ra nó.

Vì vậy, như một ví dụ:

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

Tạo một cái gì đó như:

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; 

Sử dụng "CÓ MAX alias_col> 0" sẽ không làm việc.


Tôi hy vọng điều đó hữu ích. Nếu có bất cứ điều gì tôi đã giải thích xấu cho tôi biết và tôi sẽ xem nếu tôi có thể cải thiện nó.

3

Vâng here're một số cách giải quyết

1. Đặc biệt của bạn trường hợp bạn có thể làm điều đó với một phụ:

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. cũng có thể sử dụng python đơn giản để có được dữ liệu bạn cần:

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 

hoặc

[(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. Trong thế giới SQL kiểu này tính toán các lĩnh vực mới thường được thực hiện bởi uing subquery hoặc common table expression. Tôi thích cte hơn vì nó dễ đọc. Nó có thể là như sau:

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 

bạn cũng có thể chuỗi như nhiều CTE như bạn muốn:

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 

như vậy trong django bạn có thể sử dụng raw query như:

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

và bạn sẽ có Các đối tượng hóa đơn với một số thuộc tính bổ sung.

4. Hoặc bạn có thể chỉ đơn giản là lĩnh vực thay thế trong truy vấn của bạn với python đồng bằng

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

Sẽ làm việc này?:

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

Tôi nghĩ rằng nó khá tiết kiệm cả CASE KHI và IS NOT NULL là khá db thuyết bất khả tri, ít nhất tôi cho rằng họ là, kể từ khi chúng được sử dụng trong thử nghiệm django trong định dạng thô ..

1

Bạn có thể thêm một tài sản để xác định mô hình của bạn và sau đó thực hiện:

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

này giả định bạn luôn có thể dự phòng để date.If bạn thích bạn có thể bắt một ngoại lệ DoesNotExist trên DUE_DATE và sau đó kiểm tra cho một giây.

Bạn truy cập thuộc tính như bất kỳ điều gì khác.

Đối với các truy vấn khác, tôi sẽ không sử dụng SQL để trích xuất các y/m/d từ ngày, chỉ cần sử dụng

model_instance.chosen_date.year 

chosen_date phải là một đối tượng ngày trăn (nếu bạn đang sử dụng DateField trong ORM và trường này nằm trong một mô hình)

+0

thực sự là lý do tôi làm điều đó là vì sau này tôi thực hiện tổng hợp tổng hợp dựa trên năm và tháng. Để làm điều đó, tôi cần phải gọi 'giá trị' với các giá trị tôi muốn nhóm theo ... và tôi không thể sử dụng tra cứu trường như' __year' trong 'giá trị'. – rtpg

+0

Một điều nữa là những gì tôi sử dụng cho 'selected_date' phụ thuộc vào ngữ cảnh: đôi khi tôi sử dụng' due_date' với 'ngày' dự phòng, đôi khi chỉ' ngày'. – rtpg

Các vấn đề liên quan