Полнотекстовый поиск
Функции базы данных в модуле django.contrib.postgres.search упрощают использование системы полнотекстового поиска PostgreSQL.
В примерах этого документа мы будем использовать модели, определённые в разделе Выполнение запросов.
См. также
Общие сведения о поиске см. в документации по теме.
Поиск с помощью search
Часто полнотекстовый поиск используют для поиска одного термина в одном столбце базы данных. Например:
>>> Entry.objects.filter(body_text__search="Cheese") <QuerySet [<Entry: Cheese on Toast recipes>, <Entry: Pizza Recipes>]>
В базе данных создаётся to_tsvector из поля body_text и plainto_tsquery из поискового термина 'Cheese'; для обоих используется конфигурация поиска базы данных по умолчанию. Результаты получают, сопоставляя запрос и вектор.
Чтобы использовать поиск search, в INSTALLED_APPS должен быть указан 'django.contrib.postgres'.
SearchVector
-
class SearchVector(*expressions, config=None, weight=None)[исходный код]
Поиск по одному полю удобен, но довольно ограничен. Искомые объекты Entry относятся к Blog, у которого есть поле tagline. Чтобы выполнить запрос по обоим полям, используйте SearchVector:
>>> from django.contrib.postgres.search import SearchVector
>>> Entry.objects.annotate(
... search=SearchVector("body_text", "blog__tagline"),
... ).filter(search="Cheese")
<QuerySet [<Entry: Cheese on Toast recipes>, <Entry: Pizza Recipes>]>
Аргументами SearchVector могут быть любые Expression или имена полей. Несколько аргументов будут объединены через пробел, чтобы поисковый документ включал их все.
Объекты SearchVector можно объединять, что позволяет повторно использовать их. Например:
>>> Entry.objects.annotate(
... search=SearchVector("body_text") + SearchVector("blog__tagline"),
... ).filter(search="Cheese")
<QuerySet [<Entry: Cheese on Toast recipes>, <Entry: Pizza Recipes>]>
Пояснения к параметрам config и weight см. в разделах Изменение конфигурации поиска и Взвешивание запросов.
SearchQuery
-
class SearchQuery(value, config=None, search_type='plain')[исходный код]
SearchQuery преобразует введённые пользователем термины в объект поискового запроса, который база данных сравнивает с поисковым вектором. По умолчанию все введённые пользователем слова обрабатываются алгоритмами стемминга, после чего выполняется поиск совпадений для всех полученных терминов.
Если search_type имеет значение 'plain' (по умолчанию), термины рассматриваются как отдельные ключевые слова. Если search_type имеет значение 'phrase', термины рассматриваются как единая фраза. Если search_type имеет значение 'raw', можно указать форматированный поисковый запрос с терминами и операторами. Если search_type имеет значение 'websearch', можно указать форматированный поисковый запрос, аналогичный запросам веб-поисковиков. Для 'websearch' требуется PostgreSQL ≥ 11. Подробнее о различиях и синтаксисе см. в документации PostgreSQL по полнотекстовому поиску. Примеры:
>>> from django.contrib.postgres.search import SearchQuery, Lexeme
>>> SearchQuery("red tomato") # two keywords
>>> SearchQuery("tomato red") # same results as above
>>> SearchQuery("red tomato", search_type="phrase") # a phrase
>>> SearchQuery("tomato red", search_type="phrase") # a different phrase
>>> SearchQuery("'tomato' & ('red' | 'green')", search_type="raw") # boolean operators
>>> SearchQuery(
... "'tomato' ('red' OR 'green')", search_type="websearch"
... ) # websearch operators
>>> SearchQuery(Lexeme("tomato") & (Lexeme("red") | Lexeme("green"))) # Lexeme objects
Термины SearchQuery можно логически объединять, чтобы сделать поиск более гибким:
>>> from django.contrib.postgres.search import SearchQuery
>>> SearchQuery("meat") & SearchQuery("cheese") # AND
>>> SearchQuery("meat") | SearchQuery("cheese") # OR
>>> ~SearchQuery("meat") # NOT
Пояснение к параметру config см. в разделе Изменение конфигурации поиска.
Добавлены объекты Lexeme.
SearchRank
-
class SearchRank(vector, query, weights=None, normalization=None, cover_density=False)[исходный код]
До сих пор мы возвращали результаты, для которых возможно любое совпадение вектора и запроса. Вероятно, вы захотите упорядочить результаты по релевантности. В PostgreSQL есть функция ранжирования, которая учитывает, как часто термины запроса встречаются в документе, насколько близко они расположены друг к другу и насколько важна часть документа, в которой они встречаются. Чем точнее совпадение, тем выше значение ранга. Чтобы упорядочить результаты по релевантности:
>>> from django.contrib.postgres.search import SearchQuery, SearchRank, SearchVector
>>> vector = SearchVector("body_text")
>>> query = SearchQuery("cheese")
>>> Entry.objects.annotate(rank=SearchRank(vector, query)).order_by("-rank")
<QuerySet [<Entry: Cheese on Toast recipes>, <Entry: Pizza recipes>]>
Пояснение к параметру weights см. в разделе Взвешивание запросов.
Установите для параметра cover_density значение True, чтобы включить ранжирование по плотности охвата. При таком ранжировании учитывается близость совпавших терминов запроса.
Чтобы управлять нормализацией ранга, передайте параметру normalization целое число. Это число представляет собой битовую маску, поэтому можно объединить несколько вариантов поведения:
>>> from django.db.models import Value >>> Entry.objects.annotate( ... rank=SearchRank( ... vector, ... query, ... normalization=Value(2).bitor(Value(4)), ... ) ... )
Подробнее о различных вариантах нормализации ранга см. в документации PostgreSQL.
SearchHeadline
-
class SearchHeadline(expression, query, config=None, start_sel=None, stop_sel=None, max_words=None, min_words=None, short_word=None, highlight_all=None, max_fragments=None, fragment_delimiter=None)[исходный код]
Принимает одно текстовое поле или выражение, запрос, конфигурацию и набор параметров. Возвращает результаты поиска с подсветкой.
Задайте для параметров start_sel и stop_sel строковые значения, которыми будут обрамляться подсвеченные термины запроса в документе. По умолчанию PostgreSQL использует <b> и </b>.
Передайте параметрам max_words и min_words целочисленные значения, чтобы задать максимальную и минимальную длину заголовков. По умолчанию PostgreSQL использует 35 и 15.
Передайте параметру short_word целочисленное значение, чтобы исключить из каждого заголовка слова этой длины или короче. Значение по умолчанию в PostgreSQL — 3.
Задайте для параметра highlight_all значение True, чтобы использовать весь документ вместо его фрагмента и игнорировать параметры max_words, min_words и short_word. По умолчанию в PostgreSQL эта возможность отключена.
Передайте параметру max_fragments ненулевое целочисленное значение, чтобы задать максимальное количество отображаемых фрагментов. По умолчанию в PostgreSQL эта возможность отключена.
Задайте строковому параметру fragment_delimiter значение, чтобы настроить разделитель между фрагментами. По умолчанию PostgreSQL использует " ... ".
Подробнее о подсветке результатов поиска см. в документации PostgreSQL.
Пример использования:
>>> from django.contrib.postgres.search import SearchHeadline, SearchQuery
>>> query = SearchQuery("red tomato")
>>> entry = Entry.objects.annotate(
... headline=SearchHeadline(
... "body_text",
... query,
... start_sel="<span>",
... stop_sel="</span>",
... ),
... ).get()
>>> print(entry.headline)
Sandwich with <span>tomato</span> and <span>red</span> cheese.
Пояснение к параметру config см. в разделе Изменение конфигурации поиска.
Изменение конфигурации поиска
Можно указать атрибут config у объектов SearchVector и SearchQuery, чтобы использовать другую конфигурацию поиска. Это позволяет применять разные языковые анализаторы и словари, определённые в базе данных:
>>> from django.contrib.postgres.search import SearchQuery, SearchVector
>>> Entry.objects.annotate(
... search=SearchVector("body_text", config="french"),
... ).filter(search=SearchQuery("œuf", config="french"))
<QuerySet [<Entry: Pain perdu>]>
Значение config также можно хранить в другом столбце:
>>> from django.db.models import F
>>> Entry.objects.annotate(
... search=SearchVector("body_text", config=F("blog__language")),
... ).filter(search=SearchQuery("œuf", config=F("blog__language")))
<QuerySet [<Entry: Pain perdu>]>
Взвешивание запросов
Поля в запросе могут быть неодинаково релевантны, поэтому перед их объединением можно задать веса для разных векторов:
>>> from django.contrib.postgres.search import SearchQuery, SearchRank, SearchVector
>>> vector = SearchVector("body_text", weight="A") + SearchVector(
... "blog__tagline", weight="B"
... )
>>> query = SearchQuery("cheese")
>>> Entry.objects.annotate(rank=SearchRank(vector, query)).filter(rank__gte=0.3).order_by(
... "rank"
... )
Для веса следует использовать одну из следующих букв: D, C, B, A. По умолчанию этим весам соответствуют числа 0.1, 0.2, 0.4 и 1.0 соответственно. Если вы хотите задать другие веса, передайте в SearchRank список из четырёх чисел с плавающей точкой в качестве weights, в том же порядке:
>>> rank = SearchRank(vector, query, weights=[0.2, 0.4, 0.6, 0.8])
>>> Entry.objects.annotate(rank=rank).filter(rank__gte=0.3).order_by("-rank")
Lexeme
-
class Lexeme(value, output_field=None, *, invert=False, prefix=False, weight=None)[исходный код]
Объекты Lexeme позволяют безопасно использовать операторы поиска со строками из ненадёжного источника. Содержимое каждого лексемы экранируется, поэтому любые операторы, которые могут находиться в самой строке, не будут интерпретироваться.
Лексемы можно объединять с другими лексемами с помощью операторов & и |, а также инвертировать с помощью оператора ~. Например:
>>> from django.contrib.postgres.search import SearchQuery, SearchVector, Lexeme
>>> vector = SearchVector("body_text", "blog__tagline")
>>> Entry.objects.annotate(search=vector).filter(
... search=SearchQuery(Lexeme("fruit") & Lexeme("dessert"))
... )
<QuerySet [<Entry: Apple Crumble Recipes>, <Entry: Banana Split Recipes>]>
>>> Entry.objects.annotate(search=vector).filter(
... search=SearchQuery(Lexeme("fruit") & Lexeme("dessert") & ~Lexeme("banana"))
... )
<QuerySet [<Entry: Apple Crumble Recipes>]>
Объекты Lexeme также поддерживают взвешивание терминов и префиксы:
>>> Entry.objects.annotate(search=vector).filter(
... search=SearchQuery(Lexeme("Pizza") | Lexeme("Cheese"))
... )
<QuerySet [<Entry: Cheese on Toast recipes>, <Entry: Pizza recipes>]>
>>> Entry.objects.annotate(search=vector).filter(
... search=SearchQuery(Lexeme("Pizza") | Lexeme("Cheese", weight="A"))
... )
<QuerySet [<Entry: Pizza recipes>]>
>>> Entry.objects.annotate(search=vector).filter(
... search=SearchQuery(Lexeme("za", prefix=True))
... )
<QuerySet []>
Производительность
Для использования этих функций не требуется специальная настройка базы данных. Однако при поиске более чем по нескольким сотням записей, вероятно, возникнут проблемы с производительностью. Полнотекстовый поиск требует больше ресурсов, чем, например, сравнение целочисленных значений.
Если все поля, по которым вы выполняете запрос, находятся в одной модели, можно создать функциональный индекс GIN или GiST, соответствующий нужному поисковому вектору. Например:
GinIndex(
SearchVector("body_text", "headline", config="english"),
name="search_vector_idx",
)
Подробнее о создании индексов для полнотекстового поиска см. в документации PostgreSQL.
SearchVectorField
-
class SearchVectorField[исходный код]
Если этот подход работает слишком медленно, можно добавить в модель SearchVectorField. Его потребуется заполнять с помощью триггеров, например, как описано в документации PostgreSQL. После этого можно выполнять запросы к полю так же, как к аннотированному SearchVector:
>>> Entry.objects.update(search_vector=SearchVector("body_text"))
>>> Entry.objects.filter(search_vector="cheese")
<QuerySet [<Entry: Cheese on Toast recipes>, <Entry: Pizza recipes>]>
Триграммное сходство
Ещё один подход к поиску — триграммное сходство. Триграмма — это группа из трёх последовательных символов. Помимо поисковых условий trigram_similar, trigram_word_similar и trigram_strict_word_similar, можно использовать ещё несколько выражений.
Для этого необходимо активировать расширение pg_trgm в PostgreSQL. Его можно установить с помощью операции миграции TrigramExtension.
TrigramSimilarity
-
class TrigramSimilarity(expression, string, **extra)[исходный код]
Принимает имя поля или выражение, а также строку или выражение. Возвращает триграммное сходство двух аргументов.
Пример использования:
>>> from django.contrib.postgres.search import TrigramSimilarity
>>> Author.objects.create(name="Katy Stevens")
>>> Author.objects.create(name="Stephen Keats")
>>> test = "Katie Stephens"
>>> Author.objects.annotate(
... similarity=TrigramSimilarity("name", test),
... ).filter(
... similarity__gt=0.3
... ).order_by("-similarity")
<QuerySet [<Author: Katy Stevens>, <Author: Stephen Keats>]>
TrigramWordSimilarity
-
class TrigramWordSimilarity(string, expression, **extra)[исходный код]
Принимает строку или выражение, а также имя поля или выражение. Возвращает триграммное сходство слов двух аргументов.
Пример использования:
>>> from django.contrib.postgres.search import TrigramWordSimilarity
>>> Author.objects.create(name="Katy Stevens")
>>> Author.objects.create(name="Stephen Keats")
>>> test = "Kat"
>>> Author.objects.annotate(
... similarity=TrigramWordSimilarity(test, "name"),
... ).filter(
... similarity__gt=0.3
... ).order_by("-similarity")
<QuerySet [<Author: Katy Stevens>]>
TrigramStrictWordSimilarity
-
class TrigramStrictWordSimilarity(string, expression, **extra)[исходный код]
Принимает строку или выражение, а также имя поля или выражение. Возвращает строгое триграммное сходство слов двух аргументов. Похоже на TrigramWordSimilarity(), но границы области принудительно совмещаются с границами слов.
TrigramDistance
-
class TrigramDistance(expression, string, **extra)[исходный код]
Принимает имя поля или выражение, а также строку или выражение. Возвращает триграммное расстояние между двумя аргументами.
Пример использования:
>>> from django.contrib.postgres.search import TrigramDistance
>>> Author.objects.create(name="Katy Stevens")
>>> Author.objects.create(name="Stephen Keats")
>>> test = "Katie Stephens"
>>> Author.objects.annotate(
... distance=TrigramDistance("name", test),
... ).filter(
... distance__lte=0.7
... ).order_by("distance")
<QuerySet [<Author: Katy Stevens>, <Author: Stephen Keats>]>
TrigramWordDistance
-
class TrigramWordDistance(string, expression, **extra)[исходный код]
Принимает строку или выражение, а также имя поля или выражение. Возвращает триграммное расстояние между словами двух аргументов.
Пример использования:
>>> from django.contrib.postgres.search import TrigramWordDistance
>>> Author.objects.create(name="Katy Stevens")
>>> Author.objects.create(name="Stephen Keats")
>>> test = "Kat"
>>> Author.objects.annotate(
... distance=TrigramWordDistance(test, "name"),
... ).filter(
... distance__lte=0.7
... ).order_by("distance")
<QuerySet [<Author: Katy Stevens>]>
TrigramStrictWordDistance
-
class TrigramStrictWordDistance(string, expression, **extra)[исходный код]
Принимает строку или выражение, а также имя поля или выражение. Возвращает строгое триграммное расстояние между двумя аргументами.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/6.0/ref/contrib/postgres/search/