Django4 中文入门教程 Django4.0 执行原生SQL查询-执行原生查询

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

若管理器方法 ​raw()​ 能用于执行原生 SQL 查询,就会返回模型实例:

Manager.raw(raw_query, params=(), translations=None)

该方法接受一个原生 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

将查询字段映射为模型字段

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)

索引查询

raw()​ 支持索引,所以,若你只需要第一个结果就这样写:

>>> first_person = Person.objects.raw('SELECT * FROM myapp_person')[0]

不过,索引和切片不是在数据库层面上实现的。若数据库中有非常多的 Person 对象,可以在 SQL 层面使用 limit 子句:

>>> first_person = Person.objects.raw('SELECT * FROM myapp_person LIMIT 1')[0]

延迟模型字段

也可以省略字段:

>>> people = Person.objects.raw('SELECT id, first_name FROM myapp_person')

该查询返回的 Person 对象即延迟模型实例。这意味着查询语句中省略的字段按需加载。例子:

>>> 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

表面上,看起来该查询同时检出了 ​first name​ 和 ​last name​。然而,这个例子实际上执行了三次查询。只有 ​first names​ 是由 ​raw()​ 查询检出的 —— ​last names​ 是在它们被打印时按需检出。
只有一个字段你不能省略 —— 主键字段。Django 用主键来区分模型实例,所以必须在原生查询语句中包含主键。若你忘了包含主键会抛出 ​FieldDoesNotExist​ 异常。

添加注释

你可以执行带有模型中未定义字段的查询语句。例如,我们能用 PostgreSQL 的 ​age()​ 函数 获取用户列表,他们的年龄已由数据库计算:

>>> people = Person.objects.raw('SELECT *, age(birth_date) AS age FROM myapp_person')
>>> for p in people:
... print("%s is %s." % (p.first_name, p.age))
John is 37.
Jane is 42.
...

将参数传给raw()

如果你需要执行参数化的查询,可以使用 ​raw()​ 的 ​params ​参数:

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

params ​是一个参数字典。你将用一个列表替换查询字符串中 ​%s​ 占位符,或用字典替换 ​%(key)s​ 占位符(​key ​被字典 ​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'"