来自ManyToManyField的Django最大相似度(TrigramSimilarity)

Luk*_*kas 8 python django postgresql django-queryset trigram

我必须实现一个容错的搜索功能.
目前,我有以下情况:

楷模:

class Tag(models.Model):
    name = models.CharField(max_length=255)

class Illustration(models.Model):
    name = models.CharField(max_length=255)
    tags = models.ManyToManyField(Tag)
Run Code Online (Sandbox Code Playgroud)

查询:

queryset.annotate(similarity=TrigramSimilarity('name', fulltext) + TrigramSimilarity('tags__name', fulltext))
Run Code Online (Sandbox Code Playgroud)

示例数据:

插图:

ID |  Name  |        Tags       |
---|--------|-------------------|
 1 | "Dog"  | "Animal", "Brown" |
 2 | "Cat"  | "Animals"         |
Run Code Online (Sandbox Code Playgroud)

插图有标签:

ID_Illustration | ID_Tag |
----------------|--------|
       1        |    1   |
       1        |    2   |
       2        |    3   |
Run Code Online (Sandbox Code Playgroud)

标签:

ID_Tag |   Name   |
-------|----------|
   1   |  Animal  |
   2   |  Brown   |
   3   |  Animals |
Run Code Online (Sandbox Code Playgroud)

当我运行查询时"Animal",相似性"Dog"应该高于for "Cat",因为它是完美的匹配.
不幸的是,两个标签都以某种方式一起考虑.
目前,它看起来像是在一个字符串中连接标签,然后检查相似性:

TrigramSimilarity("Animal Brown", "Animal") => X
Run Code Online (Sandbox Code Playgroud)

但我想以一种方式对其进行调整,以使Illustration实例名称与其标签之间具有最高的相似性:

Max([
    TrigramSimilarity('Name', "Animal"), 
    TrigramSimilarity("Tag_1", "Animal"), 
    TrigramSimilarity("Tag_2", "Animal"),
]) => X
Run Code Online (Sandbox Code Playgroud)

Edit1:我正在尝试查询所有插图,其中标题或其中一个标签的相似度大于X.

Edit2:附加示例:

fulltext ='动物'

TrigramSimilarity('Animal Brown',fulltext)=> x TrigramSimilarity('Animals',fulltext)=> y

其中x <y

但实际上我想要的是

TrigramSimilarity(Max(['Animal','Brown]),fulltext)=> x(与Animal相似)TrigramSimilarity('Animals',fulltext)=> y

其中x> y

Joh*_*fis 6

你不能分手tags__name(至少我不知道)。
从您的示例中,我可以假设2种可能的解决方案(第一种解决方案并非严格使用Django):


  1. 并非所有内容都必须严格通过Django
    我们具有Python功能,因此让我们使用它们:

    让我们首先组成查询:

    from difflib import SequenceMatcher
    
    from django.db.models import Q
    
    def create_query(fulltext):
        illustration_names = Illustration.objects.values_list('name', flat=True)
        tag_names = Tag.objects.values_list('name', flat=True)
        query = []
    
        for name in illustration_names:
            score = SequenceMatcher(None, name, fulltext).ratio()
            if score == 1:
                # Perfect Match for name
                return [Q(name=name)]
    
             if score >= THRESHOLD:
                query.append(Q(name=name))
    
        for name in tag_names:
            score = SequenceMatcher(None, name, fulltext).ratio()
            if score == 1:
                # Perfect Match for name
                return [Q(tags__name=name)]
    
             if score >= THRESHOLD:
                query.append(Q(tags__name=name))
    
        return query
    
    Run Code Online (Sandbox Code Playgroud)

    然后创建您的查询集:

    from functools import reduce # Needed only in python 3
    from operator import or_
    
    queryset = Illustration.objects.filter(reduce(or_, create_query(fulltext)))
    
    Run Code Online (Sandbox Code Playgroud)

    解码以上内容:

    我们将对照我们的每个IllustrationTag名称进行检查,并fulltext使用每个相似性都通过的名称组成一个查询THRESHOLD

    • SequenceMatcher方法比较序列并返回比率0 < ratio < 1,其中0表示No-Match,1表示Perfect-Match。查看此答案以获取另一个用法示例:查找两个字符串之间的相似性百分比注意:还有其他字符串比较模块,请找到一个适合您的模块)
    • Q() Django对象,允许创建复杂的查询(有关链接文档的更多信息)。
    • 使用operatorreduceQ()对象列表转换为OR分隔的查询参数:
      Q(name=name_1) | Q(name=name_2) | ... | Q(tag_name=tag_name_1) | ...

    注意: 您需要定义一个acceptable THRESHOLD
    可以想象,这会有点慢,但是在需要进行“模糊”搜索时可以预料到。


  1. (Django方式:)
    使用具有较高相似性阈值的查询,并以该相似率对查询集进行排序:

    queryset.annotate(
        similarity=Greatest(
            TrigramSimilarity('name', fulltext), 
            TrigramSimilarity('tags__name', fulltext)
        )).filter(similarity__gte=threshold).order_by('-similarity')
    
    Run Code Online (Sandbox Code Playgroud)

    解码以上内容:

    • Greatest()接受aggregate表达式或模型字段的集合(不要与Django方法混淆)并返回max项目。
    • TrigramSimilarity(word, search)返回0和1之间的速率越接近率是1,则越相似wordsearch
    • .filter(similarity__gte=threshold),将过滤相似度低于的相似度threshold
    • 0 < threshold < 1。您可以将阈值设置0.6得很高(考虑默认值是0.3)。您可以尝试一下以调整性能。
    • 最后,按照similarity降序对查询集进行排序。