【问题标题】:Return exact matches at top of Django Queryset在 Django 查询集顶部返回完全匹配
【发布时间】:2012-07-09 13:13:48
【问题描述】:

我有一个名为“用户”的 django 模型,它存储了一些关于人的基本信息,即名字和姓氏。我目前在我的 django 模型中进行了简单搜索,如果用户输入 first 名称,django 查询集将返回前 10 个匹配项,按 last 名称排序。

例如,目前,如果您搜索“Sam”,您可能会得到以下结果:

  1. 山姆·雅培
  2. 塞缪尔·贝克
  3. 萨米·罗杰斯
  4. 山姆·西蒙斯

代码很简单:

User.objects.filter(Q(first__istartswith=token)).order_by('last')

但是,我想更改此设置,以便首先返回任何 精确 名字匹配,然后是其余结果。因此,如果有人输入“Sam”,结果应该是:

  1. 山姆·雅培
  2. 山姆·西蒙斯
  3. 塞缪尔·贝克
  4. 萨米·罗杰斯

(确切的名字首先匹配,按姓氏排序,然后是其余匹配,按姓氏排序)。

我想把它变成 2 个查询集,然后只是组合列表,但我想知道是否可以在 1 个查询中做到这一点,最好坚持使用基本的 django 查询集 API(而不是编写一次性查询)。有谁知道这样做的方法吗?

提前致谢。

【问题讨论】:

  • 你也在使用django.contrib.auth.models.User吗?如果是这样,first_namelast_name 是默认字段名称。
  • 不,这是一个不同的自定义模型。

标签: python django django-queryset


【解决方案1】:

我认为仅使用一个查询(至少使用 Django ORM)是不可能做到这一点的。

所以您的 2 个查询应该如下所示:

limit = 10
q1 = User.objects.filter(first__iexact=token).order_by('last')[:limit]
limit -= len(q1)
if limit:
    q2 = User.objects.exclude(pk__in=q1).filter(first__istartswith=token).order_by('last')[:limit]
else:
    q2 = []
users = list(q1) + list(q2)

另一种方法是在 python 中过滤查询,但您必须获取所有结果,而不仅仅是最后 10 个:

query = User.objects.filter(first__istartswith=token).order_by('last')
exacts = [user for user in query if user.first == token]
others = [user for user in query if user.first != token]
users = exacts + others

【讨论】:

  • 是的,我认为这是最好的方法。我可能最终会这样做,除了使用弗朗西斯下面建议的 itertools.chain,因为这应该比构建 2 个列表并将它们组合起来更快。谢谢!
  • 我很高兴我学会了 itertools 链技巧。但在你的情况下,如果你的限制真的是 10,那不应该改变任何事情。查询将花费大部分时间,列表创建和连接时间将是微不足道的。
【解决方案2】:
# Get exact matches first
qs1 = User.objects.filter(first__iexact=token).order_by('last')

# get secondary results second
qs2 = User.objects.filter(first__istartswith=token).exclude(qs1).order_by('last')

result = itertools.chain(qs1, qs2)

还可以看看这个问题,它比您可能需要的更深入:How to combine 2 or more querysets in a Django view?

【讨论】:

  • 很好的解决方案,尽管我最终接受了另一个解决方案,因为它考虑了 10 项限制。(另外我认为 exclude() 中有一个小错字,我认为应该是 exclude(pk__in =qs1))。
【解决方案3】:

这并不漂亮,我个人建议使用搜索引擎,例如django-haystack,但是,如果您知道您使用的是什么数据库,您可以使用QuerySet.extra 添加一个字段来对记录进行排序:

extra = {'is_exact': "%s.first LIKE '%s'" % (User._meta.db_table, token)}
User.objects.filter(Q(first__istartswith=token)).extra(select=extra).order_by('-is_exact', 'last')

【讨论】:

    【解决方案4】:

    我认为您实际上可能需要全文搜索来完成此操作。查看djang-sphinx

    如果您的示例与您的完整用例一样复杂,您可以只处理用户代码中的排序和排序。

    【讨论】:

    • 感谢您的建议。在这种情况下,我认为不需要全文搜索,因为我在名称上有索引,所以可以在 django ORM 调用中做一个开始。
    【解决方案5】:

    您可以按多个属性排序。

    User.objects.filter(Q(first__istartswith=token)).order_by('first', 'last')
    

    因此,您首先按first 排序,以便您的对象根据完全匹配和后续匹配获得过滤器。然后你在last 上再次订购,按照姓氏排序。

    【讨论】:

    • 但是这不是先按名字对整个列表进行排序吗?所以我最终会换掉 Sammy Rogers 和 Samuel Baker,对吧?
    • @Chad 是的,它会,但不应该是这样。恕我直言,我觉得这是预期的行为。
    • 不,在这种情况下,这不是我们想要的,很遗憾。需要是 2 个组,名字完全匹配,然后是其他所有内容,并且每个组都需要按姓氏排序。
    猜你喜欢
    • 2012-03-26
    • 2018-10-20
    • 2021-06-28
    • 1970-01-01
    • 2020-03-25
    • 2017-10-25
    • 1970-01-01
    • 1970-01-01
    • 2014-11-01
    相关资源
    最近更新 更多