Django4.0 执行原生SQL查询-直接执行自定义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):
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
占位符。
注意,若要在查询中包含文本的百分号,你需要在传入参数使用两个百分号:
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
with connections['my_db_alias'].cursor() as cursor:
# Your code here...
默认情况下,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()
]
另一个选项是使用来自 Python 标准库的 collections.namedtuple()
。 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
连接和指针
connection 和 cursor 实现了 PEP 249 中介绍的大部分标准 Python DB-API。
若你并不熟悉 Python DB-API,要注意 cursor.execute()
中的 SQL 语句使用了占位符 "%s
",而不是直接在 SQL 中添加参数。若你使用这个技巧,潜在的数据库库会自动在需要时转义参数。
也要注意,Django 期望 "%s
" 占位符,而 不是 "?
" 占位符,后者由 SQLite Python 绑定使用。这是为了一致性和正确性。
将指针作为上下文的管理器:
with connection.cursor() as c:
c.execute(...)
相当于:
c = connection.cursor()
try:
c.execute(...)
finally:
c.close()
调用存储流程
CursorWrapper.callproc(procname, params=None, kparams=None)
以给定名称调用数据库存储流程。要提供一个序列 (params
) 或字典 (kparams
) 作为输入参数。大多数数据库不支持 kparams
。对于 Django 内置后端来说,只有 Oracle 支持。
例如,在一个 Oracle 数据库中指定存储流程:
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;
这将调用该存储流程:
with connection.cursor() as cursor:
cursor.callproc('test_procedure', [1, 'test'])