查询表达式¶
查询表达式描述可用作更新,创建,过滤,排序,注释或聚合的一部分的值或计算。有许多内置表达式(如下所述)可用于帮助您编写查询。表达式可以组合,或在某些情况下嵌套,以形成更复杂的计算。
支持在创建新模型实例时使用表达式。
支持的算术¶
Django支持使用Python常量,变量甚至其他表达式对查询表达式进行加法,减法,乘法,除法,模运算和幂运算。
一些例子¶
from django.db.models import F, Count
from django.db.models.functions import Length, Upper, Value
# 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()
对象表示模型字段或注释列的值。它使得可以引用模型字段值并使用它们执行数据库操作,而实际上不必将它们从数据库拉出到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从来不知道它 - 它完全由数据库处理。所有Python通过Django的 F()
类创建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()
可以通过以下方式提供性能优势:
获取数据库,而不是Python,做工作
减少一些操作所需的查询数
使用 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'))
但两种情况都将导致查询集,其中每个模型用一个额外的属性 field_lower
注释,粗略地,从以下SQL:
SELECT
...
LOWER("db_table"."field") as "field_lower"
有关内置数据库函数的列表,请参阅 数据库函数。
Func
API如下:
-
class
Func
(*expressions, **extra)[源代码]¶ -
-
template
¶ 类属性,作为格式字符串,描述为此函数生成的SQL。默认为
'%(function)s(%(expressions)s)'
。如果你像
strftime('%W', 'date')
一样构造SQL,并且在查询中需要一个文本%
字符,在template
属性中将其四次(%%%%
),因为字符串被插入两次:在as_sql()
中的模板插值期间一次,在查询的SQL插值中一次参数在数据库游标中。
-
arg_joiner
¶ 类属性,表示用于连接
expressions
列表的字符。默认为', '
。
-
arity
¶ - New in Django 1.10.
类属性,表示函数接受的参数数量。如果设置此属性并且使用不同数目的表达式调用函数,则会引发
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。例如:django/db/models/functions.pyclass 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)", )
Changed in Django 1.10:增加了对
arg_joiner
和**extra_context
参数的支持。
-
*expressions
参数是函数将应用于的位置表达式的列表。表达式将转换为字符串,与 arg_joiner
连接在一起,然后作为 expressions
占位符插入到 template
中。
位置参数可以是表达式或Python值。字符串被假定为列引用,并且将被包装在 F()
表达式中,而其他值将被包装在 Value()
表达式中。
**extra
kwargs是可以内插到 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如下:
-
class
Aggregate
(expression, output_field=None, **extra)[源代码]¶ -
template
¶ 类属性,作为格式字符串,描述为此聚合生成的SQL。默认为
'%(function)s( %(expressions)s )'
。
-
expression
参数可以是模型上的字段的名称,也可以是另一个表达式。它将被转换为字符串并用作 template
中的 expressions
占位符。
output_field
参数需要一个模型字段实例,如 IntegerField()
或 BooleanField()
,Django在从数据库中检索后将加载该值。通常,在实例化模型字段时不需要任何参数,因为与数据验证(max_length
,max_digits
等)相关的任何参数不会对表达式的输出值强制执行。
注意,只有当Django无法确定结果应该是什么字段类型时,才需要 output_field
。混合字段类型的复杂表达式应定义所需的 output_field
。例如,将 IntegerField()
和 FloatField()
添加在一起应该可能已经定义了 output_field=FloatField()
。
**extra
kwargs是可以内插到 template
属性中的 key=value
对。
创建自己的聚合函数¶
创建自己的聚合是非常容易的。至少,您需要定义 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()
中。
你很少需要直接使用 Value()
。当你写表达式 F('field') + 1
时,Django隐含地将 1
包装在 Value()
中,允许在更复杂的表达式中使用简单的值。当您要将字符串传递到表达式时,您将需要使用 Value()
。大多数表达式将字符串参数解释为字段的名称,如 Lower('name')
。
value
参数描述要包括在表达式中的值,例如 1
,True
或 None
。 Django知道如何将这些Python值转换为相应的数据库类型。
output_field
参数应该是一个模型字段实例,如 IntegerField()
或 BooleanField()
,Django在从数据库检索后将加载该值。通常,在实例化模型字段时不需要任何参数,因为与数据验证(max_length
,max_digits
等)相关的任何参数不会对表达式的输出值强制执行。
ExpressionWrapper()
表达式¶
ExpressionWrapper
只包围另一个表达式,并提供对其他表达式可能不可用的属性(如 output_field
)的访问。当对 使用 F() 与注释 中描述的具有不同类型的 F()
表达式使用算术时,ExpressionWrapper
是必要的。
原始SQL表达式¶
有时,数据库表达式不能轻易表达复杂的 WHERE
子句。在这些边缘情况下,使用 RawSQL
表达式。例如:
>>> from django.db.models.expressions import RawSQL
>>> queryset.annotate(val=RawSQL("select col from sometable where othercol = %s", (someparam,)))
这些额外的查找可能不能移植到不同的数据库引擎(因为你明确地写SQL代码),违反了DRY原则,所以你应该尽可能避免它们。
警告
您应该非常小心地转义任何参数,用户可以通过使用 params
来控制,以保护免受 SQL注入攻击。 params
是一个必需的参数,强制您承认您不是用用户提供的数据内插SQL。
技术信息¶
下面您将找到对图书馆作者可能有用的技术实施细节。下面的技术API和示例将有助于创建可扩展Django提供的内置功能的通用查询表达式。
表达式API¶
查询表达式实现 查询表达式API,但也暴露了下面列出的一些额外的方法和属性。所有查询表达式必须从 Expression()
或相关子类继承。
当查询表达式包装另一个表达式时,它负责调用包装表达式上的相应方法。
-
class
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
是一个布尔值,允许或拒绝在查询中使用联接。reuse
是用于多连接场景的一组可重用连接。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
的克隆(副本),并重新标记任何列别名。创建子查询时,将重命名列别名。还应该对任何嵌套表达式调用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
()¶ 返回准备好以降序排序的表达式。
-
编写自己的查询表达式¶
您可以编写自己的查询表达式类,这些类使用其他查询表达式,并可以与其集成。让我们通过写一个 COALESCE
SQL函数的实现,不使用内置的 Func()表达式 的例子。
COALESCE
SQL函数定义为获取列或值的列表。它将返回非 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
我们对参数进行一些基本验证,包括至少需要2列或值,并确保它们是表达式。我们在这里需要 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语法为某个功能,您可以通过猴子修补一个新的方法到该函数的类添加对它的支持。
假设我们正在为Microsoft的SQL Server写一个后端,它使用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()
方法。