Django文檔閱讀之執行原始SQL查詢


Django提供了兩種執行原始SQL查詢的方法:可以使用Manager.raw()來執行原始查詢並返回模型實例,或者可以完全避免模型層直接執行自定義SQL。

每次編寫原始SQL時都要關注防止SQL注入

一、raw()方法

raw()方法可以用來執行返回模型實例原始的SQL查詢:

Manager. raw raw_query params = None translations = None

此方法接受原始SQL查詢,執行它並返回 django.db.models.query.RawQuerySet實例。RawQuerySet可以像正常一樣迭代實例 QuerySet以提供對象實例。

Person.objects.raw('SELECT * FROM myapp_person')

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

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

匹配是通過名稱完成的。這意味着您可以使用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''') 

只要名稱匹配,就會正確創建模型實例。

或者,您可以使用translations參數to 將查詢中的字段映射到模型字段 raw()這是一個字典,將查詢中字段的名稱映射到模型上字段的名稱。例如,上面的查詢也可以寫成:

>>> 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]
Django使用主鍵來標識模型實例,因此它必須始終包含在原始查詢中。一InvalidQuery,如果你忘了,包括主鍵,將引發異常。

將參數傳遞給raw()

如果需要執行參數化查詢,可以使用以下params 參數raw()

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

params是參數的列表或字典。無論您的數據庫引擎如何,您都將%s 在查詢字符串中使用占位符作為列表,或者%(key)s 使用字典的占位符(當然,這key將替換為字典鍵)。這些占位符將替換為參數中的params 參數。

 
        

不要在原始查詢上使用字符串格式或在SQL字符串中引用占位符!

將上述查詢編寫為:

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

您可能還認為應該像這樣編寫查詢(帶引號%s):

>>> query = "SELECT * FROM myapp_person WHERE last_name = '%s'"

不要犯這些錯誤。

使用params 參數並保留占位符不加引號可以保護您免受SQL注入攻擊,這是攻擊者將任意SQL注入數據庫的常見漏洞。如果使用字符串插值或引用占位符,則存在SQL注入的風險。

直接執行自定義

該對象django.db.connection表示默認數據庫連接。要使用數據庫連接,請調用connection.cursor()以獲取游標對象。然后,調用執行SQL和返回結果行。

cursor.execute(sql,[params])

from django.db import connection def my_custom_sql(self): with connection.cursor() as 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 

要防止SQL注入,不得%s 在SQL字符串中占位符周圍包含引號

請注意,如果要在查詢中包含文字百分號,則必須在傳遞參數的情況下將它們加倍:

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

默認情況下,Python DB API將返回沒有字段名稱的結果,這意味着您最終會得到一個list值,而不是一個dict在較小的性能和內存成本下,您可以使用以下內容返回結果dict

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

另一種選擇是使用collections.namedtuple()Python標准庫。namedtuple是一個類似元組的對象,其字段可通過屬性查找訪問; 它也是可索引和可​​迭代的。結果是不可變的,可以通過字段名稱或索引訪問,這可能很有用:

from collections import namedtuple def namedtuplefetchall(cursor): "Return all rows from a cursor as a namedtuple" desc = cursor.description nt_result = namedtuple('Result', [col[0] for col in desc]) return [nt_result(*row) for row in cursor.fetchall()]

以下是三者之間差異的一個例子:

>>> cursor.execute("SELECT id, parent_id FROM test LIMIT 2"); >>> cursor.fetchall() ((54360982, None), (54360880, None)) >>> cursor.execute("SELECT id, parent_id FROM test LIMIT 2"); >>> dictfetchall(cursor) [{'parent_id': None, 'id': 54360982}, {'parent_id': None, 'id': 54360880}] >>> cursor.execute("SELECT id, parent_id FROM test LIMIT 2"); >>> results = namedtuplefetchall(cursor) >>> results [Result(id=54360982, parent_id=None), Result(id=54360880, parent_id=None)] >>> results[0].id 54360982 >>> results[0][0] 54360982
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM