При работе с поисковыми движками на базе Apache Lucene (Elasticsearch, Solr, OpenSearch и др.) часто возникает необходимость не только найти документы, содержащие определенные слова, но и исключить те, которые содержат нежелательные термины; Для этого в синтаксисе Lucene Query Parser используется оператор AND NOT (или его символьный аналог -).
В этой статье мы подробно разберем синтаксис, нюансы работы, приоритет операторов и частые ошибки при использовании исключений.
Базовый синтаксис и логика работы
Оператор AND NOT является бинарным оператором. Он требует наличия левого операнда (что ищем) и правого операнда (что исключаем).
Варианты записи:
- Словарная форма:
term1 AND NOT term2 - Символьная форма (префиксный минус):
term1 -term2
Логика: Документ попадает в результат, если он соответствует term1 И НЕ соответствует term2.
яблоки AND NOT пирогияблоки -пироги Приоритет операторов (Operator Precedence)
Это самый критичный момент, который вызывает ошибки. В Lucene Query Parser приоритет операторов следующий (от высшего к низшему):
- — Группировка (скобки)
+,-— Обязательное наличие / Исключение (унарные операторы)NOT,!— Логическое НЕAND,&&— Логическое ИOR,||— Логическое ИЛИ
Важный нюанс: AND NOT vs -
Хотя AND NOT семантически эквивалентен AND + NOT, парсер обрабатывает их по-разному из-за приоритета:
AND NOTпарсится как комбинацияAND(низкий приоритет) иNOT(средний приоритет).-(минус) — это унарный оператор запрета (Prohibit) с высшим приоритетом (равным плюсу+).
Пример разницы:
Запрос: a OR b AND NOT c
Парсинг: a OR (b AND (NOT c))
Результат: Документы с 'a' ИЛИ документы с 'b' но без 'c'.
Запрос: a OR b -c
Парсинг: a OR (b -c) -> a OR (+b -c)
Результат: Документы с 'a' ИЛИ документы, которые ОБЯЗАТЕЛЬНО содержат 'b' и НЕ содержат 'c'.
Работа с фразами и полями
Оператор исключения можно применять к точным фразам и конкретным полям.
Исключение фразы (Phrase Query)
Используйте кавычки для термина справа от оператора:
title:"быстрый лис" AND NOT title:"медленный черепаха"
title:"быстрый лис" -"медленный черепаха"
Исключение в конкретном поле
Если левая часть — это запрос по полю content, а исключать нужно по полю status:
content:ошибка AND NOT status:архив
content:ошибка -status:архив
Исключение нескольких терминов/полей сразу
Используйте группировку скобками:
# Исключить документы, где status=deleted ИЛИ status=spam
текст_запроса AND NOT (status:deleted OR status:spam)
текст_запроса -(status:deleted status:spam)
Оператор NOT vs AND NOT vs -
Lucene поддерживает три способа выражения отрицания. Понимание разницы критично:
| Синтаксис | Тип | Семантика (Must / Should / Must Not) | Может ли быть единственным условием? |
|---|---|---|---|
NOT term!term | Унарный (Prefix) | Must Not (Запрет) | Нет. Запрос NOT term вернет ошибку или пустой результат (в зависимости от версии/настроек), так как Lucene не может итерировать «все документы кроме…». Нужен положительный термин (Must/Should). |
term1 AND NOT term2 | Бинарный | term1 = Must, term2 = Must Not | Да, так как есть положительное условие term1. |
term1 -term2 | Бинарный (с унарным минусом) | term1 = Must/Should (зависит от default operator), term2 = Must Not | Да. |
Правило безопасности:
Никогда не используйте чистый NOT или - как начало запроса без положительного условия.
- Плохо:
NOT spam,-spam,AND NOT spam - Хорошо:
: -spam(все документы кроме спама),title:новости -spam
Влияние на релевантность (Scoring)
Документы, отсеянные оператором исключения (Must Not clause), полностью исключаются из набора результатов и не участвуют в расчете скоринга (TF/IDF, BM25).
- Они не получают
_score. - Они не влияют на нормализацию длинны поля для других документов.
- Это «жесткий фильтр» (Hard Filter).
Если вам нужно понизить релевантность, но не исключать совсем (например, «покажи новости про политику, но про спорт опусти вниз»), используйте Function Score Query или комбинацию should / boost в DSL, а не Query Parser синтаксис исключения.
Типичные ошибки и лучшие практики
Ошибка 1: Игнорирование приоритета при смешивании OR и AND NOT
Решение: Всегда ставьте скобки: (a OR b) AND NOT c или (a OR b) -c.
Ошибка 2: Использование анализатора на исключаемом термине
Если поле title использует анализатор (например, стеммер или стоп-слова), запрос title:running AND NOT title:run может не сработать так, как вы ожидаете, потому что running и run превратятся в один токен.
Решение: Для точного исключения используйте поля типа keyword (неанализируемые) или будьте уверены в токенизации.
Ошибка 3: Попытка исключить «все» через NOT
Решение: Используйте : -field:value (MatchAllDocsQuery минус условие).
Примеры для Elasticsearch / OpenSearch (Query String Query)
В Elasticsearch синтаксис Lucene Query Parser доступен через query_string и simple_query_string.
Пример 1: Поиск логов об ошибках, исключая healthcheck
GET /logs/_search
{
"query": {
"query_string": {
"query": "level:ERROR AND NOT message:/healthcheck|ping/",
"default_field": "message"
}
}
}
Пример 2: Электронная коммерция — товары в наличии, кроме снятых с производства
GET /products/_search
{
"query": {
"query_string": {
"query": "status:active -tags:discontinued -category:archive",
"fields": ["name^3", "description", "sku"]
}
}}
Пример 3: Безопасный «Match All Except» (для админок/отчетов)
GET /_search
{
"query": {
"query_string": {
"query": ": -is_deleted:true",
"allow_leading_wildcard": true
}
}
}
Резюме (Cheat Sheet)
- Синтаксис:
term1 AND NOT term2илиterm1 -term2. - Предпочитайте минус (
-): Он имеет высший приоритет, читается легче и ведет себя предсказуемее как «фильтр запрета». - Скобки, ваши друзья: Обязательно группируйте сложные выражения:
(a OR b) -c. - Позитивное условие обязательно: Нельзя искать только «НЕ что-то». Нужен базовый запрос (
:,field:value). - Это фильтр, не буст: Исключенные документы полностью исчезают из результатов и не влияют на скор остальных.
- Поля: Указывайте поле явно для исключения, если оно отличается от поля поиска:
text:query -status:hidden.
Правильное использование AND NOT / - позволяет строить точные и быстрые поисковые запросы, отсекая шум на уровне парсера запросов, не нагружая скоринг ненужными данными.