鍍金池/ 教程/ Python/ 進(jìn)行原始的sql查詢
點(diǎn)擊劫持保護(hù)
安全問(wèn)題歸檔
Model 類參考
將遺留數(shù)據(jù)庫(kù)整合到Django
關(guān)聯(lián)對(duì)象參考
內(nèi)建基于類的視圖的API
聚合
Django 中的用戶認(rèn)證
django.contrib.humanize
Django管理文檔生成器
分頁(yè)
使用Django輸出CSV
加密簽名
文件儲(chǔ)存API
安全
Django中的測(cè)試
國(guó)際化和本地化
為Django編寫首個(gè)補(bǔ)丁
條件表達(dá)式
日志
模型元選項(xiàng)
部署靜態(tài)文件
執(zhí)行查詢
使用Django認(rèn)證系統(tǒng)
基于類的視圖
中間件
編寫自定義的django-admin命令
Django 的設(shè)置
格式本地化
數(shù)據(jù)庫(kù)訪問(wèn)優(yōu)化
錯(cuò)誤報(bào)告
基于類的內(nèi)建通用視圖
編寫自定義存儲(chǔ)系統(tǒng)
編寫你的第一個(gè) Django 程序 第3部分
編寫數(shù)據(jù)庫(kù)遷移
使用表單
編寫你的第一個(gè) Django 程序 第2部分
編寫你的第一個(gè) Django 程序 第1部分
如何使用會(huì)話
系統(tǒng)檢查框架
新手入門
信號(hào)
編寫視圖
如何使用WSGI 部署
編寫你的第一個(gè)Django應(yīng)用,第6部分
常見的網(wǎng)站應(yīng)用工具
Widgets
內(nèi)建的視圖
模型實(shí)例參考
視圖層
Django中的密碼管理
高級(jí)教程:如何編寫可重用的應(yīng)用
國(guó)際化和本地化
"本地特色"附加功能
TemplateResponse 和 SimpleTemplateResponse
模式編輯器
文件上傳
快速安裝指南
部署 Django
表單 API
表單素材 ( <code>Media</code> 類)
管理文件
其它核心功能
查找 API 參考
表單
Admin
數(shù)據(jù)庫(kù)函數(shù)
自定義查找
使用基于類的視圖處理表單
管理操作
開發(fā)過(guò)程
編寫你的第一個(gè)Django應(yīng)用,第5部分
進(jìn)行原始的sql查詢
模型層
多數(shù)據(jù)庫(kù)
編寫你的第一個(gè) Django 程序 第4部分
Django安全
Django 初探
Django異常
重定向應(yīng)用
按需內(nèi)容處理
管理器
視圖裝飾器
驗(yàn)證器
使用Django輸出PDF
File對(duì)象
Django 的快捷函數(shù)
基于類的通用視圖 —— 索引
為模型提供初始數(shù)據(jù)
模板層
URL調(diào)度器
中間件
模型

進(jìn)行原始的sql查詢

模型查詢API不夠用的情況下,你可以使用原始的sql語(yǔ)句。django提供兩種方法使用原始sql進(jìn)行查詢:一種是使用Manager.raw()方法,進(jìn)行原始查詢并返回模型實(shí)例;另一種是完全避開模型層,直接執(zhí)行自定義的sql語(yǔ)句。

警告

編寫原始的sql語(yǔ)句時(shí),應(yīng)該格外小心。每次使用的時(shí)候,都要確保轉(zhuǎn)義了參數(shù)中的任何控制字符,以防受到sql注入攻擊。更多信息請(qǐng)參閱防止sql注入

進(jìn)行原始查詢

raw()方法用于原始的sql查詢,并返回模型的實(shí)例:

Manager.raw(raw_query, params=None, translations=None)

這個(gè)方法執(zhí)行原始的sql查詢之后,返回django.db.models.query.RawQuerySet的實(shí)例。RawQuerySet實(shí)例可以像一般的QuerySet那樣,通過(guò)迭代來(lái)提供對(duì)象的實(shí)例。

