MATCH 子句允许在文本字段中进行全文搜索。输入查询字符串使用与索引文本时相同的设置进行分词。除了输入文本的分词外,查询字符串还支持一系列的 全文操作符,这些操作符会强制执行各种规则,以确保关键词能够提供有效的匹配。
全文匹配子句可以与属性 过滤器 结合使用,作为 AND 布尔运算。全文匹配和属性过滤器之间的 OR 关系不被支持。
匹配查询总是首先在过滤过程中执行,然后是 属性过滤器。属性过滤器应用于匹配查询的结果集。没有匹配子句的查询称为全扫描。
SELECT 子句中最多只能有一个 MATCH()。
使用 全文查询语法,匹配将在文档的所有索引文本字段中进行,除非表达式要求在字段内进行匹配(如短语搜索)或受到字段操作符的限制。
在使用 JOIN 查询时,MATCH() 可以接受一个可选的第二个参数,指定全文搜索应应用于哪个表。默认情况下,全文查询应用于 JOIN 操作中的左表:
SELECT * FROM table1 LEFT JOIN table2 ON table1.id = table2.id WHERE MATCH('search query', table2);
这允许你在 JOIN 操作中对特定表进行全文搜索。有关使用 MATCH 与 JOIN 的详细信息,请参阅 JOIN 表 部分。
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 分数在包含 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
}查询字符串可以包含特定的操作符,用于定义查询字符串中的单词应该如何匹配的条件。
隐式的逻辑 AND 操作符总是存在,因此 "hello world" 意味着匹配文档中必须同时包含 "hello" 和 "world"。
hello world
注意:没有显式的 AND 操作符。
逻辑 OR 操作符 | 的优先级高于 AND,因此 looking for cat | dog | mouse 意味着 looking for (cat | dog | mouse),而不是 (looking for cat) | dog | mouse。
hello | world
注意:没有 OR 操作符。请使用 |。
hello MAYBE world
MAYBE 操作符的功能类似于 | 操作符,但它不会返回仅匹配右子表达式的文档。
hello -world
hello !world
否定操作符强制规则为某个单词不存在。
默认情况下,不支持仅包含否定的查询。要启用此功能,请使用服务器选项 not_terms_only_allowed。
@title hello @body world
字段限制操作符将后续搜索限制在指定字段内。默认情况下,如果查询中指定的字段名在搜索表中不存在,查询将失败并返回错误信息。但可以通过在查询开头指定 @@relaxed 选项来抑制此行为:
@@relaxed @nosuchfield my query
这在搜索具有不同模式的异构表时非常有用。
字段位置限制进一步约束搜索仅在指定字段的前 N 个位置内进行。例如,@body [50] hello 在文档中关键词 hello 出现在正文第 51 位或之后时不会匹配。
@body[50] hello
多字段搜索操作符:
@(title,body) hello world
忽略字段搜索操作符(忽略 'title' 字段中 'hello world' 的任何匹配):
@!title hello world
忽略多字段搜索操作符(如果有字段 'title'、'subject' 和 'body',则 @!(title) 等同于 @(subject,body)):
@!(title,body) hello world
全字段搜索操作符:
@* hello
"hello world"
短语操作符要求单词相邻。
短语搜索操作符可以包含“匹配任一词”修饰符。在短语操作符中,词语的位置具有实际意义。当使用“匹配任一词”修饰符时,短语查询中后续词语的位置将被平移。因此,“匹配任一词”修饰符不会影响搜索性能。
注意:当使用此操作符进行包含超过 31 个关键词的查询时,位置在第 31 及以后的关键词的排名统计(如 tf、idf、bm25)可能会被低估。这是因为内部用于跟踪匹配中词项出现的 32 位掩码。匹配逻辑(寻找文档)保持正确,但对于非常长的查询,排名分数可能会受影响。
"exact * phrase * * for terms"
你也可以在引号内使用 OR 操作符。OR 操作符(|)在短语中使用时必须用括号 () 括起来。对于每个位置,选项依次检查,只要任一选项匹配该位置,短语即匹配。
正确示例(带括号):
"( 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
临近距离以单词数计,包括单词数量,适用于引号内的所有词。例如,查询 "cat dog mouse"~5 表示这三个词必须出现在少于 8 个单词的范围内。因此,一个包含 CAT aaa bbb ccc DOG eee fff MOUSE 的文档不会匹配这个查询,因为范围正好是 8 个单词。
注意:当使用此操作符进行包含超过 31 个关键词的查询时,位置在第 31 及以后的关键词的排名统计(如 tf、idf、bm25)可能会被低估。这是因为内部用于跟踪匹配中词项出现的 32 位掩码。匹配逻辑(寻找文档)保持正确,但对于非常长的查询,排名分数可能会受影响。
你可以在临近搜索中使用 OR 操作符。OR 操作符(|)在临近搜索中使用时必须用括号 () 括起来。每个选项分别检查。
正确示例(带括号):
"( two | four ) fish chips"~5
错误示例(无括号 - 这不生效):
"two | four fish chips"~5
"the world is a wonderful place"/3
投票匹配操作符引入了一种模糊匹配方式。它只匹配满足指定词语阈值的文档。例如上面的示例 ("the world is a wonderful place"/3) 会匹配包含所给 6 个词中至少 3 个词的所有文档。该操作符支持最多 255 个关键词。除了绝对数值外,还可以提供一个介于 0.0 到 1.0 的值(表示 0% 到 100%),Manticore 会匹配至少包含给定词语指定百分比的文档。上述示例也可以写为 "the world is a wonderful place"/0.5,将匹配至少包含 6 个词中 50% 的文档。
投票操作符支持 OR (|) 操作符。OR 操作符 (|) 在投票匹配中使用时必须用括号 () 括起来。每个 OR 组中只有一个词计入匹配。
正确示例(带括号):
"( ( 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 不会。
作为影响关键字的修饰符,它可以用于短语、邻近和仲裁操作符中。对短语操作符应用精确形式修饰符是可能的,在这种情况下,它会在内部将精确形式修饰符添加到短语中的所有词项。
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 转换,这意味着例如如果所有字符根据 charset_table(默认)被转为小写,REGEX 可能无法匹配大写字符。要成功用 REGEX 表达式匹配词项,模式必须对应整个词项。若要实现部分匹配,请在模式开头和/或结尾添加 .*。
REGEX(/.{3}t/)
REGEX(/t.*\d*/)
^hello world$
字段起始和字段结束关键字修饰符确保关键字仅在其出现在全文字段的开头或结尾时匹配。例如,查询 "^hello world$"(用引号括起以结合短语操作符与起始/结束修饰符)将仅匹配包含至少一个字段含有这两个特定关键字的文档。
boosted^1.234 boostedfieldend$^1.234
提升修饰符通过指定的因子提升包含 IDF 计算的排名分数中的词汇IDF_得分。但它不会以任何方式影响匹配过程。
hello NEAR/3 world NEAR/4 "my test"
NEAR 操作符是邻近操作符的更通用版本。其语法为 NEAR/N,区分大小写,且 NEAR 关键字、斜杠符号和距离值之间不允许有空格。
原始邻近操作符仅对关键字集合起作用,而 NEAR 更灵活,可以接受任意子表达式作为两个参数。当两个子表达式相距不超过 N 个词时,无论顺序如何,都匹配文档。NEAR 为左结合,其优先级与BEFORE相同且都是最低优先级。
需要注意的是,one NEAR/7 two NEAR/7 three 与 "one two three"~7 并不完全等价。关键区别在于邻近操作符允许所有三个匹配词之间最多有 6 个不匹配词,而使用 NEAR 的版本限制较松:它允许 one 和 two 之间最多有 6 个词,然后再允许该两个词匹配与 three 之间最多有另外 6 个词。
注意:当使用此运算符处理包含超过31个关键词的查询时,位置31及以上的关键词的排名统计信息(如tf、idf、bm25)可能会被低估。这是由于内部使用32位掩码来跟踪匹配项内的词项出现情况。匹配逻辑(查找文档)保持正确,但对于非常长的查询,排名分数可能会受到影响。
Church NOTNEAR/3 street
NOTNEAR 运算符作为否定断言,是 NEAR 运算符的逻辑逆运算。当左参数存在,且右参数要么在文档中不存在,要么位于距离左参数超过指定距离的位置时,它匹配文档。
语法为 NOTNEAR/N,区分大小写,并且不允许在 NOTNEAR 关键字、斜杠符号和距离值之间存在空格。
关键行为包括:
- 对称性:与
NEAR类似,NOTNEAR运算符的适用性与文本中词项的顺序无关。如果右参数在指定距离内(无论是左参数之前还是之后)被找到,匹配将被排除。 - 距离阈值:距离
N表示邻近范围(包含)。如果词项之间相隔N个或更少的词,则匹配被丢弃。右参数必须距离左参数N + 1个或更多的词。 - 参数:此运算符的两个参数可以是词项、短语或运算符组。
all SENTENCE words SENTENCE "in one sentence"
"Bill Gates" PARAGRAPH "Steve Jobs"
SENTENCE 和 PARAGRAPH 运算符分别在它们的两个参数位于同一句子或同一段落内时匹配文档。这些参数可以是关键词、短语或同一运算符的实例。
参数在句子或段落内的顺序无关紧要。这些运算符仅在使用 index_sp(句子和段落索引功能)构建的表中生效,否则将退化为简单的 AND 操作。关于什么构成句子和段落的定义,请参阅 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 运算符会影响查询,直到遇到下一个字段或 ZONE 限制运算符,或者直到遇到右括号。它仅在使用区域支持构建的表(参见 index_zones)中生效,否则将被忽略。
ZONESPAN:(h2)
only in a (single) title
ZONESPAN 限制运算符类似于 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');
当您在 Manticore Search 中处理 JSON 数据并需要在 JSON 字符串中包含双引号(")时,正确处理它是很重要的。在 JSON 中,字符串内的双引号使用反斜杠(\)进行转义。然而,当通过 SQL 查询插入 JSON 数据时,Manticore Search 会将反斜杠(\)解释为字符串内的转义字符。
为了确保双引号正确插入 JSON 数据,您需要转义反斜杠本身。这需要在双引号前使用两个反斜杠(\)。例如:
insert into tbl(j) values('{"a": "\\"abc\\""}');
MySQL 驱动程序提供转义函数(例如 PHP 中的 mysqli_real_escape_string 或 Python 中的 conn.escape_string),但它们只会转义特定字符。
您仍然需要为前面提到的列表中未被其相应函数转义的字符添加转义。
由于这些函数会为您转义反斜杠,您只需添加一个反斜杠。
这也适用于支持(客户端)预处理语句的驱动程序。例如,使用 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个单词;(例如,“example PHP program”会匹配,但“example script to introduce outside data into the correct context for your program”则不会,因为这两个术语之间有5个或更多单词)
- 再者,同一文档的正文字段中必须包含单词“python”,同时排除“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)