Оператор AND NOT в Lucene Query Syntax

Apache Lucene — это мощная и гибкая библиотека полнотекстового поиска, лежащая в основе таких популярных решений, как Elasticsearch, Solr и OpenSearch. Одной из ключевых возможностей её синтаксиса запросов (Lucene Query Syntax) является возможность не только искать документы, содержащие определенные термины, но и исключать документы, соответствующие нежелательным критериям.

В этой статье мы подробно разберем оператор AND NOT, его синтаксис, нюансы работы, отличия от альтернативных способов исключения и лучшие практики применения.

Базовый синтаксис и принцип работы

Оператор AND NOT (часто используемый синоним — NOT или символ -) позволяет отфильтровать результаты поиска, отбрасывая документы, которые соответствуют термину или подзапросу, стоящему справа от оператора.

Основные формы записи:

  • Явная форма: term1 AND NOT term2
  • Сокращенная форма (ключевое слово): term1 NOT term2
  • Символьная форма (префикс): term1 -term2

Важно: В Lucene операторы AND, OR, NOT должны писаться заглавными буквами, иначе они будут восприняты как обычные поисковые термины.

Примеры базового использования:

# Найти документы, содержащие "java", но НЕ содержащие "script"
java AND NOT script

java NOT script

java -script

Приоритет операторов и группировка

Понимание приоритета операторов критически важно для составления правильных запросов. В Lucene приоритет (от высшего к низшему) следующий:

  1. Группировка скобками
  2. Префиксные операторы: +, -, NOT
  3. AND
  4. OR

Примеры влияния приоритета:

Запрос Интерпретация Lucene Результат
a AND NOT b OR c (a AND (NOT b)) OR c Документы с «a» без «b» ИЛИ любые документы с «c».
a AND (NOT b OR c) a AND ((NOT b) OR c) Документы с «a», в которых либо нет «b», либо есть «c».
-a b (NOT a) AND b (неявный AND) Документы с «b», в которых нет «a».

Рекомендация: Всегда используйте скобки для явного указания логики, особенно при комбинировании AND NOT с OR.

Исключение фраз и сложных подзапросов

Оператор AND NOT работает не только с отдельными термами, но и с фразами (в кавычках) и целыми подзапросами в скобках.

Исключение точной фразы:

# Исключить документы, содержащие точную фразу "deprecated api"
java AND NOT "deprecated api"

Исключение по полю (Field-level exclusion):

Можно исключать термины только в конкретном поле.

# Найти "lucene" в заголовке, но исключить, если в поле "category" стоит "draft"
title:lucene AND NOT category:draft

title:lucene -category:draft

Исключение сложных логических блоков:

# Исключить документы, которые ИМЕЮТ ("error" ИЛИ "exception") В ПОЛЕ "log_level"
message:timeout AND NOT (log_level:error OR log_level:exception)

Критически важный нюанс: "Pure Negative" запросы (Чисто отрицательные запросы)

Это самая частая ловушка для новичков.

Lucene не поддерживает запросы, состоящие только из отрицательных условий (так называемые "pure negative queries"). Запрос вроде NOT term или -term без положительного условия вернет ошибку или пустой результат (в зависимости от версии парсера и настроек allowLeadingWildcard / QueryParser конфигурации).

❌ Неверно (вызовет ошибку или вернет 0 результатов):
-status:deleted
NOT category:spam

✅ Верно (нужно добавить положительное условие, например, MatchAllDocsQuery или :)

: -status:deleted
: NOT category:spam

В программном коде (Java) для этого часто используют MatchAllDocsQuery в комбинации с BooleanQuery:

BooleanQuery.Builder builder = new BooleanQuery.Builder;
builder.add(new MatchAllDocsQuery, Occur.MUST);
builder.add(new TermQuery(new Term("status", "deleted")), Occur.MUST_NOT);
Query query = builder.build;

AND NOT vs MUST_NOT (BooleanQuery API)

При программном построении запросов через Java API (не через строковый парсер) используется класс BooleanQuery и перечисление Occur.

