SQL SELECT 子句和 HTTP /search 端点支持许多选项,可用于微调搜索行为。
SQL:
SELECT ... [OPTION <optionname>=<value> [ , ... ]] [/*+ [NO_][ColumnarScan|DocidIndex|SecondaryIndex(<attribute>[,...])]] /*]
HTTP:
POST /search
{
"table" : "table_name",
"options":
{
"optionname": "value",
"optionname2": <value2>
}
}
- SQL
- JSON
SELECT * FROM test WHERE MATCH('@title hello @body world')
OPTION ranker=bm25, max_matches=3000,
field_weights=(title=10, body=3), agent_query_timeout=10000POST /search
{
"table" : "test",
"query": {
"match": {
"title": "hello"
},
"match": {
"body": "world"
}
},
"options":
{
"ranker": "bm25",
"max_matches": 3000,
"field_weights": {
"title": 10,
"body": 3
},
"agent_query_timeout": 10000
}
}+------+-------+-------+
| id | title | body |
+------+-------+-------+
| 1 | hello | world |
+------+-------+-------+
1 row in set (0.00 sec){
"took": 0,
"timed_out": false,
"hits": {
"total": 1,
"total_relation": "eq",
"hits": [
{
"_id": 1,
"_score": 10500,
"_source": {
"title": "hello",
"body": "world"
}
}
]
}
}支持的选项包括:
整数。启用或禁用在多线程运行 groupby 查询时的保证聚合准确性。默认值为 0。
当运行 groupby 查询时,可以在普通表上使用多个伪分片并行运行(如果 pseudo_sharding 开启)。类似的方法适用于 RT 表。每个分片/块执行查询,但组的数量受 max_matches 限制。如果不同分片/块的结果集包含不同的组,组计数和聚合可能会不准确。注意 Manticore 会根据 groupby 属性的唯一值数量(从二级索引中检索)将 max_matches 增加到 max_matches_increase_threshold。如果成功,将不会出现准确性损失。
然而,如果 groupby 属性的唯一值数量很高,进一步增加 max_matches 可能不是一个好策略,因为它可能导致性能下降和内存使用增加。将 accurate_aggregation 设置为 1 会强制 groupby 搜索在单线程中运行,从而解决准确性问题。注意,仅当 max_matches 无法设置得足够高时,才会强制单线程运行;否则,带有 accurate_aggregation=1 的搜索仍会在多线程中运行。
总体而言,将 accurate_aggregation 设置为 1 可确保 RT 表和 pseudo_sharding=1 的普通表中的组计数和聚合准确性。缺点是搜索会变慢,因为它们将被强制在单线程中运行。
然而,如果我们有一个 RT 表和一个包含相同数据的普通表,并运行带有 accurate_aggregation=1 的查询,我们仍可能收到不同的结果。这是因为守护进程可能会根据 max_matches_increase_threshold 设置为 RT 表和普通表选择不同的 max_matches 值。
整数。等待远程查询完成的最大时间(以毫秒为单位),请参见 此部分。
0 或 1(默认为 1)。boolean_simplify=1 启用 查询简化 以加快速度。
此选项也可以在 searchd 配置 中全局设置,以更改所有查询的默认行为。每个查询的选项将覆盖全局设置。
字符串,用户评论会被复制到查询日志文件中。
整数。指定要处理的最大匹配数。如果未设置,Manticore 将自动选择一个适当的值。
N = 0:禁用匹配数的限制。N > 0:指示 Manticore 在找到N个匹配文档后立即停止处理结果。- 未设置:Manticore 自动决定阈值。
当 Manticore 无法确定匹配文档的确切数量时,查询 元信息 中的 total_relation 字段将显示 gte,表示 大于或等于。这表明实际匹配数至少为报告的 total_found(在 SQL 中)或 hits.total(在 JSON 中)。当计数准确时,total_relation 将显示 eq。
注意:在聚合查询中使用 cutoff 不推荐,因为它可能导致不准确或不完整的结果。
- Example
在聚合查询中使用 cutoff 可能导致不正确或误导性的结果,如下例所示:
drop table if exists t
--------------
Query OK, 0 rows affected (0.02 sec)
--------------
create table t(a int)
--------------
Query OK, 0 rows affected (0.04 sec)
--------------
insert into t(a) values(1),(2),(3),(1),(2),(3)
--------------
Query OK, 6 rows affected (0.00 sec)
--------------
select avg(a) from t option cutoff=1 facet a
--------------
+----------+
| avg(a) |
+----------+
| 1.000000 |
+----------+
1 row in set (0.00 sec)
--- 1 out of 1 results in 0ms ---
+------+----------+
| a | count(*) |
+------+----------+
| 1 | 1 |
+------+----------+
1 row in set (0.00 sec)
--- 1 out of 1 results in 0ms ---与不使用 cutoff 的相同查询进行比较:
--------------
select avg(a) from t facet a
--------------
+----------+
| avg(a) |
+----------+
| 2.000000 |
+----------+
1 row in set (0.00 sec)
--- 1 out of 1 results in 0ms ---
+------+----------+
| a | count(*) |
+------+----------+
| 1 | 2 |
| 2 | 2 |
| 3 | 2 |
+------+----------+
3 rows in set (0.00 sec)
--- 3 out of 3 results in 0ms ---整数。默认值为 3500。此选项设置在普通表中 count distinct 返回的计数保证精确的阈值。
接受的值范围为 500 到 15500。超出此范围的值将被限制。
当此选项设置为 0 时,它启用一种确保精确计数的算法。该算法收集 {group, value} 对,对其进行排序,并定期消除重复项。结果是在普通表中精确的计数。然而,由于其高内存消耗和慢查询执行,这种方法不适合高基数数据集。
当 distinct_precision_threshold 设置为大于 0 的值时,Manticore 采用不同的算法。它将计数加载到哈希表中并返回表的大小。如果哈希表变得太大,其内容将被移动到 HyperLogLog 数据结构中。此时,计数变得近似,因为 HyperLogLog 是一种概率算法。这种方法保持每组的最大内存使用量固定,但计数准确性存在权衡。
HyperLogLog 的精度和从哈希表转换到 HyperLogLog 的阈值源自 distinct_precision_threshold 设置。使用此选项时需谨慎,因为将其值加倍也会使计算计数所需的最大内存加倍。最大内存使用量可以大致通过此公式估算:64 * max_matches * distinct_precision_threshold,尽管在实践中,计数计算通常使用的内存少于最坏情况下的内存。
0 或 1(默认为 0)。尽可能扩展精确形式和/或带星号的关键词。有关更多详细信息,请参阅 expand_keywords。
0、off、1 或任何 blend_mode 选项的组合(默认为 0)。在查询解析期间,将混合关键词(包含通过 blend_chars 配置的字符的标记)扩展为其构成变体。启用后,像 "well-being"(如果 - 在 blend_chars 中配置)这样的关键词将被扩展为 "well-being"、"wellbeing"、"well" 和 "being" 等变体,然后在查询树中被分组为 OR 子树。
支持的值为:
0或off- 禁用混合扩展(默认)。混合关键词将按正常方式处理,不进行扩展。1- 启用混合扩展,并使用表的 blend_mode 设置来确定生成哪些变体。- 任何混合模式选项 - 启用混合扩展并使用指定的混合模式,覆盖表的
blend_mode设置。
有关选项的更多详细信息,请参阅 blend_mode。
命名整数列表(按字段的用户权重用于排序)。
示例:
SELECT ... OPTION field_weights=(title=10, body=3)
使用 global_idf 文件中的全局统计信息(频率)进行 IDF 计算。
带引号的、逗号分隔的 IDF 计算标志列表。已知标志包括:
normalized:BM25 变体,idf = log((N-n+1)/n),如 Robertson 等人所述plain:普通变体,idf = log(N/n),如 Sparck-Jones 所述tfidf_normalized:此外将 IDF 除以查询词数,使TF*IDF保持在 [0, 1] 范围内tfidf_unnormalized:不将 IDF 除以查询词数,其中 N 是集合大小,n 是匹配文档数
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'。
指定查询的 Jieba 分词模式。
在使用 Jieba 中文分词时,有时需要为文档和查询使用不同的分词模式。有关完整模式列表,请参阅 jieba_mode。
命名整数列表。按表的用户权重用于排序。
0 或 1,自动汇总分布式表的所有本地部分的 DF,确保本地分片表的 IDF 一致(且准确)。默认启用用于 RT 表的磁盘分片。带有通配符的查询术语被忽略。
0 或 1(默认为 0)。设置 low_priority=1 以较低优先级执行查询,其任务重新调度的频率比其他正常优先级查询低 10 倍。
整数。每查询最大匹配值。
服务器为每个表在 RAM 中保留的最大匹配数,并可返回给客户端。默认值为 1000。
引入 max_matches 设置是为了控制和限制 RAM 使用,该设置决定了在搜索每个表时将保留多少匹配项在 RAM 中。每个找到的匹配项仍会被处理,但只有最佳的 N 个匹配项会被保留在内存中,并最终返回给客户端。例如,假设一个表包含 2,000,000 个匹配项用于某个查询。很少需要检索所有匹配项。相反,你需要扫描所有匹配项,但仅选择基于某些标准(例如按相关性、价格或其他因素排序)的“最佳”500 个匹配项,并将这 500 个匹配项以每页 20 到 100 个匹配项的形式显示给最终用户。仅跟踪最佳的 500 个匹配项比保留所有 2,000,000 个匹配项、排序后丢弃除前 20 个外的所有内容,对 RAM 和 CPU 更加高效。max_matches 控制该“最佳 N”中的 N 值。
此参数会显著影响每个查询的RAM和CPU使用量。值在1,000到10,000之间通常是可以接受的,但提高限制时应谨慎使用。随意将max_matches增加到1,000,000意味着searchd必须为每个查询分配并初始化一个包含100万条匹配项的缓冲区。这不可避免地会增加每个查询的RAM使用量,并且在某些情况下可能明显影响性能。
有关它如何影响max_matches选项行为的更多信息,请参阅max_matches_increase_threshold。
整数。设置max_matches可以增加的阈值。默认值为16384。
当启用pseudo_sharding且检测到分组属性的唯一值数量小于此阈值时,Manticore可能会增加max_matches以提高分组和/或聚合的准确性。当伪分片在多个线程中执行查询或RT表在磁盘块中进行并行搜索时,可能会发生准确性下降。
如果分组属性的唯一值数量小于阈值,max_matches将设置为该数量。否则,将使用默认的max_matches。
如果在查询选项中显式设置了max_matches,则此阈值无效。
请注意,如果此阈值设置得过高,会导致内存消耗增加和整体性能下降。
您还可以使用accurate_aggregation选项强制启用保证的分组/聚合准确性模式。
设置最大搜索查询时间(以毫秒为单位)。必须是非负整数。默认值为0,表示“不限制”。本地搜索查询一旦达到指定时间就会停止。请注意,如果您执行的搜索查询了多个本地表,此限制适用于每个表。请注意,由于不断跟踪是否需要停止查询所产生的开销,这可能会略微增加查询的响应时间。
整数。最大预测搜索时间;请参阅predicted_time_costs。
none允许在表使用index_exact_words启用时,将所有查询术语替换为它们的确切形式。这对于防止对查询术语进行词干提取或词形还原很有用。
- SQL
MySQL [(none)]> select * from tbl where match('-donald');
ERROR 1064 (42000): index t: query error: query is non-computable (single NOT operator)
MySQL [(none)]> select * from t where match('-donald') option not_terms_only_allowed=1;
+---------------------+-----------+
| id | field |
+---------------------+-----------+
| 1658178727135150081 | smth else |
+---------------------+-----------+从以下选项中选择:
proximity_bm25bm25nonewordcountproximitymatchanyfieldmasksph04exprexport
有关每个排序器的更多详细信息,请参阅搜索结果排序。
允许您为ORDER BY RAND()查询指定特定的整数种子值,例如:... OPTION rand_seed=1234。默认情况下,每个查询都会自动生成一个新的且不同的种子值。
整数。分布式重试次数。
整数。分布式重试延迟(以毫秒为单位)。
字符串。用于使用Scroll分页方法分页结果的滚动令牌。
pq- 优先队列,默认设置kbuffer- 为已预先排序的数据(例如按id排序的表数据)提供更快的排序 两种情况下结果集相同;选择其中一个选项可能会简单地提高(或降低)性能。
限制当前查询处理使用的最大线程数。默认值 - 无限制(查询可以占用全局定义的threads中的所有线程)。 对于查询批次,该选项必须附加到批次中的第一个查询,并在创建工作队列时应用,对整个批次有效。此选项与选项max_threads_per_query含义相同,但仅适用于当前查询或查询批次。
用引号括起的、以冒号分隔的library name:plugin name:optional string of settings字符串。当每个表调用全文搜索时,为每个搜索创建一个查询时的标记过滤器,允许您实现自定义分词器,根据自定义规则生成标记。
SELECT * FROM index WHERE MATCH ('yes@no') OPTION token_filter='mylib.so:blend:@'
限制单个通配符扩展的关键字最大数量,默认值为0表示无限制。有关更多详细信息,请参阅expansion_limit。
在极少数情况下,Manticore内置的查询分析器可能无法正确理解查询并确定是否应使用docid索引、二级索引或列扫描。要覆盖查询优化器的决策,您可以在查询中使用以下提示:
/*+ DocidIndex(id) */强制使用docid索引,/*+ NO_DocidIndex(id) */告诉优化器忽略它/*+ SecondaryIndex(<attr_name1>[, <attr_nameN>]) */强制使用二级索引(如果可用),/*+ NO_SecondaryIndex(id) */告诉优化器忽略它/*+ ColumnarScan(<attr_name1>[, <attr_nameN>]) */强制使用列扫描(如果属性是列式的),/*+ NO_ColumnarScan(id) */告诉优化器忽略它
请注意,当使用过滤器执行全文查询时,查询优化器会决定是对全文树的结果与过滤器结果进行交集运算,还是采用标准的先匹配后过滤方法。指定任何提示都将强制守护进程使用对全文树结果与过滤器结果进行交集运算的代码路径。
有关查询优化器工作原理的更多信息,请参阅基于成本的优化器页面。
- SQL
SELECT * FROM students where age > 21 /*+ SecondaryIndex(age) */当使用MySQL/MariaDB客户端时,请确保包含--comments标志以在查询中启用提示。
- mysql
mysql -P9306 -h0 --comments高亮显示使您能够从包含匹配关键词的文档中获取高亮的文本片段(称为片段)。
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。
高亮是在所谓的 post limit 阶段执行的,这意味着片段生成被推迟,不仅直到整个最终结果集准备好,而且在应用 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"
]
}
]}
}
}- 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"
]
}
]}
}
}- 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;
| Name | Level | Type | Summary |
|---|---|---|---|
| max_lcs | query | int | 当前查询的最大可能LCS值 |
| bm25 | document | int | BM25(1.2, 0) 的快速估算值 |
| bm25a(k1, b) | document | int | 精确的BM25()值,支持配置K1、B常数和语法 |
| 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 | field | float | 匹配关键字的tf*idf之和 == 所有出现的idf之和 |
| min_hit_pos | field | int | 首次匹配出现位置,按词数,1起始 |
| min_best_span_pos | field | int | 首个最大LCS跨度位置,按词数,1起始 |
| exact_hit | field | bool | 查询是否与字段完全相等 |
| min_idf | field | float | 匹配关键字中最小的idf |
| max_idf | field | float | 匹配关键字中最大的idf |
| sum_idf | field | float | 匹配关键字的idf之和 |
| exact_order | field | bool | 是否所有查询关键字a)均匹配且b)顺序与查询中一致 |
| min_gaps | field | int | 匹配跨度中匹配关键字之间的最小间隙数 |
| lccs | field | int | 查询与文档之间的最长连续公共子序列,按词数计 |
| wlccs | field | float | 加权最长连续公共子序列,连续关键字跨度上的idf之和 |
| atc | field | float | 聚合词项接近度,log(1+匹配关键字最佳对的 idf1idf2pow(distance, -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(整数),用户指定的每字段权重(参考 SQL 中的 OPTION field_weights)。若未显式指定,权重默认为 1。 -
hit_count(整数),字段中匹配到的关键词出现次数。注意,单个关键词可出现多次。例如,若字段中 'hello' 出现了 3 次,'world' 出现了 5 次,hit_count为 8。 -
word_count(整数),字段中匹配到的不同关键词数量。例如,若字段中出现 'hello' 和 'world',word_count为 2,不论它们出现次数多少。 -
tf_idf(浮点数),字段中所有匹配关键词的 TF/IDF 之和。IDF 表示逆文档频率,是 0 到 1 之间的浮点值,描述关键词的频率(基本上,出现于每个索引文档的关键词 IDF 为 0,而只出现于单一文档的关键词 IDF 为 1)。TF 是术语频率,字段中匹配关键词出现次数。顺带一提,tf_idf实际是通过对所有匹配出现累加 IDF 计算的。按照构造方法,这等价于对所有匹配关键词累加 TF*IDF。 -
min_hit_pos(整数),第一个匹配关键词出现的位置,按单词计数。因此,这是一个相对低层次的“原始”因子,通常你会想在使用它进行排名之前对它进行调整。具体调整高度依赖你的数据和最终公式,但这里先给几个思路:(a)当
word_count<2时,可以简单忽略任何基于min_gaps的提升;(b)当
word_count>=2且min_gaps非平凡时,可以用某个“最坏情况”的常量限制它,而平凡值(即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(浮点数),聚合术语接近度(Aggregate Term Closeness)。一种基于邻近的度量,当文档包含更多且更紧密且更重要(稀有)的查询关键词组合时,该值增加。警告: 你应该在 OPTION
idf='plain,tfidf_unnormalized'下使用 ATC(参见下文);否则可能得到意外的结果。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 的原因,因为如果没有它,对数内部的表达式可能是负数。
更接近的关键词出现对ATC的贡献 远多 于更频繁的关键词。事实上,当关键词紧挨着时,距离=1且k=1;当它们之间只有一个词时,距离=2且k=0.297,两个词之间时,距离=3且k=0.146,依此类推。同时,IDF的衰减速度较慢。例如,在一个100万文档集合中,匹配10、100和1000个文档的关键词的IDF值分别为0.833、0.667和0.500。因此,一对两个相对罕见的关键词,每个出现10次,但中间有2个词,其pair_tc = 0.101,几乎无法超过一对一个出现100次和一个出现1000次的关键词,中间只有一个词,其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 可以避免这种情况。
Plain IDF在 [0, log(N)] 范围内变化,关键词永远不会被惩罚;而normalized 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=="
}