我不知道正确的术语,所以我希望标题不会导致混淆。
我有以下模型。
class Material(models.Model):
yes = models.IntegerField()
no = models.IntegerField()
def _votes(self):
return int(self.no + self.yes)
def _ratio(self):
v = self.votes
y = self.yes
try:
return float(y)/v
except ZeroDivisionError:
return float(0)
ratio = property(_ratio)
votes = property(_votes)
这允许我查询Material
项并使用每个字段。
Material.objects.all()[0].yes # returns 5
Material.objects.all()[0].no # returns 3
Material.objects.all()[0].votes # returns 8
Material.objects.all()[0].ratio # returns 0.625
目前为止,一切都好。我想过滤过滤器的值。例如,我只想在比率大于 0.8 时选择一个Material
实例。
Material.objects.filter(ratio__gt=0.8) # what I'd want to do
但是,这样做会返回错误,声称ratio
不是字段。
FieldError: Cannot resolve keyword 'ratio' into field. Choices are: id, no, yes
如何执行此查询?我假设我需要对我的模型进行一些更改,因此ratio
和votes
被注册为实际字段。怎么做?
和列
属性在模型级别定义。Python可以处理这些属性,你可以用它做非常复杂的事情(例如执行HTTP请求(。数据库不知道存在属性,大多数数据库也没有执行非常复杂功能的方法(通常数据库本身不执行非常复杂的任务(。
这意味着为了过滤,我们可以在检索所有值后执行此操作。但这当然是低效的:这意味着我们首先将所有项目加载到内存中,如果过滤器相当严格,我们将做很多工作来只丢弃大部分结果。
将属性转换为F
表达式
如果属性相当简单,我们可以编写一个等效于它的数据库。例如,您的.vote
属性实际上是:
fvotes = F('yes') + F('no') # total number of votes
其中F(..)
是 Django 用来引用列的对象。
如果对于我们的ratio
我们将始终排除没有votes
的值(因为阈值高于零(,那么我们可以编写如下注释:
fratio = F('yes') / fvotes # ratio of the votes
所以现在我们可以用这些额外的属性来注释我们的数据库:
from django.db.models import F
fvotes = F('yes') + F('no')
Material.objects.annotate(
votes=fvotes,
fratio=F('yes') / fvotes
).filter(fratio__gt=0.8)
所以在这里我们基本上写了一些查询,比如:
SELECT yes, no, yes + no AS votes, yes / (yes + no) AS ratio
FROM material
WHERE ratio > 0.8
因此,此查询是在数据库级别执行的,这通常比在 Django 级别手动执行过滤运行得更快。但如前所述,将 Python 函数转换为F
表达式需要一些技能。此外,某些函数无法转换为表达式。例如,大多数数据库无法访问文件系统,无法联系Web服务等。在这种情况下,您必须手动进行过滤。
手动过滤(通常效率较低(
如果您必须手动进行过滤(出于上述原因(,我们可以使用 Python 的filter(..)
函数。请注意,这个过滤器不会返回QuerySet
,因此我们不能对它执行额外的.filter(..)
、.first()
、.annotate(..)
等函数。在这种情况下,例如,我们可以使用 lambda 表达式:
filter(lambda x: x.ratio > 0.8, Material.objects.all())
所以这里我们把所有Material
对象加载到内存中,让 Python 手动计算ratio
并执行检查。