执行原生 SQL 查询Link to this heading
Django gives you three ways of performing raw SQL queries: you can embed raw
SQL fragments into ORM queries using
RawSQL (see Raw SQL fragments),
use Manager.raw() to perform raw queries and return model instances,
or avoid the model layer entirely and execute custom SQL directly.
Raw SQL fragmentsLink to this heading
In some cases, you may need to embed raw SQL fragments directly into ORM
queries — for example, in annotate() or
filter() calls. Use Func()
expressions for calling database functions across
backends, or
RawSQL for arbitrary parameterized SQL
fragments.
执行原生查询Link to this heading
若管理器方法 raw() 能用于执行原生 SQL 查询,就会返回模型实例:
- Manager.raw(raw_query, params=(), translations=None)Link to this definition
该方法接受一个原生 SQL 查询语句,执行它,并返回一个 django.db.models.query.RawQuerySet 实例。这个 RawQuerySet 能像普通的 QuerySet 一样被迭代获取对象实例。
最好用例子来解释。假设你有以下模型:
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() 有很多额外选项,使得它非常强大。
将查询字段映射为模型字段Link to this heading
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
... """)
...
只要名字对上了,模型实例就会被正确创建。
或者,您可以使用 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)
索引查询Link to this heading
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]
延迟模型字段Link to this heading
也可以省略字段:
>>> people = Person.objects.raw("SELECT id, first_name FROM myapp_person")
The Person objects returned by this query will be deferred model instances
(see defer()). This means that the
fields that are omitted from the query will be loaded on demand. For example:
>>> 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
从外表看,这似乎是查询检索了名字和姓氏。然而,这个示例实际上发出了 3 个查询。只有 raw() 查询检索了名字 - 姓氏是在打印时按需检索的。
只有一个字段你不能省略 —— 主键字段。Django 用主键来区分模型实例,所以必须在原生查询语句中包含主键。若你忘了包含主键会抛出 FieldDoesNotExist 异常。
将参数传给 raw()Link to this heading
如果需要执行参数化查询,可以使用 raw() 的 params 参数:
>>> lname = "Doe"
>>> Person.objects.raw("SELECT * FROM myapp_person WHERE last_name = %s", [lname])
params 是一个参数字典。你将用一个列表替换查询字符串中 %s 占位符,或用字典替换 %(key)s 占位符(key 被字典 key 替换),不论你使用哪个数据库引擎。这些占位符会被 params 参数的值替换。
直接执行自定义 SQLLink to this heading
有时候,甚至 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.
Assume the column names are unique.
"""
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.
Assume the column names are unique.
"""
desc = cursor.description
nt_result = namedtuple("Result", [col[0] for col in desc])
return [nt_result(*row) for row in cursor.fetchall()]
dictfetchall() 和 namedtuplefetchall() 示例假设列名是唯一的,因为游标无法区分来自不同表的列。
以下是三者之间的差异示例:
>>> 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
连接和指针Link to this heading
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()
调用存储流程Link to this heading
- CursorWrapper.callproc(procname, params=None, kparams=None)Link to this definition
以给定名称调用数据库存储流程。要提供一个序列 (
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"])