Клауза MATCH позволяет выполнять полнотекстовый поиск в текстовых полях. Входная строка запроса токенизируется с использованием тех же настроек, которые применялись к тексту при индексировании. Помимо токенизации входного текста, строка запроса поддерживает ряд операторов полнотекстового поиска, которые задают различные правила того, как ключевые слова должны обеспечивать валидное совпадение.
Клаузы полнотекстового поиска могут комбинироваться с атрибутными фильтрами как логическое И. Логические ИЛИ между полнотекстовыми совпадениями и атрибутными фильтрами не поддерживаются.
Запрос с match всегда выполняется первым в процессе фильтрации, за ним следуют атрибутные фильтры. Атрибутные фильтры применяются к результату запроса match. Запрос без клаузы match называется fullscan.
В SELECT должно быть не более одного MATCH().
Используя синтаксис полнотекстового запроса, поиск выполняется по всем индексированным текстовым полям документа, если выражение не требует совпадения внутри поля (например, поиск фразы) или не ограничено операторами поля.
При использовании запросов с JOIN, MATCH() может принимать необязательный второй параметр, который указывает, к какой таблице должен применяться полнотекстовый поиск. По умолчанию полнотекстовый запрос применяется к левой таблице в операции JOIN:
SELECT * FROM table1 LEFT JOIN table2 ON table1.id = table2.id WHERE MATCH('search query', table2);
Это позволяет выполнять полнотекстовый поиск по конкретным таблицам в операции соединения. Для получения дополнительной информации о использовании MATCH с JOIN смотрите раздел Joining tables.
MATCH('search query' [, table_name])
'search query': Строка полнотекстового поискового запроса, которая может включать различные операторы полнотекстового поиска.table_name: (Опционально) Имя таблицы, к которой применяется полнотекстовый поиск, используется в запросах сJOINдля указания таблицы, отличной от левой по умолчанию.
Оператор SELECT использует клаузу MATCH, которая должна идти после WHERE, для выполнения полнотекстового поиска. MATCH() принимает входную строку, в которой доступны все операторы полнотекстового поиска.
- SQL
- MATCH with filters
SELECT * FROM myindex WHERE MATCH('"find me fast"/2');Пример более сложного запроса с использованием MATCH и фильтров WHERE.
SELECT * FROM myindex WHERE MATCH('cats|birds') AND (`title`='some title' AND `id`=123);+------+------+----------------+
| id | gid | title |
+------+------+----------------+
| 1 | 11 | first find me |
| 2 | 12 | second find me |
+------+------+----------------+
2 rows in set (0.00 sec)Полнотекстовый поиск доступен в эндпоинте /search и в HTTP-клиентах. Для выполнения полнотекстового поиска можно использовать следующие клаузы:
"match" — это простой запрос, который ищет указанные ключевые слова в указанных полях.
"query":
{
"match": { "field": "keyword" }
}
Вы можете указать список полей:
"match":
{
"field1,field2": "keyword"
}
Или использовать _all или * для поиска по всем полям.
Вы можете искать по всем полям, кроме одного, используя "!field":
"match":
{
"!field1": "keyword"
}
По умолчанию ключевые слова объединяются оператором OR. Однако вы можете изменить это поведение с помощью клаузы "operator":
"query":
{
"match":
{
"content,title":
{
"query":"keyword",
"operator":"or"
}
}
}
"operator" может быть установлен в "or" или "and".
Также можно применить модификатор boost. Он повышает значение IDF_score слова на указанный коэффициент в рейтинговых оценках, которые учитывают IDF в своих вычислениях. Это не влияет на процесс сопоставления.
"query":
{
"match":
{
"field1":
{
"query": "keyword",
"boost": 2.0
}
}
}
"match_phrase" — это запрос, который ищет полную фразу. Он похож на оператор фразы в SQL. Пример:
"query":
{
"match_phrase": { "_all" : "had grown quite" }
}
"query_string" принимает входную строку в синтаксисе MATCH() для полнотекстового запроса.
"query":
{
"query_string": "Church NOTNEAR/3 street"
}
"match_all" принимает пустой объект и возвращает документы из таблицы без применения атрибутных фильтров или полнотекстового поиска. Альтернативно, можно просто опустить клаузу query в запросе, что даст тот же эффект.
"query":
{
"match_all": {}
}
Все клаузы полнотекстового поиска могут комбинироваться с операторами must, must_not и should в JSON bool запросе.
- match
- match_phrase
- query_string
- PHP
- Python
- Python-asyncio
- javascript
- Java
- C#
- Rust
- TypeScript
- Go
POST /search
-d
'{
"table" : "hn_small",
"query":
{
"match":
{
"*" : "find joe"
}
},
"_source": ["story_author","comment_author"],
"limit": 1
}'POST /search
-d
'{
"table" : "hn_small",
"query":
{
"match_phrase":
{
"*" : "find joe"
}
},
"_source": ["story_author","comment_author"],
"limit": 1
}'POST /search
-d
'{ "table" : "hn_small",
"query":
{
"query_string": "@comment_text \"find joe fast \"/2"
},
"_source": ["story_author","comment_author"],
"limit": 1
}'$search = new Search(new Client());
$result = $search->('@title find me fast');
foreach($result as $doc)
{
echo 'Document: '.$doc->getId();
foreach($doc->getData() as $field=>$value)
{
echo $field.': '.$value;
}
}searchApi.search({"table":"hn_small","query":{"query_string":"@comment_text \"find joe fast \"/2"}, "_source": ["story_author","comment_author"], "limit":1})await searchApi.search({"table":"hn_small","query":{"query_string":"@comment_text \"find joe fast \"/2"}, "_source": ["story_author","comment_author"], "limit":1})res = await searchApi.search({"table":"hn_small","query":{"query_string":"@comment_text \"find joe fast \"/2"}, "_source": ["story_author","comment_author"], "limit":1});query = new HashMap<String,Object>();
query.put("query_string", "@comment_text \"find joe fast \"/2");
searchRequest = new SearchRequest();
searchRequest.setIndex("hn_small");
searchRequest.setQuery(query);
searchRequest.addSourceItem("story_author");
searchRequest.addSourceItem("comment_author");
searchRequest.limit(1);
searchResponse = searchApi.search(searchRequest);object query = new { query_string="@comment_text \"find joe fast \"/2" };
var searchRequest = new SearchRequest("hn_small", query);
searchRequest.Source = new List<string> {"story_author", "comment_author"};
searchRequest.Limit = 1;
SearchResponse searchResponse = searchApi.Search(searchRequest);let query = SearchQuery {
query_string: Some(serde_json::json!("@comment_text \"find joe fast \"/2").into()),
..Default::default()
};
let search_req = SearchRequest {
table: "hn_small".to_string(),
query: Some(Box::new(query)),
source: serde_json::json!(["story_author", "comment_author"]),
limit: serde_json::json!(1),
..Default::default(),
};
let search_res = search_api.search(search_req).await;res = await searchApi.search({
index: 'test',
query: { query_string: "test document 1" },
"_source": ["content", "title"],
limit: 1
});searchRequest := manticoresearch.NewSearchRequest("test")
query := map[string]interface{} {"query_string": "test document 1"}
searchReq.SetSource([]string{"content", "title"})
searchReq.SetLimit(1)
resp, httpRes, err := search.SearchRequest(*searchRequest).Execute(){
"took" : 3,
"timed_out" : false,
"hits" : {
"hits" : [
{
"_id": 668018,
"_score" : 3579,
"_source" : {
"story_author" : "IgorPartola",
"comment_author" : "joe_the_user"
}
}
],
"total" : 88063,
"total_relation" : "eq"
}
}{
"took" : 3,
"timed_out" : false,
"hits" : {
"hits" : [
{
"_id": 807160,
"_score" : 2599,
"_source" : {
"story_author" : "rbanffy",
"comment_author" : "runjake"
}
}
],
"total" : 2,
"total_relation" : "eq"
}
}{
"took" : 3,
"timed_out" : false,
"hits" : {
"hits" : [
{
"_id": 807160,
"_score" : 2566,
"_source" : {
"story_author" : "rbanffy",
"comment_author" : "runjake"
}
}
],
"total" : 1864,
"total_relation" : "eq"
}
}Document: 1
title: first find me fast
gid: 11
Document: 2
title: second find me fast
gid: 12{'aggregations': None,
'hits': {'hits': [{'_id': '807160',
'_score': 2566,
'_source': {'comment_author': 'runjake',
'story_author': 'rbanffy'}}],
'max_score': None,
'total': 1864,
'total_relation': 'eq'},
'profile': None,
'timed_out': False,
'took': 2,
'warning': None}{'aggregations': None,
'hits': {'hits': [{'_id': '807160',
'_score': 2566,
'_source': {'comment_author': 'runjake',
'story_author': 'rbanffy'}}],
'max_score': None,
'total': 1864,
'total_relation': 'eq'},
'profile': None,
'timed_out': False,
'took': 2,
'warning': None}{
took: 1,
timed_out: false,
hits:
exports {
total: 1864,
total_relation: 'eq',
hits:
[ { _id: '807160',
_score: 2566,
_source: { story_author: 'rbanffy', comment_author: 'runjake' }
}
]
}
}class SearchResponse {
took: 1
timedOut: false
aggregations: null
hits: class SearchResponseHits {
maxScore: null
total: 1864
totalRelation: eq
hits: [{_id=807160, _score=2566, _source={story_author=rbanffy, comment_author=runjake}}]
}
profile: null
warning: null
}class SearchResponse {
took: 1
timedOut: false
aggregations: null
hits: class SearchResponseHits {
maxScore: null
total: 1864
totalRelation: eq
hits: [{_id=807160, _score=2566, _source={story_author=rbanffy, comment_author=runjake}}]
}
profile: null
warning: null
}class SearchResponse {
took: 1
timedOut: false
aggregations: null
hits: class SearchResponseHits {
maxScore: null
total: 1864
totalRelation: eq
hits: [{_id=807160, _score=2566, _source={story_author=rbanffy, comment_author=runjake}}]
}
profile: null
warning: null
}{
took: 1,
timed_out: false,
hits:
exports {
total: 5,
total_relation: 'eq',
hits:
[ { _id: '1',
_score: 2566,
_source: { content: 'This is a test document 1', title: 'Doc 1' }
}
]
}
}{
"hits": {
"hits": [
{
"_id": 1,
"_score": 2566,
"_source": {
"content": "This is a test document 1",
"title": "Doc 1"
}
}
],
"total": 5,
"total_relation": "eq"
},
"timed_out": false,
"took": 0
}В строке запроса могут быть включены специальные операторы, которые определяют условия для сопоставления слов из строки запроса.
Неявный логический оператор И всегда присутствует, поэтому "hello world" подразумевает, что и "hello", и "world" должны быть найдены в соответствующем документе.
hello world
Примечание: Нет явного оператора И.
Логический оператор ИЛИ | имеет более высокий приоритет, чем И, поэтому looking for cat | dog | mouse означает looking for (cat | dog | mouse), а не (looking for cat) | dog | mouse.
hello | world
Примечание: Нет оператора ИЛИ. Пожалуйста, используйте | вместо этого.
hello MAYBE world
Оператор МОЖЕТ БЫТЬ работает аналогично оператору |, но не возвращает документы, которые соответствуют только выражению правого поддерева.
hello -world
hello !world
Оператор отрицания налагает правило, что слово не должно существовать.
Запросы, содержащие только отрицания, по умолчанию не поддерживаются. Чтобы включить, используйте параметр сервера not_terms_only_allowed.
@title hello @body world
Оператор ограничения поля ограничивает последующий поиск указанным полем. По умолчанию запрос завершится ошибкой, если указанное имя поля не существует в searched таблице. Однако это поведение можно подавить, указав опцию @@relaxed в начале запроса:
@@relaxed @nosuchfield my query
Это может быть полезно при поиске через разнородные таблицы с различными схемами.
Ограничения позиции поля дополнительно ограничивают поиск первыми N позициями в заданном поле (или полях). Например, @body [50] hello не будет сопоставлять документы, где ключевое слово hello появляется в позиции 51 или позже в теле.
@body[50] hello
Оператор поиска по нескольким полям:
@(title,body) hello world
Оператор игнорирования поиска по полю (игнорирует любые совпадения 'hello world' из поля 'title'):
@!title hello world
Оператор игнорирования поиска по нескольким полям (если есть поля 'title', 'subject' и 'body', то @!(title) эквивалентно @(subject,body)):
@!(title,body) hello world
Оператор поиска по всем полям:
@* hello
"hello world"
Оператор фраз требует, чтобы слова были расположены рядом друг с другом.
Оператор поиска фраз может включать модификатор сопоставление любого термина. В операторе фразы термины имеют позиционную значимость. При использовании модификатора 'сопоставление любого' позиции последующих терминов в этом запросе фразы будут смещены. Как следствие, модификатор 'сопоставление любого' не влияет на производительность поиска.
Примечание: При использовании этого оператора с запросами, содержащими более 31 ключевого слова, статистика ранжирования (такая как tf, idf, bm25) для ключевых слов в позиции 31 и выше может быть недосчитана. Это связано с 32-битной маской, используемой внутренне для отслеживания вхождений терминов в совпадении. Логика сопоставления (поиск документов) остается корректной, но оценки ранжирования могут быть затронуты для очень длинных запросов.
"exact * phrase * * for terms"
Можно также использовать оператор ИЛИ внутри кавычек. Оператор ИЛИ (|) должен быть заключен в квадратные скобки () при использовании внутри фраз. Каждый вариант проверяется в одной позиции, и фраза совпадает, если любой из вариантов подходит к этой позиции.
Правильные примеры (со скобками):
"( a | b ) c"
"( ( a b c ) | d ) e"
"man ( happy | sad ) but all ( ( as good ) | ( as fast ) )"
Неправильные примеры (без скобок - они не сработают):
"a | b c"
"happy | sad"
"hello world"~10
Proximity-расстояние измеряется в словах, учитывает количество слов и применяется ко всем словам в кавычках. Например, запрос "cat dog mouse"~5 означает, что должен быть промежуток менее 8 слов, содержащий все 3 слова. Поэтому документ с CAT aaa bbb ccc DOG eee fff MOUSE не будет соответствовать этому запросу, так как промежуток составляет ровно 8 слов.
Примечание: При использовании этого оператора с запросами, содержащими более 31 ключевого слова, статистика ранжирования (такая как tf, idf, bm25) для ключевых слов в позиции 31 и выше может быть недосчитана. Это связано с 32-битной маской, используемой внутренне для отслеживания вхождений терминов в совпадении. Логика сопоставления (поиск документов) остается корректной, но оценки ранжирования могут быть затронуты для очень длинных запросов.
Можно также использовать оператор ИЛИ внутри proximity-поиска. Оператор ИЛИ (|) должен быть заключен в квадратные скобки () при использовании внутри proximity-поисков. Каждый вариант проверяется отдельно.
Правильный пример (со скобками):
"( two | four ) fish chips"~5
Неправильный пример (без скобок - это не сработает):
"two | four fish chips"~5
"the world is a wonderful place"/3
Оператор кворум-сопоставления вводит тип нечеткого сопоставления. Он будет сопоставлять только те документы, которые удовлетворяют заданному порогу указанных слов. В примере выше ("the world is a wonderful place"/3) он будет сопоставлять все документы, содержащие по крайней мере 3 из 6 указанных слов. Оператор ограничен 255 ключевыми словами. Вместо абсолютного числа можно также указать значение от 0.0 до 1.0 (представляющее 0% и 100%), и Manticore будет сопоставлять только документы, содержащие по крайней мере указанный процент данных слов. Тот же пример выше также может быть выражен как "the world is a wonderful place"/0.5, и он будет сопоставлять документы не менее чем с 50% слов.
Кворум-оператор поддерживает оператор ИЛИ (|). Оператор ИЛИ (|) должен быть заключен в квадратные скобки () при использовании внутри кворум-сопоставления. Только одно слово из каждой группы ИЛИ учитывается при сопоставлении.
Правильные примеры (со скобками):
"( ( a b c ) | d ) e f g"/0.5
"happy ( sad | angry ) man"/2
Неправильный пример (без скобок - это не сработает):
"a b c | d e f g"/0.5
aaa << bbb << ccc
Оператор строгого порядка (также известный как оператор "перед") сопоставляет документ только в том случае, если ключевые слова его аргументов появляются в документе строго в порядке, указанном в запросе. Например, запрос black << cat будет сопоставлен с документом "black and white cat", но не с документом "that cat was black". Оператор порядка имеет самый низкий приоритет. Он может быть применен как к отдельным ключевым словам, так и к более сложным выражениям. Например, это допустимый запрос:
(bag of words) << "exact phrase" << red|green|blue
raining =cats and =dogs
="exact phrase"
Ключевое слово точного модификатора формы сопоставляет документ только в том случае, если ключевое слово встречается в точно указанной форме. По умолчанию документ считается совпадающим, если стемминговое/лемматизированное ключевое слово совпадает. Например, запрос "runs" будет соответствовать как документу, содержащему "runs", так и содержащему "running", поскольку обе формы стемируются до "run". Однако запрос =runs будет соответствовать только первому документу. Точный модификатор формы требует включения параметра index_exact_words.
Другой вариант использования - предотвращение расширения ключевого слова до его формы *keyword*. Например, при index_exact_words=1 + expand_keywords=1/star, bcd найдет документ, содержащий abcde, а =bcd - нет.
Как модификатор, влияющий на ключевое слово, он может использоваться внутри операторов, таких как фразовый, proximity и кворумный операторы. Применение точного модификатора формы к фразовому оператору возможно, и в этом случае он внутренне добавляет точный модификатор формы ко всем терминам в фразе.
nation* *nation* *national
Требует min_infix_len для префикса (расширение в конце) и/или суффикса (расширение в начале). Если требуется только префиксация, можно использовать min_prefix_len.
Поиск попытается найти все расширения токенов с подстановочными знаками, и каждое расширение записывается как найденное совпадение. Количество расширений для токена можно контролировать с помощью параметра таблицы expansion_limit. Токены с подстановочными знаками могут существенно влиять на время поиска запроса, особенно когда токены имеют небольшую длину. В таких случаях желательно использовать ограничение расширения.
Оператор подстановочных знаков может быть автоматически применен при использовании параметра таблицы expand_keywords.
Кроме того, поддерживаются следующие встроенные операторы подстановочных знаков:
?может соответствовать любому одному символу:t?stбудет соответствоватьtest, но неteast%может соответствовать нулю или одному символу:tes%будет соответствоватьtesилиtest, но неtesting
Встроенные операторы требуют dict=keywords (включено по умолчанию) и включенной префиксации/инфиксации.
REGEX(/t.?e/)
Требует установки параметров min_infix_len или min_prefix_len и dict=keywords (что является настройкой по умолчанию).
Подобно операторам подстановочных знаков, оператор REGEX пытается найти все токены, соответствующие указанному шаблону, и каждое расширение записывается как найденное совпадение. Обратите внимание, что это может существенно повлиять на время поиска запроса, так как сканируется весь словарь, и каждый термин в словаре проходит сопоставление с шаблоном REGEX.
Шаблоны должны соответствовать синтаксису RE2. Разделитель выражения REGEX - первый символ после открывающей скобки. Другими словами, весь текст между открывающей скобкой, за которой следует разделитель, и разделителем и закрывающей скобкой считается выражением RE2.
Обратите внимание, что термины, хранящиеся в словаре, проходят преобразование charset_table, что означает, например, REGEX может не суметь сопоставить прописные буквы, если все символы преобразованы в строчные согласно charset_table (что происходит по умолчанию). Чтобы успешно сопоставить термин с помощью выражения REGEX, шаблон должен соответствовать всему токену. Для достижения частичного сопоставления поместите .* в начало и/или конец вашего шаблона.
REGEX(/.{3}t/)
REGEX(/t.*\d*/)
^hello world$
Модификаторы ключевого слова начала и конца поля гарантируют, что ключевое слово будет соответствовать только в том случае, если оно встречается в самом начале или в самом конце полнотекстового поля соответственно. Например, запрос "^hello world$" (заключенный в кавычки для объединения фразового оператора с модификаторами начала/конца) будет исключительно соответствовать документам, содержащим хотя бы одно поле с этими двумя конкретными ключевыми словами.
boosted^1.234 boostedfieldend$^1.234
Модификатор усиления повышает IDF_score на указанный коэффициент при вычислении оценок ранжирования, включающих IDF. Он не влияет на процесс сопоставления никоим образом.
hello NEAR/3 world NEAR/4 "my test"
Оператор NEAR является более обобщенной версией proximity-оператора. Его синтаксис - NEAR/N, который чувствителен к регистру и не допускает пробелов между ключевыми словами NEAR, знаком косой черты и значением расстояния.
Если исходный proximity-оператор работает только с наборами ключевых слов, NEAR является более универсальным и может принимать произвольные подвыражения в качестве своих двух аргументов. Он сопоставляет документ, когда оба подвыражения находятся в пределах N слов друг от друга, независимо от их порядка. NEAR левоассоциативен и имеет такой же (самый низкий) приоритет, как и BEFORE.
Важно отметить, что one NEAR/7 two NEAR/7 three не совсем эквивалентен "one two three"~7. Ключевое различие заключается в том, что proximity-оператор допускает до 6 несовпадающих слов между всеми тремя совпадающими словами, в то время как версия с NEAR менее ограничительна: она допускает до 6 слов между one и two, а затем еще до 6 между этим двухсловным совпадением и three.
Примечание: При использовании этого оператора в запросах, содержащих более 31 ключевого слова, статистика ранжирования (такая как tf, idf, bm25) для ключевых слов в позиции 31 и выше может быть занижена. Это связано с 32-битной маской, используемой внутренне для отслеживания вхождений терминов в совпадении. Логика сопоставления (поиск документов) остается корректной, но оценки ранжирования могут быть затронуты для очень длинных запросов.
Church NOTNEAR/3 street
Оператор NOTNEAR служит для отрицательного утверждения. Он находит документ, когда левый аргумент присутствует, а правый аргумент либо отсутствует в документе, либо находится на указанном расстоянии от конца левого совпавшего аргумента. Расстояние указывается в словах. Синтаксис — NOTNEAR/N, который чувствителен к регистру и не допускает пробелов между ключевым словом NOTNEAR, знаком косой черты и значением расстояния. Оба аргумента этого оператора могут быть терминами, операторами или группами операторов.
all SENTENCE words SENTENCE "in one sentence"
"Bill Gates" PARAGRAPH "Steve Jobs"
Операторы SENTENCE и PARAGRAPH находят документ, когда оба их аргумента находятся в одном предложении или в одном абзаце текста соответственно. Этими аргументами могут быть ключевые слова, фразы или экземпляры того же оператора.
Порядок аргументов внутри предложения или абзаца не имеет значения. Эти операторы работают только с таблицами, построенными с включенной функцией index_sp (индексирование предложений и абзацев), в противном случае они вырождаются в простую операцию И. Информацию о том, что считается предложением и абзацем, см. в документации по директиве index_sp.
ZONE:(h3,h4)
only in these titles
Оператор ZONE limit очень похож на оператор ограничения поля, но ограничивает сопоставление указанной зоной внутри поля или списком зон. Важно отметить, что последующие подвыражения не обязательно должны совпадать в пределах одного непрерывного участка данной зоны и могут совпадать в разных участках. Например, запрос (ZONE:th hello world) найдет следующий образец документа:
<th>Table 1. Local awareness of Hello Kitty brand.</th>
.. some table data goes here ..
<th>Table 2. World-wide brand awareness.</th>
Оператор ZONE влияет на запрос до следующего оператора ограничения поля или зоны либо до закрывающей скобки. Он работает исключительно с таблицами, построенными с поддержкой зон (см. index_zones), в противном случае будет проигнорирован.
ZONESPAN:(h2)
only in a (single) title
Оператор ZONESPAN limit похож на оператор ZONE, но требует, чтобы совпадение происходило в пределах одного непрерывного участка. В приведенном ранее примере ZONESPAN:th hello world не найдет документ, поскольку слова "hello" и "world" не находятся в одном участке.
Поскольку некоторые символы выполняют функции операторов в строке запроса, их необходимо экранировать, чтобы избежать ошибок запроса или непреднамеренных условий совпадения.
Следующие символы должны быть экранированы с помощью обратного слэша (\):
! " $ ' ( ) - / < @ \ ^ | ~
Для экранирования одинарной кавычки ('), используйте один обратный слэш:
SELECT * FROM your_index WHERE MATCH('l\'italiano');
Для остальных символов из ранее упомянутого списка, которые являются операторами или конструкциями запроса, они должны рассматриваться движком как обычные символы с предшествующим символом экранирования. Обратный слэш также должен быть экранирован, что приводит к двум обратным слэшам:
SELECT * FROM your_index WHERE MATCH('r\\&b | \\(official video\\)');
Чтобы использовать обратный слэш как символ, необходимо экранировать и обратный слэш как символ, и обратный слэш как оператор экранирования, что требует четырёх обратных слэшей:
SELECT * FROM your_index WHERE MATCH('\\\\ABC');
Когда вы работаете с JSON-данными в Manticore Search и нужно включить двойную кавычку (") внутри JSON-строки, важно правильно её экранировать. В JSON двойная кавычка внутри строки экранируется с помощью обратного слэша (\). Однако при вставке JSON-данных через SQL-запрос Manticore Search интерпретирует обратный слэш (\) как символ экранирования внутри строк.
Чтобы двойная кавычка была корректно вставлена в JSON-данные, необходимо экранировать сам обратный слэш. Это приводит к использованию двух обратных слэшей (\\) перед двойной кавычкой. Например:
insert into tbl(j) values('{"a": "\\"abc\\""}');
Драйверы MySQL предоставляют функции экранирования (например, mysqli_real_escape_string в PHP или conn.escape_string в Python), но они экранируют только определённые символы.
Вам всё равно нужно будет добавить экранирование для символов из ранее упомянутого списка, которые не экранируются соответствующими функциями.
Поскольку эти функции экранируют обратный слэш за вас, вам нужно добавить только один обратный слэш.
Это также относится к драйверам, поддерживающим (клиентские) подготовленные выражения. Например, с подготовленными выражениями PHP PDO необходимо добавить обратный слэш для символа $:
$statement = $ln_sph->prepare( "SELECT * FROM index WHERE MATCH(:match)");
$match = '\$manticore';
$statement->bindParam(':match',$match,PDO::PARAM_STR);
$results = $statement->execute();
В результате получается итоговый запрос SELECT * FROM index WHERE MATCH('\\$manticore');
Те же правила, что и для SQL-протокола, применимы, за исключением того, что для JSON двойная кавычка должна экранироваться одним обратным слэшем, а остальные символы требуют двойного экранирования.
При использовании JSON-библиотек или функций, которые преобразуют структуры данных в JSON-строки, двойная кавычка и одиночный обратный слэш автоматически экранируются этими функциями и не требуют явного экранирования.
Официальные клиенты используют общие JSON-библиотеки/функции, доступные в соответствующих языках программирования. Применяются те же правила экранирования, упомянутые ранее.
Звёздочка (*) — уникальный символ, который выполняет две функции:
- как подстановочный символ в начале/конце слова
- как модификатор "любой термин" внутри поиска по фразе.
В отличие от других специальных символов, которые функционируют как операторы, звёздочка не может быть экранирована, когда она находится в позиции, обеспечивающей одну из своих функций.
В запросах без подстановочных символов звёздочка не требует экранирования, независимо от того, находится ли она в charset_table или нет.
В запросах с подстановочными символами звёздочка в середине слова не требует экранирования. В качестве оператора подстановки (в начале или конце слова) звёздочка всегда будет интерпретироваться как оператор подстановки, даже если применяется экранирование.
Для экранирования специальных символов в узлах JSON используйте обратные кавычки. Например:
MySQL [(none)]> select * from t where json.`a=b`=234;
+---------------------+-------------+------+
| id | json | text |
+---------------------+-------------+------+
| 8215557549554925578 | {"a=b":234} | |
+---------------------+-------------+------+
MySQL [(none)]> select * from t where json.`a:b`=123;
+---------------------+-------------+------+
| id | json | text |
+---------------------+-------------+------+
| 8215557549554925577 | {"a:b":123} | |
+---------------------+-------------+------+
Рассмотрим этот пример сложного запроса:
"hello world" @title "example program"~5 @body python -(php|perl) @* code
Полное значение этого поиска:
- Найти слова 'hello' и 'world' рядом друг с другом в любом поле документа;
- Кроме того, в том же документе должны содержаться слова 'example' и 'program' в поле заголовка, с не более чем 5 словами между ними (не включая 5); (например, "example PHP program" подойдет, а "example script to introduce outside data into the correct context for your program" — нет, так как между двумя терминами 5 или более слов)
- Более того, в том же документе должно быть слово 'python' в поле body, при этом исключая 'php' или 'perl';
- Наконец, в том же документе должно содержаться слово 'code' в любом поле.
Оператор OR имеет приоритет над AND, поэтому "looking for cat | dog | mouse" означает "looking for (cat | dog | mouse)", а не "(looking for cat) | dog | mouse".
Чтобы понять, как будет выполняться запрос, Manticore Search предоставляет инструменты профилирования запросов для изучения дерева запроса, сгенерированного выражением запроса.
Чтобы включить профилирование полнотекстового запроса с помощью SQL-запроса, необходимо активировать его перед выполнением нужного запроса:
SET profiling =1;
SELECT * FROM test WHERE MATCH('@title abc* @body hey');
Чтобы просмотреть дерево запроса, выполните команду SHOW PLAN сразу после выполнения запроса:
SHOW PLAN;
Эта команда вернет структуру выполненного запроса. Имейте в виду, что 3 оператора — SET profiling, сам запрос и SHOW — должны выполняться в одной сессии.
При использовании протокола HTTP JSON можно просто включить "profile":true, чтобы получить в ответе структуру дерева полнотекстового запроса.
{
"table":"test",
"profile":true,
"query":
{
"match_phrase": { "_all" : "had grown quite" }
}
}
В ответе будет объект profile, содержащий член query.
Свойство query содержит преобразованное дерево полнотекстового запроса. Каждый узел состоит из:
type: тип узла, который может быть AND, OR, PHRASE, KEYWORD и т.д.description: поддерево запроса для этого узла, представленное в виде строки (в форматеSHOW PLAN)children: дочерние узлы, если естьmax_field_pos: максимальная позиция в поле
У узла ключевого слова дополнительно будут:
word: преобразованное ключевое слово.querypos: позиция этого ключевого слова в запросе.excluded: ключевое слово исключено из запроса.expanded: ключевое слово добавлено расширением префикса.field_start: ключевое слово должно появиться в начале поля.field_end: ключевое слово должно появиться в конце поля.boost: IDF ключевого слова будет умножен на это значение.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- Java
- C#
- Rust
- TypeScript
- Go
SET profiling=1;
SELECT * FROM test WHERE MATCH('@title abc* @body hey');
SHOW PLAN \GPOST /search
{
"table": "forum",
"query": {"query_string": "i me"},
"_source": { "excludes":["*"] },
"limit": 1,
"profile":true
}$result = $index->search('i me')->setSource(['excludes'=>['*']])->setLimit(1)->profile()->get();
print_r($result->getProfile());searchApi.search({"table":"forum","query":{"query_string":"i me"},"_source":{"excludes":["*"]},"limit":1,"profile":True})await searchApi.search({"table":"forum","query":{"query_string":"i me"},"_source":{"excludes":["*"]},"limit":1,"profile":True})res = await searchApi.search({"table":"forum","query":{"query_string":"i me"},"_source":{"excludes":["*"]},"limit":1,"profile":true});query = new HashMap<String,Object>();
query.put("query_string","i me");
searchRequest = new SearchRequest();
searchRequest.setIndex("forum");
searchRequest.setQuery(query);
searchRequest.setProfile(true);
searchRequest.setLimit(1);
searchRequest.setSort(new ArrayList<String>(){{
add("*");
}});
searchResponse = searchApi.search(searchRequest);object query = new { query_string="i me" };
var searchRequest = new SearchRequest("forum", query);
searchRequest.Profile = true;
searchRequest.Limit = 1;
searchRequest.Sort = new List<Object> { "*" };
var searchResponse = searchApi.Search(searchRequest);let query = SearchQuery {
query_string: Some(serde_json::json!("i me").into()),
..Default::default()
};
let search_req = SearchRequest {
table: "forum".to_string(),
query: Some(Box::new(query)),
sort: serde_json::json!(["*"]),
limit: serde_json::json!(1),
profile: serde_json::json!(true),
..Default::default(),
};
let search_res = search_api.search(search_req).await;res = await searchApi.search({
index: 'test',
query: { query_string: 'Text' },
_source: { excludes: ['*'] },
limit: 1,
profile: true
});searchRequest := manticoresearch.NewSearchRequest("test")
query := map[string]interface{} {"query_string": "Text"}
source := map[string]interface{} { "excludes": []string {"*"} }
searchRequest.SetQuery(query)
searchRequest.SetSource(source)
searchReq.SetLimit(1)
searchReq.SetProfile(true)
res, _, _ := apiClient.SearchAPI.Search(context.Background()).SearchRequest(*searchRequest).Execute()*************************** 1\. row ***************************
Variable: transformed_tree
Value: AND(
OR(fields=(title), KEYWORD(abcx, querypos=1, expanded), KEYWORD(abcm, querypos=1, expanded)),
AND(fields=(body), KEYWORD(hey, querypos=2)))
1 row in set (0.00 sec){
"took":1503,
"timed_out":false,
"hits":
{
"total":406301,
"hits":
[
{
"_id": 406443,
"_score":3493,
"_source":{}
}
]
},
"profile":
{
"query":
{
"type":"AND",
"description":"AND( AND(KEYWORD(i, querypos=1)), AND(KEYWORD(me, querypos=2)))",
"children":
[
{
"type":"AND",
"description":"AND(KEYWORD(i, querypos=1))",
"children":
[
{
"type":"KEYWORD",
"word":"i",
"querypos":1
}
]
},
{
"type":"AND",
"description":"AND(KEYWORD(me, querypos=2))",
"children":
[
{
"type":"KEYWORD",
"word":"me",
"querypos":2
}
]
}
]
}
}
}Array
(
[query] => Array
(
[type] => AND
[description] => AND( AND(KEYWORD(i, querypos=1)), AND(KEYWORD(me, querypos=2)))
[children] => Array
(
[0] => Array
(
[type] => AND
[description] => AND(KEYWORD(i, querypos=1))
[children] => Array
(
[0] => Array
(
[type] => KEYWORD
[word] => i
[querypos] => 1
)
)
)
[1] => Array
(
[type] => AND
[description] => AND(KEYWORD(me, querypos=2))
[children] => Array
(
[0] => Array
(
[type] => KEYWORD
[word] => me
[querypos] => 2
)
)
)
)
)
){'hits': {'hits': [{u'_id': u'100', u'_score': 2500, u'_source': {}}],
'total': 1},
'profile': {u'query': {u'children': [{u'children': [{u'querypos': 1,
u'type': u'KEYWORD',
u'word': u'i'}],
u'description': u'AND(KEYWORD(i, querypos=1))',
u'type': u'AND'},
{u'children': [{u'querypos': 2,
u'type': u'KEYWORD',
u'word': u'me'}],
u'description': u'AND(KEYWORD(me, querypos=2))',
u'type': u'AND'}],
u'description': u'AND( AND(KEYWORD(i, querypos=1)), AND(KEYWORD(me, querypos=2)))',
u'type': u'AND'}},
'timed_out': False,
'took': 0}{'hits': {'hits': [{u'_id': u'100', u'_score': 2500, u'_source': {}}],
'total': 1},
'profile': {u'query': {u'children': [{u'children': [{u'querypos': 1,
u'type': u'KEYWORD',
u'word': u'i'}],
u'description': u'AND(KEYWORD(i, querypos=1))',
u'type': u'AND'},
{u'children': [{u'querypos': 2,
u'type': u'KEYWORD',
u'word': u'me'}],
u'description': u'AND(KEYWORD(me, querypos=2))',
u'type': u'AND'}],
u'description': u'AND( AND(KEYWORD(i, querypos=1)), AND(KEYWORD(me, querypos=2)))',
u'type': u'AND'}},
'timed_out': False,
'took': 0}{"hits": {"hits": [{"_id": 100, "_score": 2500, "_source": {}}],
"total": 1},
"profile": {"query": {"children": [{"children": [{"querypos": 1,
"type": "KEYWORD",
"word": "i"}],
"description": "AND(KEYWORD(i, querypos=1))",
"type": "AND"},
{"children": [{"querypos": 2,
"type": "KEYWORD",
"word": "me"}],
"description": "AND(KEYWORD(me, querypos=2))",
"type": "AND"}],
"description": "AND( AND(KEYWORD(i, querypos=1)), AND(KEYWORD(me, querypos=2)))",
"type": "AND"}},
"timed_out": False,
"took": 0}class SearchResponse {
took: 18
timedOut: false
hits: class SearchResponseHits {
total: 1
hits: [{_id=100, _score=2500, _source={}}]
aggregations: null
}
profile: {query={type=AND, description=AND( AND(KEYWORD(i, querypos=1)), AND(KEYWORD(me, querypos=2))), children=[{type=AND, description=AND(KEYWORD(i, querypos=1)), children=[{type=KEYWORD, word=i, querypos=1}]}, {type=AND, description=AND(KEYWORD(me, querypos=2)), children=[{type=KEYWORD, word=me, querypos=2}]}]}}
}class SearchResponse {
took: 18
timedOut: false
hits: class SearchResponseHits {
total: 1
hits: [{_id=100, _score=2500, _source={}}]
aggregations: null
}
profile: {query={type=AND, description=AND( AND(KEYWORD(i, querypos=1)), AND(KEYWORD(me, querypos=2))), children=[{type=AND, description=AND(KEYWORD(i, querypos=1)), children=[{type=KEYWORD, word=i, querypos=1}]}, {type=AND, description=AND(KEYWORD(me, querypos=2)), children=[{type=KEYWORD, word=me, querypos=2}]}]}}
}class SearchResponse {
took: 18
timedOut: false
hits: class SearchResponseHits {
total: 1
hits: [{_id=100, _score=2500, _source={}}]
aggregations: null
}
profile: {query={type=AND, description=AND( AND(KEYWORD(i, querypos=1)), AND(KEYWORD(me, querypos=2))), children=[{type=AND, description=AND(KEYWORD(i, querypos=1)), children=[{type=KEYWORD, word=i, querypos=1}]}, {type=AND, description=AND(KEYWORD(me, querypos=2)), children=[{type=KEYWORD, word=me, querypos=2}]}]}}
}{
"hits":
{
"hits":
[{
"_id": 1,
"_score": 1480,
"_source": {}
}],
"total": 1
},
"profile":
{
"query": {
"children":
[{
"children":
[{
"querypos": 1,
"type": "KEYWORD",
"word": "i"
}],
"description": "AND(KEYWORD(i, querypos=1))",
"type": "AND"
},
{
"children":
[{
"querypos": 2,
"type": "KEYWORD",
"word": "me"
}],
"description": "AND(KEYWORD(me, querypos=2))",
"type": "AND"
}],
"description": "AND( AND(KEYWORD(i, querypos=1)), AND(KEYWORD(me, querypos=2)))",
"type": "AND"
}
},
"timed_out": False,
"took": 0
}{
"hits":
{
"hits":
[{
"_id": 1,
"_score": 1480,
"_source": {}
}],
"total": 1
},
"profile":
{
"query": {
"children":
[{
"children":
[{
"querypos": 1,
"type": "KEYWORD",
"word": "i"
}],
"description": "AND(KEYWORD(i, querypos=1))",
"type": "AND"
},
{
"children":
[{
"querypos": 2,
"type": "KEYWORD",
"word": "me"
}],
"description": "AND(KEYWORD(me, querypos=2))",
"type": "AND"
}],
"description": "AND( AND(KEYWORD(i, querypos=1)), AND(KEYWORD(me, querypos=2)))",
"type": "AND"
}
},
"timed_out": False,
"took": 0
}В некоторых случаях оцениваемое дерево запроса может значительно отличаться от исходного из-за расширений и других преобразований.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- Java
- C#
- Rust
- TypeScript
- Go
SET profiling=1;
SELECT id FROM forum WHERE MATCH('@title way* @content hey') LIMIT 1;
SHOW PLAN;POST /search
{
"table": "forum",
"query": {"query_string": "@title way* @content hey"},
"_source": { "excludes":["*"] },
"limit": 1,
"profile":true
}$result = $index->search('@title way* @content hey')->setSource(['excludes'=>['*']])->setLimit(1)->profile()->get();
print_r($result->getProfile());searchApi.search({"table":"forum","query":{"query_string":"@title way* @content hey"},"_source":{"excludes":["*"]},"limit":1,"profile":true})await searchApi.search({"table":"forum","query":{"query_string":"@title way* @content hey"},"_source":{"excludes":["*"]},"limit":1,"profile":true})res = await searchApi.search({"table":"forum","query":{"query_string":"@title way* @content hey"},"_source":{"excludes":["*"]},"limit":1,"profile":true});query = new HashMap<String,Object>();
query.put("query_string","@title way* @content hey");
searchRequest = new SearchRequest();
searchRequest.setIndex("forum");
searchRequest.setQuery(query);
searchRequest.setProfile(true);
searchRequest.setLimit(1);
searchRequest.setSort(new ArrayList<String>(){{
add("*");
}});
searchResponse = searchApi.search(searchRequest);object query = new { query_string="@title way* @content hey" };
var searchRequest = new SearchRequest("forum", query);
searchRequest.Profile = true;
searchRequest.Limit = 1;
searchRequest.Sort = new List<Object> { "*" };
var searchResponse = searchApi.Search(searchRequest);let query = SearchQuery {
query_string: Some(serde_json::json!("@title way* @content hey").into()),
..Default::default()
};
let search_req = SearchRequest {
table: "forum".to_string(),
query: Some(Box::new(query)),
sort: serde_json::json!(["*"]),
limit: serde_json::json!(1),
profile: serde_json::json!(true),
..Default::default(),
};
let search_res = search_api.search(search_req).await;res = await searchApi.search({
index: 'test',
query: { query_string: '@content 1'},
_source: { excludes: ["*"] },
limit:1,
profile":true
});searchRequest := manticoresearch.NewSearchRequest("test")
query := map[string]interface{} {"query_string": "1*"}
source := map[string]interface{} { "excludes": []string {"*"} }
searchRequest.SetQuery(query)
searchRequest.SetSource(source)
searchReq.SetLimit(1)
searchReq.SetProfile(true)
res, _, _ := apiClient.SearchAPI.Search(context.Background()).SearchRequest(*searchRequest).Execute()Query OK, 0 rows affected (0.00 sec)
+--------+
| id |
+--------+
| 711651 |
+--------+
1 row in set (0.04 sec)
+------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Variable | Value |
+------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| transformed_tree | AND(
OR(
OR(
AND(fields=(title), KEYWORD(wayne, querypos=1, expanded)),
OR(
AND(fields=(title), KEYWORD(ways, querypos=1, expanded)),
AND(fields=(title), KEYWORD(wayyy, querypos=1, expanded)))),
AND(fields=(title), KEYWORD(way, querypos=1, expanded)),
OR(fields=(title), KEYWORD(way*, querypos=1, expanded))),
AND(fields=(content), KEYWORD(hey, querypos=2))) |
+------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec){
"took":33,
"timed_out":false,
"hits":
{
"total":105,
"hits":
[
{
"_id": 711651,
"_score":2539,
"_source":{}
}
]
},
"profile":
{
"query":
{
"type":"AND",
"description":"AND( OR( OR( AND(fields=(title), KEYWORD(wayne, querypos=1, expanded)), OR( AND(fields=(title), KEYWORD(ways, querypos=1, expanded)), AND(fields=(title), KEYWORD(wayyy, querypos=1, expanded)))), AND(fields=(title), KEYWORD(way, querypos=1, expanded)), OR(fields=(title), KEYWORD(way*, querypos=1, expanded))), AND(fields=(content), KEYWORD(hey, querypos=2)))",
"children":
[
{
"type":"OR",
"description":"OR( OR( AND(fields=(title), KEYWORD(wayne, querypos=1, expanded)), OR( AND(fields=(title), KEYWORD(ways, querypos=1, expanded)), AND(fields=(title), KEYWORD(wayyy, querypos=1, expanded)))), AND(fields=(title), KEYWORD(way, querypos=1, expanded)), OR(fields=(title), KEYWORD(way*, querypos=1, expanded)))",
"children":
[
{
"type":"OR",
"description":"OR( AND(fields=(title), KEYWORD(wayne, querypos=1, expanded)), OR( AND(fields=(title), KEYWORD(ways, querypos=1, expanded)), AND(fields=(title), KEYWORD(wayyy, querypos=1, expanded))))",
"children":
[
{
"type":"AND",
"description":"AND(fields=(title), KEYWORD(wayne, querypos=1, expanded))",
"fields":["title"],
"max_field_pos":0,
"children":
[
{
"type":"KEYWORD",
"word":"wayne",
"querypos":1,
"expanded":true
}
]
},
{
"type":"OR",
"description":"OR( AND(fields=(title), KEYWORD(ways, querypos=1, expanded)), AND(fields=(title), KEYWORD(wayyy, querypos=1, expanded)))",
"children":
[
{
"type":"AND",
"description":"AND(fields=(title), KEYWORD(ways, querypos=1, expanded))",
"fields":["title"],
"max_field_pos":0,
"children":
[
{
"type":"KEYWORD",
"word":"ways",
"querypos":1,
"expanded":true
}
]
},
{
"type":"AND",
"description":"AND(fields=(title), KEYWORD(wayyy, querypos=1, expanded))",
"fields":["title"],
"max_field_pos":0,
"children":
[
{
"type":"KEYWORD",
"word":"wayyy",
"querypos":1,
"expanded":true
}
]
}
]
}
]
},
{
"type":"AND",
"description":"AND(fields=(title), KEYWORD(way, querypos=1, expanded))",
"fields":["title"],
"max_field_pos":0,
"children":
[
{
"type":"KEYWORD",
"word":"way",
"querypos":1,
"expanded":true
}
]
},
{
"type":"OR",
"description":"OR(fields=(title), KEYWORD(way*, querypos=1, expanded))",
"fields":["title"],
"max_field_pos":0,
"children":
[
{
"type":"KEYWORD",
"word":"way*",
"querypos":1,
"expanded":true
}
]
}
]
},
{
"type":"AND",
"description":"AND(fields=(content), KEYWORD(hey, querypos=2))",
"fields":["content"],
"max_field_pos":0,
"children":
[
{
"type":"KEYWORD",
"word":"hey",
"querypos":2
}
]
}
]
}
}
}Array
(
[query] => Array
(
[type] => AND
[description] => AND( OR( OR( AND(fields=(title), KEYWORD(wayne, querypos=1, expanded)), OR( AND(fields=(title), KEYWORD(ways, querypos=1, expanded)), AND(fields=(title), KEYWORD(wayyy, querypos=1, expanded)))), AND(fields=(title), KEYWORD(way, querypos=1, expanded)), OR(fields=(title), KEYWORD(way*, querypos=1, expanded))), AND(fields=(content), KEYWORD(hey, querypos=2)))
[children] => Array
(
[0] => Array
(
[type] => OR
[description] => OR( OR( AND(fields=(title), KEYWORD(wayne, querypos=1, expanded)), OR( AND(fields=(title), KEYWORD(ways, querypos=1, expanded)), AND(fields=(title), KEYWORD(wayyy, querypos=1, expanded)))), AND(fields=(title), KEYWORD(way, querypos=1, expanded)), OR(fields=(title), KEYWORD(way*, querypos=1, expanded)))
[children] => Array
(
[0] => Array
(
[type] => OR
[description] => OR( AND(fields=(title), KEYWORD(wayne, querypos=1, expanded)), OR( AND(fields=(title), KEYWORD(ways, querypos=1, expanded)), AND(fields=(title), KEYWORD(wayyy, querypos=1, expanded))))
[children] => Array
(
[0] => Array
(
[type] => AND
[description] => AND(fields=(title), KEYWORD(wayne, querypos=1, expanded))
[fields] => Array
(
[0] => title
)
[max_field_pos] => 0
[children] => Array
(
[0] => Array
(
[type] => KEYWORD
[word] => wayne
[querypos] => 1
[expanded] => 1
)
)
)
[1] => Array
(
[type] => OR
[description] => OR( AND(fields=(title), KEYWORD(ways, querypos=1, expanded)), AND(fields=(title), KEYWORD(wayyy, querypos=1, expanded)))
[children] => Array
(
[0] => Array
(
[type] => AND
[description] => AND(fields=(title), KEYWORD(ways, querypos=1, expanded))
[fields] => Array
(
[0] => title
)
[max_field_pos] => 0
[children] => Array
(
[0] => Array
(
[type] => KEYWORD
[word] => ways
[querypos] => 1
[expanded] => 1
)
)
)
[1] => Array
(
[type] => AND
[description] => AND(fields=(title), KEYWORD(wayyy, querypos=1, expanded))
[fields] => Array
(
[0] => title
)
[max_field_pos] => 0
[children] => Array
(
[0] => Array
(
[type] => KEYWORD
[word] => wayyy
[querypos] => 1
[expanded] => 1
)
)
)
)
)
)
)
[1] => Array
(
[type] => AND
[description] => AND(fields=(title), KEYWORD(way, querypos=1, expanded))
[fields] => Array
(
[0] => title
)
[max_field_pos] => 0
[children] => Array
(
[0] => Array
(
[type] => KEYWORD
[word] => way
[querypos] => 1
[expanded] => 1
)
)
)
[2] => Array
(
[type] => OR
[description] => OR(fields=(title), KEYWORD(way*, querypos=1, expanded))
[fields] => Array
(
[0] => title
)
[max_field_pos] => 0
[children] => Array
(
[0] => Array
(
[type] => KEYWORD
[word] => way*
[querypos] => 1
[expanded] => 1
)
)
)
)
)
[1] => Array
(
[type] => AND
[description] => AND(fields=(content), KEYWORD(hey, querypos=2))
[fields] => Array
(
[0] => content
)
[max_field_pos] => 0
[children] => Array
(
[0] => Array
(
[type] => KEYWORD
[word] => hey
[querypos] => 2
)
)
)
)
)
){'hits': {'hits': [{u'_id': u'2811025403043381551',
u'_score': 2643,
u'_source': {}}],
'total': 1},
'profile': {u'query': {u'children': [{u'children': [{u'expanded': True,
u'querypos': 1,
u'type': u'KEYWORD',
u'word': u'way*'}],
u'description': u'AND(fields=(title), KEYWORD(way*, querypos=1, expanded))',
u'fields': [u'title'],
u'type': u'AND'},
{u'children': [{u'querypos': 2,
u'type': u'KEYWORD',
u'word': u'hey'}],
u'description': u'AND(fields=(content), KEYWORD(hey, querypos=2))',
u'fields': [u'content'],
u'type': u'AND'}],
u'description': u'AND( AND(fields=(title), KEYWORD(way*, querypos=1, expanded)), AND(fields=(content), KEYWORD(hey, querypos=2)))',
u'type': u'AND'}},
'timed_out': False,
'took': 0}{'hits': {'hits': [{u'_id': u'2811025403043381551',
u'_score': 2643,
u'_source': {}}],
'total': 1},
'profile': {u'query': {u'children': [{u'children': [{u'expanded': True,
u'querypos': 1,
u'type': u'KEYWORD',
u'word': u'way*'}],
u'description': u'AND(fields=(title), KEYWORD(way*, querypos=1, expanded))',
u'fields': [u'title'],
u'type': u'AND'},
{u'children': [{u'querypos': 2,
u'type': u'KEYWORD',
u'word': u'hey'}],
u'description': u'AND(fields=(content), KEYWORD(hey, querypos=2))',
u'fields': [u'content'],
u'type': u'AND'}],
u'description': u'AND( AND(fields=(title), KEYWORD(way*, querypos=1, expanded)), AND(fields=(content), KEYWORD(hey, querypos=2)))',
u'type': u'AND'}},
'timed_out': False,
'took': 0}{"hits": {"hits": [{"_id": 2811025403043381551,
"_score": 2643,
"_source": {}}],
"total": 1},
"profile": {"query": {"children": [{"children": [{"expanded": True,
"querypos": 1,
"type": "KEYWORD",
"word": "way*"}],
"description": "AND(fields=(title), KEYWORD(way*, querypos=1, expanded))",
"fields": ["title"],
"type": "AND"},
{"children": [{"querypos": 2,
"type": "KEYWORD",
"word": "hey"}],
"description": "AND(fields=(content), KEYWORD(hey, querypos=2))",
"fields": ["content"],
"type": "AND"}],
"description": "AND( AND(fields=(title), KEYWORD(way*, querypos=1, expanded)), AND(fields=(content), KEYWORD(hey, querypos=2)))",
"type": "AND"}},
"timed_out": False,
"took": 0}class SearchResponse {
took: 18
timedOut: false
hits: class SearchResponseHits {
total: 1
hits: [{_id=2811025403043381551, _score=2643, _source={}}]
aggregations: null
}
profile: {query={type=AND, description=AND( AND(fields=(title), KEYWORD(way*, querypos=1, expanded)), AND(fields=(content), KEYWORD(hey, querypos=2))), children=[{type=AND, description=AND(fields=(title), KEYWORD(way*, querypos=1, expanded)), fields=[title], children=[{type=KEYWORD, word=way*, querypos=1, expanded=true}]}, {type=AND, description=AND(fields=(content), KEYWORD(hey, querypos=2)), fields=[content], children=[{type=KEYWORD, word=hey, querypos=2}]}]}}
}class SearchResponse {
took: 18
timedOut: false
hits: class SearchResponseHits {
total: 1
hits: [{_id=2811025403043381551, _score=2643, _source={}}]
aggregations: null
}
profile: {query={type=AND, description=AND( AND(fields=(title), KEYWORD(way*, querypos=1, expanded)), AND(fields=(content), KEYWORD(hey, querypos=2))), children=[{type=AND, description=AND(fields=(title), KEYWORD(way*, querypos=1, expanded)), fields=[title], children=[{type=KEYWORD, word=way*, querypos=1, expanded=true}]}, {type=AND, description=AND(fields=(content), KEYWORD(hey, querypos=2)), fields=[content], children=[{type=KEYWORD, word=hey, querypos=2}]}]}}
}class SearchResponse {
took: 18
timedOut: false
hits: class SearchResponseHits {
total: 1
hits: [{_id=2811025403043381551, _score=2643, _source={}}]
aggregations: null
}
profile: {query={type=AND, description=AND( AND(fields=(title), KEYWORD(way*, querypos=1, expanded)), AND(fields=(content), KEYWORD(hey, querypos=2))), children=[{type=AND, description=AND(fields=(title), KEYWORD(way*, querypos=1, expanded)), fields=[title], children=[{type=KEYWORD, word=way*, querypos=1, expanded=true}]}, {type=AND, description=AND(fields=(content), KEYWORD(hey, querypos=2)), fields=[content], children=[{type=KEYWORD, word=hey, querypos=2}]}]}}
}{
"hits":
{
"hits":
[{
"_id": 1,
"_score": 1480,
"_source": {}
}],
"total": 1
},
"profile":
{
"query":
{
"children":
[{
"children":
[{
"expanded": True,
"querypos": 1,
"type": "KEYWORD",
"word": "1*"
}],
"description": "AND(fields=(content), KEYWORD(1*, querypos=1, expanded))",
"fields": ["content"],
"type": "AND"
}],
"description": "AND(fields=(content), KEYWORD(1*, querypos=1))",
"type": "AND"
}},
"timed_out": False,
"took": 0
}{
"hits":
{
"hits":
[{
"_id": 1,
"_score": 1480,
"_source": {}
}],
"total": 1
},
"profile":
{
"query":
{
"children":
[{
"children":
[{
"expanded": True,
"querypos": 1,
"type": "KEYWORD",
"word": "1*"
}],
"description": "AND(fields=(content), KEYWORD(1*, querypos=1, expanded))",
"fields": ["content"],
"type": "AND"
}],
"description": "AND(fields=(content), KEYWORD(1*, querypos=1))",
"type": "AND"
}},
"timed_out": False,
"took": 0
}SQL-оператор EXPLAIN QUERY позволяет отобразить дерево выполнения для заданного полнотекстового запроса без фактического выполнения поискового запроса по таблице.
- SQL
EXPLAIN QUERY index_base '@title running @body dog'\G EXPLAIN QUERY index_base '@title running @body dog'\G
*************************** 1\. row ***************************
Variable: transformed_tree
Value: AND(
OR(
AND(fields=(title), KEYWORD(run, querypos=1, morphed)),
AND(fields=(title), KEYWORD(running, querypos=1, morphed))))
AND(fields=(body), KEYWORD(dog, querypos=2, morphed)))EXPLAIN QUERY ... option format=dot позволяет отобразить дерево выполнения заданного полнотекстового запроса в иерархическом формате, подходящем для визуализации с помощью существующих инструментов, таких как https://dreampuf.github.io/GraphvizOnline:

