← Назад к вопросам
Как работает поиск с морфологией в PostgreSQL?
3.0 Senior🔥 101 комментариев
#Базы данных (SQL)
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Поиск с морфологией в PostgreSQL
Морфологический поиск в PostgreSQL — это полнотекстовый поиск, который понимает различные формы слова (лемматизация и стемминг). PostgreSQL имеет встроенную поддержку для полнотекстового поиска с русским языком.
Основные компоненты
1. Типы данных
# PostgreSQL поддерживает два типа:
# tsvector — преобразованный в индексируемый формат текст
# tsquery — запрос в формате, понятном engine'у поиска
CREATE TABLE articles (
id SERIAL PRIMARY KEY,
title VARCHAR(255),
content TEXT,
search_vector tsvector -- индекс для поиска
);
CREATE INDEX idx_search ON articles USING gin(search_vector);
2. Функции морфологического анализа
# to_tsvector() — преобразует текст в tsvector
# Автоматически применяет морфологию (лемматизацию)
# Пример на SQL:
SELECT to_tsvector('russian', 'Кошка красивая');
-- Результат: 'красив':2 'кош':1
-- Слова приведены к базовой форме
SELECT to_tsvector('russian', 'бегут бегал бежит');
-- Результат: 'бежа':1,2,3
-- Все формы привели к базовой
Примеры поиска
Базовый поиск
# Поиск точного совпадения слова
SELECT * FROM articles
WHERE search_vector @@ to_tsquery('russian', 'кошка');
-- Найдёт: кошка, кошки, кошке, кошкой и т.д.
# С оператором &| (OR)
SELECT * FROM articles
WHERE search_vector @@ to_tsquery('russian', 'кошка | собака');
-- Найдёт статьи про кошек ИЛИ про собак
# С оператором & (AND)
SELECT * FROM articles
WHERE search_vector @@ to_tsquery('russian', 'кошка & красивая');
-- Найдёт статьи про красивых кошек
# С оператором ! (NOT)
SELECT * FROM articles
WHERE search_vector @@ to_tsquery('russian', 'кошка & ! серая');
-- Найдёт кошек, но не серых
Рейтинг релевантности
# ts_rank() вычисляет релевантность (от 0 до 1)
SELECT id, title, ts_rank(search_vector, query) as rank
FROM articles,
to_tsquery('russian', 'красивая кошка') as query
WHERE search_vector @@ query
ORDER BY rank DESC;
# ts_rank_cd() — более сложный алгоритм (cover density)
SELECT id, title, ts_rank_cd(search_vector, query) as rank
FROM articles,
to_tsquery('russian', 'красивая кошка') as query
WHERE search_vector @@ query
ORDER BY rank DESC;
Интеграция с Python (SQLAlchemy)
from sqlalchemy import Column, Integer, String, Text, func, and_
from sqlalchemy.dialects.postgresql import TSVECTOR
from sqlalchemy.sql import text
from sqlalchemy.orm import Session
class Article(Base):
__tablename__ = 'articles'
id = Column(Integer, primary_key=True)
title = Column(String(255))
content = Column(Text)
search_vector = Column(TSVECTOR)
class ArticleRepository:
def __init__(self, session: Session):
self.session = session
def search(self, query: str, language: str = 'russian'):
# Создаём tsquery из строки поиска
tsquery = func.to_tsquery(language, query)
# Поиск с рейтингом
stmt = self.session.query(
Article,
func.ts_rank(Article.search_vector, tsquery).label('rank')
).filter(
Article.search_vector.op('@@')(tsquery)
).order_by(text('rank DESC'))
return stmt.all()
def search_advanced(self, query: str, exclude: str = None):
language = 'russian'
base_query = query
if exclude:
# query & !exclude
base_query = f"{query} & !{exclude}"
tsquery = func.to_tsquery(language, base_query)
return self.session.query(Article).filter(
Article.search_vector.op('@@')(tsquery)
).all()
Обновление индекса
Вариант 1: Триггер
# Автоматическое обновление search_vector при INSERT/UPDATE
CREATE TRIGGER articles_search_update BEFORE INSERT OR UPDATE
ON articles FOR EACH ROW
EXECUTE FUNCTION
tsvector_update_trigger(search_vector, 'russian', title, content);
# На Python это означает, что поле обновляется автоматически
db.session.add(Article(
title="Красивая кошка",
content="Это статья про кошек..."
))
db.session.commit() # search_vector обновится автоматически
Вариант 2: Ручное обновление
from sqlalchemy import text
class ArticleService:
def __init__(self, session: Session):
self.session = session
def update_search_vector(self, article_id: int):
# Обновляем вручную
self.session.execute(
text("""
UPDATE articles
SET search_vector =
to_tsvector('russian', title) ||
to_tsvector('russian', content)
WHERE id = :id
"""),
{"id": article_id}
)
self.session.commit()
def rebuild_all_search_vectors(self):
# Переиндексируем всю таблицу
self.session.execute(
text("""
UPDATE articles
SET search_vector =
to_tsvector('russian', title) ||
to_tsvector('russian', content)
""")
)
self.session.commit()
Выделение контекста (highlighting)
# ts_headline() выделяет совпадения в тексте
SELECT
id,
title,
ts_headline('russian', content, query, 'StartSel=<mark>, StopSel=</mark>')
as snippet
FROM articles,
to_tsquery('russian', 'кошка') as query
WHERE search_vector @@ query;
# На Python:
from sqlalchemy import func
stmt = self.session.query(
Article,
func.ts_headline(
'russian',
Article.content,
func.to_tsquery('russian', query_text),
'StartSel=<mark>, StopSel=</mark>'
).label('snippet')
).filter(
Article.search_vector.op('@@')(
func.to_tsquery('russian', query_text)
)
)
Русский язык и морфология
# PostgreSQL имеет встроенную поддержку русского
SELECT *
FROM pg_ts_dict
WHERE dictname = 'russian_stem'; -- русский стеммер
# Но для лучших результатов используй
# расширение pg_hunspell или словарь Ispell
# Проверка доступных конфигураций:
SELECT cfgname FROM pg_ts_config;
-- russian, russian_stemming и т.д.
Производительность
# 1. Используй GIN индекс
CREATE INDEX idx_articles_search ON articles USING gin(search_vector);
# 2. Статистика для оптимизатора
ANALYZE articles;
# 3. Объединение полей с разными весами
SELECT id, ts_rank(search_vector, query) as rank
FROM articles,
to_tsquery('russian', 'кошка') as query
WHERE search_vector @@ query
LIMIT 100; -- ограничивай результаты
Альтернативные подходы
# 1. Простой LIKE (медленно на больших объёмах)
SELECT * FROM articles
WHERE title ILIKE '%кошка%';
# 2. Расширение pg_trgm (триграммы)
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE INDEX idx_title_trgm ON articles USING gin(title gin_trgm_ops);
SELECT * FROM articles
WHERE title % 'красивая кошка'; # нечёткий поиск
# 3. Elasticsearch (на очень больших объёмах)
# более гибкий и быстрый для полнотекстового поиска
Лучшие практики
- Используй GIN индекс для tsvector
- Обновляй search_vector автоматически через триггер
- Ограничивай результаты (LIMIT) для производительности
- Кешируй результаты часто используемых поисков
- Проверяй explain для оптимизации запросов
- Для сложного поиска рассмотри Elasticsearch
- Тестируй морфологию на реальных данных
Полнотекстовый поиск в PostgreSQL — это мощный встроенный инструмент для поиска по документам, который не требует дополнительных сервисов для базовых сценариев.