Django4 中文入门教程 Django4.0 执行原生SQL查询-直接执行自定义SQL

2024-02-25 开发教程 Django4 中文入门教程 匿名 5

有时候,甚至 ​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'])