基于文本长度的Django过滤器


Answers:


-18

这将是更好更快,如果你只是增加一列,预先计算(memoizes)文本的长度。

例如

class MyModel(models.Model):
    text = models.TextField()
    text_len = models.PositiveIntegerField()

     def save(self, *args, **kwargs):
         self.text_len = len(self.text)
         return super(MyModel, self).save(*args, **kwargs)

MyModel.objects.filter(text_len__gt = 10)     # Here text_len is pre-calculated by us on `save`

是因为文本字段未建立索引,并且每次查询命中数据库时都会计算文本长度。lain建议的解决方案也可以这样做(不是该解决方案对我不起作用)。
ashish 2012年

@ashish 1)是的,它是预先计算的。2)没有缺点就不会做同样的事情。
rantanplan

1)因此,如果长度是预先计算的,那么为什么我需要再加上一列2)如果字符出现的次数大于n,则lain的解决方案不会检查每个表达式。
ashish 2012年

1
@ashish我在以上代码的最后一行添加了注释。我们在模型中添加一列以存储的长度text。每次修改文本时都会更新。因此,当我们查询模型时,我们可以根据我们在save方法中预先计算出的文本长度进行过滤。
rantanplan

212

对于Django> = 1.8,您可以使用Length函数,它是CHAR_LENGTH()MySQL或LENGTH()其他一些数据库的@Pratyush函数

from django.db.models.functions import Length
qs = MyModel.objects.annotate(text_len=Length('text_field_name')).filter(
    text_len__gt=10)

1
假设我不想过滤查询集,而是将对象返回text_len__gt=10到第一位(order_by)。有什么提示吗?
vabada

3
@dabad,您可以使用text_len 注释中你可以使用任何其他数据库的相同方式领域,因此它可以在order_bySum或什么的。要以减小的文本长度顺序对结果进行排序并返回长度值:MyModel.objects.annotate(text_len=Length('text_field_name')).order_by('-text_len').values_list('text_len', flat=True)
滚刀

1
@guettli接受的答案的一个问题是,原始海报上次在2015年9月在SO上看到,您钦佩的利他主义是唯一的可能性:-)我必须编辑此答案才能投票。我为Django> = 1.9添加了类似的答案,它不需要注释,但需要LengthTransform的全局注册。
hynekcer

1
这在文档中很难找到,因为它没有与其他聚合分组,例如Sum。在许多情况下,这也非常重要。我遇到了一种情况,我需要预先检查查询可能返回的最大数据大小,这不会使服务器的内存不足,并且这种情况的一种变体效果很好。
AlanSE '17

@AlanSE之所以没有使用Sum和其他聚合来记录它,是因为它不是聚合。它适用于单个记录(行)。结果查询集具有与运行Length运算符之前相同的行数。因此,它称为转换映射。聚合减少了记录数。映射没有。
滚刀

59

另一种方法是:

MyModel.objects.extra(where=["CHAR_LENGTH(text) > 300"])

如果文本长度也超过255个字符,则可以使用此选项。


4
如果您有sqlite,那就是LENGTH(..)
Andrei-Niculae Petre

43

通过将内置函数注册Length为用于查找的Transform,可以为Django> = 1.9提供一个不错的解决方案CharField

在项目中注册一次转换。(最好的地方可能是models.py。)

from django.db.models import CharField
from django.db.models.functions import Length

CharField.register_lookup(Length, 'length')

用途

result = MyModel.objects.filter(text__length__gt=10)

请参阅docs中与Length作为转换的完全相同的示例。


它适用于所有后端,由LENGTH()大多数后端和CHAR_LENGTH()MySQL编译。然后,它会自动为CharField的所有子类注册,例如为EmailField注册。将TextField必须单独注册。注册名称“长度”是安全的,因为转换名称永远不会被同名字段名或相关字段名遮蔽或遮蔽。

唯一的缺点可能是可读性之谜:“长度”从何而来?(查找是全局的,但是幸运的是,如果对可读性有用,则可以将其安全地重复注册到更多模块中,而不会在查询运行时产生任何开销。)

其他类似的有价值的解决方案是上面的滚刀,如果注册计数并且如果不重复使用类似的查询,则较短。


@guettli出乎意料的是,您在开始悬赏之前先写了一个解决方案?我也以一种奇怪的顺序做到了这一点:我从Django的源代码中找到了解决方案的详细信息,然后发现所有这些最终都在文档中,您首先知道了该解决方案。
hynekcer '17

自从接受的问题(很不幸的是,仍然是最重要的问题)已经过时以来,我就开始悬赏。我希望Length函数(> = Django 1.8)的答案越来越多。AFAIK发生了这种情况,但不幸的是,过时的答案仍然是最重要的。
guettli

29

您可以使用正则表达式过滤器来搜索特定长度的文本:

MyModel.objects.filter(text__regex = r'.{10}.*')

注意:对于MySQL,最大长度值为255。否则将引发异常:

DatabaseError: (1139, "Got error 'invalid repetition count(s)' from regexp")

3
正如文档所述Using raw strings (e.g., r'foo' instead of 'foo') for passing in the regular expression syntax is recommended.
Sergey Goliney 2012年

执行代码OperationalError:(1139,“从regexp获得错误'无效的重复计数(s)'”)后出现此异常 ,这是由于花括号引起的。
ashish 2012年

实际上,上述异常基本上是mysql异常。
ashish 2012年

这工作正常,为任意数量低于256 MySQL有256的最大重复计数
周华健Stenström

2
@ emil-stenstrom实际上是255
glarrain

-6

我会在您的应用服务器上解决该问题,而不会对数据库征税。您可以通过以下方式做到这一点:

models_less_than_ten = []
mymodel = MyModel.objects.all()
for m in mymodel:
    if len(m.text) > 10:
          models_less_than_ten.append(m)

2
对于MyModel中的许多行,这不能很好地缩放。如果您有100,000行,那么对数据库进行一次艰苦的工作并决定不发送行,要比向应用程序服务器发送大量数据以进行过滤的负担少。在db上进行工作几乎总是更好,并且如果它太慢或太费力,可以优化查询。
nevelis
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.