這里最好通過(guò)例子展示一下,假設(shè)存在以下模型:

class Person(models.Model):
    first_name = models.CharField(...)
    last_name = models.CharField(...)
    birth_date = models.DateField(...)

你可以像這樣執(zhí)行自定義的sql語(yǔ)句:

>>> for p in Person.objects.raw('SELECT * FROM myapp_person'):
...     print(p)
John Smith
Jane Jones

當(dāng)然,這個(gè)例子不是特別有趣,和直接使用Person.objects.all()的結(jié)果一模一樣。但是,raw()擁有其它更強(qiáng)大的使用方法。

模型表的名稱

在上面的例子中,Person表的名稱是從哪里得到的?

通常,Django通過(guò)將模型的名稱和模型的“應(yīng)用標(biāo)簽”(你在manage.py startapp中使用的名稱)進(jìn)行關(guān)聯(lián),用一條下劃線連接他們,來(lái)組合表的名稱。在這里我們假定Person模型存在于一個(gè)叫做myapp的應(yīng)用中,所以表就應(yīng)該叫做myapp_person。

更多細(xì)節(jié)請(qǐng)查看db_table選項(xiàng)的文檔,它也可以讓你自定義表的名稱。

警告

傳遞給raw()方法的sql語(yǔ)句并沒有任何檢查。django默認(rèn)它會(huì)返回一個(gè)數(shù)據(jù)集,但這不是強(qiáng)制性的。如果查詢的結(jié)果不是數(shù)據(jù)集,則會(huì)產(chǎn)生一個(gè)錯(cuò)誤。

警告

如果你在mysql上執(zhí)行查詢,注意在類型不一致的時(shí)候,mysql的靜默類型強(qiáng)制可能導(dǎo)致意想不到的結(jié)果發(fā)生。如果你在一個(gè)字符串類型的列上查詢一個(gè)整數(shù)類型的值,mysql會(huì)在比較前強(qiáng)制把每個(gè)值的類型轉(zhuǎn)成整數(shù)。例如,如果你的表中包含值'abc''def',你查詢'where mycolumn=0',那么兩行都會(huì)匹配。要防止這種情況,在查詢中使用值之前,要做好正確的類型轉(zhuǎn)換。

警告

雖然RawQuerySet可以像普通的QuerySet一樣迭代,RawQuerySet并沒有實(shí)現(xiàn)可以在QuerySet上使用的所有方法。例如,__bool__()__len__()RawQuerySet中沒有被定義,所以所有RawQuerySet轉(zhuǎn)化為布爾值的結(jié)果都是True。RawQuerySet中沒有實(shí)現(xiàn)他們的原因是,在沒有內(nèi)部緩存的情況下會(huì)導(dǎo)致性能下降,而且增加內(nèi)部緩存不向后兼容。

將查詢字段映射到模型字段

raw()方法自動(dòng)將查詢字段映射到模型字段。

字段的順序并不重要。換句話說(shuō),下面兩種查詢的作用相同:

>>> Person.objects.raw('SELECT id, first_name, last_name, birth_date FROM myapp_person')
...
>>> Person.objects.raw('SELECT last_name, birth_date, first_name, id FROM myapp_person')
...

Django會(huì)根據(jù)名字進(jìn)行匹配。這意味著你可以使用sql的as子句來(lái)映射二者。所以如果在其他的表中有一些Person數(shù)據(jù),你可以很容易地把它們映射成Person實(shí)例。

>>> Person.objects.raw('''SELECT first AS first_name,
...                              last AS last_name,
...                              bd AS birth_date,
...                              pk AS id,
...                       FROM some_other_table''')

只要名字能對(duì)應(yīng)上,模型的實(shí)例就會(huì)被正確創(chuàng)建。 又或者,你可以在raw()方法中使用翻譯參數(shù)。翻譯參數(shù)是一個(gè)字典,將表中的字段名稱映射為模型中的字段名稱、例如,上面的查詢可以寫成這樣:

>>> name_map = {'first': 'first_name', 'last': 'last_name', 'bd': 'birth_date', 'pk': 'id'}
>>> Person.objects.raw('SELECT * FROM some_other_table', translations=name_map)

索引訪問(wèn)

raw()方法支持索引訪問(wèn),所以如果只需要第一條記錄,可以這樣寫:

>>> first_person = Person.objects.raw('SELECT * FROM myapp_person')[0]

然而,索引和切片并不在數(shù)據(jù)庫(kù)層面上進(jìn)行操作。如果數(shù)據(jù)庫(kù)中有很多的Person對(duì)象,更加高效的方法是在sql層面限制查詢中結(jié)果的數(shù)量:

>>> first_person = Person.objects.raw('SELECT * FROM myapp_person LIMIT 1')[0]

延遲加載模型字段

字段也可以被省略:

>>> people = Person.objects.raw('SELECT id, first_name FROM myapp_person')

查詢返回的Person對(duì)象是一個(gè)延遲的模型實(shí)例(請(qǐng)見 defer())。這意味著被省略的字段,在訪問(wèn)時(shí)才被加載。例如:

>>> for p in Person.objects.raw('SELECT id, first_name FROM myapp_person'):
...     print(p.first_name, # This will be retrieved by the original query
...           p.last_name) # This will be retrieved on demand
...
John Smith
Jane Jones

從表面上來(lái)看,看起來(lái)這個(gè)查詢獲取了first_namelast_name。然而,這個(gè)例子實(shí)際上執(zhí)行了3次查詢。只有first_name字段在raw()查詢中獲取,last_name字符按在執(zhí)行打印命令時(shí)才被獲取。

只有一種字段不可以被省略,就是主鍵。Django 使用主鍵來(lái)識(shí)別模型的實(shí)例,所以它在每次原始查詢中都必須包含。如果你忘記包含主鍵的話,會(huì)拋出一個(gè)InvalidQuery異常。

增加注解

你也可以在查詢中包含模型中沒有定義的字段。例如,我們可以使用PostgreSQL的age()函數(shù)來(lái)獲得一群人的列表,帶有數(shù)據(jù)庫(kù)計(jì)算出的年齡。

>>> people = Person.objects.raw('SELECT *, age(birth_date) AS age FROM myapp_person')
>>> for p in people:
...     print("%s is %s." % (p.first_name, p.age))
John is 37.
Jane is 42.
...

raw() 方法中傳遞參數(shù)

如果你需要參數(shù)化的查詢,可以向raw() 方法傳遞params參數(shù)。

>>> lname = 'Doe'
>>> Person.objects.raw('SELECT * FROM myapp_person WHERE last_name = %s', [lname])

params是存放參數(shù)的列表或字典。你可以在查詢語(yǔ)句中使用%s占位符,或者對(duì)于字典使用%(key)占位符(key會(huì)被替換成字典中鍵為key的值),無(wú)論你的數(shù)據(jù)庫(kù)引擎是什么。這樣的占位符會(huì)被替換成參數(shù)表中正確的參數(shù)。

注意

SQLite后端不支持字典,你必須以列表的形式傳遞參數(shù)。

警告

不要在原始查詢中使用字符串格式化!

它類似于這種樣子:

>> query = 'SELECT * FROM myapp_person WHERE last_name = %s' % lname
>> Person.objects.raw(query)

使用參數(shù)化查詢可以完全防止sql注入,一種普遍的漏洞使攻擊者可以向你的數(shù)據(jù)庫(kù)中注入任何sql語(yǔ)句。如果你使用字符串格式化,早晚會(huì)受到sql輸入的攻擊。只要你記住默認(rèn)使用參數(shù)化查詢,就可以免于攻擊。

直接執(zhí)行自定義sql

有時(shí)Manager.raw()方法并不十分好用,你不需要將查詢結(jié)果映射成模型,或者你需要執(zhí)行UPDATE、INSERT以及DELETE查詢。

