Django使用原生的SQL進行查詢

進行原始的SQL查詢

模型查詢API不夠用的狀況下,你可使用原始的SQL語句。Django 提供兩種方法使用原始SQL進行查詢:一種是使用Manager.raw()方法,進行原始查詢並返回模型實例;另外一種是徹底避開模型層,直接執行自定義的SQL語句。mysql

警告sql

編寫原始的SQL語句時,應該格外當心。 每次使用的時候,都要確保轉義了參數中任何用戶能夠控制的字符,以防受到SQL注入攻擊。數據庫

進行原始查詢

raw() 管理器方法用於原始的SQL查詢,並返回模型的實例:django

Manager.raw(raw_queryparams=Nonetranslations=None)後端

這個方法執行原始的SQL查詢,並返回一個django.db.models.query.RawQuerySet 實例。這個RawQuerySet 實例能夠像通常的查詢集那樣,經過迭代來提供對象實例。緩存

這裏最好經過例子展現一下, 假設存在如下模型:app

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

你能夠像這樣執行自定義的SQL語句:函數

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

固然,這個例子不是特別有趣——和直接使用Person.objects.all()的結果如出一轍。可是,raw() 擁有其它更強大的使用方法。性能

模型表的名稱fetch

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

一般,Django經過將模型的名稱和模型的「應用標籤」(你在manage.py startapp中使用的名稱)進行關聯,用一條下劃線鏈接他們,來組合表的名稱。在這裏咱們假定Person模型存在於一個叫作myapp的應用中,因此表就應該叫作myapp_person。

更多細節請查看db_table選項的文檔,它也可讓你自定義表的名稱。

警告

傳遞給 .raw()方法的sql語句並無任何檢查。django默認它會返回一個數據集,但這不是強制性的。 若是查詢的結果不是數據集,則會產生一個錯誤。

警告

若是你在mysql上執行查詢,注意在類型不一致的時候,mysql的靜默類型強制可能致使意想不到的結果發生。 若是你在一個字符串類型的列上查詢一個整數類型的值,mysql會在比較前強制把每一個值的類型轉成整數。例如,若是你的表中包含值'abc'和'def',你查詢WHERE mycolumn=0,那麼兩行都會匹配。要防止這種狀況,在查詢中使用值以前,要作好正確的類型轉換。

警告

雖然RawQuerySet能夠像普通的QuerySet同樣迭代,RawQuerySet並無實現能夠在 QuerySet上使用的全部方法。例如,__bool__()和__len__()在RawQuerySet中沒有被定義,因此全部RawQuerySet轉化爲布爾值的結果都是True。RawQuerySet中沒有實現他們的緣由是,在沒有內部緩存的狀況下會致使性能降低,並且增長內部緩存不向後兼容。

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

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

字段的順序並不重要。 換句話說,下面兩種查詢的做用相同:

>>> 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會根據名字進行匹配。 這意味着你可使用SQL的AS子句來將查詢中的字段映射成模型的字段。因此若是在其餘的表中有一些Person數據,你能夠很容易地把它們映射成Person實例:

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

只要名字能對應上,模型的實例就會被正確建立。 

又或者,你能夠在raw()方法中使用translations 參數。這個參數是一個字典,將查詢中的字段名稱映射爲模型中的字段名稱。例如,上面的查詢能夠寫成這樣:

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

索引訪問

raw()方法支持索引訪問,因此若是隻須要第一條記錄,能夠這樣寫:

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

然而,索引和切片並不在數據庫層面上進行操做。 若是數據庫中有不少的Person對象,更加高效的方法是在SQL層面限制查詢中結果的數量:

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

延遲加載模型字段

字段也能夠像這樣被省略:

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

查詢返回的Person對象是一個延遲的模型實例。這意味着被省略的字段,在訪問時才被加載。例如:

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

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

只有一種字段不能夠被省略——就是主鍵。 Django 使用主鍵來識別模型的實例,因此它在每次原始查詢中都必須包含。 若是你忘記包含主鍵的話,會拋出一個InvalidQuery異常。

增長註解

你也能夠在查詢中包含模型中沒有定義的字段。 例如,咱們可使用PostgreSQL 的age() 函數來得到一羣人的列表,帶有數據庫計算出的年齡:

>>> 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()方法中傳遞參數

若是你須要參數化的查詢,能夠向raw()方法傳遞params參數。

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

params是存放參數的列表或字典。你能夠在查詢語句中使用%s佔位符,或者對於字典使用%(key)s佔位符(key替換成字典中相應的key值),不管你的數據庫引擎是什麼。這些佔位符將用params 參數中的值替換。

注意

SQLite後端不支持字典,你必須以列表的形式傳遞參數。

警告

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

它相似於這種樣子:

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

千萬不要。

使用params參數能夠徹底防止SQL注入攻擊,它是一種廣泛的漏洞,使攻擊者能夠向你的數據庫中注入任何SQL語句。若是你使用字符串格式化,遲早會受到SQL注入攻擊。 只要你記住默認使用 params 參數,就能夠免於攻擊。

直接執行自定義的SQL

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

在這些狀況下,你能夠直接訪問數據庫,徹底避開模型層。

django.db.connection對象提供了常規數據庫鏈接的方式。爲了使用數據庫鏈接,先要調用connection.cursor()方法來獲取一個遊標對象以後,調用cursor.execute(sql, [params])來執行sql語句,調用cursor.fetchone()或者cursor.fetchall()來返回結果行。

例如:

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

注意若是你的查詢中包含百分號字符,你須要寫成兩個百分號字符,以便能正確傳遞參數:

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

若是你使用了不止一個數據庫,你可使用django.db.connections來獲取針對特定數據庫的鏈接(以及遊標)對象。django.db.connections是一個相似於字典的對象,容許你經過它的別名獲取特定的鏈接。

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

默認狀況下,Python DB API會返回不帶字段的結果,這意味着你獲得的是一個列表,而不是一個字典。花費一點性能代價以後,你能夠返回一個字典形式的結果,像這樣:

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

下面是一個體現兩者區別的例子:

>>> 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}]

鏈接和遊標

鏈接和遊標主要實現 PEP 249中描述的Python DB API標準——除非它涉及到事務處理

若是你不熟悉Python DB-API,注意cursor.execute()中的SQL語句使用佔位符"%s",而不是直接在SQL中添加參數。若是你使用這種方法,底層數據庫的庫會在必要時自動轉義你的參數。

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

Changed in Django 1.7.

PEP 249並無說明遊標是否能夠做爲上下文管理器使用。在Python 2.7以前,因爲魔術方法查詢(Python ticket #9220)中的一個意想不到的行爲,遊標能夠用做上下文管理器。Django 1.7 明確添加容許使用遊標做爲上下文管理器的支持。

將遊標做爲上下文管理器使用:

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

等價於:

c = connection.cursor()
try:
    c.execute(...)
finally:
    c.close()
相關文章
相關標籤/搜索