Строковый синтаксис (QueryParser) Java API (BooleanClause.Occur) Семантика
term1 AND term2 MUST, MUST Оба обязательны (пересечение).
term1 AND NOT term2 MUST, MUST_NOT term1 обязателен, term2 запрещен.
term1 OR term2 SHOULD, SHOULD Хотя бы одно (объединение).
-term2 (без MUST) MUST_NOT (одиночный) Ошибка / Пусто (Pure Negative).

Оператор AND NOT в парсере ровно соответствует комбинации Occur.MUST (левая часть) + Occur.MUST_NOT (правая часть).

Влияние на релевантность (Scoring)

Клаузылы, помеченные как MUST_NOT (через AND NOT, NOT или -), не влияют на счет релевантности (score) документов.

  • Они работают как фильтр (filter context): документ либо проходит, либо отбрасывается.
  • Они не увеличивают и не уменьшают _score совпавших документов.
  • Это делает их очень производительными, так как Lucene может кэшировать набор исключенных документов (bitset) и быстро применять их при последующих поисках.

Распространенные ошибки и лучшие практики

Ошибка 1: Путаница с оператором - внутри фразы

Символ дефиса - является оператором только в начале термина (после пробела или скобки). Внутри слова или фразы он является частью термина (если анализатор не разбивает его).

# Это исключение термина "script"
java -script

java-script

Ошибка 2: Игнорирование анализатора (Analyzer)

Запрос проходит через тот же анализатор, что и индексация. Если поле проиндексировано с StandardAnalyzer (приводит к нижнему регистру, удаляет стоп-слова), то и запрос будет приведен к нижнему регистру.

# В индексе терм сохранен как "java"

Java AND NOT SCRIPT 

Но если вы используете KeywordAnalyzer (точное совпадение), регистр имеет значение.

Лучшая практика: Использование Filter Context (в Elasticsearch/OpenSearch)

Если вы пишете запрос для Elasticsearch (DSL), для исключения лучше использовать bool.filter с must_not, а не query_string с AND NOT.

GET /_search
{
 "query": {
 "bool": {
 "must": [ { "match": { "title": "lucene" } } ],
 "filter": [ 
 { "bool": { "must_not": { "term": { "status": "archived" } } } }
 ]
 }
 }
}

Это гарантирует кэширование и отсутствие влияния на скор.

Резюме: Шпаргалка по синтаксису

| Задача | Синтаксис QueryParser | Примечание |
| :--- | :--- | :--- |
| Исключить термин | `term1 -term2` | Самый краткий и читаемый вариант. |
| Исключить фразу | `term1 -"exact phrase"` | Кавычки обязательны для фразы. |
| Исключить в поле | `title:search -status:draft` | Префикс поля применяется к термину после минуса. |
| Исключить группу | `term1 -(term2 term3)` | Исключить документы, содержащие И term2 И term3 (неявный AND). |
| Исключить ИЛИ-группу | `term1 -(term2 OR term3)` | Явное OR внутри исключения. |
| Только исключение | `: -term` | Обязательно добавить `:` или `MatchAllDocsQuery`. |

Оператор AND NOT (и его аналоги NOT, -) — это фундаментальный инструмент для точной настройки релевантности поиска в Lucene. Он позволяет отсекать шумовые данные, устаревшие записи или нежелательные категории без ущерба для производительности, так как работает в контексте фильтрации и не влияет на расчет скорa.

Главные правила запомнить:

  1. Пишите операторы ЗАГЛАВНЫМИ буквами.
  2. Используйте скобки для управления приоритетом при смешивании с OR.
  3. Никогда не используйте чисто отрицательные запросы без положительного условия (добавляйте `:`).
  4. Для программной генерации запросов предпочитайте BooleanQuery.Builder с Occur.MUST_NOT строковому парсеру, это безопаснее и быстрее.

Правильное применение исключений — залог быстрого и релевантного поиска в ваших приложениях на базе Lucene, Solr или Elasticsearch.