使用多个术语和 Q 过滤器对 reduce 查询进行排序

Posted

技术标签:

【中文标题】使用多个术语和 Q 过滤器对 reduce 查询进行排序【英文标题】:sorting a reduce query with multiple terms and Q filters 【发布时间】:2015-08-19 21:48:47 【问题描述】:

我正在尝试创建一个从模型中查询多个属性的搜索功能。为了使事情变得更难,我希望能够在列表理解中使用多个术语来完成它,然后按更准确的结果进行排序。

例如,如果搜索项是 ['green', 'shoe'] 并且我有一个名为 'green shoe' 的对象,我希望它成为我结果中的第一项,然后是 'black shoe''green pants'

这是我到目前为止从查询参数中提取搜索词然后运行 ​​Q 查询的内容。

def get_queryset(self):
    search_terms = self.request.GET.getlist('search', None)
    terms = []
    x = [terms.extend(term.lower().replace('/', '').split(" ")) 
         for term in search_terms]
    # x is useless, but it is just better to look at. 
    results = reduce(operator.or_, 
                     (Item.objects.filter(Q(name__icontains=term) | 
                                          Q(description__icontains=term) | 
                                          Q(option__name__icontains=term)) 
                      for term in terms))
    return results

这将返回无序的['black shoe', 'green pants', 'green shoe'],但它是所有匹配结果。

我意识到我可以让它不将搜索词分成多个词,只会得到一个结果,但我也不会得到其他类似的东西。

感谢观看

编辑 1

所以在第一个答案之后,我开始玩弄它。现在这产生了我想要的结果,但由于将查询集添加到列表中,我觉得这可能很糟糕。让我知道你的想法:

def get_queryset(self):
    search_terms = self.request.GET.getlist('search', None)
    if not search_terms or '' in search_terms or ' ' in search_terms:
        return []
    terms = [term.lower().replace('/', '').split(" ") for term in search_terms][0]
    results = reduce(operator.or_,
                     (Item.objects.filter
                      (Q(name__icontains=term) | Q(description__icontains=term) | Q(option__name__icontains=term))
                      for term in terms))

    # creating a list so I can index later
    # Couldn't find an easy way to index on a generator/queryset
    results = list(results)

    # Using enumerate so I can get the index, storing index at end of list for future reference
    # Concats the item name and the item description into one list, using that for the items weight in the result
    results_split = [t.name.lower().split() + t.description.lower().split() + list((x,)) for x, t in enumerate(results)]
    query_with_weights = [(x, len(search_terms[0].split()) - search_terms[0].split().index(x)) for x in terms]
    get_weight = lambda x: ([weight for y, weight in query_with_weights if y==x] or [0])[0]
    sorted_results = sorted([(l, sum([(get_weight(m)) for m in l])) for l in results_split], key=lambda lst: lst[1], reverse=True)

    # Building the final list based off the sorted list and the index of the items.
    final_sorted = [results[result[0][-1]] for result in sorted_results]
    print results_split
    print query_with_weights
    print final_sorted
    return final_sorted

[red, shoes, pants] 的查询将打印出以下内容:

# Combined name and description of each item
[[u'red', u'shoe', u'sweet', u'red', u'shoes', u'bro', 0], [u'blue', u'shoe', u'sweet', u'blue', u'shoes', u'bro', 1], [u'red', u'pants', u'sweet', u'red', u'pants', u'bro', 2], [u'blue', u'pants', u'sweet', u'blue', u'pants', u'bro', 3], [u'red', u'swim', u'trunks', u'sweet', u'red', u'trunks', u'bro', 4]]

# Weighted query
[(u'red', 3), (u'shoes', 2), (u'pants', 1)]

# Final list of sorted items from queryset
[<Item: Red Shoe>, <Item: Red Pants>, <Item: Red Swim Trunks>, <Item: Blue Shoe>, <Item: Blue Pants>]

【问题讨论】:

【参考方案1】:

这不完全是 QuerySet 问题。

这需要一个单独的算法来决定您创建的结果集的顺序。我会编写一个新的算法来决定排序 - 可能是整个算法数组,因为您的结果将取决于查询本身的category

现在我可以考虑为结果集中的每个结果添加权重,根据一些参数决定它与完成查询的接近程度。

在您的情况下,您的参数如下:

匹配了多少字? 首先出现的单词应该获得最高优先级 任何完全匹配的查询也应该具有最高优先级 查询远端的单词优先级最低

无论如何,这是一个开始的想法,我相信您可能会有更复杂的想法。

所以这里是创建排序的代码:

query = 'green shoe'
query_with_weights = [(x, len(query.split()) - query.split().index(x)) for x in query.split()]
results = ['black pants', 'green pants', 'green shoe']
results_split = [res.split() for res in results]

get_weight = lambda x: ([weight for y, weight in query_with_weights if y==x] or [0])[0]
sorted_results = sorted([ (l, sum([( get_weight(m)) for m in l])) for l in results_split], key = lambda lst: lst[1], reverse=True)
print('sorted_results='.format(sorted_results))

一旦你尝试这个,你会得到以下结果:

sorted_results=[(['green', 'shoe'], 3), (['green', 'pants'], 2), (['黑色', '裤子'], 0)]

我希望这能解释这一点。但是,此算法仅适用于简单文本。您可能必须根据电气项目更改算法,例如,如果您的网站依赖于它。有时您可能需要查看对象本身的属性。这应该是一个很好的开端。

【讨论】:

首先,谢谢。这真的让我走上了正轨。请参阅编辑 1。 我再次查看了您的编辑。好吧,当我看到较小的图片时很难说。这段代码能解决你所有的情况吗?如果是,那么保持这样就很好。话虽如此,如果您愿意,您可以尝试直接处理对象,而无需将结果集更改为列表。在这种情况下,您可能可以更好地访问对象本身 我不确定我是否知道如何为查询集建立索引以构建最终结果列表。有没有一种简单的方法可以根据它拥有的原始索引和它应该是的新索引来重新组织查询集? 如果你不介意看一下代码审查,我发了一篇文章:codereview.stackexchange.com/questions/92847/…

以上是关于使用多个术语和 Q 过滤器对 reduce 查询进行排序的主要内容,如果未能解决你的问题,请参考以下文章

在弹性搜索中不过滤的情况下提升匹配术语查询的结果

Django Q过滤器,无法在单个查询中获得结果

在多表列中具有多个术语的简单 PHP 过滤器

Django 结合两个没有 Q 的查询

lucene卷曲查询的多个术语

Drupal Views - 按多个分类术语过滤