- SQL
EXPLAIN QUERY tbl 'i me' option format=dot\GEXPLAIN QUERY tbl 'i me' option format=dot\G
*************************** 1. row ***************************
Variable: transformed_tree
Value: digraph "transformed_tree"
{
0 [shape=record,style=filled,bgcolor="lightgrey" label="AND"]
0 -> 1
1 [shape=record,style=filled,bgcolor="lightgrey" label="AND"]
1 -> 2
2 [shape=record label="i | { querypos=1 }"]
0 -> 3
3 [shape=record,style=filled,bgcolor="lightgrey" label="AND"]
3 -> 4
4 [shape=record label="me | { querypos=2 }"]
}При использовании ранжировщика выражений можно вывести значения вычисленных факторов с помощью функции PACKEDFACTORS().
Функция возвращает:
- Значения факторов на уровне документа (таких как bm25, field_mask, doc_word_count)
- Список каждого поля, которое сгенерировало совпадение (включая lcs, hit_count, word_count, sum_idf, min_hit_pos и т.д.)
- Список каждого ключевого слова из запроса вместе с их значениями tf и idf
Эти значения можно использовать для понимания, почему определённые документы получают более низкие или высокие оценки в поиске, или для уточнения существующего выражения ранжирования.
- SQL
SELECT id, PACKEDFACTORS() FROM test1 WHERE MATCH('test one') OPTION ranker=expr('1')\G id: 1
packedfactors(): bm25=569, bm25a=0.617197, field_mask=2, doc_word_count=2,
field1=(lcs=1, hit_count=2, word_count=2, tf_idf=0.152356,
min_idf=-0.062982, max_idf=0.215338, sum_idf=0.152356, min_hit_pos=4,
min_best_span_pos=4, exact_hit=0, max_window_hits=1, min_gaps=2,
exact_order=1, lccs=1, wlccs=0.215338, atc=-0.003974),
word0=(tf=1, idf=-0.062982),
word1=(tf=1, idf=0.215338)
1 row in set (0.00 sec)