Django查询不区分大小写的列表匹配

54
我有一个名字列表,想要进行不区分大小写的匹配。有没有一种方法可以避免使用像下面这样的循环呢?
我有一个名字列表,想要进行不区分大小写的匹配。有没有一种方法可以避免使用像下面这样的循环呢?
a = ['name1', 'name2', 'name3']
result = any([Name.objects.filter(name__iexact=name) for name in a])
9个回答

66

很不幸,不存在任何__iin字段查找。 但是有一个iregex可能会有用,用法如下:

result = Name.objects.filter(name__iregex=r'(name1|name2|name3)')

或者甚至:

a = ['name1', 'name2', 'name3']
result = Name.objects.filter(name__iregex=r'(' + '|'.join(a) + ')')

请注意,如果a中包含在正则表达式中具有特殊含义的字符,你需要适当转义它们。

消息:在 Django 1.7+ 中,可以创建自己的查找(lookups),因此在正确初始化后,您实际上可以使用filter(name__iin=['name1', 'name2', 'name3'])。有关详细信息,请参见文档参考


2
Postgres支持不区分大小写的索引,因此对于这种情况,运行每个项目的单独“iexact”查询可能比iregex匹配更快。在Django的postgres后端中,“iexact”搜索使用UPPER()转换,因此通过对该行的UPPER()进行自定义索引,可以实现加速。 - Evgeny
19
我希望他们能够实现__iin。 - JREAM
@Evgeny,希望您能添加一个答案或给我们提供一个链接。谢谢! - Grijesh Chauhan
@GrijeshChauhan 当然,看看我下面的帖子。 - Evgeny
2
有两件事需要考虑:(1) 正则表达式转义,如@Martin Smith在下面进一步提到的,以及 (2) 如果您想要一个 __in 运算符,请确保您正在使用起始和结束定界符 result = Name.objects.filter(name__iregex=r'^(' + '|'.join([re.escape(b) for b in a]) + ')$') - blacklwhite

33

使用Django查询函数和注释还有另一种方法

from django.db.models.functions import Lower
Record.objects.annotate(name_lower=Lower('name')).filter(name_lower__in=['two', 'one']

如果我没记错的话,请记住,除非在“name”字段中已经以Lower('name')的方式创建了索引,否则无法使用任何索引:https://dev59.com/T2w05IYBdhLWcg3w0VKa#7005656 - Chesco Igual

29

在PostgreSQL中,您可以尝试创建一个不区分大小写的索引,方法在此处描述:

https://dev59.com/YW855IYBdhLWcg3w0H93#4124225

然后运行查询:

from django.db.models import Q
name_filter = Q()
for name in names:
    name_filter |= Q(name__iexact=name)
result = Name.objects.filter(name_filter)

索引查询会比正则匹配查询运行得更快。


谢谢!我明白了。 - Grijesh Chauhan
4
注意这段代码!如果变量名为空,则.filter将返回该模型的所有对象!请小心。 - Benjamin
当列表增长时,这个方法能够工作,但速度会迅速下降。 - Martin Faucheux

5
请记住,在MySQL中,您必须在表中设置utf8_bin排序规则才能使它们区分大小写。否则,它们是大小写保留但不区分大小写的。例如:
>>> models.Person.objects.filter(first__in=['John', 'Ringo'])
[<Person: John Lennon>, <Person: Ringo Starr>]
>>> models.Person.objects.filter(first__in=['joHn', 'RiNgO'])
[<Person: John Lennon>, <Person: Ringo Starr>]

因此,如果可移植性并不重要,而且您使用的是MySQL,则可以选择完全忽略此问题。

5

继Rasmuj所说的,要对任何用户输入进行转义,如下所示:

import re
result = Name.objects.filter(name__iregex=r'(' + '|'.join([re.escape(n) for n in a]) + ')')

2
尝试了多种方法(包括使用“annotate”),但都导致了重复对象。后来我发现了转换器(transformers)(https://docs.djangoproject.com/en/4.1/howto/custom-lookups/#a-transformer-example),它可以提供一个简单的解决方案。
在模型声明之前,将以下内容添加到“models.py”中:
class LowerCase(models.Transform):
    lookup_name = "lower"
    function = "LOWER"


models.CharField.register_lookup(LowerCase)
models.TextField.register_lookup(LowerCase)

现在您可以在任何查找中使用__lower转换器,例如field__lower__in。您还可以将bilateral = True添加到转换器类中,以便它适用于字段和列表项,这应该在功能上等同于__iin


2
我正在将Exgeny的想法扩展成一个两行代码的概念。
import functools
Name.objects.filter(functools.reduce(lambda acc,x: acc | Q(name_iexact=x)), names, Q()))

1
这是一个自定义用户模型的示例classmethod,用于按不区分大小写的电子邮件过滤用户。
from django.db.models import Q

@classmethod
def get_users_by_email_query(cls, emails):
    q = Q()
    for email in [email.strip() for email in emails]:
        q = q | Q(email__iexact=email)
    return cls.objects.filter(q)

0
如果这是任何人的常见用例,您可以通过调整Django的InIExact转换器的代码来实现此功能。
请确保在所有模型声明之前导入以下代码:
from django.db.models import Field
from django.db.models.lookups import In


@Field.register_lookup
class IIn(In):
    lookup_name = 'iin'

    def process_lhs(self, *args, **kwargs):
        sql, params = super().process_lhs(*args, **kwargs)

        # Convert LHS to lowercase
        sql = f'LOWER({sql})'

        return sql, params

    def process_rhs(self, qn, connection):
        rhs, params = super().process_rhs(qn, connection)

        # Convert RHS to lowercase
        params = tuple(p.lower() for p in params)

        return rhs, params

使用示例:

result = Name.objects.filter(name__iin=['name1', 'name2', 'name3'])

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接