使用 SearchVector 查询时出现重复结果

Aze*_*zer 5 python django django-models django-queryset python-3.x

我有一个名为的抽象模型类,PostType并有一些从中派生的子模型。该模型如下所示:


class PostType(models.Model):
    title = models.CharField(
        max_length=255,
        unique=True,
        verbose_name='Title'
    )
    description = models.CharField(
        max_length=255,
        default='',
        verbose_name='Description'
    )
    tags = models.ManyToManyField(
        to=Tag,
        blank=True,
        verbose_name='Tags'
    )
    content = RichTextUploadingField(
        default='',
        verbose_name='Post Content'
    )

    class Meta:
        abstract = True

Run Code Online (Sandbox Code Playgroud)

它有一个相关的标签模型,如下所示:


class Tag(models.Model):
    name = models.CharField(
        max_length=255,
        verbose_name='Name',
        unique=True
    )
    description = models.TextField(
        verbose_name='Description',
        default=''
    )

Run Code Online (Sandbox Code Playgroud)

在我的一种观点中,我尝试使用 aSearchVector并搜索多个字段来查询所有子类:


query = request.GET.get('s')

from django.contrib.postgres.search import SearchVector
get_qs_list = [model.objects.annotate(
    search=SearchVector('title', 'description', 'content', 'tags__name')
).filter(search=query) for model in models.PostType.__subclasses__()]

Run Code Online (Sandbox Code Playgroud)

现在,搜索正在返回所有应有的结果。但是,由于某种原因,它会返回某些项目的重复结果。

'tags__name'如果我从 中删除,问题似乎就会消失SearchVector,但我不明白为什么。

我做错了什么,如果我不搜索相关领域,为什么它会起作用?

编辑:

Tag我刚刚意识到,对于与该项目相关的每个项目,我都会得到重复的结果。因此,如果该项目具有三个标签,我将在搜索结果中获得该项目的三个副本。我仍然不明白为什么会发生这种情况,非常感谢您的建议!

Lis*_*Lis 4

我知道这个问题很老了,但我今天一直在努力解决一个非常类似的问题并找到了解决方案。如果你看一下搜索向量的定义:

search = SearchVector(
    'title',
    'description',
    'content',
    'tags__name',
)
Run Code Online (Sandbox Code Playgroud)

解释

您可能会注意到,虽然titledescriptioncontent是模型上的字段PostType,但tags__name指的是相关模型Tag。在这种情况下,关系不是一对一的 - 可能有多个标签与某个 PostType 相关。如果具有多个标签的搜索结果与您的搜索“匹配”,那么您将获得完全相同的结果,而 Djangodistinct()不会帮助您(至少不会以我所知道的任何方式)。

但是,由于您使用的是 postgres,因此您还可以利用另一个功能ArrayAgg。它允许您使用所有标签的聚合值来注释您的查询集,稍后使用就像它是 PostType 上的字段一样:

from django.contrib.postgres.aggregates import ArrayAgg

# 1. annotate with aggregated tag name values
queryset = queryset.annotate(
    tags_names=ArrayAgg(
        "tags__name",
        distinct=True,
    ),
)

# 2. use the annotation in the SearchVector
search = SearchVector(
    'title',
    'description',
    'content',
    'tags_names',  # <-- note: not `tags__name`
)
Run Code Online (Sandbox Code Playgroud)

对于每个现在PostType只有一个tags_names由聚合值组成tags__name,因此换句话说 - 是相关标签名称的列表;SearchVector现在可以像任何其他领域一样使用。

最终解决方案

旁注:除此之外,为了获得更好的结果,您还应该检查是否search__icontains=query.

from django.contrib.postgres.search import SearchVector
from django.contrib.postgres.aggregates import ArrayAgg
from django.db.models import Q

query = request.GET.get('s')

get_qs_list = [
    model.objects.annotate(
        tags_names=ArrayAgg("tags__name", distinct=True),
        search=SearchVector(
            'title',
            'description',
            'content',
            'tags_names',
        ),
    ).filter(
        Q(search=query) | Q(search__icontains=query)
    ) for model in models.PostType.__subclasses__()
]
Run Code Online (Sandbox Code Playgroud)