Использование полнотекстового поиска + GIN в представлении (Django 1.11)

Мне нужна помощь в создании правильного запроса в представлении django для полнотекстового поиска с использованием индекса GIN. У меня довольно большая база данных (~ 400 тысяч строк), и мне нужно выполнить полнотекстовый поиск по 3 полям из нее. Пытался использовать поиск документов django, и это код ДО ДЖИН. Это работает, но поиск по всем полям занимает 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})

person Chiefir    schedule 19.11.2017    source источник
comment
Почему мы не можем назначить search_vector напрямую instance.search_vector? Я не могу понять, пожалуйста, объясните мне. спасибо   -  person achilles    schedule 03.04.2019
comment
@achilles, как сказано в документах (docs.djangoproject.com/en/2.0/ref/models/instances/) - .save(update_fields=['search_vector']) может обновлять только выбранные поля и повышать производительность. Если я правильно понимаю вопрос.   -  person Chiefir    schedule 03.04.2019
comment
Я спрашивал, почему мы это делаем instance = self._meta.default_manager.with_documents().get(pk=self.pk) instance.search_vector = instance.document Почему мы не можем назначать так: instance.search_vector = SearchVector(...)   -  person achilles    schedule 03.04.2019
comment
@achilles Я думаю, вы правы, это тоже может сработать (но я не уверен) - часть этого рецепта взята из ссылки из ответа. Может быть, это упрощает вызовы запросов, просто вызывая метод менеджера. Если вы это проверите - поделитесь результатами, пожалуйста, мне любопытно.   -  person Chiefir    schedule 03.04.2019


Ответы (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() — это пользовательская функция пользовательского ProductManager(). Рецепт этого изменения: здесь (стр. 29).

Что делает весь этот код:

  1. создает search_vector с оценками для полей, поле с большей оценкой - получает более высокое место в сортировке результатов.
  2. создает индекс GIN для полнотекстового поиска через ORM Django
  3. обновляет индекс GIN каждый раз при изменении экземпляра модели

    Чего этот код не делает:
  4. Он не сортирует по релевантности запрашиваемой подстроки. Возможное решение.

    Надеюсь, это поможет кому-нибудь с немного сложным полнотекстовым поиском в Django.
person Chiefir    schedule 07.12.2017
comment
здесь (стр. 30) не работает. - person Anshul Tiwari; 16.02.2021
comment
@AnshulTiwari ааа, я проверил пару месяцев назад, и он все еще работает :( - person Chiefir; 16.02.2021
comment
@AnshulTiwari взгляните на эту презентацию - ep2017.europython.eu/media/conference/slides/ похоже на то, что я основывал ранее (стр. 29) - person Chiefir; 16.02.2021
comment
Спасибо, что поделился. - person Anshul Tiwari; 18.02.2021