在Django中查询全名

29

我该如何在Django中查询全名?

为了澄清,我想创建一个临时列,将first_name和last_name组合成fullname,然后在这个fullname上执行LIKE操作,像这样:

select [fields] from Users where CONCAT(first_name, ' ', last_name) LIKE '%John Smith%";

上述查询将返回所有名为John Smith的用户。如果可能,我想避免使用原始的 SQL 调用。

我要谈论的模型是特定的django.contrib.auth.models User模型。直接对模型进行更改不是问题。

例如,如果用户搜索'John Paul Smith',它应该匹配名字为'John Paul'和姓氏为'Smith'的用户,以及名字为'John'和姓氏为'Paul Smith'的用户。


你知道吗,顺便说一下,CONCAT(first_name, ' ', last_name) LIKE '%John Smith%" 这种写法非常低效吗?使用 first_name LIKE '%John' AND last_name LIKE 'Smith%' 可以更高效。既然有非 CONCAT 的方法,为什么还要使用 CONCAT 呢? - S.Lott
这是标准的django.contrib.auth.models用户模型。我们已经在该模型中添加/更改字段,因此修改不是问题。 - user719958
在这个模型中添加/更改字段,为什么不使用配置文件扩展?https://docs.djangoproject.com/en/1.3/topics/auth/#storing-additional-information-about-users - S.Lott
查询用户是一个单一字段吗?所以我不能只按空格分割。CONCAT 函数绝对依赖空格。你怎么突然不再依赖空格了呢?SQL 的哪部分不能准确反映您的要求? - S.Lott
"可能会出现的大连接"?问题在哪里?您有测量过性能吗?为什么MongoDB无法做到这一点?这个连接对您的性能有什么影响? - S.Lott
显示剩余7条评论
7个回答

64

这个问题很久以前就被发布了,但我遇到了类似的问题,并且在这里找到的答案非常糟糕。被接受的答案只允许您通过名字和姓氏进行精确匹配。第二个答案稍微好一点,但仍然很糟糕,因为每有一个单词就会命中数据库。
这是我的解决方案:将名字和姓氏连接起来进行注释,然后在此字段中搜索:

from django.db.models import Value as V
from django.db.models.functions import Concat   

users = User.objects.annotate(full_name=Concat('first_name', V(' '), 'last_name')).\
                filter(full_name__icontains=query)
例如,如果这个人的名字是John Smith,你可以通过输入john smith、john、smith、hn smi等来查找他。它只会在数据库中命中一次。我认为这将是你在公开帖子中想要的确切SQL语句。

但仍然不好,因为你会像单词一样频繁地访问数据库。Django查询是惰性的,它只会访问一次数据库。Concat注释会产生非常复杂的SQL语句。从功能上讲,唯一的区别在于你的答案允许在连接中包含(如你所指出的“hn smi”),而我的答案将允许在任何列中包含(例如搜索“John Smith”或“Smith John”都将匹配具有first_name“John Fitzgerald”和last_name“Smith”的行)。 - laffuste
这应该是被接受的答案。其他答案没有恰当地回答问题。 - I_am_learning_now
2
祝福你。在初始问题7年后发布更好的解决方案值得尊重。 - Lewis Menelaws
这个解决方案相当优雅。不确定粗糙度的相关性是什么。 - Siphiwe Gwebu
1
太好了,谢谢!以防对其他人有帮助:我最初遇到了“无法设置属性错误”,后来发现是因为我的用户模型上已经有了full_name @property。所以将上面的代码更改为使用the_full_name就解决了这个问题。 - Ffion

13

更容易:

from django.db.models import Q 

def find_user_by_name(query_name):
   qs = User.objects.all()
   for term in query_name.split():
     qs = qs.filter( Q(first_name__icontains = term) | Q(last_name__icontains = term))
   return qs

查询名称可以是“John Smith”(但也会检索任何用户的Smith John)。


我已经开始使用它了,但只要有超过一个搜索词,HTTP响应时间就从100毫秒变成了5秒钟。有任何想法是为什么吗? - abarax
首先,你必须确定问题出在哪里。是数据库?ORM?还是模板?在 SQL 客户端中执行查询(print qs.query)以查看问题是否出在数据库中。如果不是,通过排除法(逐步删除代码段),尝试猜测问题是否出在模板中。如果还不是,可能是 ORM 的问题(尝试返回较少行的查询)。你可能会面临 ORM N+1 问题(使用 select_relatedprefetch_related 来解决)。你能否通过使用 EXPLAIN(mysql/postgres)与查询(从 SQL 客户端)来猜测任何错误?你的表格是否庞大?如果是,将 first_name 和 last_name 索引化是否可以缓解问题? - laffuste
感谢这些技巧。 - abarax

5
class User( models.Model ):
    first_name = models.CharField( max_length=64 )
    last_name = models.CharField( max_length=64 )
    full_name = models.CharField( max_length=128 )
    def save( self, *args, **kw ):
        self.full_name = '{0} {1}'.format( first_name, last_name )
        super( User, self ).save( *args, **kw )

3
除非我漏掉了什么,你可以像这样使用Python查询你的数据库:
from django.contrib.auth.models import User
x = User.objects.filter(first_name='John', last_name='Smith') 

编辑: 回答你的问题:

如果用户搜索'John Smith'时需要返回'John Paul Smith',则可以使用'contains',它将被翻译成 SQL 的 LIKE。如果只需要存储名称“John Paul”,请将两个名称都放在 first_name 列中。

User.objects.filter(first_name__contains='John', last_name__contains='Smith') 

这意味着:
SELECT * FROM USERS
WHERE first_name LIKE'%John%' 
AND last_name LIKE'%Smith%'

2
如果查询是“John Paul Smith”,会发生什么? - user719958
如果 first_name == "John Paul" 或 last_name == "Paul Smith",它仍然会这样匹配。 - jdi
1
我想说的是,如果用户输入了John Paul Smith,会怎么样? - user719958
(first_name like '%John' or first_name like '%John Paul') and (last_name like 'Paul Smith%' or last_name like 'Smith%') 可以通过在 first_name 的 likes 中使用 words[:-1],在 last_name 的 likes 中使用 words[1:] 来轻松地扩展到 n 个单词。这避免了昂贵且无法索引的 CONCAT 操作。 - S.Lott

3
我使用了这个查询来搜索名字、姓氏以及全名。这解决了我的问题。
from django.db.models import Q, F
from django.db.models import Value as V
from django.db.models.functions import Concat 

user_list = models.User.objects.annotate(
                        full_name=Concat('first_name', V(' '), 'last_name')
                    ).filter(   
                        Q(full_name__icontains=keyword) | 
                        Q(first_name__icontains=keyword) | 
                        Q(last_name__icontains=keyword)
                    )

0

0
这个怎么样:
query = request.GET.get('query')
users = []

try:
    firstname = query.split(' ')[0]
    lastname  = query.split(' ')[1]
    users += Users.objects.filter(firstname__icontains=firstname,lastname__icontains=lastname)
    users += Users.objects.filter(firstname__icontains=lastname,lastname__icontains=firstname)

users = set(users)

经过尝试和测试!


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