在這些情況下,你可以直接訪問(wèn)數(shù)據(jù)庫(kù),完全避開模型層。

django.db.connection對(duì)象提供了常規(guī)數(shù)據(jù)庫(kù)連接的方式。為了使用數(shù)據(jù)庫(kù)連接,調(diào)用connection.cursor()方法來(lái)獲取一個(gè)游標(biāo)對(duì)象之后,調(diào)用cursor.execute(sql, [params])來(lái)執(zhí)行sql語(yǔ)句,調(diào)用cursor.fetchone()或者curser.fetchall()來(lái)返回結(jié)果行。

例如:

from django.db import connection

def my_custom_sql(self):
    cursor = connection.cursor()

    cursor.execute("UPDATE bar SET foo = 1 WHERE baz = %s", [self.baz])

    cursor.execute("SELECT foo FROM bar WHERE baz = %s", [self.baz])
    row = cursor.fetchone()

    return row

注意如果你的查詢中包含百分號(hào)字符,你需要寫成兩個(gè)百分號(hào)字符,以便能正確傳遞參數(shù):

cursor.execute("SELECT foo FROM bar WHERE baz = '30%'")
cursor.execute("SELECT foo FROM bar WHERE baz = '30%%' AND id = %s", [self.id])

如果你使用了不止一個(gè)數(shù)據(jù)庫(kù),你可以使用django.db.connections來(lái)獲取針對(duì)特定數(shù)據(jù)庫(kù)的連接(以及游標(biāo))對(duì)象。django.db.connections是一個(gè)類似于字典的對(duì)象,允許你通過(guò)它的別名獲取特定的連接

from django.db import connections
cursor = connections['my_db_alias'].cursor()
# Your code here...

通常,Python DB API會(huì)返回不帶字段的結(jié)果,這意味著你需要以一個(gè)列表結(jié)束,而不是一個(gè)字典?;ㄙM(fèi)一點(diǎn)性能之后,你可以返回一個(gè)字典形式的結(jié)果,像這樣:

def dictfetchall(cursor):
    "Returns all rows from a cursor as a dict"
    desc = cursor.description
    return [
        dict(zip([col[0] for col in desc], row))
        for row in cursor.fetchall()
    ]

下面是一個(gè)體現(xiàn)二者區(qū)別的例子:

>>> cursor.execute("SELECT id, parent_id FROM test LIMIT 2");
>>> cursor.fetchall()
((54360982L, None), (54360880L, None))

>>> cursor.execute("SELECT id, parent_id FROM test LIMIT 2");
>>> dictfetchall(cursor)
[{'parent_id': None, 'id': 54360982L}, {'parent_id': None, 'id': 54360880L}]

連接和游標(biāo)

連接和游標(biāo)主要實(shí)現(xiàn)PEP 249中描述的Python DB API標(biāo)準(zhǔn),除非它涉及到事務(wù)處理。

如果你不熟悉Python DB-API,注意cursor.execute()中的sql語(yǔ)句使用占位符"%s",而不是直接在sql中添加參數(shù)。如果你使用它,下面的數(shù)據(jù)庫(kù)會(huì)在必要時(shí)自動(dòng)轉(zhuǎn)義你的參數(shù)。

也要注意Django使用"%s"占位符,而不是SQLite Python綁定的"?"占位符。這是一致性和可用性的緣故。

Django 1.7中的改變。

PEP 249并沒有說(shuō)明游標(biāo)是否可以作為上下文管理器使用。在python2.7之前,游標(biāo)可以用作上下文管理器,由于魔術(shù)方法lookups中意想不到的行為(Python ticket #9220)。Django 1.7 顯式添加了對(duì)允許游標(biāo)作為上下文管理器使用的支持。

將游標(biāo)作為上下文管理器使用:

with connection.cursor() as c:
    c.execute(...)

等價(jià)于:

c = connection.cursor()
try:
    c.execute(...)
finally:
    c.close()