高亮显示使您能够从包含匹配关键字的文档中获取高亮的文本片段(称为摘要)。
SQL 中的 HIGHLIGHT() 函数、通过 HTTP 的 JSON 查询中的 "highlight" 属性以及 PHP 客户端中的 highlight() 函数都利用内置的文档存储来检索原始字段内容(默认启用)。
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- Javascript
- Java
- C#
- Rust
- TypeScript
- Go
SELECT HIGHLIGHT() FROM books WHERE MATCH('try');POST /search
{
"table": "books",
"query": { "match": { "*" : "try" } },
"highlight": {}
}$results = $index->search('try')->highlight()->get();
foreach($results as $doc)
{
echo 'Document: '.$doc->getId();
foreach($doc->getData() as $field=>$value)
{
echo $field.': '.$value;
}
foreach($doc->getHighlight() as $field=>$snippets)
{
echo "Highlight for ".$field.":\n";
foreach($snippets as $snippet)
{
echo "- ".$snippet."\n";
}
}
}
res = searchApi.search({"table":"books","query":{"match":{"*":"try"}},"highlight":{}})res = await searchApi.search({"table":"books","query":{"match":{"*":"try"}},"highlight":{}})res = await searchApi.search({"table":"books","query":{"match":{"*":"try"}},"highlight":{}});searchRequest = new SearchRequest();
searchRequest.setIndex("books");
query = new HashMap<String,Object>();
query.put("match",new HashMap<String,Object>(){{
put("*","try|gets|down|said");
}});
searchRequest.setQuery(query);
highlight = new HashMap<String,Object>(){{
}};
searchRequest.setHighlight(highlight);
searchResponse = searchApi.search(searchRequest);var searchRequest = new SearchRequest("books");
searchRequest.FulltextFilter = new MatchFilter("*", "try|gets|down|said");
var highlight = new Highlight();
searchRequest.Highlight = highlight;
var searchResponse = searchApi.Search(searchRequest);let match_filter = HashMap::new();
match_filter.insert("*".to_string(), "try|gets|down|said".to_string());
let query = SearchQuery {
match: Some(serde_json::json!(match_filter).into()),
..Default::default(),
};
let highlight = Highlight::new();
let search_req = SearchRequest {
table: "books".to_string(),
query: Some(Box::new(query)),
highlight: serde_json::json!(highlight),
..Default::default(),
};res = await searchApi.search({
index: 'test',
query: {
match: {
*: 'Text 1'
}
},
highlight: {}
});matchClause := map[string]interface{} {"*": "Text 1"};
query := map[string]interface{} {"match": matchClause};
searchRequest.SetQuery(query);
highlight := manticoreclient.NewHighlight()
searchRequest.SetHighlight(highlight)
res, _, _ := apiClient.SearchAPI.Search(context.Background()).SearchRequest(*searchRequest).Execute()+----------------------------------------------------------+
| highlight() |
+----------------------------------------------------------+
| Don`t <b>try</b> to compete in childishness, said Bliss. |
+----------------------------------------------------------+
1 row in set (0.00 sec){
"took":1,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[
{
"_id": 4,
"_score":1704,
"_source":
{
"title":"Book four",
"content":"Don`t try to compete in childishness, said Bliss."
},
"highlight":
{
"title": ["Book four"],
"content": ["Don`t <b>try</b> to compete in childishness, said Bliss."]
}
}
]
}
}Document: 14
title: Book four
content: Don`t try to compete in childishness, said Bliss.
Highlight for title:
- Book four
Highlight for content:
- Don`t <b>try</b> to compete in childishness, said Bliss.
{'aggregations': None,
'hits': {'hits': [{u'_id': u'4',
u'_score': 1695,
u'_source': {u'content': u'Don`t try to compete in childishness, said Bliss.',
u'title': u'Book four'},
u'highlight': {u'content': [u'Don`t <b>try</b> to compete in childishness, said Bliss.'],
u'title': [u'Book four']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{'aggregations': None,
'hits': {'hits': [{u'_id': u'4',
u'_score': 1695,
u'_source': {u'content': u'Don`t try to compete in childishness, said Bliss.',
u'title': u'Book four'},
u'highlight': {u'content': [u'Don`t <b>try</b> to compete in childishness, said Bliss.'],
u'title': [u'Book four']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{"took":0,"timed_out":false,"hits":{"total":1,"hits":[{"_id": 4,"_score":1695,"_source":{"title":"Book four","content":"Don`t try to compete in childishness, said Bliss."},"highlight":{"title":["Book four"],"content":["Don`t <b>try</b> to compete in childishness, said Bliss."]}}]}}class SearchResponse {
took: 0
timedOut: false
hits: class SearchResponseHits {
total: 3
maxScore: null
hits: [{_id=3, _score=1597, _source={title=Book three, content=Trevize whispered, "It gets infantile pleasure out of display. I`d love to knock it down."}, highlight={title=[Book three], content=[, "It <b>gets</b> infantile pleasure , to knock it <b>down</b>."]}}, {_id=4, _score=1563, _source={title=Book four, content=Don`t try to compete in childishness, said Bliss.}, highlight={title=[Book four], content=[Don`t <b>try</b> to compete in childishness, <b>said</b> Bliss.]}}, {_id=5, _score=1514, _source={title=Books two, content=A door opened before them, revealing a small room. Bander said, "Come, half-humans, I want to show you how we live."}, highlight={title=[Books two], content=[ a small room. Bander <b>said</b>, "Come, half-humans, I]}}]
aggregations: null
}
profile: null
}class SearchResponse {
took: 0
timedOut: false
hits: class SearchResponseHits {
total: 3
maxScore: null
hits: [{_id=3, _score=1597, _source={title=Book three, content=Trevize whispered, "It gets infantile pleasure out of display. I`d love to knock it down."}, highlight={title=[Book three], content=[, "It <b>gets</b> infantile pleasure , to knock it <b>down</b>."]}}, {_id=4, _score=1563, _source={title=Book four, content=Don`t try to compete in childishness, said Bliss.}, highlight={title=[Book four], content=[Don`t <b>try</b> to compete in childishness, <b>said</b> Bliss.]}}, {_id=5, _score=1514, _source={title=Books two, content=A door opened before them, revealing a small room. Bander said, "Come, half-humans, I want to show you how we live."}, highlight={title=[Books two], content=[ a small room. Bander <b>said</b>, "Come, half-humans, I]}}]
aggregations: null
}
profile: null
}class SearchResponse {
took: 0
timedOut: false
hits: class SearchResponseHits {
total: 3
maxScore: null
hits: [{_id=3, _score=1597, _source={title=Book three, content=Trevize whispered, "It gets infantile pleasure out of display. I`d love to knock it down."}, highlight={title=[Book three], content=[, "It <b>gets</b> infantile pleasure , to knock it <b>down</b>."]}}, {_id=4, _score=1563, _source={title=Book four, content=Don`t try to compete in childishness, said Bliss.}, highlight={title=[Book four], content=[Don`t <b>try</b> to compete in childishness, <b>said</b> Bliss.]}}, {_id=5, _score=1514, _source={title=Books two, content=A door opened before them, revealing a small room. Bander said, "Come, half-humans, I want to show you how we live."}, highlight={title=[Books two], content=[ a small room. Bander <b>said</b>, "Come, half-humans, I]}}]
aggregations: null
}
profile: null
}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1"
},
"highlight":
{
"content":
[
"<b>Text 1</b>"
]
}
]}
}
}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1"
},
"highlight":
{
"content":
[
"<b>Text 1</b>"
]
}
]}
}
}使用 SQL 进行高亮搜索结果时,由于 MySQL 协议的限制,您将收到来自多个字段的摘要合并成的单个字符串。您可以通过下面详细说明的 field_separator 和 snippet_separator 选项调整连接分隔符。
通过 HTTP 执行 JSON 查询或使用 PHP 客户端时,没有此类限制,结果集包含字段数组,每个字段包含摘要数组(无分隔符)。
请注意,摘要生成选项如 limit、limit_words 和 limit_snippets 默认分别应用于每个字段。您可以使用 limits_per_field 选项更改此行为,但可能导致不希望的结果。例如,一个字段可能包含匹配关键字,但由于该字段的摘要排名不如其他字段,结果集中不包含该字段的摘要。
高亮算法当前优先考虑更好的摘要(短语匹配更接近),然后是包含尚未包含关键字的摘要。通常,它旨在突出显示查询的最佳匹配,并尽可能突出显示所有查询关键字(受限于限制)。如果当前字段中未找到匹配项,则默认根据限制修剪文档开头并返回。若要返回空字符串,请将 allow_empty 选项设置为 1。
高亮是在所谓的“后限制”阶段执行的,这意味着摘要生成不仅推迟到整个最终结果集准备好之后,还推迟到应用 LIMIT 子句之后。例如,对于 LIMIT 20,10 子句,HIGHLIGHT() 函数最多调用 10 次。
有几个可选的高亮选项可用于微调摘要生成,这些选项在 SQL、HTTP 和 PHP 客户端中通用。
在关键字匹配前插入的字符串。此字符串中可以使用 %SNIPPET_ID% 宏。宏的第一次出现会被替换为当前摘要内递增的摘要编号。编号默认从 1 开始,但可以通过 start_snippet_id 选项覆盖。%SNIPPET_ID% 在每个新文档开始时重置。默认值为 <b>。
在关键字匹配后插入的字符串。默认值为 </b>。
摘要的最大大小,单位为符号(代码点)。默认值为 256。默认情况下此限制按字段应用,详见 limits_per_field。
限制结果中包含的最大单词数。请注意,此限制适用于所有单词,而不仅仅是要高亮的匹配关键字。例如,如果高亮 Mary,且选中的摘要为 Mary had a little lamb,则此摘要贡献了 5 个单词,而不仅仅是 1 个。默认值为 0(无限制)。默认情况下此限制按字段应用,详见 limits_per_field。
限制结果中包含的最大摘要数。默认值为 0(无限制)。默认情况下此限制按字段应用,详见 limits_per_field。
确定 limit、limit_words 和 limit_snippets 是作为每个被高亮文档字段的单独限制,还是作为整个文档的全局限制。将此选项设置为 0 表示一个文档的所有合并高亮结果必须在指定限制内。缺点是如果高亮引擎认为某些字段的摘要更相关,可能会在一个字段中高亮多个摘要,而另一个字段中没有摘要。默认值为 1(使用每字段限制)。
在每个匹配关键字块周围选择的单词数。默认值为 5。
确定是否额外按短语边界字符拆分摘要,这些边界字符在表设置中通过 phrase_boundary 指令配置。默认值为 0(不使用边界)。
指定是否按相关性(权重递减)排序提取的摘要,或按文档中出现顺序(位置递增)排序。默认值为 0(不使用权重排序)。
忽略长度限制,直到结果包含所有关键字。默认值为 0(不强制包含所有关键字)。
设置 %SNIPPET_ID% 宏的起始值(该宏在 before_match、after_match 字符串中被检测并展开)。默认值为 1。
定义HTML剥离模式设置。默认值为index,表示将使用表设置。其他值包括none和strip,无论表设置如何,强制跳过或应用剥离;以及retain,保留HTML标记并保护其不被高亮。retain模式只能在高亮完整文档时使用,因此要求不设置片段大小限制。允许的字符串值为none、strip、index和retain。
允许在当前字段无法生成片段时(无关键字匹配或无片段符合限制)返回空字符串作为高亮结果。默认情况下,将返回原始文本的开头而不是空字符串。默认值为0(不允许空结果)。
确保片段不会跨越句子、段落或区域边界(当与启用了相应索引设置的表一起使用时)。允许的值为sentence、paragraph和zone。
在每个片段前发出包含区域名称的HTML标签。默认值为0(不发出区域名称)。
确定是否强制生成片段,即使限制允许高亮整个文本。默认值为0(不强制生成片段)。
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- Javascript
- Java
- C#
- Rust
- TypeScript
- Go
SELECT HIGHLIGHT({limit=50}) FROM books WHERE MATCH('try|gets|down|said');POST /search
{
"table": "books",
"query": {"query_string": "try|gets|down|said"},
"highlight": { "limit":50 }
}$results = $index->search('try|gets|down|said')->highlight([],['limit'=>50])->get();
foreach($results as $doc)
{
echo 'Document: '.$doc->getId();
foreach($doc->getData() as $field=>$value)
{
echo $field.': '.$value;
}
foreach($doc->getHighlight() as $field=>$snippets)
{
echo "Highlight for ".$field.":\n";
foreach($snippets as $snippet)
{
echo $snippet."\n";
}
}
}res = searchApi.search({"table":"books","query":{"match":{"*":"try"}},"highlight":{"limit":50}})res = await searchApi.search({"table":"books","query":{"match":{"*":"try"}},"highlight":{"limit":50}})res = await searchApi.search({"table":"books","query":{"query_string":"try|gets|down|said"},"highlight":{"limit":50}});searchRequest = new SearchRequest();
searchRequest.setIndex("books");
query = new HashMap<String,Object>();
query.put("match",new HashMap<String,Object>(){{
put("*","try|gets|down|said");
}});
searchRequest.setQuery(query);
highlight = new HashMap<String,Object>(){{
put("limit",50);
}};
searchRequest.setHighlight(highlight);
searchResponse = searchApi.search(searchRequest);var searchRequest = new SearchRequest("books");
searchRequest.FulltextFilter = new MatchFilter("*", "try|gets|down|said");
var highlight = new Highlight();
highlight.Limit = 50;
searchRequest.Highlight = highlight;
var searchResponse = searchApi.Search(searchRequest);let match_filter = HashMap::new();
match_filter.insert("*".to_string(), "try|gets|down|said".to_string());
let query = SearchQuery {
match: Some(serde_json::json!(match_filter).into()),
..Default::default(),
};
let highlight = Highlight {
limit: Some(50),
..Default::default(),
};
let search_req = SearchRequest {
table: "books".to_string(),
query: Some(Box::new(query)),
highlight: serde_json::json!(highlight),
..Default::default(),
};res = await searchApi.search({
index: 'test',
query: { match: { *: 'Text } },
highlight: { limit: 2}
});matchClause := map[string]interface{} {"*": "Text 1"};
query := map[string]interface{} {"match": matchClause};
searchRequest.SetQuery(query);
highlight := manticoreclient.NewHighlight()
searchRequest.SetHighlight(highlight)
res, _, _ := apiClient.SearchAPI.Search(context.Background()).SearchRequest(*searchRequest).Execute()+---------------------------------------------------------------------------+
| highlight({limit=50}) |
+---------------------------------------------------------------------------+
| ... , "It <b>gets</b> infantile pleasure ... to knock it <b>down</b>." |
| Don`t <b>try</b> to compete in childishness, <b>said</b> Bliss. |
| ... a small room. Bander <b>said</b>, "Come, half-humans, I ... |
+---------------------------------------------------------------------------+
3 rows in set (0.00 sec){
"took":2,
"timed_out":false,
"hits":
{
"total":3,
"hits":
[
{
"_id": 3,
"_score":1602,
"_source":
{
"title":"Book three",
"content":"Trevize whispered, \"It gets infantile pleasure out of display. I`d love to knock it down.\""
},
"highlight":
{
"title":
[
"Book three"
],
"content":
[
", \"It <b>gets</b> infantile pleasure ",
" to knock it <b>down</b>.\""
]
}
},
{
"_id": 4,
"_score":1573,
"_source":
{
"title":"Book four",
"content":"Don`t try to compete in childishness, said Bliss."
},
"highlight":
{
"title":
[
"Book four"
],
"content":
[
"Don`t <b>try</b> to compete in childishness, <b>said</b> Bliss."
]
}
},
{
"_id": 2,
"_score":1521,
"_source":
{
"title":"Book two",
"content":"A door opened before them, revealing a small room. Bander said, \"Come, half-humans, I want to show you how we live.\""
},
"highlight":
{
"title":
[
"Book two"
],
"content":
[
" a small room. Bander <b>said</b>, \"Come, half-humans, I"
]
}
}
]
}
}Document: 3
title: Book three
content: Trevize whispered, "It gets infantile pleasure out of display. I`d love to knock it down."
Highlight for title:
- Book four
Highlight for content:
, "It <b>gets</b> infantile pleasure
to knock it <b>down</b>."
Document: 4
title: Book four
content: Don`t try to compete in childishness, said Bliss.
Highlight for title:
- Book four
Highlight for content:
Don`t <b>try</b> to compete in childishness, <b>said</b> Bliss.
Document: 2
title: Book two
content: A door opened before them, revealing a small room. Bander said, "Come, half-humans, I want to show you how we live.
Highlight for title:
- Book two
Highlight for content:
a small room. Bander <b>said</b>, \"Come, half-humans, I{'aggregations': None,
'hits': {'hits': [{u'_id': u'4',
u'_score': 1695,
u'_source': {u'content': u'Don`t try to compete in childishness, said Bliss.',
u'title': u'Book four'},
u'highlight': {u'content': [u'Don`t <b>try</b> to compete in childishness, said Bliss.'],
u'title': [u'Book four']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{'aggregations': None,
'hits': {'hits': [{u'_id': u'4',
u'_score': 1695,
u'_source': {u'content': u'Don`t try to compete in childishness, said Bliss.',
u'title': u'Book four'},
u'highlight': {u'content': [u'Don`t <b>try</b> to compete in childishness, said Bliss.'],
u'title': [u'Book four']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{"took":0,"timed_out":false,"hits":{"total":3,"hits":[{"_id": 3,"_score":1597,"_source":{"title":"Book three","content":"Trevize whispered, \"It gets infantile pleasure out of display. I`d love to knock it down.\""},"highlight":{"title":["Book three"],"content":[", \"It <b>gets</b> infantile pleasure "," to knock it <b>down</b>.\""]}},{"_id": 4,"_score":1563,"_source":{"title":"Book four","content":"Don`t try to compete in childishness, said Bliss."},"highlight":{"title":["Book four"],"content":["Don`t <b>try</b> to compete in childishness, <b>said</b> Bliss."]}},{"_id": 5,"_score":1514,"_source":{"title":"Books two","content":"A door opened before them, revealing a small room. Bander said, \"Come, half-humans, I want to show you how we live.\""},"highlight":{"title":["Books two"],"content":[" a small room. Bander <b>said</b>, \"Come, half-humans, I"]}}]}}class SearchResponse {
took: 0
timedOut: false
hits: class SearchResponseHits {
total: 3
maxScore: null
hits: [{_id=3, _score=1597, _source={title=Book three, content=Trevize whispered, "It gets infantile pleasure out of display. I`d love to knock it down."}, highlight={title=[Book three], content=[, "It <b>gets</b> infantile pleasure , to knock it <b>down</b>."]}}, {_id=4, _score=1563, _source={title=Book four, content=Don`t try to compete in childishness, said Bliss.}, highlight={title=[Book four], content=[Don`t <b>try</b> to compete in childishness, <b>said</b> Bliss.]}}, {_id=5, _score=1514, _source={title=Books two, content=A door opened before them, revealing a small room. Bander said, "Come, half-humans, I want to show you how we live."}, highlight={title=[Books two], content=[ a small room. Bander <b>said</b>, "Come, half-humans, I]}}]
aggregations: null
}
profile: null
}class SearchResponse {
took: 0
timedOut: false
hits: class SearchResponseHits {
total: 3
maxScore: null
hits: [{_id=3, _score=1597, _source={title=Book three, content=Trevize whispered, "It gets infantile pleasure out of display. I`d love to knock it down."}, highlight={title=[Book three], content=[, "It <b>gets</b> infantile pleasure , to knock it <b>down</b>."]}}, {_id=4, _score=1563, _source={title=Book four, content=Don`t try to compete in childishness, said Bliss.}, highlight={title=[Book four], content=[Don`t <b>try</b> to compete in childishness, <b>said</b> Bliss.]}}, {_id=5, _score=1514, _source={title=Books two, content=A door opened before them, revealing a small room. Bander said, "Come, half-humans, I want to show you how we live."}, highlight={title=[Books two], content=[ a small room. Bander <b>said</b>, "Come, half-humans, I]}}]
aggregations: null
}
profile: null
}class SearchResponse {
took: 0
timedOut: false
hits: class SearchResponseHits {
total: 3
maxScore: null
hits: [{_id=3, _score=1597, _source={title=Book three, content=Trevize whispered, "It gets infantile pleasure out of display. I`d love to knock it down."}, highlight={title=[Book three], content=[, "It <b>gets</b> infantile pleasure , to knock it <b>down</b>."]}}, {_id=4, _score=1563, _source={title=Book four, content=Don`t try to compete in childishness, said Bliss.}, highlight={title=[Book four], content=[Don`t <b>try</b> to compete in childishness, <b>said</b> Bliss.]}}, {_id=5, _score=1514, _source={title=Books two, content=A door opened before them, revealing a small room. Bander said, "Come, half-humans, I want to show you how we live."}, highlight={title=[Books two], content=[ a small room. Bander <b>said</b>, "Come, half-humans, I]}}]
aggregations: null
}
profile: null
}{
"took":0,
"timed_out":false,
"hits":
{
"total":2,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text 1</b>"
]
}
},
{
"_id": 2,
"_score":1480,
"_source":
{
"content":"Text 2",
"name":"Doc 2",
"cat":2
},
"highlight":
{
"content":
[
"<b>Text 2</b>"
]
}
}]
}
}{
"took":0,
"timed_out":false,
"hits":
{
"total":2,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text 1</b>"
]
}
},
{
"_id": 2,
"_score":1480,
"_source":
{
"content":"Text 2",
"name":"Doc 2",
"cat":2
},
"highlight":
{
"content":
[
"<b>Text 2</b>"
]
}
}]
}
}HIGHLIGHT()函数可用于高亮搜索结果。语法如下:
HIGHLIGHT([options], [field_list], [query] )
默认情况下,无需参数即可使用。
- SQL
SELECT HIGHLIGHT() FROM books WHERE MATCH('before');+-----------------------------------------------------------+
| highlight() |
+-----------------------------------------------------------+
| A door opened <b>before</b> them, revealing a small room. |
+-----------------------------------------------------------+
1 row in set (0.00 sec)HIGHLIGHT()从文档存储中检索所有可用的全文字段,并针对提供的查询进行高亮。查询中支持字段语法。字段文本由field_separator分隔,可在选项中修改。
- SQL
SELECT HIGHLIGHT() FROM books WHERE MATCH('@title one');+-----------------+
| highlight() |
+-----------------+
| Book <b>one</b> |
+-----------------+
1 row in set (0.00 sec)HIGHLIGHT()的可选第一个参数是选项列表。
- SQL
SELECT HIGHLIGHT({before_match='[match]',after_match='[/match]'}) FROM books WHERE MATCH('@title one');+------------------------------------------------------------+
| highlight({before_match='[match]',after_match='[/match]'}) |
+------------------------------------------------------------+
| Book [match]one[/match] |
+------------------------------------------------------------+
1 row in set (0.00 sec)可选的第二个参数是包含单个字段或逗号分隔字段列表的字符串。如果存在此参数,则仅从文档存储中获取并高亮指定字段。第二个参数为空字符串表示“获取所有可用字段”。
- SQL
SELECT HIGHLIGHT({},'title,content') FROM books WHERE MATCH('one|robots');+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| highlight({},'title,content') |
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Book <b>one</b> | They followed Bander. The <b>robots</b> remained at a polite distance, but their presence was a constantly felt threat. |
| Bander ushered all three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander gestured the other <b>robots</b> away and entered itself. The door closed behind it. |
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
2 rows in set (0.00 sec)或者,可以使用第二个参数指定字符串属性或字段名(不带引号)。在这种情况下,提供的字符串将针对查询进行高亮,但字段语法将被忽略。
- SQL
SELECT HIGHLIGHT({}, title) FROM books WHERE MATCH('one');+---------------------+
| highlight({},title) |
+---------------------+
| Book <b>one</b> |
| Book five |
+---------------------+
2 rows in set (0.00 sec)可选的第三个参数是查询。用于针对与搜索时不同的查询进行高亮。
- SQL
SELECT HIGHLIGHT({},'title', 'five') FROM books WHERE MATCH('one');+-------------------------------+
| highlight({},'title', 'five') |
+-------------------------------+
| Book one |
| Book <b>five</b> |
+-------------------------------+
2 rows in set (0.00 sec)虽然HIGHLIGHT()设计用于存储的全文字段和字符串属性,但也可用于高亮任意文本。请注意,如果查询包含任何字段搜索操作符(例如@title hello @body world),则在此情况下忽略它们的字段部分。
- SQL
SELECT HIGHLIGHT({},TO_STRING('some text to highlight'), 'highlight') FROM books WHERE MATCH('@title one');+----------------------------------------------------------------+
| highlight({},TO_STRING('some text to highlight'), 'highlight') |
+----------------------------------------------------------------+
| some text to <b>highlight</b> |
+----------------------------------------------------------------+
1 row in set (0.00 sec)某些选项仅在生成单个字符串结果(而非片段数组)时相关。这仅适用于SQL的HIGHLIGHT()函数:
插入片段之间的字符串。默认值为...。
插入字段之间的字符串。默认值为|。
另一种高亮文本的方法是使用CALL SNIPPETS语句。它大致复制了HIGHLIGHT()的功能,但不能使用内置文档存储。不过,它可以从文件加载源文本。
要通过HTTP在JSON查询中高亮全文搜索结果,字段内容必须存储在文档存储中(默认启用)。示例中,从文档存储中获取全文字段content和title,并针对query子句中指定的查询进行高亮。
高亮片段在hits数组的highlight属性中返回。
- JSON
- PHP
- Python
- Python-asyncio
- Javascript
- Java
- C#
- Rust
- TypeScript
- Go
POST /search
{
"table": "books",
"query": { "match": { "*": "one|robots" } },
"highlight":
{
"fields": ["content"]
}
}$index->setName('books');
$results = $index->search('one|robots')->highlight(['content'])->get();
foreach($results as $doc)
{
echo 'Document: '.$doc->getId()."\n";
foreach($doc->getData() as $field=>$value)
{
echo $field.' : '.$value."\n";
}
foreach($doc->getHighlight() as $field=>$snippets)
{
echo "Highlight for ".$field.":\n";
foreach($snippets as $snippet)
{
echo "- ".$snippet."\n";
}
}
}res = searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content"]}}))res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content"]}}))res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content"]}});searchRequest = new SearchRequest();
searchRequest.setIndex("books");
query = new HashMap<String,Object>();
query.put("match",new HashMap<String,Object>(){{
put("*","one|robots");
}});
searchRequest.setQuery(query);
highlight = new HashMap<String,Object>(){{
put("fields",new String[] {"content"});
}};
searchRequest.setHighlight(highlight);
searchResponse = searchApi.search(searchRequest);var searchRequest = new SearchRequest("books");
searchRequest.FulltextFilter = new MatchFilter("*", "one|robots");
var highlight = new Highlight();
highlight.Fieldnames = new List<string> {"content"};
searchRequest.Highlight = highlight;
var searchResponse = searchApi.Search(searchRequest);let match_filter = HashMap::new();
match_filter.insert("*".to_string(), "one|robots".to_string());
let query = SearchQuery {
match: Some(serde_json::json!(match_filter).into()),
..Default::default(),
};
let highlight_fields [String; 1] = ["content".to_string()];
let highlight = Highlight {
fields: Some(serde_json::json!(highlight_fields)),
..Default::default(),
};
let search_req = SearchRequest {
table: "books".to_string(),
query: Some(Box::new(query)),
highlight: serde_json::json!(highlight),
..Default::default(),
};res = await searchApi.search({
index: 'test',
query: {
match: {
*: 'Text 1|Text 9'
}
},
highlight: {}
});matchClause := map[string]interface{} {"*": "Text 1|Text 9"};
query := map[string]interface{} {"match": matchClause};
searchRequest.SetQuery(query);
highlight := manticoreclient.NewHighlight()
searchRequest.SetHighlight(highlight)
res, _, _ := apiClient.SearchAPI.Search(context.Background()).SearchRequest(*searchRequest).Execute(){
"took": 0,
"timed_out": false,
"hits": {
"total": 1,
"hits": [
{
"_id": 1,
"_score": 2788,
"_source": {
"title": "Books one",
"content": "They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. "
},
"highlight": {
"content": [
"They followed Bander. The <b>robots</b> remained at a polite distance, ",
" three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander",
" gestured the other <b>robots</b> away and entered itself. The"
]
}
}
]
}
}Document: 1
title : Books one
content : They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it.
Highlight for content:
- They followed Bander. The <b>robots</b> remained at a polite distance,
- three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander
- gestured the other <b>robots</b> away and entered itself. The{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u'They followed Bander. The <b>robots</b> remained at a polite distance, ',
u' three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander',
u' gestured the other <b>robots</b> away and entered itself. The']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u'They followed Bander. The <b>robots</b> remained at a polite distance, ',
u' three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander',
u' gestured the other <b>robots</b> away and entered itself. The']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{"took":0,"timed_out":false,"hits":{"total":1,"hits":[{"_id": 1,"_score":2788,"_source":{"title":"Books one","content":"They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. "},"highlight":{"content":["They followed Bander. The <b>robots</b> remained at a polite distance, "," three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander"," gestured the other <b>robots</b> away and entered itself. The"]}}]}}class SearchResponse {
took: 0
timedOut: false
hits: class SearchResponseHits {
total: 1
maxScore: null
hits: [{_id=1, _score=2788, _source={title=Books one, content=They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. }, highlight={title=[Books <b>one</b>], content=[They followed Bander. The <b>robots</b> remained at a polite distance, , three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander, gestured the other <b>robots</b> away and entered itself. The]}}]
aggregations: null
}
profile: null
}class SearchResponse {
took: 0
timedOut: false
hits: class SearchResponseHits {
total: 1
maxScore: null
hits: [{_id=1, _score=2788, _source={title=Books one, content=They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. }, highlight={title=[Books <b>one</b>], content=[They followed Bander. The <b>robots</b> remained at a polite distance, , three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander, gestured the other <b>robots</b> away and entered itself. The]}}]
aggregations: null
}
profile: null
}class SearchResponse {
took: 0
timedOut: false
hits: class SearchResponseHits {
total: 1
maxScore: null
hits: [{_id=1, _score=2788, _source={title=Books one, content=They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. }, highlight={title=[Books <b>one</b>], content=[They followed Bander. The <b>robots</b> remained at a polite distance, , three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander, gestured the other <b>robots</b> away and entered itself. The]}}]
aggregations: null
}
profile: null
}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text 1</b>"
]
}
]}
}
}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text 1</b>"
]
}
]}
}
}要突出显示所有可能的字段,请将一个空对象作为 highlight 属性传递。
- JSON
- PHP
- Python
- Python-asyncio
- Javascript
- Java
- C#
- Rust
- TypeScript
- Go
POST /search
{
"table": "books",
"query": { "match": { "*": "one|robots" } },
"highlight": {}
}$index->setName('books');
$results = $index->search('one|robots')->highlight()->get();
foreach($results as $doc)
{
echo 'Document: '.$doc->getId()."\n";
foreach($doc->getData() as $field=>$value)
{
echo $field.' : '.$value."\n";
}
foreach($doc->getHighlight() as $field=>$snippets)
{
echo "Highlight for ".$field.":\n";
foreach($snippets as $snippet)
{
echo "- ".$snippet."\n";
}
}
}res = searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{}})res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{}})res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{}});searchRequest = new SearchRequest();
searchRequest.setIndex("books");
query = new HashMap<String,Object>();
query.put("match",new HashMap<String,Object>(){{
put("*","one|robots");
}});
searchRequest.setQuery(query);
highlight = new HashMap<String,Object>(){{
}};
searchRequest.setHighlight(highlight);
searchResponse = searchApi.search(searchRequest);var searchRequest = new SearchRequest("books");
searchRequest.FulltextFilter = new MatchFilter("*", "one|robots");
var highlight = new Highlight();
searchRequest.Highlight = highlight;
var searchResponse = searchApi.Search(searchRequest);let match_filter = HashMap::new();
match_filter.insert("*".to_string(), "one|robots".to_string());
let query = SearchQuery {
match: Some(serde_json::json!(match_filter).into()),
..Default::default(),
};
let highlight = Highlight::new();
let search_req = SearchRequest {
table: "books".to_string(),
query: Some(Box::new(query)),
highlight: serde_json::json!(highlight),
..Default::default(),
};res = await searchApi.search({
index: 'test',
query: {
match: {
*: 'Text 1|Doc 1'
}
},
highlight: {}
});matchClause := map[string]interface{} {"*": "Text 1|Doc 1"};
query := map[string]interface{} {"match": matchClause};
searchRequest.SetQuery(query);
highlight := manticoreclient.NewHighlight()
searchRequest.SetHighlight(highlight)
res, _, _ := apiClient.SearchAPI.Search(context.Background()).SearchRequest(*searchRequest).Execute(){
"took": 0,
"timed_out": false,
"hits": {
"total": 1,
"hits": [
{
"_id": 1,
"_score": 2788,
"_source": {
"title": "Books one",
"content": "They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. "
},
"highlight": {
"title": [
"Books <b>one</b>"
],
"content": [
"They followed Bander. The <b>robots</b> remained at a polite distance, ",
" three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander",
" gestured the other <b>robots</b> away and entered itself. The"
]
}
}
]
}
}Document: 1
title : Books one
content : They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it.
Highlight for title:
- Books <b>one</b>
Highlight for content:
- They followed Bander. The <b>robots</b> remained at a polite distance,
- three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander
- gestured the other <b>robots</b> away and entered itself. The{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u'They followed Bander. The <b>robots</b> remained at a polite distance, ',
u' three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander',
u' gestured the other <b>robots</b> away and entered itself. The'],
u'title': [u'Books <b>one</b>']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u'They followed Bander. The <b>robots</b> remained at a polite distance, ',
u' three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander',
u' gestured the other <b>robots</b> away and entered itself. The'],
u'title': [u'Books <b>one</b>']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{"took":0,"timed_out":false,"hits":{"total":1,"hits":[{"_id": 1,"_score":2788,"_source":{"title":"Books one","content":"They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. "},"highlight":{"title":["Books <b>one</b>"],"content":["They followed Bander. The <b>robots</b> remained at a polite distance, "," three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander"," gestured the other <b>robots</b> away and entered itself. The"]}}]}}class SearchResponse {
took: 0
timedOut: false
hits: class SearchResponseHits {
total: 1
maxScore: null
hits: [{_id=1, _score=2788, _source={title=Books one, content=They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. }, highlight={title=[Books <b>one</b>], content=[They followed Bander. The <b>robots</b> remained at a polite distance, , three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander, gestured the other <b>robots</b> away and entered itself. The]}}]
aggregations: null
}
profile: null
}class SearchResponse {
took: 0
timedOut: false
hits: class SearchResponseHits {
total: 1
maxScore: null
hits: [{_id=1, _score=2788, _source={title=Books one, content=They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. }, highlight={title=[Books <b>one</b>], content=[They followed Bander. The <b>robots</b> remained at a polite distance, , three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander, gestured the other <b>robots</b> away and entered itself. The]}}]
aggregations: null
}
profile: null
}class SearchResponse {
took: 0
timedOut: false
hits: class SearchResponseHits {
total: 1
maxScore: null
hits: [{_id=1, _score=2788, _source={title=Books one, content=They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. }, highlight={title=[Books <b>one</b>], content=[They followed Bander. The <b>robots</b> remained at a polite distance, , three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander, gestured the other <b>robots</b> away and entered itself. The]}}]
aggregations: null
}
profile: null
}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text 1</b>"
],
"name":
[
"<b>Doc 1</b>"
]
}
]}
}
}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text 1</b>"
],
"name":
[
"<b>Doc 1</b>"
]
}
]}
}
}除了常见的高亮选项外,通过 HTTP 的 JSON 查询还提供了几个同义词:
fields 对象包含带有选项的属性名称。它也可以是字段名称的数组(不带任何选项)。
请注意,默认情况下,高亮尝试突出显示全文查询后的结果。在一般情况下,当您不指定要高亮的字段时,高亮基于您的全文查询。然而,如果您指定了要高亮的字段,则只有当全文查询匹配所选字段时才会高亮。
encoder 可以设置为 default 或 html。当设置为 html 时,它在高亮时保留 HTML 标记。这与 html_strip_mode=retain 选项的作用类似。
- JSON
- PHP
- Python
- Python-asyncio
- Javascript
- Java
- C#
- Rust
- TypeScript
- Go
POST /search
{
"table": "books",
"query": { "match": { "content": "one|robots" } },
"highlight":
{
"fields": [ "content"],
"highlight_query": { "match": { "*":"polite distance" } }
}
}$index->setName('books');
$bool = new \Manticoresearch\Query\BoolQuery();
$bool->must(new \Manticoresearch\Query\Match(['query' => 'one|robots'], 'content'));
$results = $index->search($bool)->highlight(['content'],['highlight_query'=>['match'=>['*'=>'polite distance']]])->get();
foreach($results as $doc)
{
echo 'Document: '.$doc->getId()."\n";
foreach($doc->getData() as $field=>$value)
{
echo $field.' : '.$value."\n";
}
foreach($doc->getHighlight() as $field=>$snippets)
{
echo "Highlight for ".$field.":\n";
foreach($snippets as $snippet)
{
echo "- ".$snippet."\n";
}
}
}res = searchApi.search({"table":"books","query":{"match":{"content":"one|robots"}},"highlight":{"fields":["content"],"highlight_query":{"match":{"*":"polite distance"}}}})res = await searchApi.search({"table":"books","query":{"match":{"content":"one|robots"}},"highlight":{"fields":["content"],"highlight_query":{"match":{"*":"polite distance"}}}})res = await searchApi.search({"table":"books","query":{"match":{"content":"one|robots"}},"highlight":{"fields":["content"],"highlight_query":{"match":{"*":"polite distance"}}}});searchRequest = new SearchRequest();
searchRequest.setIndex("books");
query = new HashMap<String,Object>();
query.put("match",new HashMap<String,Object>(){{
put("*","one|robots");
}});
searchRequest.setQuery(query);
highlight = new HashMap<String,Object>(){{
put("fields",new String[] {"content","title"});
put("highlight_query",
new HashMap<String,Object>(){{
put("match", new HashMap<String,Object>(){{
put("*","polite distance");
}});
}});
}};
searchRequest.setHighlight(highlight);
searchResponse = searchApi.search(searchRequest);var searchRequest = new SearchRequest("books");
searchRequest.FulltextFilter = new MatchFilter("*", "one|robots");
var highlight = new Highlight();
highlight.Fieldnames = new List<string> {"content", "title"};
Dictionary<string, Object> match = new Dictionary<string, Object>();
match.Add("*", "polite distance");
Dictionary<string, Object> highlightQuery = new Dictionary<string, Object>();
highlightQuery.Add("match", match);
highlight.HighlightQuery = highlightQuery;
searchRequest.Highlight = highlight;
var searchResponse = searchApi.Search(searchRequest);let match_filter = HashMap::new();
match_filter.insert("*".to_string(), "one|robots".to_string());
let query = SearchQuery {
match: Some(serde_json::json!(match_filter).into()),
..Default::default(),
};
let mut highlight_match_filter = HashMap::new();
highlight_match_filter.insert("*".to_string(), "polite distance".to_string());
let highlight_query = QueryFilter {
r#match: Some(serde_json::json!(highlight_match_filter)),
..Default::default(),
};
let highlight_fields [String; 2] = ["content".to_string(), "title".to_string()];
let highlight = Highlight {
fields: Some(serde_json::json!(highlight_fields)),
highlight_query: Some(Box::new(highlight_query)),
..Default::default(),
};
let search_req = SearchRequest {
table: "books".to_string(),
query: Some(Box::new(query)),
highlight: serde_json::json!(highlight),
..Default::default(),
};
let search_res = search_api.search(search_req).await;res = await searchApi.search({
index: 'test',
query: {
match: {
*: 'Text 1'
}
},
highlight: {
fields: ['content'],
highlight_query: {
match: {*: 'Text'}
}
}
});matchClause := map[string]interface{} {"*": "Text 1"};
query := map[string]interface{} {"match": matchClause};
searchRequest.SetQuery(query);
highlight := manticoreclient.NewHighlight()
highlightField := manticoreclient.NetHighlightField("content")
highlightFields := []interface{} { highlightField }
highlight.SetFields(highlightFields)
queryMatchClause := map[string]interface{} {"*": "Text"};
highlightQuery := map[string]interface{} {"match": queryMatchClause};
highlight.SetHighlightQuery(highlightQuery)
searchRequest.SetHighlight(highlight)
res, _, _ := apiClient.SearchAPI.Search(context.Background()).SearchRequest(*searchRequest).Execute(){'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 1788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u'. The robots remained at a <b>polite distance</b>, but their presence was a']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 1788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u'. The robots remained at a <b>polite distance</b>, but their presence was a']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{"took":0,"timed_out":false,"hits":{"total":1,"hits":[{"_id": 1,"_score":1788,"_source":{"title":"Books one","content":"They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. "},"highlight":{"content":[". The robots remained at a <b>polite distance</b>, but their presence was a"]}}]}}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text</b> 1"
]
}
]}
}
}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text</b> 1"
]
}
]}
}
}pre_tags 和 post_tags 设置高亮文本片段的起始和结束标签。它们的功能类似于 before_match 和 after_match 选项。这些是可选的,默认值分别为 <b> 和 </b>。
- JSON
- PHP
- Python
- Python-asyncio
- Javascript
- Java
- C#
- Rust
- TypeScript
- Go
POST /search
{
"table": "books",
"query": { "match": { "*": "one|robots" } },
"highlight":
{
"fields": [ "content", "title" ],
"pre_tags": "before_",
"post_tags": "_after"
}
}$index->setName('books');
$bool = new \Manticoresearch\Query\BoolQuery();
$bool->must(new \Manticoresearch\Query\Match(['query' => 'one|robots'], '*'));
$results = $index->search($bool)->highlight(['content','title'],['pre_tags'=>'before_','post_tags'=>'_after'])->get();
foreach($results as $doc)
{
echo 'Document: '.$doc->getId()."\n";
foreach($doc->getData() as $field=>$value)
{
echo $field.' : '.$value."\n";
}
foreach($doc->getHighlight() as $field=>$snippets)
{
echo "Highlight for ".$field.":\n";
foreach($snippets as $snippet)
{
echo "- ".$snippet."\n";
}
}
}res = searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content","title"],"pre_tags":"before_","post_tags":"_after"}})res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content","title"],"pre_tags":"before_","post_tags":"_after"}})res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content","title"],"pre_tags":"before_","post_tags":"_after"}});searchRequest = new SearchRequest();
searchRequest.setIndex("books");
query = new HashMap<String,Object>();
query.put("match",new HashMap<String,Object>(){{
put("*","one|robots");
}});
searchRequest.setQuery(query);
highlight = new HashMap<String,Object>(){{
put("fields",new String[] {"content","title"});
put("pre_tags","before_");
put("post_tags","_after");
}};
searchRequest.setHighlight(highlight);
searchResponse = searchApi.search(searchRequest);var searchRequest = new SearchRequest("books");
searchRequest.FulltextFilter = new MatchFilter("*", "one|robots");
var highlight = new Highlight();
highlight.Fieldnames = new List<string> {"content", "title"};
highlight.PreTags = "before_";
highlight.PostTags = "_after";
searchRequest.Highlight = highlight;
var searchResponse = searchApi.Search(searchRequest);let match_filter = HashMap::new();
match_filter.insert("*".to_string(), "one|robots".to_string());
let query = SearchQuery {
match: Some(serde_json::json!(match_filter).into()),
..Default::default(),
};
let highlight_fields [String; 2] = ["content".to_string(), "title".to_string()];
let highlight = Highlight {
fields: Some(serde_json::json!(highlight_fields)),
pre_tags: Some("before_".to_string()),
post_tags: Some("_after".to_string()),
..Default::default(),
};
let search_req = SearchRequest {
table: "books".to_string(),
query: Some(Box::new(query)),
highlight: serde_json::json!(highlight),
..Default::default(),
};
let search_res = search_api.search(search_req).await;res = await searchApi.search({
index: 'test',
query: {
match: {
*: 'Text 1'
}
},
highlight: {
pre_tags: 'before_',
post_tags: '_after'
}
});matchClause := map[string]interface{} {"*": "Text 1"}
query := map[string]interface{} {"match": matchClause}
searchRequest.SetQuery(query)
highlight := manticoreclient.NewHighlight()
highlight.SetPreTags("before_")
highlight.SetPostTags("_after")
searchRequest.SetHighlight(highlight)
res, _, _ := apiClient.SearchAPI.Search(context.Background()).SearchRequest(*searchRequest).Execute()Document: 1
title : Books one
content : They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it.
Highlight for content:
- They followed Bander. The before_robots_after remained at a polite distance,
- three into the room. before_One_after of the before_robots_after followed as well. Bander
- gestured the other before_robots_after away and entered itself. The
Highlight for title:
- Books before_one_after{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u'They followed Bander. The before_robots_after remained at a polite distance, ',
u' three into the room. before_One_after of the before_robots_after followed as well. Bander',
u' gestured the other before_robots_after away and entered itself. The'],
u'title': [u'Books before_one_after']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u'They followed Bander. The before_robots_after remained at a polite distance, ',
u' three into the room. before_One_after of the before_robots_after followed as well. Bander',
u' gestured the other before_robots_after away and entered itself. The'],
u'title': [u'Books before_one_after']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{"took":0,"timed_out":false,"hits":{"total":1,"hits":[{"_id": 1,"_score":2788,"_source":{"title":"Books one","content":"They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. "},"highlight":{"content":["They followed Bander. The before_robots_after remained at a polite distance, "," three into the room. before_One_after of the before_robots_after followed as well. Bander"," gestured the other before_robots_after away and entered itself. The"],"title":["Books before_one_after"]}}]}}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"before_Text 1_after"
]
}
]}
}
}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"before_Text 1_after"
]
}
]}
}
}no_match_size 的功能类似于 allow_empty 选项。如果设置为 0,则相当于 allow_empty=1,允许在无法生成片段时返回空字符串作为高亮结果。否则,将返回字段的开头。这是可选的,默认值为 1。
- JSON
- PHP
- Python
- Python-asyncio
- Javascript
- Java
- C#
- Rust
- TypeScript
- Go
POST /search
{
"table": "books",
"query": { "match": { "*": "one|robots" } },
"highlight":
{
"fields": [ "content", "title" ],
"no_match_size": 0
}
}$index->setName('books');
$bool = new \Manticoresearch\Query\BoolQuery();
$bool->must(new \Manticoresearch\Query\Match(['query' => 'one|robots'], '*'));
$results = $index->search($bool)->highlight(['content','title'],['no_match_size'=>0])->get();
foreach($results as $doc)
{
echo 'Document: '.$doc->getId()."\n";
foreach($doc->getData() as $field=>$value)
{
echo $field.' : '.$value."\n";
}
foreach($doc->getHighlight() as $field=>$snippets)
{
echo "Highlight for ".$field.":\n";
foreach($snippets as $snippet)
{
echo "- ".$snippet."\n";
}
}
}res = searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content","title"],"no_match_size":0}})res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content","title"],"no_match_size":0}})res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content","title"],"no_match_size":0}});searchRequest = new SearchRequest();
searchRequest.setIndex("books");
query = new HashMap<String,Object>();
query.put("match",new HashMap<String,Object>(){{
put("*","one|robots");
}});
searchRequest.setQuery(query);
highlight = new HashMap<String,Object>(){{
put("fields",new String[] {"content","title"});
put("no_match_size",0);
}};
searchRequest.setHighlight(highlight);
searchResponse = searchApi.search(searchRequest);var searchRequest = new SearchRequest("books");
searchRequest.FulltextFilter = new MatchFilter("*", "one|robots");
var highlight = new Highlight();
highlight.Fieldnames = new List<string> {"content", "title"};
highlight.NoMatchSize = 0;
searchRequest.Highlight = highlight;
var searchResponse = searchApi.Search(searchRequest);let match_filter = HashMap::new();
match_filter.insert("*".to_string(), "one|robots".to_string());
let query = SearchQuery {
match: Some(serde_json::json!(match_filter).into()),
..Default::default(),
};
let highlight_fields [String; 2] = ["content".to_string(), "title".to_string()];
let highlight = Highlight {
fields: Some(serde_json::json!(highlight_fields)),
no_match_size: Some(NoMatchSize::Variant0),
..Default::default(),
};
let search_req = SearchRequest {
table: "books".to_string(),
query: Some(Box::new(query)),
highlight: serde_json::json!(highlight),
..Default::default(),
};
let search_res = search_api.search(search_req).await;res = await searchApi.search({
index: 'test',
query: {
match: {
*: 'Text 1'
}
},
highlight: {no_match_size: 0}
});matchClause := map[string]interface{} {"*": "Text 1"};
query := map[string]interface{} {"match": matchClause};
searchRequest.SetQuery(query);
highlight := manticoreclient.NewHighlight()
highlight.SetNoMatchSize(0)
searchRequest.SetHighlight(highlight)
res, _, _ := apiClient.SearchAPI.Search(context.Background()).SearchRequest(*searchRequest).Execute()Document: 1
title : Books one
content : They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it.
Highlight for content:
- They followed Bander. The <b>robots</b> remained at a polite distance,
- three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander
- gestured the other <b>robots</b> away and entered itself. The
Highlight for title:
- Books <b>one</b>{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u'They followed Bander. The <b>robots</b> remained at a polite distance, ',
u' three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander',
u' gestured the other <b>robots</b> away and entered itself. The'],
u'title': [u'Books <b>one</b>']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u'They followed Bander. The <b>robots</b> remained at a polite distance, ',
u' three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander',
u' gestured the other <b>robots</b> away and entered itself. The'],
u'title': [u'Books <b>one</b>']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{"took":0,"timed_out":false,"hits":{"total":1,"hits":[{"_id": 1,"_score":2788,"_source":{"title":"Books one","content":"They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. "},"highlight":{"content":["They followed Bander. The <b>robots</b> remained at a polite distance, "," three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander"," gestured the other <b>robots</b> away and entered itself. The"],"title":["Books <b>one</b>"]}}]}}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text 1</b>"
]
}
]}
}
}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text 1</b>"
]
}
]}
}
}- JSON
- PHP
- Python
- Python-asyncio
- Javascript
- Java
- C#
- Rust
- TypeScript
- Go
POST /search
{
"table": "books",
"query": { "match": { "*": "one|robots" } },
"highlight":
{
"fields": [ "content", "title" ],
"order": "score"
}
}$index->setName('books');
$bool = new \Manticoresearch\Query\BoolQuery();
$bool->must(new \Manticoresearch\Query\Match(['query' => 'one|robots'], '*'));
$results = $index->search($bool)->highlight(['content','title'],['order'=>"score"])->get();
foreach($results as $doc)
{
echo 'Document: '.$doc->getId()."\n";
foreach($doc->getData() as $field=>$value)
{
echo $field.' : '.$value."\n";
}
foreach($doc->getHighlight() as $field=>$snippets)
{
echo "Highlight for ".$field.":\n";
foreach($snippets as $snippet)
{
echo "- ".$snippet."\n";
}
}
}res = searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content","title"],"order":"score"}})res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content","title"],"order":"score"}})res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content","title"],"order":"score"}});searchRequest = new SearchRequest();
searchRequest.setIndex("books");
query = new HashMap<String,Object>();
query.put("match",new HashMap<String,Object>(){{
put("*","one|robots");
}});
searchRequest.setQuery(query);
highlight = new HashMap<String,Object>(){{
put("fields",new String[] {"content","title"});
put("order","score");
}};
searchRequest.setHighlight(highlight);
searchResponse = searchApi.search(searchRequest);var searchRequest = new SearchRequest("books");
searchRequest.FulltextFilter = new MatchFilter("*", "one|robots");
var highlight = new Highlight();
highlight.Fieldnames = new List<string> {"content", "title"};
highlight.Order = "score";
searchRequest.Highlight = highlight;
var searchResponse = searchApi.Search(searchRequest);let match_filter = HashMap::new();
match_filter.insert("*".to_string(), "one|robots".to_string());
let query = SearchQuery {
match: Some(serde_json::json!(match_filter).into()),
..Default::default(),
};
let highlight_fields [String; 2] = ["content".to_string(), "title".to_string()];
let highlight = Highlight {
fields: Some(serde_json::json!(highlight_fields)),
order: Some(Order::Score),
post_tags: Some("_after".to_string()),
..Default::default(),
};
let search_req = SearchRequest {
table: "books".to_string(),
query: Some(Box::new(query)),
highlight: serde_json::json!(highlight),
..Default::default(),
};
let search_res = search_api.search(search_req).await;res = await searchApi.search({
index: 'test',
query: {
match: {
*: 'Text 1'
}
},
highlight: { order: 'score' }
});matchClause := map[string]interface{} {"*": "Text 1"};
query := map[string]interface{} {"match": matchClause};
searchRequest.SetQuery(query);
highlight := manticoreclient.NewHighlight()
highlight.SetOrder("score")
searchRequest.SetHighlight(highlight)
res, _, _ := apiClient.SearchAPI.Search(context.Background()).SearchRequest(*searchRequest).Execute()Document: 1
title : Books one
content : They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it.
Highlight for content:
- three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander
- gestured the other <b>robots</b> away and entered itself. The
- They followed Bander. The <b>robots</b> remained at a polite distance,
Highlight for title:
- Books <b>one</b>{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u' three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander',
u' gestured the other <b>robots</b> away and entered itself. The',
u'They followed Bander. The <b>robots</b> remained at a polite distance, '],
u'title': [u'Books <b>one</b>']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u' three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander',
u' gestured the other <b>robots</b> away and entered itself. The',
u'They followed Bander. The <b>robots</b> remained at a polite distance, '],
u'title': [u'Books <b>one</b>']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{"took":0,"timed_out":false,"hits":{"total":1,"hits":[{"_id": 1,"_score":2788,"_source":{"title":"Books one","content":"They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. "},"highlight":{"content":[" three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander"," gestured the other <b>robots</b> away and entered itself. The","They followed Bander. The <b>robots</b> remained at a polite distance, "],"title":["Books <b>one</b>"]}}]}}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text 1</b>"
]
}
]}
}
}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text 1</b>"
]
}
]}
}
}- JSON
- PHP
- Python
- Python-asyncio
- Javascript
- Java
- C#
- Rust
- TypeScript
- Go
POST /search
{
"table": "books",
"query": { "match": { "*": "one|robots" } },
"highlight":
{
"fields": [ "content", "title" ],
"fragment_size": 100
}
}$index->setName('books');
$bool = new \Manticoresearch\Query\BoolQuery();
$bool->must(new \Manticoresearch\Query\Match(['query' => 'one|robots'], '*'));
$results = $index->search($bool)->highlight(['content','title'],['fragment_size'=>100])->get();
foreach($results as $doc)
{
echo 'Document: '.$doc->getId()."\n";
foreach($doc->getData() as $field=>$value)
{
echo $field.' : '.$value."\n";
}
foreach($doc->getHighlight() as $field=>$snippets)
{
echo "Highlight for ".$field.":\n";
foreach($snippets as $snippet)
{
echo "- ".$snippet."\n";
}
}
}res = searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content","title"],"fragment_size":100}})res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content","title"],"fragment_size":100}})res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content","title"],"fragment_size":100}});searchRequest = new SearchRequest();
searchRequest.setIndex("books");
query = new HashMap<String,Object>();
query.put("match",new HashMap<String,Object>(){{
put("*","one|robots");
}});
searchRequest.setQuery(query);
highlight = new HashMap<String,Object>(){{
put("fields",new String[] {"content","title"});
put("fragment_size",100);
}};
searchRequest.setHighlight(highlight);
searchResponse = searchApi.search(searchRequest);var searchRequest = new SearchRequest("books");
searchRequest.FulltextFilter = new MatchFilter("*", "one|robots");
var highlight = new Highlight();
highlight.Fieldnames = new List<string> {"content", "title"};
highlight.FragmentSize = 100;
searchRequest.Highlight = highlight;
var searchResponse = searchApi.Search(searchRequest);let match_filter = HashMap::new();
match_filter.insert("*".to_string(), "one|robots".to_string());
let query = SearchQuery {
match: Some(serde_json::json!(match_filter).into()),
..Default::default(),
};
let highlight_fields [String; 2] = ["content".to_string(), "title".to_string()];
let highlight = Highlight {
fields: Some(serde_json::json!(highlight_fields)),
fragment_size: Some(serde_json::json!(100)),
..Default::default(),
};
let search_req = SearchRequest {
table: "books".to_string(),
query: Some(Box::new(query)),
highlight: serde_json::json!(highlight),
..Default::default(),
};
let search_res = search_api.search(search_req).await;res = await searchApi.search({
index: 'test',
query: {
match: {
*: 'Text 1'
}
},
highlight: { fragment_size: 4}
});matchClause := map[string]interface{} {"*": "Text 1"};
query := map[string]interface{} {"match": matchClause};
searchRequest.SetQuery(query);
highlight := manticoreclient.NewHighlight()
highlight.SetFragmentSize(4)
searchRequest.SetHighlight(highlight)
res, _, _ := apiClient.SearchAPI.Search(context.Background()).SearchRequest(*searchRequest).Execute()Document: 1
title : Books one
content : They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it.
Highlight for content:
- the room. <b>One</b> of the <b>robots</b> followed as well
- Bander gestured the other <b>robots</b> away and entered
Highlight for title:
- Books <b>one</b>{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u' the room. <b>One</b> of the <b>robots</b> followed as well',
u'Bander gestured the other <b>robots</b> away and entered '],
u'title': [u'Books <b>one</b>']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u' the room. <b>One</b> of the <b>robots</b> followed as well',
u'Bander gestured the other <b>robots</b> away and entered '],
u'title': [u'Books <b>one</b>']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{"took":0,"timed_out":false,"hits":{"total":1,"hits":[{"_id": 1,"_score":2788,"_source":{"title":"Books one","content":"They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. "},"highlight":{"content":[" the room. <b>One</b> of the <b>robots</b> followed as well","Bander gestured the other <b>robots</b> away and entered "],"title":["Books <b>one</b>"]}}]}}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text</b>"
]
}
]}
}
}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text</b>"
]
}
]}
}
}number_of_fragments 限制结果中片段的最大数量。与 fragment_size 类似,它可以是全局设置或按字段设置。此项为可选,默认值为 0(无限制)。其工作方式类似于 limit_snippets 选项。
- JSON
- PHP
- Python
- Python-asyncio
- Javascript
- Java
- C#
- Rust
- TypeScript
- Go
POST /search
{
"table": "books",
"query": { "match": { "*": "one|robots" } },
"highlight":
{
"fields": [ "content", "title" ],
"number_of_fragments": 10
}
}$index->setName('books');
$bool = new \Manticoresearch\Query\BoolQuery();
$bool->must(new \Manticoresearch\Query\Match(['query' => 'one|robots'], '*'));
$results = $index->search($bool)->highlight(['content','title'],['number_of_fragments'=>10])->get();
foreach($results as $doc)
{
echo 'Document: '.$doc->getId()."\n";
foreach($doc->getData() as $field=>$value)
{
echo $field.' : '.$value."\n";
}
foreach($doc->getHighlight() as $field=>$snippets)
{
echo "Highlight for ".$field.":\n";
foreach($snippets as $snippet)
{
echo "- ".$snippet."\n";
}
}
}res =searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content","title"],"number_of_fragments":10}})res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content","title"],"number_of_fragments":10}})res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content","title"],"number_of_fragments":10}});searchRequest = new SearchRequest();
searchRequest.setIndex("books");
query = new HashMap<String,Object>();
query.put("match",new HashMap<String,Object>(){{
put("*","one|robots");
}});
searchRequest.setQuery(query);
highlight = new HashMap<String,Object>(){{
put("fields",new String[] {"content","title"});
put("number_of_fragments",10);
}};
searchRequest.setHighlight(highlight);
searchResponse = searchApi.search(searchRequest);var searchRequest = new SearchRequest("books");
searchRequest.FulltextFilter = new MatchFilter("*", "one|robots");
var highlight = new Highlight();
highlight.Fieldnames = new List<string> {"content", "title"};
highlight.NumberOfFragments = 10;
searchRequest.Highlight = highlight;
var searchResponse = searchApi.Search(searchRequest);let match_filter = HashMap::new();
match_filter.insert("*".to_string(), "one|robots".to_string());
let query = SearchQuery {
match: Some(serde_json::json!(match_filter).into()),
..Default::default(),
};
let highlight_fields [String; 2] = ["content".to_string(), "title".to_string()];
let highlight = Highlight {
fields: Some(serde_json::json!(highlight_fields)),
number_of_fragments: Some(serde_json::json!(10)),
..Default::default(),
};
let search_req = SearchRequest {
table: "books".to_string(),
query: Some(Box::new(query)),
highlight: serde_json::json!(highlight),
..Default::default(),
};
let search_res = search_api.search(search_req).await;res = await searchApi.search({
index: 'test',
query: {
match: {
*: 'Text 1'
}
},
highlight: { number_of_fragments: 1}
});matchClause := map[string]interface{} {"*": "Text 1"};
query := map[string]interface{} {"match": matchClause};
searchRequest.SetQuery(query);
highlight := manticoreclient.NewHighlight()
highlight.SetNumberOfFragments(1)
searchRequest.SetHighlight(highlight)
res, _, _ := apiClient.SearchAPI.Search(context.Background()).SearchRequest(*searchRequest).Execute()Document: 1
title : Books one
content : They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it.
Highlight for content:
- They followed Bander. The <b>robots</b> remained at a polite distance,
- three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander
- gestured the other <b>robots</b> away and entered itself. The
Highlight for title:
- Books <b>one</b>{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u'They followed Bander. The <b>robots</b> remained at a polite distance, ',
u' three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander',
u' gestured the other <b>robots</b> away and entered itself. The'],
u'title': [u'Books <b>one</b>']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u'They followed Bander. The <b>robots</b> remained at a polite distance, ',
u' three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander',
u' gestured the other <b>robots</b> away and entered itself. The'],
u'title': [u'Books <b>one</b>']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{"took":0,"timed_out":false,"hits":{"total":1,"hits":[{"_id": 1,"_score":2788,"_source":{"title":"Books one","content":"They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. "},"highlight":{"content":["They followed Bander. The <b>robots</b> remained at a polite distance, "," three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander"," gestured the other <b>robots</b> away and entered itself. The"],"title":["Books <b>one</b>"]}}]}}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text 1</b>"
]
}
]}
}
}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text 1</b>"
]
}
]}
}
}选项如 limit、limit_words 和 limit_snippets 可以设置为全局或按字段的选项。全局选项作为按字段的限制使用,除非被按字段选项覆盖。在示例中,title 字段使用默认限制设置进行高亮,而 content 字段使用不同的限制。
- JSON
- PHP
- Python
- Python-asyncio
- Javascript
- Java
- C#
- Rust
- TypeScript
- Go
POST /search
{
"table": "books",
"query": { "match": { "*": "one|robots" } },
"highlight":
{
"fields":
{
"title": {},
"content" : { "limit": 50 }
}
}
}$index->setName('books');
$bool = new \Manticoresearch\Query\BoolQuery();
$bool->must(new \Manticoresearch\Query\Match(['query' => 'one|robots'], '*'));
$results = $index->search($bool)->highlight(['content'=>['limit'=>50],'title'=>new \stdClass])->get();
foreach($results as $doc)
{
echo 'Document: '.$doc->getId()."\n";
foreach($doc->getData() as $field=>$value)
{
echo $field.' : '.$value."\n";
}
foreach($doc->getHighlight() as $field=>$snippets)
{
echo "Highlight for ".$field.":\n";
foreach($snippets as $snippet)
{
echo "- ".$snippet."\n";
}
}
}res =searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":{"title":{},"content":{"limit":50}}}})res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":{"title":{},"content":{"limit":50}}}})res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":{"title":{},"content":{"limit":50}}}});searchRequest = new SearchRequest();
searchRequest.setIndex("books");
query = new HashMap<String,Object>();
query.put("match",new HashMap<String,Object>(){{
put("*","one|robots");
}});
searchRequest.setQuery(query);
highlight = new HashMap<String,Object>(){{
put("fields",new HashMap<String,Object>(){{
put("title",new HashMap<String,Object>(){{}});
put("content",new HashMap<String,Object>(){{
put("limit",50);
}});
}}
);
}};
searchRequest.setHighlight(highlight);
searchResponse = searchApi.search(searchRequest);var searchRequest = new SearchRequest("books");
searchRequest.FulltextFilter = new MatchFilter("*", "one|robots");
var highlight = new Highlight();
var highlightField = new HighlightField("title");
highlightField.Limit = 50;
highlight.Fields = new List<Object> {highlightField};
searchRequest.Highlight = highlight;
var searchResponse = searchApi.Search(searchRequest);let match_filter = HashMap::new();
match_filter.insert("*".to_string(), "one|robots".to_string());
let query = SearchQuery {
match: Some(serde_json::json!(match_filter).into()),
..Default::default(),
};
let highlight_fields [String; 1] = ["title".to_string()];
let highlight = Highlight {
fields: Some(serde_json::json!(highlight_fields)),
limit: Some(serde_json::json!(50)),
..Default::default(),
};
let search_req = SearchRequest {
table: "books".to_string(),
query: Some(Box::new(query)),
highlight: serde_json::json!(highlight),
..Default::default(),
};
let search_res = search_api.search(search_req).await;res = await searchApi.search({
index: 'test',
query: {
match: {
*: 'Text 1'
}
},
highlight: {
fields: {
content: { limit:1 }
}
}
});matchClause := map[string]interface{} {"*": "Text 1"};
query := map[string]interface{} {"match": matchClause};
searchRequest.SetQuery(query);
highlight := manticoreclient.NewHighlight()
highlightField := manticoreclient.NetHighlightField("content")
highlightField.SetLimit(1);
highlightFields := []interface{} { highlightField }
highlight.SetFields(highlightFields)
searchRequest.SetHighlight(highlight)
res, _, _ := apiClient.SearchAPI.Search(context.Background()).SearchRequest(*searchRequest).Execute()Document: 1
title : Books one
content : They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it.
Highlight for content:
- into the room. <b>One</b> of the <b>robots</b> followed as well
Highlight for title:
- Books <b>one</b>{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u' into the room. <b>One</b> of the <b>robots</b> followed as well'],
u'title': [u'Books <b>one</b>']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u' into the room. <b>One</b> of the <b>robots</b> followed as well'],
u'title': [u'Books <b>one</b>']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{"took":0,"timed_out":false,"hits":{"total":1,"hits":[{"_id": 1,"_score":2788,"_source":{"title":"Books one","content":"They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. "},"highlight":{"title":["Books <b>one</b>"],"content":[" into the room. <b>One</b> of the <b>robots</b> followed as well"]}}]}}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text</b>"
]
}
]}
}
}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text</b>"
]
}
]}
}
}全局限制也可以通过指定 limits_per_field=0 来强制执行。设置此选项意味着所有合并的高亮结果必须在指定的限制内。缺点是,如果高亮引擎认为某些片段更相关,可能会在一个字段中获得多个高亮片段,而另一个字段则没有。
- JSON
- PHP
- Python
- Python-asyncio
- Javascript
- Java
- C#
- Rust
- TypeScript
- Go
POST /search
{
"table": "books",
"query": { "match": { "content": "and first" } },
"highlight":
{
"limits_per_field": false,
"fields":
{
"content" : { "limit": 50 }
}
}
}$index->setName('books');
$bool = new \Manticoresearch\Query\BoolQuery();
$bool->must(new \Manticoresearch\Query\Match(['query' => 'and first'], 'content'));
$results = $index->search($bool)->highlight(['content'=>['limit'=>50]],['limits_per_field'=>false])->get();
foreach($results as $doc)
{
echo 'Document: '.$doc->getId()."\n";
foreach($doc->getData() as $field=>$value)
{
echo $field.' : '.$value."\n";
}
foreach($doc->getHighlight() as $field=>$snippets)
{
echo "Highlight for ".$field.":\n";
foreach($snippets as $snippet)
{
echo "- ".$snippet."\n";
}
}
}res =searchApi.search({"table":"books","query":{"match":{"content":"and first"}},"highlight":{"fields":{"content":{"limit":50}},"limits_per_field":False}})res = await searchApi.search({"table":"books","query":{"match":{"content":"and first"}},"highlight":{"fields":{"content":{"limit":50}},"limits_per_field":False}})res = await searchApi.search({"table":"books","query":{"match":{"content":"and first"}},"highlight":{"fields":{"content":{"limit":50}},"limits_per_field":false}});searchRequest = new SearchRequest();
searchRequest.setIndex("books");
query = new HashMap<String,Object>();
query.put("match",new HashMap<String,Object>(){{
put("*","one|robots");
}});
searchRequest.setQuery(query);
highlight = new HashMap<String,Object>(){{
put("limits_per_field",0);
put("fields",new HashMap<String,Object>(){{
put("content",new HashMap<String,Object>(){{
put("limit",50);
}});
}}
);
}};
searchRequest.setHighlight(highlight);
searchResponse = searchApi.search(searchRequest);var searchRequest = new SearchRequest("books");
searchRequest.FulltextFilter = new MatchFilter("*", "one|robots");
var highlight = new Highlight();
highlight.LimitsPerField = 0;
var highlightField = new HighlightField("title");
highlight.Fields = new List<Object> {highlightField};
searchRequest.Highlight = highlight;
var searchResponse = searchApi.Search(searchRequest);let match_filter = HashMap::new();
match_filter.insert("*".to_string(), "one|robots".to_string());
let query = SearchQuery {
match: Some(serde_json::json!(match_filter).into()),
..Default::default(),
};
let highlight_fields [String; 1] = ["title".to_string()];
let highlight = Highlight {
fields: Some(serde_json::json!(highlight_fields)),
limit_per_field: Some(serde_json::json!(false)),
..Default::default(),
};
let search_req = SearchRequest {
table: "books".to_string(),
query: Some(Box::new(query)),
highlight: serde_json::json!(highlight),
..Default::default(),
};
let search_res = search_api.search(search_req).await;res = await searchApi.search({
index: 'test',
query: {
match: {
*: 'Text 1'
}
},
highlight: { limits_per_field: 0 }
});matchClause := map[string]interface{} {"*": "Text 1"};
query := map[string]interface{} {"match": matchClause};
searchRequest.SetQuery(query);
highlight := manticoreclient.NewHighlight()
highlight.SetLimitsPerField(0)
searchRequest.SetHighlight(highlight)
res, _, _ := apiClient.SearchAPI.Search(context.Background()).SearchRequest(*searchRequest).Execute()Document: 1
title : Books one
content : They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it.
Highlight for content:
- gestured the other robots away <b>and</b> entered itself. The door closed{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 1597,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u' gestured the other robots away <b>and</b> entered itself. The door closed']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 1597,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u' gestured the other robots away <b>and</b> entered itself. The door closed']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{"took":0,"timed_out":false,"hits":{"total":1,"hits":[{"_id": 1,"_score":1597,"_source":{"title":"Books one","content":"They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. "},"highlight":{"content":[" gestured the other robots away <b>and</b> entered itself. The door closed"]}}]}}CALL SNIPPETS 语句使用指定的表设置,从提供的数据和查询中构建片段。它无法访问内置的文档存储,因此建议使用 HIGHLIGHT() 函数。
语法如下:
CALL SNIPPETS(data, table, query[, opt_value AS opt_name[, ...]])
data 作为提取片段的来源。它可以是单个字符串,也可以是用花括号括起来的字符串列表。
table 指定提供文本处理设置以生成片段的表名。
query 是用于构建摘要的全文查询。
opt_value 和 opt_name 表示摘要生成选项。
- SQL
CALL SNIPPETS(('this is my document text','this is my another text'), 'forum', 'is text', 5 AS around, 200 AS limit);+----------------------------------------+
| snippet |
+----------------------------------------+
| this <b>is</b> my document <b>text</b> |
| this <b>is</b> my another <b>text</b> |
+----------------------------------------+
2 rows in set (0.02 sec)大多数选项与HIGHLIGHT() 函数中的相同。然而,有几个选项只能与 CALL SNIPPETS 一起使用。
以下选项可用于突出显示存储在单独文件中的文本:
启用此选项时,将第一个参数视为文件名,而不是用于提取摘要的数据。服务器端将加载指定的文件以获取数据。当启用此标志时,每个请求将使用最多 max_threads_per_query 个工作线程来并行处理工作。默认值为 0(无限制)。要在远程代理之间分配摘要生成,请在仅包含一个(!)本地代理和多个远程代理的分布式表中调用摘要生成。snippets_file_prefix 选项用于生成最终文件名。例如,当 searchd 配置为 snippets_file_prefix = /var/data_ 并且提供了文件名 text.txt 时,摘要将从 /var/data_text.txt 的内容生成。
此选项仅适用于带有远程代理的分布式摘要生成。摘要生成的源文件可以分布在不同的代理上,主服务器将合并所有无错误的结果。例如,如果分布式表的一个代理有 file1.txt,另一个代理有 file2.txt,并且你使用 CALL SNIPPETS 处理这两个文件,searchd 将合并代理结果,因此你将获得来自 file1.txt 和 file2.txt 的结果。默认值为 0。
如果同时启用了 load_files 选项,则如果任何文件在任何地方不可用,请求将返回错误。否则(如果未启用 load_files),对于所有缺失的文件将返回空字符串。searchd 不会将此标志传递给代理,因此如果文件不存在,代理不会生成严重错误。如果你想确保所有源文件都被加载,请将 load_files_scattered 和 load_files 都设置为 1。如果某些代理上缺少某些源文件不是关键问题,只需将 load_files_scattered 设置为 1。
- SQL
CALL SNIPPETS(('data/doc1.txt','data/doc2.txt'), 'forum', 'is text', 1 AS load_files);+----------------------------------------+
| snippet |
+----------------------------------------+
| this <b>is</b> my document <b>text</b> |
| this <b>is</b> my another <b>text</b> |
+----------------------------------------+
2 rows in set (0.02 sec)查询结果可以按全文排名权重、一个或多个属性或表达式进行排序。
全文 查询默认返回排序的匹配项。如果未指定任何内容,则按相关性排序,这相当于 SQL 格式中的 ORDER BY weight() DESC。
非全文 查询默认不执行任何排序。
当您通过添加 SQL 格式的 ORDER BY 子句或通过 HTTP JSON 使用 sort 选项显式提供排序规则时,自动启用扩展模式。
通用语法:
SELECT ... ORDER BY
{attribute_name | expr_alias | weight() | random() } [ASC | DESC],
...
{attribute_name | expr_alias | weight() | random() } [ASC | DESC]
在排序子句中,您可以使用最多 5 列的任意组合,每列后跟 asc 或 desc。函数和表达式不允许作为排序子句的参数,除了 weight() 和 random() 函数(后者只能通过 SQL 以 ORDER BY random() 形式使用)。但是,您可以在 SELECT 列表中使用任何表达式,并按其别名排序。
- SQL
select *, a + b alias from test order by alias desc;+------+------+------+----------+-------+
| id | a | b | f | alias |
+------+------+------+----------+-------+
| 1 | 2 | 3 | document | 5 |
+------+------+------+----------+-------+"sort" 指定一个数组,其中每个元素可以是属性名,或者如果您想按匹配权重排序则为 _score,如果想要随机匹配顺序则为 _random。在这种情况下,属性的排序顺序默认为升序,_score 默认为降序。
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- Java
- C#
- Rust
- typescript
- go
{
"table":"test",
"query":
{
"match": { "title": "Test document" }
},
"sort": [ "_score", "id" ],
"_source": "title",
"limit": 3
}$search->setIndex("test")->match('Test document')->sort('_score')->sort('id');search_request.index = 'test'
search_request.fulltext_filter = manticoresearch.model.QueryFilter('Test document')
search_request.sort = ['_score', 'id']search_request.index = 'test'
search_request.fulltext_filter = manticoresearch.model.QueryFilter('Test document')
search_request.sort = ['_score', 'id']searchRequest.index = "test";
searchRequest.fulltext_filter = new Manticoresearch.QueryFilter('Test document');
searchRequest.sort = ['_score', 'id'];searchRequest.setIndex("test");
QueryFilter queryFilter = new QueryFilter();
queryFilter.setQueryString("Test document");
searchRequest.setFulltextFilter(queryFilter);
List<Object> sort = new ArrayList<Object>( Arrays.asList("_score", "id") );
searchRequest.setSort(sort);var searchRequest = new SearchRequest("test");
searchRequest.FulltextFilter = new QueryFilter("Test document");
searchRequest.Sort = new List<Object> {"_score", "id"};let query = SearchQuery {
query_string: Some(serde_json::json!("Test document").into()),
..Default::default(),
};
let sort: [String; 2] = ["_score".to_string(), "id".to_string()];
let search_req = SearchRequest {
table: "test".to_string(),
query: Some(Box::new(query)),
sort: Some(serde_json::json!(sort)),
..Default::default(),
};searchRequest = {
index: 'test',
query: {
query_string: {'Test document'},
},
sort: ['_score', 'id'],
}searchRequest.SetIndex("test")
query := map[string]interface{} {"query_string": "Test document"}
searchRequest.SetQuery(query)
sort := map[string]interface{} {"_score": "asc", "id": "asc"}
searchRequest.SetSort(sort) {
"took": 0,
"timed_out": false,
"hits": {
"total": 5,
"total_relation": "eq",
"hits": [
{
"_id": 5406864699109146628,
"_score": 2319,
"_source": {
"title": "Test document 1"
}
},
{
"_id": 5406864699109146629,
"_score": 2319,
"_source": {
"title": "Test document 2"
}
},
{
"_id": 5406864699109146630,
"_score": 2319,
"_source": {
"title": "Test document 3"
}
}
]
}
}您也可以显式指定排序顺序:
asc:升序排序desc:降序排序
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- Java
- C#
- Rust
- typescript
- go
{
"table":"test",
"query":
{
"match": { "title": "Test document" }
},
"sort":
[
{ "id": "desc" },
"_score"
],
"_source": "title",
"limit": 3
}$search->setIndex("test")->match('Test document')->sort('id', 'desc')->sort('_score');search_request.index = 'test'
search_request.fulltext_filter = manticoresearch.model.QueryFilter('Test document')
sort_by_id = manticoresearch.model.SortOrder('id', 'desc')
search_request.sort = [sort_by_id, '_score']search_request.index = 'test'
search_request.fulltext_filter = manticoresearch.model.QueryFilter('Test document')
sort_by_id = manticoresearch.model.SortOrder('id', 'desc')
search_request.sort = [sort_by_id, '_score']searchRequest.index = "test";
searchRequest.fulltext_filter = new Manticoresearch.QueryFilter('Test document');
sortById = new Manticoresearch.SortOrder('id', 'desc');
searchRequest.sort = [sortById, 'id'];searchRequest.setIndex("test");
QueryFilter queryFilter = new QueryFilter();
queryFilter.setQueryString("Test document");
searchRequest.setFulltextFilter(queryFilter);
List<Object> sort = new ArrayList<Object>();
SortOrder sortById = new SortOrder();
sortById.setAttr("id");
sortById.setOrder(SortOrder.OrderEnum.DESC);
sort.add(sortById);
sort.add("_score");
searchRequest.setSort(sort);var searchRequest = new SearchRequest("test");
searchRequest.FulltextFilter = new QueryFilter("Test document");
searchRequest.Sort = new List<Object>();
var sortById = new SortOrder("id", SortOrder.OrderEnum.Desc);
searchRequest.Sort.Add(sortById);
searchRequest.Sort.Add("_score");let query = SearchQuery {
query_string: Some(serde_json::json!("Test document").into()),
..Default::default(),
};
let sort_by_id = HashMap::new();
sort_by_id.insert("id".to_string(), "desc".to_string());
let mut sort = Vec::new();
sort.push(sort_by_id);
sort.push("_score".to_string());
let search_req = SearchRequest {
table: "test".to_string(),
query: Some(Box::new(query)),
sort: Some(serde_json::json!(sort)),
..Default::default(),
};searchRequest = {
index: 'test',
query: {
query_string: {'Test document'},
},
sort: [{'id': 'desc'}, '_score'],
}searchRequest.SetIndex("test")
query := map[string]interface{} {"query_string": "Test document"}
searchRequest.SetQuery(query)
sortById := map[string]interface{} {"id": "desc"}
sort := map[string]interface{} {"id": "desc", "_score": "asc"}
searchRequest.SetSort(sort) {
"took": 0,
"timed_out": false,
"hits": {
"total": 5,
"total_relation": "eq",
"hits": [
{
"_id": 5406864699109146632,
"_score": 2319,
"_source": {
"title": "Test document 5"
}
},
{
"_id": 5406864699109146631,
"_score": 2319,
"_source": {
"title": "Test document 4"
}
},
{
"_id": 5406864699109146630,
"_score": 2319,
"_source": {
"title": "Test document 3"
}
}
]
}
}您还可以使用另一种语法,通过 order 属性指定排序顺序:
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- Java
- C#
- Rust
- typescript
- go
{
"table":"test",
"query":
{
"match": { "title": "Test document" }
},
"sort":
[
{ "id": { "order":"desc" } }
],
"_source": "title",
"limit": 3
}$search->setIndex("test")->match('Test document')->sort('id', 'desc');search_request.index = 'test'
search_request.fulltext_filter = manticoresearch.model.QueryFilter('Test document')
sort_by_id = manticoresearch.model.SortOrder('id', 'desc')
search_request.sort = [sort_by_id]search_request.index = 'test'
search_request.fulltext_filter = manticoresearch.model.QueryFilter('Test document')
sort_by_id = manticoresearch.model.SortOrder('id', 'desc')
search_request.sort = [sort_by_id]searchRequest.index = "test";
searchRequest.fulltext_filter = new Manticoresearch.QueryFilter('Test document');
sortById = new Manticoresearch.SortOrder('id', 'desc');
searchRequest.sort = [sortById];searchRequest.setIndex("test");
QueryFilter queryFilter = new QueryFilter();
queryFilter.setQueryString("Test document");
searchRequest.setFulltextFilter(queryFilter);
List<Object> sort = new ArrayList<Object>();
SortOrder sortById = new SortOrder();
sortById.setAttr("id");
sortById.setOrder(SortOrder.OrderEnum.DESC);
sort.add(sortById);
searchRequest.setSort(sort);var searchRequest = new SearchRequest("test");
searchRequest.FulltextFilter = new QueryFilter("Test document");
searchRequest.Sort = new List<Object>();
var sortById = new SortOrder("id", SortOrder.OrderEnum.Desc);
searchRequest.Sort.Add(sortById);let query = SearchQuery {
query_string: Some(serde_json::json!("Test document").into()),
..Default::default(),
};
let mut sort_by_id = HashMap::new();
sort_by_id.insert("id".to_string(), "desc".to_string());
let sort = [HashMap; 1] = [sort_by_id];
let search_req = SearchRequest {
table: "test".to_string(),
query: Some(Box::new(query)),
sort: Some(serde_json::json!(sort)),
..Default::default(),
};searchRequest = {
index: 'test',
query: {
query_string: {'Test document'},
},
sort: { {'id': {'order':'desc'} },
}searchRequest.SetIndex("test")
query := map[string]interface{} {"query_string": "Test document"}
searchRequest.SetQuery(query)
sort := map[string]interface{} { "id": {"order":"desc"} }
searchRequest.SetSort(sort) {
"took": 0,
"timed_out": false,
"hits": {
"total": 5,
"total_relation": "eq",
"hits": [
{
"_id": 5406864699109146632,
"_score": 2319,
"_source": {
"title": "Test document 5"
}
},
{
"_id": 5406864699109146631,
"_score": 2319,
"_source": {
"title": "Test document 4"
}
},
{
"_id": 5406864699109146630,
"_score": 2319,
"_source": {
"title": "Test document 3"
}
}
]
}
}JSON 查询中也支持按 MVA 属性排序。排序模式可以通过 mode 属性设置。支持以下模式:
min:按最小值排序max:按最大值排序
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- Java
- C#
- Rust
- typescript
- go
{
"table":"test",
"query":
{
"match": { "title": "Test document" }
},
"sort":
[
{ "attr_mva": { "order":"desc", "mode":"max" } }
],
"_source": "title",
"limit": 3
}$search->setIndex("test")->match('Test document')->sort('id','desc','max');search_request.index = 'test'
search_request.fulltext_filter = manticoresearch.model.QueryFilter('Test document')
sort = manticoresearch.model.SortMVA('attr_mva', 'desc', 'max')
search_request.sort = [sort]search_request.index = 'test'
search_request.fulltext_filter = manticoresearch.model.QueryFilter('Test document')
sort = manticoresearch.model.SortMVA('attr_mva', 'desc', 'max')
search_request.sort = [sort]searchRequest.index = "test";
searchRequest.fulltext_filter = new Manticoresearch.QueryFilter('Test document');
sort = new Manticoresearch.SortMVA('attr_mva', 'desc', 'max');
searchRequest.sort = [sort];searchRequest.setIndex("test");
QueryFilter queryFilter = new QueryFilter();
queryFilter.setQueryString("Test document");
searchRequest.setFulltextFilter(queryFilter);
SortMVA sort = new SortMVA();
sort.setAttr("attr_mva");
sort.setOrder(SortMVA.OrderEnum.DESC);
sort.setMode(SortMVA.ModeEnum.MAX);
searchRequest.setSort(sort);var searchRequest = new SearchRequest("test");
searchRequest.FulltextFilter = new QueryFilter("Test document");
var sort = new SortMVA("attr_mva", SortMVA.OrderEnum.Desc, SortMVA.ModeEnum.Max);
searchRequest.Sort.Add(sort);let query = SearchQuery {
query_string: Some(serde_json::json!("Test document").into()),
..Default::default(),
};
let mut sort_mva_opts = HashMap::new();
sort_mva_opts.insert("order".to_string(), "desc".to_string());
sort_mva_opts.insert("mode".to_string(), "max".to_string());
let mut sort_mva = HashMap::new();
sort_mva.insert("attr_mva".to_string(), sort_mva_opts);
let search_req = SearchRequest {
table: "test".to_string(),
query: Some(Box::new(query)),
sort: Some(serde_json::json!(sort_mva)),
..Default::default(),
};searchRequest = {
index: 'test',
query: {
query_string: {'Test document'},
},
sort: { "attr_mva": { "order":"desc", "mode":"max" } },
}searchRequest.SetIndex("test")
query := map[string]interface{} {"query_string": "Test document"}
searchRequest.SetQuery(query)
sort := map[string]interface{} { "attr_mva": { "order":"desc", "mode":"max" } }
searchRequest.SetSort(sort) {
"took": 0,
"timed_out": false,
"hits": {
"total": 5,
"total_relation": "eq",
"hits": [
{
"_id": 5406864699109146631,
"_score": 2319,
"_source": {
"title": "Test document 4"
}
},
{
"_id": 5406864699109146629,
"_score": 2319,
"_source": {
"title": "Test document 2"
}
},
{
"_id": 5406864699109146628,
"_score": 2319,
"_source": {
"title": "Test document 1"
}
}
]
}
}当按属性排序时,默认禁用匹配权重(分数)计算(不使用排名器)。您可以通过将 track_scores 属性设置为 true 来启用权重计算:
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- Java
- C#
- Rust
- typescript
- go
{
"table":"test",
"track_scores": true,
"query":
{
"match": { "title": "Test document" }
},
"sort":
[
{ "attr_mva": { "order":"desc", "mode":"max" } }
],
"_source": "title",
"limit": 3
}$search->setIndex("test")->match('Test document')->sort('id','desc','max')->trackScores(true);search_request.index = 'test'
search_request.track_scores = true
search_request.fulltext_filter = manticoresearch.model.QueryFilter('Test document')
sort = manticoresearch.model.SortMVA('attr_mva', 'desc', 'max')
search_request.sort = [sort]search_request.index = 'test'
search_request.track_scores = true
search_request.fulltext_filter = manticoresearch.model.QueryFilter('Test document')
sort = manticoresearch.model.SortMVA('attr_mva', 'desc', 'max')
search_request.sort = [sort]searchRequest.index = "test";
searchRequest.trackScores = true;
searchRequest.fulltext_filter = new Manticoresearch.QueryFilter('Test document');
sort = new Manticoresearch.SortMVA('attr_mva', 'desc', 'max');
searchRequest.sort = [sort];searchRequest.setIndex("test");
searchRequest.setTrackScores(true);
QueryFilter queryFilter = new QueryFilter();
queryFilter.setQueryString("Test document");
searchRequest.setFulltextFilter(queryFilter);
SortMVA sort = new SortMVA();
sort.setAttr("attr_mva");
sort.setOrder(SortMVA.OrderEnum.DESC);
sort.setMode(SortMVA.ModeEnum.MAX);
searchRequest.setSort(sort);var searchRequest = new SearchRequest("test");
searchRequest.SetTrackScores(true);
searchRequest.FulltextFilter = new QueryFilter("Test document");
var sort = new SortMVA("attr_mva", SortMVA.OrderEnum.Desc, SortMVA.ModeEnum.Max);
searchRequest.Sort.Add(sort);let query = SearchQuery {
query_string: Some(serde_json::json!("Test document").into()),
..Default::default(),
};
let mut sort_mva_opts = HashMap::new();
sort_mva_opts.insert("order".to_string(), "desc".to_string());
sort_mva_opts.insert("mode".to_string(), "max".to_string());
let mut sort_mva = HashMap::new();
sort_mva.insert("attr_mva".to_string(), sort_mva_opts);
let search_req = SearchRequest {
table: "test".to_string(),
query: Some(Box::new(query)),
sort: Some(serde_json::json!(sort_mva)),
track_scores: Some(serde_json::json!(true)),
..Default::default(),
};searchRequest = {
index: 'test',
track_scores: true,
query: {
query_string: {'Test document'},
},
sort: { "attr_mva": { "order":"desc", "mode":"max" } },
}searchRequest.SetIndex("test")
searchRequest.SetTrackScores(true)
query := map[string]interface{} {"query_string": "Test document"}
searchRequest.SetQuery(query)
sort := map[string]interface{} { "attr_mva": { "order":"desc", "mode":"max" } }
searchRequest.SetSort(sort) {
"took": 0,
"timed_out": false,
"hits": {
"total": 5,
"total_relation": "eq",
"hits": [
{
"_id": 5406864699109146631,
"_score": 2319,
"_source": {
"title": "Test document 4"
}
},
{
"_id": 5406864699109146629,
"_score": 2319,
"_source": {
"title": "Test document 2"
}
},
{
"_id": 5406864699109146628,
"_score": 2319,
"_source": {
"title": "Test document 1"
}
}
]
}
}排名(也称为加权)搜索结果可以定义为一个过程,即针对每个匹配的文档计算所谓的相关性(权重),以对应匹配它的查询。因此,相关性最终只是附加到每个文档上的一个数字,用于估计该文档与查询的相关程度。然后可以基于该数字和/或一些附加参数对搜索结果进行排序,使得最受关注的结果出现在结果页的更高位置。
没有一种适用于所有场景的单一标准排名方法。更重要的是,永远不可能有这样的方式,因为相关性是主观的。也就是说,对你来说相关的内容,对我来说可能不相关。因此,在一般情况下,相关性不仅难以计算;从理论上讲是不可能的。
所以 Manticore 中的排名是可配置的。它有一个所谓的ranker(排名器)的概念。排名器可以形式化定义为一个函数,输入为文档和查询,输出为相关性值。通俗地说,排名器控制 Manticore 使用哪种具体算法来为文档分配权重。
Manticore 附带了几个适用于不同用途的内置排名器。它们中的许多使用两个因素:短语接近度(也称为 LCS)和 BM25。短语接近度基于关键词位置,而 BM25 基于关键词频率。基本上,文档正文与查询之间的短语匹配程度越好,短语接近度越高(当文档包含整个查询的逐字引用时达到最大值)。而 BM25 在文档包含更多稀有词时更高。详细讨论留待后面。
当前实现的排名器有:
proximity_bm25,默认排名模式,结合使用短语接近度和 BM25 排名。bm25,统计排名模式,仅使用 BM25 排名(类似大多数其他全文引擎)。此模式更快,但对于包含多个关键词的查询可能导致质量较差。none,无排名模式。此模式显然是最快的。所有匹配赋予权重 1。有时称为布尔搜索,只匹配文档但不排名。wordcount,按关键词出现次数排名。该排名器计算每个字段的关键词出现次数,然后乘以字段权重,最后求和。proximity返回原始短语接近度值。此模式内部用于模拟SPH_MATCH_ALL查询。matchany返回之前在SPH_MATCH_ANY模式下计算的排名,内部用于模拟SPH_MATCH_ANY查询。fieldmask返回一个 32 位掩码,第 N 位对应第 N 个全文字段(从 0 开始编号)。只有当相应字段有满足查询的关键词出现时,该位才会被设置。sph04通常基于默认的proximity_bm25排名器,但额外提升匹配出现在文本字段开头或结尾的权重。因此,如果字段等于精确查询,sph04应该将其排名高于包含精确查询但不完全相等的字段。(例如,当查询为“Hyde Park”时,标题为“Hyde Park”的文档应排名高于标题为“Hyde Park, London”或“The Hyde Park Cafe”的文档。)expr允许你在运行时指定排名公式。它暴露了几个内部文本因素,并允许你定义如何从这些因素计算最终权重。你可以在下面的小节中找到其语法和可用因素的参考详情。
排名器名称不区分大小写。示例:
SELECT ... OPTION ranker=sph04;
| 名称 | 级别 | 类型 | 概要 |
|---|---|---|---|
| max_lcs | query | int | 当前查询的最大可能 LCS 值 |
| bm25 | document | int | BM25(1.2, 0) 的快速估计 |
| bm25a(k1, b) | document | int | 具有可配置 K1、B 常数和语法支持的精确 BM25() 值 |
| bm25f(k1, b, {field=weight, ...}) | document | int | 具有额外可配置字段权重的精确 BM25F() 值 |
| field_mask | document | int | 匹配字段的位掩码 |
| query_word_count | document | int | 查询中唯一包含的关键词数量 |
| doc_word_count | document | int | 文档中匹配的唯一关键词数量 |
| lcs | field | int | 查询与文档之间的最长公共子序列(以词为单位) |
| user_weight | field | int | 用户字段权重 |
| hit_count | field | int | 关键词出现的总次数 |
| word_count | field | int | 匹配的唯一关键词数量 |
| tf_idf | 字段 | 浮点型 | 匹配关键词的 tf*idf 之和 == 出现次数的 idf 之和 |
| min_hit_pos | 字段 | 整型 | 第一个匹配出现的位置,以词为单位,从1开始 |
| min_best_span_pos | 字段 | 整型 | 第一个最大LCS跨度的位置,以词为单位,从1开始 |
| exact_hit | 字段 | 布尔型 | 查询是否 == 字段 |
| min_idf | 字段 | 浮点型 | 匹配关键词的最小 idf |
| max_idf | 字段 | 浮点型 | 匹配关键词的最大 idf |
| sum_idf | 字段 | 浮点型 | 匹配关键词的 idf 之和 |
| exact_order | 字段 | 布尔型 | 查询关键词是否a) 全部匹配且 b) 按查询顺序 |
| min_gaps | 字段 | 整型 | 匹配跨度中关键词之间的最小间隔数 |
| lccs | 字段 | 整型 | 查询和文档之间的最长连续公共子序列,以词为单位 |
| wlccs | 字段 | 浮点型 | 加权最长连续公共子序列,连续关键词跨度的 idf 之和 |
| atc | 字段 | 浮点型 | 聚合词项接近度,log(1+sum(idf1idf2pow(距离, -1.75)) 在最佳关键词对上 |
注意:对于使用短语、邻近或NEAR运算符且关键词超过31个的查询,依赖词频的排名因子(如 tf、idf、bm25、hit_count、word_count)可能会对第31个及以上位置的关键词进行低估。这是由于内部使用了32位掩码来跟踪这些复杂运算符中的词项出现情况。
文档级因子是排名引擎为每个匹配文档针对当前查询计算的数值。因此它不同于普通文档属性,属性不依赖于全文查询,而因子可能会依赖。这些因子可以在排名表达式的任何位置使用。目前实现的文档级因子包括:
bm25(整型),文档级BM25估计(不进行关键词出现过滤)。max_lcs(整型),查询级可能的最大值,sum(lcs*user_weight)表达式可能达到的值。这可用于权重提升缩放。例如,MATCHANY排名器公式使用此值以确保任何字段中的完全短语匹配都高于所有字段中任何部分匹配的组合。field_mask(整型),匹配字段的32位掩码。query_word_count(整型),查询中唯一关键词的数量,根据排除的关键词数量进行调整。例如,(one one one one)和(one !two)查询都应赋值为1,因为只有一个唯一的非排除关键词。doc_word_count(整型),在整个文档中匹配的唯一关键词数量。
字段级因子是排名引擎为每个匹配的文档内文本字段针对当前查询计算的数值。由于可能有多个字段匹配查询,但最终权重需要是单一整型值,这些值需要折叠为单一值。为实现这一点,字段级因子只能在字段聚合函数中使用,不能在表达式的任何位置使用。例如,不能使用(lcs+bm25)作为排名表达式,因为lcs有多个值(每个匹配字段一个)。应使用(sum(lcs)+bm25),该表达式对所有匹配字段的lcs求和,然后将bm25加到每字段和上。目前实现的字段级因子包括:
-
lcs(整型),文档和查询之间最大逐字匹配的长度,以词计算。LCS代表最长公共子序列(或子集)。当仅匹配零散关键词时取最小值1,当整个查询以精确查询关键词顺序在字段中逐字匹配时取最大值(查询关键词数量)。例如,如果查询是'hello world'且字段包含从查询直接引用的这两个词(即相邻且完全按查询顺序),lcs将为2。如果查询是'hello world program'且字段包含'hello world',lcs将为2。注意,查询关键词的任何子集都可以匹配,不仅限于相邻关键词。例如,如果查询是'hello world program'且字段包含'hello (test program)',lcs也将为2,因为'hello'和'program'分别在查询中的相同位置匹配。最后,如果查询是'hello world program'且字段包含'hello world program',lcs将为3。(希望这一点此时已经不足为奇了。) -
user_weight(整型),用户指定的每字段权重(参考OPTION field_weights中的SQL)。如果未明确指定,权重默认为1。 -
hit_count(整型),字段中匹配的关键词出现次数。注意单个关键词可能出现多次。例如,如果'hello'在字段中出现3次,'world'出现5次,hit_count将为8。 -
word_count(整数),匹配字段中唯一关键词的数量。例如,如果'hello'和'world'在字段中的任何位置出现,word_count将为2,无论这两个关键词出现多少次。 -
tf_idf(浮点数),匹配字段中所有关键词的TF/IDF之和。IDF是逆文档频率,是介于0和1之间的浮点值,描述关键词的频率(基本上,对于出现在索引的每个文档中的关键词为0,对于仅出现在单个文档中的唯一关键词为1)。TF是词频,是字段中匹配关键词出现的次数。顺便说一下,tf_idf实际上是通过对所有匹配出现的IDF求和来计算的。这从构造上等同于对所有匹配关键词求TF*IDF的和。 -
min_hit_pos(整数),以单词计算的第一个匹配关键词出现的位置因此,这是一个相对低级的"原始"因子,你可能需要在使用它进行排名之前进行调整。具体的调整取决于你的数据和最终的公式,但以下是一些起步的想法:(a) 当
word_count<2时,可以简单地忽略任何基于min_gaps的提升;(b) 对于非平凡的min_gaps值(即当
word_count>=2时),可以用某个"最坏情况"常数进行截断,而对于平凡值(即当min_gaps=0且word_count<2时),可以用该常数替换;(c) 可以应用类似1/(1+min_gaps)的传递函数(使得更好的、较小的min_gaps值会使其最大化,而更差的、较大的min_gaps值会缓慢下降);等等。
-
lccs(整数)。最长公共连续子序列。查询和文档之间最长的公共子短语的长度,以关键词计算。LCCS因子与LCS有些相似,但更加严格。虽然LCS即使没有两个查询词相邻也可能大于1,但LCCS只有在文档中存在完全的连续查询子短语时才会大于1。例如,(one two three four five)查询与(one hundred three hundred five hundred)文档会得到lcs=3,但lccs=1,因为尽管3个关键词(one, three, five)在查询和文档之间的相对位置相同,但实际上没有两个匹配位置相邻。
请注意,LCCS仍然无法区分频繁和罕见的关键词;对于这一点,请参见WLCCS。
-
wlccs(浮点数)。加权最长公共连续子序列。查询和文档之间最长子短语的关键词的IDF之和。WLCCS的计算方式类似于LCCS,但每个"合适的"关键词出现会使其增加关键词的IDF,而不是像LCS和LCCS那样简单地加1。这使得能够对罕见和更重要的关键词序列进行更高的排名,即使它们比频繁关键词的序列更短。例如,查询
(Zanzibar bed and breakfast)对于文档(hotels of Zanzibar)会得到lccs=1,而对于(London bed and breakfast)会得到lccs=3,尽管"Zanzibar"实际上比整个"bed and breakfast"短语更罕见。WLCCS因子通过使用关键词频率来解决这个问题。 -
atc(浮点数)。聚合词项接近度。一种基于接近度的度量,当文档包含更多更紧密定位且更重要(罕见)的查询关键词组时,其值会增加。警告:你应该将ATC与OPTION idf='plain,tfidf_unnormalized'一起使用(见下文);否则,你可能会得到意外的结果。
ATC基本上的工作原理如下。对于文档中的每个关键词出现,我们计算所谓的词项接近度。为此,我们检查主要出现位置左右所有查询关键词的最近其他出现位置,计算距离衰减系数k = pow(distance, -1.75),并对这些出现位置求和衰减后的IDF。因此,对于每个关键词的每次出现,我们得到一个"接近度"值,描述该出现的"邻居"。然后我们将这些每次出现的接近度乘以其各自关键词的IDF,将它们全部求和,最后计算该和的对数。
换句话说,我们处理文档中最佳(最近)的匹配关键词对,并计算成对的"接近度",作为其IDF乘积并按距离系数缩放:
pair_tc = idf(pair_word1) * idf(pair_word2) * pow(pair_distance, -1.75)
然后我们对这些接近度求和,并计算最终的对数衰减ATC值:
atc = log(1+sum(pair_tc))
请注意,这个最终的衰减对数正是你应该使用OPTION idf=plain的原因,因为没有它,log()内的表达式可能是负数。
更近的关键词出现对ATC的贡献远远多于更频繁的关键词。事实上,当关键词紧挨着时,distance=1且k=1;当它们之间只有一个词时,distance=2且k=0.297,两个词之间时,distance=3且k=0.146,以此类推。同时,IDF衰减得相对较慢。例如,在一个100万文档的集合中,匹配10、100和1000个文档的关键词的IDF值分别为0.833、0.667和0.500。所以,一对仅在10个文档中各出现一次但中间有2个其他词的相当罕见的关键词将产生pair_tc = 0.101,因此几乎可以与一对100个文档和1000个文档的关键词(中间有1个其他词)的pair_tc = 0.099相媲美。更重要的是,一对两个唯一的、1个文档的关键词(中间有3个词)将得到pair_tc = 0.088,输给一对1000个文档的关键词(紧挨着且pair_tc = 0.25)。因此,基本上,虽然ATC确实结合了关键词频率和接近度,但它仍然稍微偏好接近度。
一个字段聚合函数是一个单参数函数,接受带有字段级因子的表达式,遍历所有匹配的字段,并计算最终结果。当前已实现的字段聚合函数包括:
sum,它将参数表达式在所有匹配的字段上相加。例如sum(1)应返回匹配字段的数量。top,它返回所有匹配字段中参数的最高值。max_window_hits,管理命中位置的滑动窗口,以跟踪指定窗口大小内的最大命中数。它移除落在窗口外的过时命中,并添加最新命中,更新在该窗口内找到的最大命中数。
大多数其他排名器实际上可以使用基于表达式的排名器模拟。您只需提供适当的表达式。虽然这种模拟可能比使用内置的编译排名器慢,但如果您想从现有排名器开始微调排名公式,这仍可能很有趣。此外,这些公式以清晰、可读的方式描述了排名器的细节。
- proximity_bm25(默认排名器)=
sum(lcs*user_weight)*1000+bm25 - bm25 =
sum(user_weight)*1000+bm25 - none =
1 - wordcount =
sum(hit_count*user_weight) - proximity =
sum(lcs*user_weight) - matchany =
sum((word_count+(lcs-1)*max_lcs)*user_weight) - fieldmask =
field_mask - sph04 =
sum((4*lcs+2*(min_hit_pos==1)+exact_hit)*user_weight)*1000+bm25
Manticore中历史上默认的IDF(逆文档频率)等同于 OPTION idf='normalized,tfidf_normalized',这些归一化可能会导致几个不期望的效果。
首先,idf=normalized会导致关键词惩罚。例如,如果您搜索 the | something,且 the 出现在超过50%的文档中,那么同时包含关键词 the 和 something 的文档将比只包含关键词 something 的文档权重更低。使用 OPTION idf=plain 可以避免这种情况。
普通IDF在 [0, log(N)] 范围内变化,关键词不会被惩罚;而归一化IDF在 [-log(N), log(N)] 范围内变化,过于频繁的关键词会被惩罚。
其次,idf=tfidf_normalized会导致查询间IDF漂移。历史上,我们额外将IDF除以查询关键词数量,以便所有关键词的 sum(tf*idf) 仍然保持在 [0,1] 范围内。然而,这意味着查询 word1 和 word1 | nonmatchingword2 会为完全相同的结果集分配不同的权重,因为 word1 和 nonmatchingword2 的IDF都会被除以2。OPTION idf='tfidf_unnormalized' 修复了这个问题。请注意,一旦禁用此归一化,BM25、BM25A、BM25F()排名因子将相应缩放。
IDF标志可以混合使用;plain 和 normalized 是互斥的;tfidf_unnormalized 和 tfidf_normalized 是互斥的;而在这种互斥组中未指定的标志将采用其默认值。这意味着 OPTION idf=plain 等同于完整的 OPTION idf='plain,tfidf_normalized' 规范。
Manticore Search 默认返回结果集中排名前20的匹配文档。
在 SQL 中,可以使用 LIMIT 子句来导航结果集。
LIMIT 可以接受一个数字作为返回集的大小(偏移量为零),也可以接受一对偏移量和大小的值。
使用 HTTP JSON 时,节点 offset 和 limit 控制结果集的偏移量和返回集的大小。或者,也可以使用 size 和 from 这对参数。
- SQL
- JSON
SELECT ... FROM ... [LIMIT [offset,] row_count]
SELECT ... FROM ... [LIMIT row_count][ OFFSET offset]{
"table": "<table_name>",
"query": ...
...
"limit": 20,
"offset": 0
}
{
"table": "<table_name>",
"query": ...
...
"size": 20,
"from": 0
}默认情况下,Manticore Search 使用一个包含1000个最佳排名文档的结果集窗口,这些文档可以返回在结果集中。如果结果集分页超出此值,查询将以错误结束。
此限制可以通过查询选项 max_matches 进行调整。
只有在需要导航到如此深的位置时,才应将 max_matches 增加到非常高的值。较高的 max_matches 值需要更多内存,并可能增加查询响应时间。处理深度结果集的一种方法是将 max_matches 设置为偏移量和限制的总和。
将 max_matches 降低到1000以下的好处是减少查询使用的内存。它也可以减少查询时间,但在大多数情况下,这种提升可能不明显。
- SQL
- JSON
SELECT ... FROM ... OPTION max_matches=<value>{
"table": "<table_name>",
"query": ...
...
"max_matches":<value>
}
}滚动搜索选项提供了一种高效且可靠的方式来分页浏览大型结果集。与传统的基于偏移量的分页不同,滚动搜索在深度分页时性能更好,并且实现分页更简单。
虽然它使用与基于偏移量分页相同的 max_matches 窗口,但滚动搜索可以通过使用滚动令牌在多次请求中检索结果,返回超过 max_matches 值的文档。
使用滚动分页时,无需同时使用 offset 和 limit —— 这是多余的,通常被认为是过度设计。只需指定 limit 和 scroll 令牌即可获取每个后续页面。
带排序条件的初始查询
首先执行带有所需排序条件的初始查询。唯一要求是 id 必须包含在 ORDER BY 子句中,以确保分页的一致性。查询将返回结果和用于后续页面的滚动令牌。
SELECT ... ORDER BY [... ,] id {ASC|DESC};
- Initial Query Example
SELECT weight(), id FROM test WHERE match('hello') ORDER BY weight() desc, id asc limit 2;+----------+------+
| weight() | id |
+----------+------+
| 1281 | 1 |
| 1281 | 2 |
+----------+------+
2 rows in set (0.00 sec)获取滚动令牌
执行初始查询后,通过执行 SHOW SCROLL 命令获取滚动令牌。
必须在滚动序列中的每次查询后调用 SHOW SCROLL,以获取下一页的更新滚动令牌。
每次查询都会生成一个反映最新滚动位置的新令牌。
SHOW SCROLL;
响应:
| scroll_token |
|------------------------------------|
| <base64 encoded scroll token> |
- Scroll Token Example
SHOW SCROLL;+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| scroll_token |
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| eyJvcmRlcl9ieV9zdHIiOiJ3ZWlnaHQoKSBkZXNjLCBpZCBhc2MiLCJvcmRlcl9ieSI6W3siYXR0ciI6IndlaWdodCgpIiwiZGVzYyI6dHJ1ZSwidmFsdWUiOjEyODEsInR5cGUiOiJpbnQifSx7ImF0dHIiOiJpZCIsImRlc2MiOmZhbHNlLCJ2YWx1ZSI6MiwidHlwZSI6ImludCJ9XX0= |
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)使用 scroll 的分页查询
要获取下一页结果,在后续查询中将滚动令牌作为选项包含。当提供 scroll 选项时,指定排序条件是可选的。
记得在此查询后再次调用 SHOW SCROLL,以获取下一页所需的新令牌。
SELECT ... [ORDER BY [... ,] id {ASC|DESC}] OPTION scroll='<base64 encoded scroll token>'[, ...];
这确保分页无缝继续,保持初始查询中建立的排序上下文。
- Paginated Query Example
SELECT weight(), id FROM test WHERE match('hello') limit 2
OPTION scroll='eyJvcmRlcl9ieV9zdHIiOiJ3ZWlnaHQoKSBkZXNjLCBpZCBhc2MiLCJvcmRlcl9ieSI6W3siYXR0ciI6IndlaWdodCgpIiwiZGVzYyI6dHJ1ZSwidmFsdWUiOjEyODEsInR5cGUiOiJpbnQifSx7ImF0dHIiOiJpZCIsImRlc2MiOmZhbHNlLCJ2YWx1ZSI6MiwidHlwZSI6ImludCJ9XX0=';+----------+------+
| weight() | id |
+----------+------+
| 1281 | 3 |
| 1281 | 4 |
+----------+------+
2 rows in set (0.00 sec)初始请求
在初始请求中,在选项中指定 "scroll": true 和所需的排序条件。注意,id 必须出现在 sort 数组中。响应将包含滚动令牌,可用于后续请求的分页。
POST /search
{
"table": "<table_names>",
"options": {
"scroll": true
},
...
"sort": [
...
{ "id":{ "order":"{asc|desc}"} }
]
}
示例输出:
{
"timed_out": false,
"hits": {
...
},
"scroll": "<base64 encoded scroll token>"
}
- Initial Request Example
POST /search
{
"table": "test",
"options":
{
"scroll": true
},
"query":
{
"query_string":"hello"
},
"sort":
[
{ "_score":{ "order":"desc"} },
{ "id":{ "order":"asc"} }
],
"track_scores": true,
"limit":2
}{
"took": 0,
"timed_out": false,
"hits":
{
"total": 10,
"total_relation": "eq",
"hits":
[
{
"_id": 1,
"_score": 1281,
"_source":
{
"title": "hello world1"
}
},
{
"_id": 2,
"_score": 1281,
"_source":
{
"title": "hello world2"
}
}
]
},
"scroll": "eyJvcmRlcl9ieV9zdHIiOiJAd2VpZ2h0IGRlc2MsIGlkIGFzYyIsIm9yZGVyX2J5IjpbeyJhdHRyIjoid2VpZ2h0KCkiLCJkZXNjIjp0cnVlLCJ2YWx1ZSI6MTI4MSwidHlwZSI6ImludCJ9LHsiYXR0ciI6ImlkIiwiZGVzYyI6ZmFsc2UsInZhbHVlIjoyLCJ0eXBlIjoiaW50In1dfQ=="
}使用 scroll 的分页请求
要继续分页,在下一次请求的选项对象中包含从上一次响应获得的滚动令牌。指定排序条件是可选的。
POST /search
{
"table": "<table_names>",
"options": {
"scroll": "<base64 encoded scroll token>"
},
...
}
- Paginated Request Example
POST /search
{
"table": "test",
"options":
{
"scroll": "eyJvcmRlcl9ieV9zdHIiOiJAd2VpZ2h0IGRlc2MsIGlkIGFzYyIsIm9yZGVyX2J5IjpbeyJhdHRyIjoid2VpZ2h0KCkiLCJkZXNjIjp0cnVlLCJ2YWx1ZSI6MTI4MSwidHlwZSI6ImludCJ9LHsiYXR0ciI6ImlkIiwiZGVzYyI6ZmFsc2UsInZhbHVlIjoyLCJ0eXBlIjoiaW50In1dfQ=="
},
"query":
{
"query_string":"hello"
},
"track_scores": true,
"limit":2
}{
"took": 0,
"timed_out": false,
"hits":
{
"total": 8,
"total_relation": "eq",
"hits":
[
{
"_id": 3,
"_score": 1281,
"_source":
{
"title": "hello world3"
}
},
{
"_id": 4,
"_score": 1281,
"_source":
{
"title": "hello world4"
}
}
]
},
"scroll": "eyJvcmRlcl9ieV9zdHIiOiJAd2VpZ2h0IGRlc2MsIGlkIGFzYyIsIm9yZGVyX2J5IjpbeyJhdHRyIjoid2VpZ2h0KCkiLCJkZXNjIjp0cnVlLCJ2YWx1ZSI6MTI4MSwidHlwZSI6ImludCJ9LHsiYXR0ciI6ImlkIiwiZGVzYyI6ZmFsc2UsInZhbHVlIjo0LCJ0eXBlIjoiaW50In1dfQ=="
}Manticore 设计为通过其分布式搜索功能实现有效扩展。分布式搜索有助于提高查询延迟(即搜索时间)和吞吐量(即最大查询数/秒),适用于多服务器、多CPU或多核环境。这对于需要搜索海量数据(即数十亿条记录和数TB文本)的应用至关重要。
其主要理念是将被搜索的数据水平分区到多个搜索节点,并行处理。
分区是手动完成的。设置步骤如下:
- 在不同服务器上设置多个 Manticore 实例
- 将数据集的不同部分分配给不同实例
- 在某些
searchd实例上配置特殊的分布式表 - 将查询路由到分布式表
这种类型的表仅包含对其他本地和远程表的引用,因此不能直接重新索引。相反,应重新索引它所引用的表。
当 Manticore 收到针对分布式表的查询时,会执行以下步骤:
- 连接到配置的远程代理
- 向它们发送查询
- 同时搜索配置的本地表(远程代理搜索的同时)
- 从远程代理检索搜索结果
- 合并所有结果,去除重复项
- 将合并后的结果发送给客户端
从应用程序的角度来看,通过常规表或分布式表搜索没有区别。换句话说,分布式表对应用程序完全透明,无法判断查询的表是分布式还是本地。
了解更多关于远程节点的信息。