在视图中使用全文搜索 + GIN (Django 1.11 )

Posted

技术标签:

【中文标题】在视图中使用全文搜索 + GIN (Django 1.11 )【英文标题】:Use of full-text search + GIN in a view (Django 1.11 ) 【发布时间】:2018-05-02 20:12:18 【问题描述】:

我需要一些帮助来在 django 视图中构建正确的查询,以便使用 GIN 索引进行全文搜索。我有一个相当大的数据库(约 40 万行),需要对其中的 3 个字段进行全文搜索。尝试使用django docs search,这是 GIN 之前的代码。它有效,但搜索所有字段需要 6 秒以上的时间。接下来我尝试实现一个GIN 索引来加快我的搜索速度。如何构建它已经存在很多问题。但我的问题是 - 使用 GIN 索引进行搜索时,视图查询有何变化?我应该搜索哪些字段? 在 GIN 之前:models.py

class Product(TimeStampedModel):
    product_id = models.AutoField(primary_key=True)
    shop = models.ForeignKey("Shop", to_field="shop_name")
    brand = models.ForeignKey("Brand", to_field="brand_name")
    title = models.TextField(blank=False, null=False)
    description = models.TextField(blank=True, null=True)

views.py

   
def get_cosmetic(request):
    if request.method == "GET":
        pass
    else:
        search_words = request.POST.get("search")
        search_vectors = (
            SearchVector("title", weight="B")
            + SearchVector("description", weight="C")
            + SearchVector("brand__brand_name", weight="A")
        )

        products = (
            Product.objects.annotate(
                search=search_vectors, rank=SearchRank(search_vectors, search)
            )
            .filter(search=search_words)
            .order_by("-rank")
        )

        return render(request, "example.html", "products": products)

在 GIN 之后: models.py

class ProductManager(models.Manager):
    def with_documents(self):
        vector = (
            pg_search.SearchVector("brand__brand_name", weight="A")
            + pg_search.SearchVector("title", weight="A")
            + pg_search.SearchVector("description", weight="C")
        )
        return self.get_queryset().annotate(document=vector)


class Product(TimeStampedModel):
    product_id = models.AutoField(primary_key=True)
    shop = models.ForeignKey("Shop", to_field="shop_name")
    brand = models.ForeignKey("Brand", to_field="brand_name")
    title = models.TextField(blank=False, null=False)
    description = models.TextField(blank=True, null=True)

    search_vector = pg_search.SearchVectorField(null=True)

    objects = ProductManager()

    class Meta:
        indexes = [
            indexes.GinIndex(
                fields=["search_vector"],
                name="title_index",
            ),
        ]

    # update search_vector every time the entry updates
    def save(self, *args, **kwargs):
        super().save(*args, **kwargs)
        if (
            "update_fields" not in kwargs
            or "search_vector" not in kwargs["update_fields"]
        ):
            instance = (
                self._meta.default_manager
                .with_documents().get(pk=self.pk)
            )
            instance.search_vector = instance.document
            instance.save(update_fields=["search_vector"])

views.py

def get_cosmetic(request):
    if request.method == "GET":
        pass

    else:
        search_words = request.POST.get('search')    
        products = ?????????
        return render(request, 'example.html', "products": products)

【问题讨论】:

为什么我们不能将search_vector直接分配给instance.search_vector?我无法理解,请为我解释。谢谢 @achilles 正如在文档中所说的那样 (docs.djangoproject.com/en/2.0/ref/models/instances/…) - .save(update_fields=['search_vector']) 只能更新选定的字段并提高性能。如果我正确理解了一个问题。 我在问我们为什么要这样做 instance = self._meta.default_manager.with_documents().get(pk=self.pk) instance.search_vector = instance.document 为什么我们不能这样分配:instance.search_vector = SearchVector(...) @achilles 我认为你是对的,它也可以工作(但我不确定)——这个食谱的一部分来自答案的链接。可能只是通过调用管理器方法来简化查询集调用。如果您对此进行测试-请分享结果,我很好奇。 【参考方案1】:

回答我自己的问题:

products = (
    Product.objects.annotate(rank=SearchRank(F("search_vector"), search_words))
    .filter(search_vector=search_words)
    .order_by("-rank")
)

这意味着您应该搜索索引字段 - 在我的情况下为 search_vector 字段。 我还在 ProductManager() 类中更改了一些代码,所以现在我可以使用

products = Product.objects.with_documents(search_words)

其中with_documents() 是custom ProductManager() 的自定义函数。此更改的秘诀是 here (page 29)。所有这些代码的作用:

    为字段创建带有分数的 search_vector,得分越大的字段 - 在结果排序中获得更高的位置。 为通过 ORM Django 的全文搜索创建 GIN 索引 每次更改模型实例时更新 GIN 索引 此代码不能做什么: 它不按查询的子字符串的相关性排序。 Possible solution. 希望这对在 Django 中进行有点复杂的全文搜索的人有所帮助。

【讨论】:

here (page 30) 不工作。 @AnshulTiwari 啊,几个月前我检查过它,它仍在工作:( @AnshulTiwari 看看这个演示文稿 - ep2017.europython.eu/media/conference/slides/… 它看起来与我之前的基础相似(第 29 页)

以上是关于在视图中使用全文搜索 + GIN (Django 1.11 )的主要内容,如果未能解决你的问题,请参考以下文章

Django Admin 搜索查询未命中 Postgres 索引

全文搜索的索引列

全文检索-02 | Django开发

如何使用 QuerySets 和 MySql“全文搜索”在多个字段中进行 Django 搜索?

使用haystack实现Django的全文搜索 -- Elasticsearch搜索引擎

Django:如何在 Postgresql 中对日语(多字节字符串)进行全文搜索