查询表达式是用作更新, 创建, 过滤, 排序, 注解和聚合的一部分值或计算. 这里(下文)有很多内置表达式可以帮助你完成查询. 表达式可以通过组合和嵌套来完成复杂的计算.
添加了对在创建新模型实例时使用表达式的支持.
Django支持在查询表达式中使用加, 减, 乘, 除, 求模, 幂运算, Python常量, 变量等.
from django.db.models import F, Count, Value
from django.db.models.functions import Length, Upper
# Find companies that have more employees than chairs.
Company.objects.filter(num_employees__gt=F('num_chairs'))
# Find companies that have at least twice as many employees
# as chairs. Both the querysets below are equivalent.
Company.objects.filter(num_employees__gt=F('num_chairs') * 2)
Company.objects.filter(
num_employees__gt=F('num_chairs') + F('num_chairs'))
# How many chairs are needed for each company to seat all employees?
>>> company = Company.objects.filter(
... num_employees__gt=F('num_chairs')).annotate(
... chairs_needed=F('num_employees') - F('num_chairs')).first()
>>> company.num_employees
120
>>> company.num_chairs
50
>>> company.chairs_needed
70
# Create a new company using expressions.
>>> company = Company.objects.create(name='Google', ticker=Upper(Value('goog')))
# Be sure to refresh it if you need to access the field.
>>> company.refresh_from_db()
>>> company.ticker
'GOOG'
# Annotate models with an aggregated value. Both forms
# below are equivalent.
Company.objects.annotate(num_products=Count('products'))
Company.objects.annotate(num_products=Count(F('products')))
# Aggregates can contain complex computations also
Company.objects.annotate(num_offerings=Count(F('products') + F('services')))
# Expressions can also be used in order_by()
Company.objects.order_by(Length('name').asc())
Company.objects.order_by(Length('name').desc())
注解
这些表达式定义在 django.db.models.expressions
和 django.db.models.aggregates
中, 但是为了方便可以直接从 django.db.models
导入.
F()
表达式¶F
¶F()
对象代表模型的字段或注释列的值. 它使得引用模型字段值并使用它们执行数据库操作成为可能, 而不用再把它们查询出来放到python内存中.
取而代之的是, Django使用 F()
对象生成描述数据库级所需操作的SQL表达式.
这可以通过一个例子很容易理解. 往常我们会这样做:
# Tintin filed a news story!
reporter = Reporters.objects.get(name='Tintin')
reporter.stories_filed += 1
reporter.save()
这里, 我们把 reporter.stories_filed
的值从数据库查询出来放到内存中, 然后再用python运算符操作它, 最后再把它保存到数据库. 但是我们还可以这样做:
from django.db.models import F
reporter = Reporters.objects.get(name='Tintin')
reporter.stories_filed = F('stories_filed') + 1
reporter.save()
看上去 reporter.stories_filed = F('stories_filed') + 1
是一个给实例属性赋值的普通Python操作, 事实上这是一个描述数据库操作的SQL结构.
当Django遇到一个 F()
的实例时, 它会重写标准的Python运算符来创建一个封装的SQL表达式; 在本例中, 它指示数据库增加 reporter.stories_filed
字段表示的数据库字段的值.
无论 reporter.stories_filed
的值是或曾是什么, Python不需要知道–它完全由数据库处理. 通过Django的 F()
类, Python所做的只是去创建SQL语句引用字段和描述操作.
要访问以这种方式保存的新值, 必须重新加载该对象:
reporter = Reporters.objects.get(pk=reporter.pk)
# Or, more succinctly:
reporter.refresh_from_db()
除了如上所述在单个实例的操作中使用外, F()
表达式还可以与 update()
一起用于对象实例的 QuerySets
.
这将我们在上面使用的两个查询 get()
和 save()
减少到一个:
reporter = Reporters.objects.filter(name='Tintin')
reporter.update(stories_filed=F('stories_filed') + 1)
我们还可以使用 update()
来批量增加多个对象的字段值.
这比将所有对象从数据库中存入Python, 然后循环增加每个对象的字段值并将每个对象保存回数据库要快得多:
Reporter.objects.all().update(stories_filed=F('stories_filed') + 1)
F()
表达式的效率上的优点主要体现在:
F()
避免竞争条件¶F()
的另一个好处是让数据库去操作——而不是用Python更新字段的值, 这避免了 竞争条件.
假设有两个Python线程同时执行上面第一个例子中的代码, 一个线程可能是在另一个线程刚从数据库中获取完字段值后检索, 增加, 保存该字段值. 第二个线程保存的值则是基于原始值的. 第一个线程的工作将会丢失.
如果让数据库负责更新字段, 那么这个过程就更加健壮: 它会在执行 save()
或 update()
时根据数据库中字段的值更新字段, 而不是根据检索实例时的值更新字段.
F()
赋值在 Model.save()
后持续存在¶模型实例被保存后, 分配给模型字段的 F()
对象仍然存在, 并将应用于每个 save()
. 例如:
reporter = Reporters.objects.get(name='Tintin')
reporter.stories_filed = F('stories_filed') + 1
reporter.save()
reporter.name = 'Tintin Jr.'
reporter.save()
stories_filed
将会被更新两次. 如果初始值是 1
, 那么最终值将是 3
.
F()
¶F()
可用于通过将不同字段与算术组合, 在模型上创建动态字段:
company = Company.objects.annotate(
chairs_needed=F('num_employees') - F('num_chairs'))
如果组合的字段是不同类型的, 需要告诉Django返回的字段类型. 由于 F()
不支持 output_field
参数, 你需要用 ExpressionWrapper
来封装表达式:
from django.db.models import DateTimeField, ExpressionWrapper, F
Ticket.objects.annotate(
expires=ExpressionWrapper(
F('active_at') + F('duration'), output_field=DateTimeField()))
当引用的是关联字段比如 ForeignKey
时, F()
返回的是主键值而不是关联模型实例:
>> car = Company.objects.annotate(built_by=F('manufacturer'))[0]
>> car.manufacturer
<Manufacturer: Toyota>
>> car.built_by
3
Func()
表达式¶Func()
表达式是所有涉及数据库函数的表达式基类, 例如 COALESCE
和 LOWER
, 还包括聚合函数如 SUM
. 可以通过下面方式直接使用:
from django.db.models import Func, F
queryset.annotate(field_lower=Func(F('field'), function='LOWER'))
也可以用来构建数据库函数库:
class Lower(Func):
function = 'LOWER'
queryset.annotate(field_lower=Lower('field'))
这两种写法都会产生一个查询集, 其中的每个模型对象都会用大致类似下面的SQL生成一个额外属性 field_lower
:
SELECT
...
LOWER("db_table"."field") as "field_lower"
参见 数据库函数 查看内置的数据库函数列表.
Func
API如下:
Func
(*expressions, **extra)¶template
¶类属性, 格式化字符串, 描述该函数要生成的SQL. 默认值为 '%(function)s(%(expressions)s)'
.
如果你想构造类似 strftime('%W', 'date')
这样的SQL. 需要在查询中使用 %
字符, 则需要在 template
属性中使用四个(%%%%
),
因为这个字符串会被插值两次, 一次是在模板 as_sql()
时插值, 另一次是在数据库查询SQL中参数的插值.
arg_joiner
¶一个表示join expressions
列表的字符. 默认为 ', '
.
arity
¶类属性, 表示函数接受的参数数量. 如果设置了该属性, 并且调用时使用了不同数量的参数将引发 TypeError
异常. 默认值为 None
.
as_sql
(compiler, connection, function=None, template=None, arg_joiner=None, **extra_context)¶生成数据库函数SQL.
as_vendor()
方法会使用 function
, template
, arg_joiner
, 和 **extra_context
参数根据需要定制SQL. 例如:
class ConcatPair(Func):
...
function = 'CONCAT'
...
def as_mysql(self, compiler, connection):
return super(ConcatPair, self).as_sql(
compiler, connection,
function='CONCAT_WS',
template="%(function)s('', %(expressions)s)",
)
新增参数 arg_joiner
和 **extra_context
.
*expressions
参数是将要应用到函数的表达式列表的位置参数. 表达式将被 arg_joiner
连接成字符串, 然后插入 template
中的 expressions
占位符.
位置参数可以是表达式和Python值. 字符串会被认为是列引用, 并将其封装在 F()
表达式中. 其他值将被封装在 Value()
表达式中.
**extra
是插入 template
的 key=value
对. function
, template
和 arg_joiner
用于替换相同名称的属性, 而无需定义自己的类. output_field
用于定义期望的返回类型.
Aggregate()
表达式¶聚合表达式是一种特殊的 Func() 表达式, 它告知查询需要一个 GROUP BY
子句.
所有 聚合函数 例如 Sum()
和 Count()
都继承至 Aggregate()
.
由于 Aggregate
是表达式或封装的表达式, 因此可以做一些复杂的计算:
from django.db.models import Count
Company.objects.annotate(
managers_required=(Count('num_employees') / 4) + Count('num_managers'))
Aggregate
API如下:
Aggregate
(expression, output_field=None, **extra)¶template
¶类属性, 格式化字符串, 描述要生成的聚合SQL. 默认为 '%(function)s( %(expressions)s )'
.
expression
参数可以是模型上的字段名称或其他表达式. 它将会被转换成字符串插入到 template
中的 expressions
占位符.
output_field
参数接收一个模型字段实例, 如 output_field()
和 BooleanField()
.
Django将从数据库中检索的值装载到其中. 通常在实例化模型字段时不需要任何参数. 因为所有数据验证有关的参数(max_length
, max_digits
, etc.)都不会在表达式的输出值上执行.
注意, 只有当Django无法确定结果应该是什么字段类型时才需要 output_field
. 混合字段类型的复杂表达式应定义 output_field
.
例如, 将一个 IntegerField()
和 FloatField()
相加, 就可以定义 output_field=FloatField()
.
**extra
关键字是 key=value
对, 用来插入到 template
属性中.
自定义聚合函数非常容易. 你可以定义 function
也可以完全自定义生成SQL. 下面是一个简单例子:
from django.db.models import Aggregate
class Count(Aggregate):
# supports COUNT(distinct field)
function = 'COUNT'
template = '%(function)s(%(distinct)s%(expressions)s)'
def __init__(self, expression, distinct=False, **extra):
super(Count, self).__init__(
expression,
distinct='DISTINCT ' if distinct else '',
output_field=IntegerField(),
**extra
)
Value()
表达式¶Value
(value, output_field=None)¶Value()
对象是表达式中最小的组件: 普通值. 当需要在表达式中表示整数, 布尔或字符串的值时, 可以使用 Value()
封装该值.
很少需要直接使用 Value()
. 在表达式 F('field') + 1
中, Django会隐式地将1用 Value()
封装起来, 这样可以将普通值使用在更复杂的表达式中.
但是当你想要将字符串传递给表达式时需要使用 Value()
. 因为大多数表达式会将字符串参数解释为字段的名称, 如 Lower('name')
.
value
参数表示包含在表达式中的值, 例如 1
, True
或 None
. Django会自动将Python值转换为对应的数据库类型.
output_field
参数接收一个模型字段实例, 如 output_field()
和 BooleanField()
.
Django将从数据库中检索的值装载到其中. 通常在实例化模型字段时不需要任何参数. 因为所有数据验证有关的参数(max_length
, max_digits
, etc.)都不会在表达式的输出值上执行.
ExpressionWrapper()
表达式¶ExpressionWrapper
(expression, output_field)¶ExpressionWrapper
用来封装表达式并提供 output_field
等可访问属性, 这些属性可能在其他表达式上无法使用.
当使用 F()
对不同类型表达式进行算术运算时 ExpressionWrapper
是必要的, 详见 在注解中使用 F().
RawSQL
(sql, params, output_field=None)¶有些时候数据库表达式不太容易表达式比较复杂的 WHERE
子句. 在这些边缘情况下, 可以使用 RawSQL
表达式. 例如:
>>> from django.db.models.expressions import RawSQL
>>> queryset.annotate(val=RawSQL("select col from sometable where othercol = %s", (someparam,)))
这些额外的查询可能无法移植到不同的数据库引擎中(因为存在硬编码的SQL代码), 并且违反DRY原则, 因此应该尽可能避免使用它们.
警告
为了防止 SQL注入攻击, 你应该小心地避免使用用户可以通过 params
控制的任何参数. params
是一个必传的参数, 用于强制确认没有使用用户提供的数据插入SQL.
在下面是可能对库作者有用的技术细节. 下面的技术API和示例将有助于创建可以扩展Django内置功能的通用查询表达式.
查询表达式实现了 查询表达式API, 同时也提供了下面列出的一些额外方法和属性. 所有查询表达式都必须继承于 Expression()
或相关子类.
如果查询表达式封装另一个表达式, 它负责在被封装的表达式上调用相应的方法.
Expression
¶contains_aggregate
¶告诉Django该表达式包含聚合, 需要将 GROUP BY
子句添加到查询中.
resolve_expression
(query=None, allow_joins=True, reuse=None, summarize=False, for_save=False)¶提供在将表达式添加到查询之前对其进行预处理和验证的机会. 所有嵌套表达式都必须调用 resolve_expression()
. 返回一个 self
的 copy()
, 并进行必要的转换.
query
是后端查询实现.
allow_joins
是一个允许或拒绝在查询中使用join的布尔值.
reuse
是用于多join场景的一组可重用join.
summarize
是一个布尔值, 当 True
时表示正在计算的查询是终端聚合查询.
get_source_expressions
()¶返回内部表达式的有序列表. 例如:
>>> Sum(F('foo')).get_source_expressions()
[F('foo')]
set_source_expressions
(expressions)¶接收一个表达式列表将其存储, 以便 get_source_expressions()
能够返回他们.
relabeled_clone
(change_map)¶返回 self
的clone(副本), 并重新标记所有列别名. 创建子查询时, 列别名会被重新命名. relabeled_clone()
也应该对所有嵌套的表达式进行调用并分配给副本.
change_map
是将旧别名映射到新别名的字典.
例如:
def relabeled_clone(self, change_map):
clone = copy.copy(self)
clone.expression = self.expression.relabeled_clone(change_map)
return clone
convert_value
(self, value, expression, connection, context)¶允许表达式将 value
强制转换为更适当类型的钩子.
get_group_by_cols
()¶负责返回该表达式的列引用列表. get_group_by_cols()
应在所有嵌套的表达式上调用. 尤其是持有列的引用的 F()
对象.
asc
()¶返回准备按升序排序的表达式.
desc
()¶返回准备按降序排序的表达式.
可以编写自己的查询表达式类, 它们可以使用和集成其他查询表达式.
下面例子演示不使用 Func()表达式 来实现SQL的 COALESCE
函数.
SQL的 COALESCE
函数接收一组列或值. 它返回不为 NULL
的第一个列或值.
首先定义用于生成SQL的模板和 __init__()
方法来设置一些属性:
import copy
from django.db.models import Expression
class Coalesce(Expression):
template = 'COALESCE( %(expressions)s )'
def __init__(self, expressions, output_field):
super(Coalesce, self).__init__(output_field=output_field)
if len(expressions) < 2:
raise ValueError('expressions must have at least 2 elements')
for expression in expressions:
if not hasattr(expression, 'resolve_expression'):
raise TypeError('%r is not an Expression' % expression)
self.expressions = expressions
我们对参数进行一些基本验证, 包括至少需要两个列或值, 并确保它们是表达式.
我们在这里要求 output_field
, 以便Django知道要将最终结果分配给什么样的模型字段.
下面实现预处理和验证. 由于此时没有任何自己的验证, 所以这里委托给嵌套表达式:
def resolve_expression(self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False):
c = self.copy()
c.is_summary = summarize
for pos, expression in enumerate(self.expressions):
c.expressions[pos] = expression.resolve_expression(query, allow_joins, reuse, summarize, for_save)
return c
接下来, 编写负责生成SQL的方法:
def as_sql(self, compiler, connection, template=None):
sql_expressions, sql_params = [], []
for expression in self.expressions:
sql, params = compiler.compile(expression)
sql_expressions.append(sql)
sql_params.extend(params)
template = template or self.template
data = {'expressions': ','.join(sql_expressions)}
return template % data, params
def as_oracle(self, compiler, connection):
"""
Example of vendor specific handling (Oracle in this case).
Let's make the function name lowercase.
"""
return self.as_sql(compiler, connection, template='coalesce( %(expressions)s )')
as_sql()
方法可以支持自定义关键字参数, 允许 as_vendorname()
方法覆盖用于生成SQL字符串的数据.
使用 as_sql()
定制的关键字参数比在 as_vendorname()
方法中突变 self
更好,
因为后者在不同的数据库后端发生错误. 如果您的类依赖于类属性来定义数据, 可以考虑在 as_sql()
方法中允许覆盖.
我们使用 compiler.compile()
方法为每个 expressions
生成SQL, 并用逗号拼接. 然后在模板中填入数据, 并返回SQL和参数.
我们还针对特定数据后端Oracle做了的自定义实现. 如果使用Oracle后端则将调用 as_oracle()
函数, 而不是 as_sql()
.
最后, 我们实现允许使查询表达式能够与其他查询表达式很好地配合方法:
def get_source_expressions(self):
return self.expressions
def set_source_expressions(self, expressions):
self.expressions = expressions
让我们看看它是如何工作的:
>>> from django.db.models import F, Value, CharField
>>> qs = Company.objects.annotate(
... tagline=Coalesce([
... F('motto'),
... F('ticker_name'),
... F('description'),
... Value('No Tagline')
... ], output_field=CharField()))
>>> for c in qs:
... print("%s: %s" % (c.name, c.tagline))
...
Google: Do No Evil
Apple: AAPL
Yahoo: Internet Company
Django Software Foundation: No Tagline
如果你使用的数据库后端对某个函数使用了不同的SQL语法, 你可以通过在函数的类上添加一个新的方法来增加对它的支持.
比如说, 我们为微软的SQLServer编写一个后端, 它使用SQL的 LEN
而不是 LENGTH
来实现 Length
函数.
我们将把一个名为 as_sqlserver()
的新方法添加到 Length
类上:
from django.db.models.functions import Length
def sqlserver_length(self, compiler, connection):
return self.as_sql(compiler, connection, function='LEN')
Length.as_sqlserver = sqlserver_length
也可以使用 as_sql()
的 template
参数自定义SQL.
我们使用 as_sqlserver()
是因为 django.db.connection.vendor
返回 sqlserver
作为后端.
第三方后端可以在后端程序包的顶级 __init__.py
文件或从顶级 __init__.py
导入的顶级 expressions.py
文件(或程序包)中注册其函数.
对于希望给自己正在使用的后端打补丁的用户项目来说, 这段代码应该存在于 AppConfig.ready()
方法中.
10月 29, 2021