1. 程式人生 > >Django學習筆記(三十一):django orm extra

Django學習筆記(三十一):django orm extra

extra 在django orm中使用複雜的sql語句

extra(select=None, where=None, params=None, tables=None, order_by=None, select_params=None)

有些情況下,Django 的查詢語法難以簡練地表達複雜的 WHERE 子句。對於這種情況,Django 提供了 extra() QuerySet 修改機制,它能在QuerySet 生成的 SQL 從句中注入新子句。

由於產品差異的原因,這些自定義的查詢難以保障在不同的資料庫之間相容(因為你手寫 SQL 程式碼的原因),而且違背了 DRY 原則,所以如非必要,還是儘量避免寫 extra。

在 extra 可以指定一個或多個 params 引數,如 select,where 或 tables。所有引數都是可選的,但你至少要使用一個。

   select
select 引數可以讓你在 SELECT 從句中新增其他欄位資訊。它應該是一個字典,存放著屬性名到 SQL 從句的對映。

例如:

1

Entry.objects.extra(select={'is_recent'"pub_date > '2006-01-01'"})

結果中每個 Entry 物件都有一個額外的 is_recent 屬性,它是一個布林值,表示 pub_date 是否晚於2006年1月1號。

Django 會直接在 SELECT 中加入對應的 SQL 片斷,所以轉換後的 SQL 如下:

1

2

SELECT blog_entry.*, (pub_date > '2006-01-01')

FROM blog_entry;

下面這個例子更復雜一些;它會在每個 Blog 物件中新增一個 entry_count 屬性,它會執行一個子查詢,得到相關聯的 Entry 物件的數量:

1

2

3

4

5

Blog.objects.extra(

select={

'entry_count'

'SELECT COUNT(*) FROM blog_entry WHERE blog_entry.blog_id = blog_blog.id'

},

)

(在上面這個特例中,我們要了解這個事實,就是 blog_blog 表已經存在於 FROM 從句中。)

翻譯成 SQL 如下:

1

2

SELECT blog_blog.*, (SELECT COUNT(*) FROM blog_entry WHERE blog_entry.blog_id = blog_blog.id) AS entry_count

FROM blog_blog;

要注意的是,大多數資料庫需要在子句兩端新增括號,而在 Django 的 select 從句中卻無須這樣。同樣要引起注意的是,在某些資料庫中,比如某些 MySQL 版本,是不支援子查詢的。

某些時候,你可能想給 extra(select=...) 中的 SQL 語句傳遞引數,這時就可以使用 select_params 引數。因為 select_params 是一個佇列,而 select 屬性是一個字典,所以兩者在匹配時應正確地一一對應。在這種情況下中,你應該使用 django.utils.datastructures.SortedDict 匹配 select 的值,而不是使用一般的 Python 佇列。

例如:

1

2

3

Blog.objects.extra(

select=SortedDict([('a''%s'), ('b''%s')]),

select_params=('one''two'))

在使用 extra() 時要避免在 select 字串含有 "%%s" 子串, 這是因為在 Django 中,處理 select 字串時查詢的是 %s 而並非轉義後的 % 字元。所以如果對 % 進行了轉義,反而得不到正確的結果。

   where / tables
你可以使用 where 引數顯示定義 SQL 中的 WHERE 從句,有時也可以執行非顯式地連線。你還可以使用 tables 手動地給 SQL FROM 從句新增其他表。

where 和 tables 都接受字串列表做為引數。所有的 where 引數彼此之間都是 "AND" 關係。

例如:

1

Entry.objects.extra(where=['id IN (3, 4, 5, 20)'])

大致可以翻譯為如下的 SQL:

1

SELECT * FROM blog_entry WHERE id IN (34520);

在使用 tables 時,如果你指定的表在查詢中已出現過,那麼要格外小心。當你通過 tables 引數新增其他資料表時,如果這個表已經被包含在查詢中,那麼 Django 就會認為你想再一次包含這個表。這就導致了一個問題:由於重複出現多次的表會被賦予一個別名,所以除了第一次之外,每個重複的表名都會分別由 Django 分配一個別名。所以,如果你同時使用了 where 引數,在其中用到了某個重複表,卻不知它的別名,那麼就會導致錯誤。

一般情況下,你只會新增一個未在查詢中出現的新表。但是如果上面所提到的特殊情況發生了,那麼可以採用如下措施解決。首先,判斷是否有必要要出現重複的表,能否將重複的表去掉。如果這點行不通,就試著把 extra() 呼叫放在查詢結構的起始處,因為首次出現的表名不會被重新命名,所以可能能解決問題。如果這也不行,那就檢視生成的 SQL 語句,從中找出各個資料庫的別名,然後依此重寫 where 引數,因為只要你每次都用同樣的方式呼叫查詢(queryset),表的別名都不會發生變化。所以你可以直接使用表的別名來構造 where。

   order_by
如果你已通過 extra() 添加了新欄位或是資料庫,此時若想對新欄位進行排序,就可以給 extra() 中的 order_by 引數傳遞一個排序字串序列。字串可以是 model 原生的欄位名(與使用普通的 order_by() 方法一樣),也可以是 table_name.column_name 這種形式,或者是你在 extra() 的 select 中所定義的欄位。

例如:

1

2

= Entry.objects.extra(select={'is_recent'"pub_date > '2006-01-01'"})

= q.extra(order_by = ['-is_recent'])

這段程式碼按照 is_recent 對記錄進行排序,欄位值是 True 的排在前面,False 的排在後面。(True 在降序排序時是排在 False 的前面)。

順便說一下,上面這段程式碼同時也展示出,可以依你所願的那樣多次呼叫 extra() 操作(每次新增新的語句結構即可)。