Использование полей SearchVectorFields на многих и многих связанных моделях
У меня есть две модели Author
и Book
, которые связаны через m2m (один автор может иметь много книг, одна книга может иметь много авторов)
Часто нам необходимо запрашивать и сопоставлять записи для входов, используя текстовые строки, в обеих моделях, например: "JRR Tolkien - Return of the King", когда уникальные идентификаторы недоступны.
Я хотел бы проверить, может ли использование SearchVectorField
с индексами GIN улучшить время отклика полнотекстового поиска - но поскольку поисковый запрос будет SearchVector(author__name, book__title)
Похоже, что обе модели нуждаются в добавлении SearchVectorField.
Это становится сложнее, когда каждая таблица требует обновления, поскольку оказывается, что Postgres Triggers должны быть настроены на обе таблицы, что может сделать обновление чего-либо совершенно несостоятельным.
Вопрос
Какова современная лучшая практика в Django для принятия векторных методов полнотекстового поиска, когда речь идет о моделях, связанных с m2m? Следует ли размещать SearchVectorField
через таблицу? Или в каждой модели? Как следует применять триггеры?
Я искал руководства именно по этому вопросу - но никто, похоже, не упоминает m2ms, когда говорит о SearchVectorFields. Я нашел этот старый вопрос
Кроме того, если Postgres действительно не является путем вперед в современном Django, я также буду рад направить вас в сторону чего-то более подходящего/поддерживаемого/документированного. В нашем случае мы используем Postgres 11.6.
Repro
from django.db import models
from django.contrib.postgres.search import SearchVectorField
from django.contrib.postgres.indexes import GinIndex
class Author(models.Model):
name = models.CharField(max_length=100, unique=True)
main_titles = models.ManyToManyField(
"Book",
through="BookMainAuthor",
related_name="main_authors",
)
search = SearchVectorField(null=True)
class BookMainAuthor(models.Model):
"""The m2m through table for book and author (main)"""
book = models.ForeignKey("Book", on_delete=models.CASCADE)
artist = models.ForeignKey("Author", on_delete=models.CASCADE)
class Meta:
unique_together = ["book", "author"]
class Book(models.Model):
title = models.CharField(max_length=100, unique=True)
search = SearchVectorField(null=True)
Наконец-то получилось. Я предполагаю, что вам нужно искать по запросу, содержащему автора и название книги одновременно. И вы не сможете разделить их, чтобы посмотреть на таблицу Book
для части запроса "книга" и то же самое для Author
.
Да, создание индекса полей из отдельных таблиц невозможно в PostgreSQL. Я не считаю это слабостью PostgreSQL, просто это очень необычный случай, когда вам действительно нужен такой индекс. В большинстве случаев есть другие решения, не худшие по эффективности. Конечно, вы всегда можете посмотреть на ElasticSearch
, если по какой-то причине вы уверены, что это необходимо.
Я посоветую вам такой подход. Вы можете сделать BookMainAuthor
с такой структурой:
class BookMainAuthor(models.Model):
"""The m2m through table for book and author (main)"""
book = models.ForeignKey("Book", on_delete=models.CASCADE)
artist = models.ForeignKey("Author", on_delete=models.CASCADE)
book_full_name = models.CharField(max_length=200, unique=True)
search = SearchVectorField(null=True)
class Meta:
unique_together = ["book", "author"]
Как мне кажется, не должно вызвать затруднений ведение поля book_full_name
, которое будет содержать как имена автора, так и названия книг с соответствующим разделителем в нем. Все остальное - дело учебника.
Из моего опыта, если таблица BookMainAuthor
будет содержать не более 10M записей, то на среднем одном сервере (например, как AX161 из здесь) все будет просто отлично.