Django4.0 執(zhí)行原生SQL查詢-直接執(zhí)行自定義SQL

2022-03-16 17:36 更新

有時候,甚至 ?Manager.raw()? 都無法滿足需求:你可能要執(zhí)行不明確映射至模型的查詢語句,或者就是直接執(zhí)行 ?UPDATE?, ?INSERT ?或 ?DELETE ?語句。

這些情況下,你總是能直接訪問數(shù)據(jù)庫,完全繞過模型層。

對象 ?django.db.connection? 代表默認數(shù)據(jù)庫連接。要使用這個數(shù)據(jù)庫連接,調(diào)用 ?connection.cursor()? 來獲取一個指針對象。然后,調(diào)用 ?cursor.execute(sql, [params])? 來執(zhí)行該 SQL 和 ?cursor.fetchone()?,或 ?cursor.fetchall()? 獲取結(jié)果數(shù)據(jù)。

例如:

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 注入,你絕對不能在 SQL 字符串中用引號包裹 ?%s? 占位符。
注意,若要在查詢中包含文本的百分號,你需要在傳入?yún)?shù)使用兩個百分號:

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

若你同時使用不止一個數(shù)據(jù)庫,你可以使用 ?django.db.connections? 獲取指定數(shù)據(jù)庫的連接(和指針)。 ?django.db.connections? 是一個類字典對象,它允許你通過連接別名獲取指定連接:

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

默認情況下,Python DB API 返回的結(jié)果不會包含字段名,這意味著你最終會收到一個 ?list?,而不是一個 ?dict?。要追求較少的運算和內(nèi)存消耗,你可以以 ?dict ?返回結(jié)果,通過使用如下的玩意:

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

另一個選項是使用來自 Python 標準庫的 ?collections.namedtuple()?。 ?namedtuple ?是一個類元組對象,可以通過屬性查找來訪問其包含的字段;也能通過索引和迭代。結(jié)果都是不可變的,但能通過字段名或索引訪問,這很實用:

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

連接和指針

connection 和 cursor 實現(xiàn)了 PEP 249 中介紹的大部分標準 Python DB-API。
若你并不熟悉 Python DB-API,要注意 ?cursor.execute()? 中的 SQL 語句使用了占位符 "?%s?",而不是直接在 SQL 中添加參數(shù)。若你使用這個技巧,潛在的數(shù)據(jù)庫庫會自動在需要時轉(zhuǎn)義參數(shù)。
也要注意,Django 期望 "?%s?" 占位符,而 不是 "???" 占位符,后者由 SQLite Python 綁定使用。這是為了一致性和正確性。
將指針作為上下文的管理器:

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

相當于:

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

調(diào)用存儲流程

CursorWrapper.callproc(procname, params=None, kparams=None)

以給定名稱調(diào)用數(shù)據(jù)庫存儲流程。要提供一個序列 (?params?) 或字典 (?kparams?) 作為輸入?yún)?shù)。大多數(shù)數(shù)據(jù)庫不支持 ?kparams?。對于 Django 內(nèi)置后端來說,只有 Oracle 支持。
例如,在一個 Oracle 數(shù)據(jù)庫中指定存儲流程:

CREATE PROCEDURE "TEST_PROCEDURE"(v_i INTEGER, v_text NVARCHAR2(10)) AS
    p_i INTEGER;
    p_text NVARCHAR2(10);
BEGIN
    p_i := v_i;
    p_text := v_text;
    ...
END;

這將調(diào)用該存儲流程:

with connection.cursor() as cursor:
    cursor.callproc('test_procedure', [1, 'test'])


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號