在 Django 1.11 中使用全文搜索和 GIN 视图

8

我需要在Django视图中构建适当的查询来使用GIN索引进行全文搜索。我有一个相当大的数据库(~400k行),需要在其中的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
@achilles 正如文档中所述(https://docs.djangoproject.com/en/2.0/ref/models/instances/#specifying-which-fields-to-save),`.save(update_fields=['search_vector'])` 可以仅更新选定的字段并提高性能。如果我正确理解了问题。 - Chiefir
我在问为什么我们要这样做:instance = self._meta.default_manager.with_documents().get(pk=self.pk) instance.search_vector = instance.document为什么不能像这样赋值:instance.search_vector = SearchVector(...) - achilles
@achilles 我认为你是对的,它也可以工作(但我不确定)- 这个配方的一部分来自答案中的链接。也许通过只调用管理器方法简化了查询集调用。如果你测试了一下 - 请分享结果,我很好奇。 - Chiefir
1个回答

10

回答自己的问题:

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() 是自定义 ProductManager() 的自定义函数。这个更改的步骤可以在这里(第29页)找到。

这段代码的作用:

  1. 创建搜索向量,为字段得分,得分更高的字段 - 在结果排序中占据更高的位置。
  2. 通过ORM Django创建全文本搜索的GIN索引
  3. 每次更改模型实例时更新GIN索引

    这段代码的不足之处:
  4. 它不按查询的子字符串相关性进行排序。 可能的解决方案。

    希望这对在Django中进行有点复杂的全文本搜索的人有所帮助。


这里(第30页)无法工作。 - Anshul Tiwari
@AnshulTiwari 啊,我几个月前检查过它,当时还能用 :( - Chiefir
@AnshulTiwari看一下这个演示文稿-https://ep2017.europython.eu/media/conference/slides/full-text-search-in-django-with-postgresql.pdf,它看起来很像我之前参考的内容(第29页)。 - Chiefir

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接