Manticore Search 中的表连接使您能够通过匹配相关列来组合两个表中的文档。此功能允许更复杂的查询和跨多个表的增强数据检索。
SELECT
select_expr [, select_expr] ...
FROM tbl_name
{INNER | LEFT} JOIN tbl2_name
ON join_condition
[...other select options]
join_condition: {
left_table.attr = right_table.attr
| left_table.json_attr.string_id = string(right_table.json_attr.string_id)
| left_table.json_attr.int_id = int(right_table.json_attr.int_id)
| [..右表属性上的过滤器]
}
有关选择选项的更多信息,请参阅SELECT部分。
当通过 JSON 属性中的值进行连接时,您需要使用 int() 或 string() 函数显式指定该值的类型。
SELECT ... ON left_table.json_attr.string_id = string(right_table.json_attr.string_id)
SELECT ... ON left_table.json_attr.int_id = int(right_table.json_attr.int_id)
POST /search
{
"table": "table_name",
"query": {
<optional full-text query against the left table>
},
"join": [
{
"type": "inner" | "left",
"table": "joined_table_name",
"query": {
<optional full-text query against the right table>
},
"on": [
{
"left": {
"table": "left_table_name",
"field": "field_name",
"type": "<common field's type when joining using json attributes>"
},
"operator": "eq",
"right": {
"table": "right_table_name",
"field": "field_name"
}
}
]
}
],
"options": {
...
}
}
on.type: {
int
| string
}
注意,left 操作数部分中有一个 type 字段,当使用 json 属性连接两个表时应使用。允许的值为 string 和 int。
Manticore Search 支持两种类型的连接:
- INNER JOIN:仅返回两个表中都有匹配的行。例如,该查询在
orders和customers表之间执行 INNER JOIN,仅包含具有匹配客户的订单。
- SQL
- JSON
SELECT product, customers.email, customers.name, customers.address
FROM orders
INNER JOIN customers
ON customers.id = orders.customer_id
WHERE MATCH('maple', customers)
ORDER BY customers.email ASC;POST /search
{
"table": "orders",
"join": [
{
"type": "inner",
"table": "customers",
"query": {
"query_string": "maple"
},
"on": [
{
"left": {
"table": "orders",
"field": "customer_id"
},
"operator": "eq",
"right": {
"table": "customers",
"field": "id"
}
}
]
}
],
"_source": ["product", "customers.email", "customers.name", "customers.address"],
"sort": [{"customers.email": "asc"}]
}+---------+-------------------+----------------+-------------------+
| product | customers.email | customers.name | customers.address |
+---------+-------------------+----------------+-------------------+
| Laptop | alice@example.com | Alice Johnson | 123 Maple St |
| Tablet | alice@example.com | Alice Johnson | 123 Maple St |
+---------+-------------------+----------------+-------------------+
2 rows in set (0.00 sec){
"took": 0,
"timed_out": false,
"hits": {
"total": 2,
"total_relation": "eq",
"hits": [
{
"_id": 1,
"_score": 1,
"_source": {
"product": "Laptop",
"customers.email": "alice@example.com",
"customers.name": "Alice Johnson",
"customers.address": "123 Maple St"
}
},
{
"_id": 3,
"_score": 1,
"_source": {
"product": "Tablet",
"customers.email": "alice@example.com",
"customers.name": "Alice Johnson",
"customers.address": "123 Maple St"
}
}
]
}
}- LEFT JOIN:返回左表中的所有行以及右表中匹配的行。如果没有匹配,则右表的列返回 NULL 值。例如,此查询使用 LEFT JOIN 检索所有客户及其对应的订单。如果不存在对应订单,则显示 NULL 值。结果按客户的电子邮件排序,仅选择客户的姓名和订单数量。
- SQL
- JSON
SELECT
name, orders.quantity
FROM customers
LEFT JOIN orders
ON orders.customer_id = customers.id
ORDER BY email ASC;POST /search
{
"table": "customers",
"_source": ["name", "orders.quantity"],
"join": [
{
"type": "left",
"table": "orders",
"on": [
{
"left": {
"table": "orders",
"field": "customer_id"
},
"operator": "eq",
"right": {
"table": "customers",
"field": "id"
}
}
]
}
],
"sort": [{"email": "asc"}]
}+---------------+-----------------+-------------------+
| name | orders.quantity | @int_attr_email |
+---------------+-----------------+-------------------+
| Alice Johnson | 1 | alice@example.com |
| Alice Johnson | 1 | alice@example.com |
| Bob Smith | 2 | bob@example.com |
| Carol White | 1 | carol@example.com |
| John Smith | NULL | john@example.com |
+---------------+-----------------+-------------------+
5 rows in set (0.00 sec){
"took": 0,
"timed_out": false,
"hits": {
"total": 5,
"total_relation": "eq",
"hits": [
{
"_id": 1,
"_score": 1,
"_source": {
"name": "Alice Johnson",
"address": "123 Maple St",
"email": "alice@example.com",
"orders.id": 3,
"orders.customer_id": 1,
"orders.quantity": 1,
"orders.order_date": "2023-01-03",
"orders.tags": [
101,
104
],
"orders.details": {
"price": 450,
"warranty": "1 year"
},
"orders.product": "Tablet"
}
},
{
"_id": 1,
"_score": 1,
"_source": {
"name": "Alice Johnson",
"address": "123 Maple St",
"email": "alice@example.com",
"orders.id": 1,
"orders.customer_id": 1,
"orders.quantity": 1,
"orders.order_date": "2023-01-01",
"orders.tags": [
101,
102
],
"orders.details": {
"price": 1200,
"warranty": "2 years"
},
"orders.product": "Laptop"
}
},
{
"_id": 2,
"_score": 1,
"_source": {
"name": "Bob Smith",
"address": "456 Oak St",
"email": "bob@example.com",
"orders.id": 2,
"orders.customer_id": 2,
"orders.quantity": 2,
"orders.order_date": "2023-01-02",
"orders.tags": [
103
],
"orders.details": {
"price": 800,
"warranty": "1 year"
},
"orders.product": "Phone"
}
},
{
"_id": 3,
"_score": 1,
"_source": {
"name": "Carol White",
"address": "789 Pine St",
"email": "carol@example.com",
"orders.id": 4,
"orders.customer_id": 3,
"orders.quantity": 1,
"orders.order_date": "2023-01-04",
"orders.tags": [
105
],
"orders.details": {
"price": 300,
"warranty": "1 year"
},
"orders.product": "Monitor"
}
},
{
"_id": 4,
"_score": 1,
"_source": {
"name": "John Smith",
"address": "15 Barclays St",
"email": "john@example.com",
"orders.id": 0,
"orders.customer_id": 0,
"orders.quantity": 0,
"orders.order_date": "",
"orders.tags": [],
"orders.details": null,
"orders.product": ""
}
}
]
}
}Manticore Search 表连接的强大功能之一是能够同时对左表和右表执行全文搜索。这允许您创建基于多个表中文本内容过滤的复杂查询。
您可以在 JOIN 查询中为每个表使用单独的 MATCH() 函数。查询基于两个表中的文本内容过滤结果。
- SQL
- JSON
SELECT t1.f, t2.f
FROM t1
LEFT JOIN t2 ON t1.id = t2.id
WHERE MATCH('hello', t1) AND MATCH('goodbye', t2);POST /search
{
"table": "t1",
"query": {
"query_string": "hello"
},
"join": [
{
"type": "left",
"table": "t2",
"query": {
"query_string": "goodbye"
},
"on": [
{
"left": {
"table": "t1",
"field": "id"
},
"operator": "eq",
"right": {
"table": "t2",
"field": "id"
}
}
]
}
],
"_source": ["f", "t2.f"]
}+-------------+---------------+
| f | t2.f |
+-------------+---------------+
| hello world | goodbye world |
+-------------+---------------+
1 row in set (0.00 sec){
"took": 1,
"timed_out": false,
"hits": {
"total": 1,
"total_relation": "eq",
"hits": [
{
"_id": 2,
"_score": 1680,
"t2._score": 1680,
"_source": {
"f": "hello world",
"t2.f": "goodbye world"
}
}
]
}
}在 JSON API 查询中,特定表的全文匹配结构与 SQL 不同:
主表查询:根级别的 "query" 字段应用于主表(在 "table" 中指定)。
连接表查询:每个连接定义可以包含其自己的 "query" 字段,专门应用于该连接表。
- JSON
POST /search
{
"table": "t1",
"query": {
"query_string": "hello"
},
"join": [
{
"type": "left",
"table": "t2",
"query": {
"match": {
"*": "goodbye"
}
},
"on": [
{
"left": {
"table": "t1",
"field": "id"
},
"operator": "eq",
"right": {
"table": "t2",
"field": "id"
}
}
]
}
]
}{
"took": 1,
"timed_out": false,
"hits": {
"total": 1,
"total_relation": "eq",
"hits": [
{
"_id": 1,
"_score": 1680,
"t2._score": 1680,
"_source": {
"f": "hello world",
"t2.id": 1,
"t2.f": "goodbye world"
}
}
]
}
}1. 仅主表查询:返回主表中所有匹配的行。对于未匹配的连接记录(LEFT JOIN),SQL 返回 NULL 值,而 JSON API 返回默认值(数字为 0,文本为空字符串)。
- SQL
- JSON
SELECT * FROM t1
LEFT JOIN t2 ON t1.id = t2.id
WHERE MATCH('database', t1);POST /search
{
"table": "t1",
"query": {
"query_string": "database"
},
"join": [
{
"type": "left",
"table": "t2",
"on": [
{
"left": {
"table": "t1",
"field": "id"
},
"operator": "eq",
"right": {
"table": "t2",
"field": "id"
}
}
]
}
]
}+------+-----------------+-------+------+
| id | f | t2.id | t2.f |
+------+-----------------+-------+------+
| 3 | database search | NULL | NULL |
+------+-----------------+-------+------+
1 row in set (0.00 sec){
"took": 0,
"timed_out": false,
"hits": {
"total": 1,
"total_relation": "eq",
"hits": [
{
"_id": 3,
"_score": 1680,
"t2._score": 0,
"_source": {
"f": "database search",
"t2.id": 0,
"t2.f": ""
}
}
]
}
}2. 连接表上的查询作为过滤器:当连接表有查询时,仅返回同时满足连接条件和查询条件的记录。
- JSON
POST /search
{
"table": "t1",
"query": {
"query_string": "database"
},
"join": [
{
"type": "left",
"table": "t2",
"query": {
"query_string": "nonexistent"
},
"on": [
{
"left": {
"table": "t1",
"field": "id"
},
"operator": "eq",
"right": {
"table": "t2",
"field": "id"
}
}
]
}
]
}{
"took": 0,
"timed_out": false,
"hits": {
"total": 0,
"total_relation": "eq",
"hits": []
}
}3. JOIN 类型影响过滤:INNER JOIN 需要同时满足连接和查询条件,而 LEFT JOIN 即使右表条件不满足,也返回匹配的左表行。
使用连接进行全文匹配时,请注意以下几点:
-
特定表匹配:
- SQL:每个
MATCH()函数应指定搜索的表:MATCH('term', table_name) - JSON:主表使用根级
"query",连接表使用各自连接定义内的"query"
- SQL:每个
-
查询语法灵活性:JSON API 支持全文查询的
"query_string"和"match"语法 -
性能影响:对两个表进行全文匹配可能影响查询性能,尤其是大数据集。请考虑使用适当的索引和批处理大小。
-
NULL/默认值处理:使用 LEFT JOIN 时,如果右表无匹配记录,查询优化器会根据性能决定先评估全文条件还是过滤条件。SQL 返回 NULL 值,JSON API 返回默认值(数字为 0,文本为空字符串)。
-
过滤行为:连接表上的查询作为过滤器——限制结果为同时满足连接和查询条件的记录。
-
全文操作符支持:JOIN 查询支持所有全文操作符,包括短语、邻近、字段搜索、NEAR、法定人数匹配和高级操作符。
-
评分计算:每个表维护自己的相关性评分,可通过 SQL 中的
table_name.weight()或 JSON 响应中的table_name._score访问。
基于前面的示例,让我们探索一个更高级的场景,将表连接与分面和跨多个表的全文匹配结合起来。这展示了 Manticore JOIN 功能在复杂过滤和聚合中的全部威力。
该查询演示了跨 customers 和 orders 两个表的全文匹配,结合范围过滤和分面搜索。它搜索名为 "Alice" 或 "Bob" 的客户及其包含 "laptop"、"phone" 或 "tablet" 且价格高于 500 美元的订单。结果按订单 ID 排序,并按保修条款进行分面。
- SQL
- JSON
SELECT orders.product, name, orders.details.price, orders.tags
FROM customers
LEFT JOIN orders ON customers.id = orders.customer_id
WHERE orders.details.price > 500
AND MATCH('laptop | phone | tablet', orders)
AND MATCH('alice | bob', customers)
ORDER BY orders.id ASC
FACET orders.details.warranty;POST /search
{
"table": "customers",
"query": {
"bool": {
"must": [
{
"range": {
"orders.details.price": {
"gt": 500
}
},
"query_string": "alice | bob"
]
}
},
"join": [
{
"type": "left",
"table": "orders",
"query": {
"query_string": "laptop | phone | tablet"
},
"on": [
{
"left": {
"table": "customers",
"field": "id"
},
"operator": "eq",
"right": {
"table": "orders",
"field": "customer_id"
}
}
]
}
],
"_source": ["orders.product", "name", "orders.details.price", "orders.tags"],
"sort": [{"orders.id": "asc"}],
"aggs": {
"warranty_facet": {
"terms": {
"field": "orders.details.warranty"
}
}
}
}+-----------------+---------------+----------------------+-------------+
| orders.product | name | orders.details.price | orders.tags |
+-----------------+---------------+----------------------+-------------+
| Laptop Computer | Alice Johnson | 1200 | 101,102 |
| Smart Phone | Bob Smith | 800 | 103 |
+-----------------+---------------+----------------------+-------------+
2 rows in set (0.00 sec)
+-------------------------+----------+
| orders.details.warranty | count(*) |
+-------------------------+----------+
| 2 years | 1 |
| 1 year | 1 |
+-------------------------+----------+
2 rows in set (0.00 sec){
"took": 0,
"timed_out": false,
"hits": {
"total": 3,
"total_relation": "eq",
"hits": [
{
"_id": 1,
"_score": 1,
"orders._score": 1565,
"_source": {
"name": "Alice Johnson",
"orders.tags": [
101,
102
],
"orders.product": "Laptop Computer"
}
},
{
"_id": 2,
"_score": 1,
"orders._score": 1565,
"_source": {
"name": "Bob Smith",
"orders.tags": [
103
],
"orders.product": "Smart Phone"
}
},
{
"_id": 1,
"_score": 1,
"orders._score": 1565,
"_source": {
"name": "Alice Johnson",
"orders.tags": [
101,
104
],
"orders.product": "Tablet Device"
}
}
]
},
"aggregations": {
"warranty_facet": {
"buckets": [
{
"key": "2 years",
"doc_count": 1
},
{
"key": "1 year",
"doc_count": 2
}
]
}
}
}可以为连接中的查询分别指定选项:左表和右表。语法为 SQL 查询中的 OPTION(<table_name>),以及 JSON 查询中 "options" 下的一个或多个子对象。
下面是如何为右表的全文查询指定不同字段权重的示例。要通过 SQL 获取匹配权重,请使用 <table_name>.weight() 表达式。
在 JSON 查询中,该权重表示为 <table_name>._score。
- SQL
- JSON
SELECT product, customers.email, customers.name, customers.address, customers.weight()
FROM orders
INNER JOIN customers
ON customers.id = orders.customer_id
WHERE MATCH('maple', customers)
OPTION(customers) field_weights=(address=1500);POST /search
{
"table": "orders",
"options": {
"customers": {
"field_weights": {
"address": 1500
}
}
},
"join": [
{
"type": "inner",
"table": "customers",
"query": {
"query_string": "maple"
},
"on": [
{
"left": {
"table": "orders",
"field": "customer_id"
},
"operator": "eq",
"right": {
"table": "customers",
"field": "id"
}
}
]
}
],
"_source": ["product", "customers.email", "customers.name", "customers.address"]
}+---------+-------------------+----------------+-------------------+--------------------+
| product | customers.email | customers.name | customers.address | customers.weight() |
+---------+-------------------+----------------+-------------------+--------------------+
| Laptop | alice@example.com | Alice Johnson | 123 Maple St | 1500680 |
| Tablet | alice@example.com | Alice Johnson | 123 Maple St | 1500680 |
+---------+-------------------+----------------+-------------------+--------------------+
2 rows in set (0.00 sec){
"took": 0,
"timed_out": false,
"hits": {
"total": 2,
"total_relation": "eq",
"hits": [
{
"_id": 1,
"_score": 1,
"customers._score": 15000680,
"_source": {
"product": "Laptop",
"customers.email": "alice@example.com",
"customers.name": "Alice Johnson",
"customers.address": "123 Maple St"
}
},
{
"_id": 3,
"_score": 1,
"customers._score": 15000680,
"_source": {
"product": "Tablet",
"customers.email": "alice@example.com",
"customers.name": "Alice Johnson",
"customers.address": "123 Maple St"
}
}
]
}
}执行表连接时,Manticore Search 会批量处理结果以优化性能和资源使用。工作原理如下:
-
批处理工作原理:
- 首先执行左表的查询,并将结果累积到一个批次中。
- 然后将该批次用作右表查询的输入,右表查询作为单次操作执行。
- 这种方法减少了发送到右表的查询次数,提高了效率。
-
配置批次大小:
- 可以使用
join_batch_size搜索选项调整批次大小。 - 也可以在配置文件的
searchd部分通过 join_batch_size 进行配置。 - 默认批次大小为
1000,您可以根据使用场景增大或减小。 - 设置
join_batch_size=0可完全禁用批处理,这在调试或特定场景下可能有用。
- 可以使用
-
性能考虑:
- 较大的批次大小可以通过减少右表查询次数来提升性能。
- 但较大的批次可能会消耗更多内存,尤其是对于复杂查询或大数据集。
- 通过尝试不同批次大小,找到性能和资源使用的最佳平衡点。
为了进一步优化连接操作,Manticore Search 对右表执行的查询采用缓存机制。您需要了解以下内容:
-
缓存工作原理:
- 右表的每个查询由
JOIN ON条件定义。 - 如果多个查询中重复相同的
JOIN ON条件,结果会被缓存并重用。 - 这避免了冗余查询,加快了后续连接操作。
- 右表的每个查询由
-
配置缓存大小:
- 可以通过配置文件
searchd部分的 join_cache_size 选项配置连接缓存大小。 - 默认缓存大小为
20MB,您可以根据工作负载和可用内存调整。 - 设置
join_cache_size=0可完全禁用缓存。
- 可以通过配置文件
-
内存考虑:
- 每个线程维护自己的缓存,因此总内存使用取决于线程数和缓存大小。
- 确保服务器有足够内存以容纳缓存,尤其是在高并发环境下。
仅包含本地表的分布式表支持作为连接查询的左侧和右侧表。然而,包含远程表的分布式表不被支持。
在 Manticore Search 中使用 JOIN 时,请注意以下几点:
-
字段选择:在 JOIN 中选择两个表的字段时,不要为左表字段加前缀,但要为右表字段加前缀。例如:
SELECT field_name, right_table.field_name FROM ... -
JOIN 条件:始终在 JOIN 条件中显式指定表名:
JOIN ON table_name.some_field = another_table_name.some_field -
带 JOIN 的表达式:当使用结合两个连接表字段的表达式时,为表达式结果设置别名:
SELECT *, (nums2.n + 3) AS x, x * n FROM nums LEFT JOIN nums2 ON nums2.id = nums.num2_id -
基于别名表达式的过滤:不能在 WHERE 子句中使用涉及两个表字段的表达式别名。
-
JSON 属性:连接 JSON 属性时,必须显式将值转换为适当类型:
-- 正确: SELECT * FROM t1 LEFT JOIN t2 ON int(t1.json_attr.id) = t2.json_attr.id -- 错误: SELECT * FROM t1 LEFT JOIN t2 ON t1.json_attr.id = t2.json_attr.id -
NULL 处理:可以对连接字段使用 IS NULL 和 IS NOT NULL 条件:
SELECT * FROM t1 LEFT JOIN t2 ON t1.id = t2.id WHERE t2.name IS NULL SELECT * FROM t1 LEFT JOIN t2 ON t1.id = t2.id WHERE t2.name IS NOT NULL -
使用带 MVA 的 ANY:在 JOIN 中使用带多值属性的
ANY()函数时,为连接表的多值属性设置别名:SELECT *, t2.m AS alias FROM t LEFT JOIN t2 ON t.id = t2.t_id WHERE ANY(alias) IN (3, 5)
遵循这些指南,您可以有效地使用 Manticore Search 中的 JOIN 来组合多个索引的数据并执行复杂查询。
Manticore 通过 SQL 和 HTTP 支持使用任意算术表达式,包含属性值、内部属性(文档 ID 和相关性权重)、算术运算、多种内置函数和用户定义函数。以下是完整的参考列表,方便快速查阅。
+, -, *, /, %, DIV, MOD
支持标准算术运算符。涉及这些运算符的算术计算可以通过三种不同模式执行:
- 使用单精度 32 位 IEEE 754 浮点数(默认),
- 使用有符号 32 位整数,
- 使用有符号 64 位整数。
表达式解析器会自动切换到整数模式,如果没有运算结果为浮点数。否则,使用默认的浮点模式。例如,a+b 如果两个参数都是 32 位整数,则使用 32 位整数计算;如果两个参数都是整数但其中一个是 64 位,则使用 64 位整数计算;否则使用浮点数计算。但是,a/b 或 sqrt(a) 总是以浮点数计算,因为这些运算返回非整数结果。为避免这种情况,可以使用 IDIV(a,b) 或 DIV b 形式。此外,a*b 在参数为 32 位时不会自动提升为 64 位。要强制使用 64 位结果,请使用 BIGINT(),但请注意,如果存在非整数运算,BIGINT() 会被忽略。
<, > <=, >=, =, <>
比较运算符在条件为真时返回 1.0,否则返回 0.0。例如,当属性 a 等于属性 b 时,(a=b)+3 计算结果为 4;当 a 不等于 b 时,结果为 3。与 MySQL 不同,等值比较(即 = 和 <> 运算符)包含一个小的相等阈值(默认 1e-6)。如果比较值之间的差异在阈值内,则视为相等。
对于多值属性,BETWEEN 和 IN 运算符如果至少有一个值满足条件,则返回真(类似于 ANY())。IN 运算符不支持 JSON 属性。IS (NOT) NULL 运算符仅支持 JSON 属性。
AND, OR, NOT
布尔运算符(AND、OR、NOT)行为符合预期。它们是左结合的,并且优先级最低。NOT 的优先级高于 AND 和 OR,但仍低于其他运算符。AND 和 OR 具有相同优先级,建议在复杂表达式中使用括号以避免混淆。
&, |
这些运算符分别执行按位与和按位或。操作数必须是整数类型。
- ABS()
- ALL()
- ANY()
- ATAN2()
- BIGINT()
- BITDOT()
- BM25F()
- CEIL()
- CONCAT()
- CONTAINS()
- COS()
- CRC32()
- DATE_HISTOGRAM()
- DATE_RANGE()
- DAY()
- DOUBLE()
- EXP()
- FIBONACCI()
- FLOOR()
- GEODIST()
- GEOPOLY2D()
- GREATEST()
- HOUR()
- HISTOGRAM()
- IDIV()
- IF()
- IN()
- INDEXOF()
- INTEGER()
- INTERVAL()
- LAST_INSERT_ID()
- LEAST()
- LENGTH()
- LN()
- LOG10()
- LOG2()
- MAX()
- MIN()
- MINUTE()
- MIN_TOP_SORTVAL()
- MIN_TOP_WEIGHT()
- MONTH()
- NOW()
- PACKEDFACTORS()
- POLY2D()
- POW()
- RAND()
- RANGE()
- REGEX()
- REMAP()
- SECOND()
- SIN()
- SINT()
- SQRT()
- SUBSTRING_INDEX()
- TO_STRING()
- UINT()
- YEAR()
- YEARMONTH()
- YEARMONTHDAY()
- WEIGHT()
在 HTTP JSON 接口中,通过 script_fields 和 expressions 支持表达式。
{
"table": "test",
"query": {
"match_all": {}
}, "script_fields": {
"add_all": {
"script": {
"inline": "( gid * 10 ) | crc32(title)"
}
},
"title_len": {
"script": {
"inline": "crc32(title)"
}
}
}
}
在此示例中,创建了两个表达式:add_all 和 title_len。第一个表达式计算 ( gid * 10 ) | crc32(title) 并将结果存储在 add_all 属性中。第二个表达式计算 crc32(title) 并将结果存储在 title_len 属性中。
目前仅支持 inline 表达式。inline 属性的值(要计算的表达式)具有与 SQL 表达式相同的语法。
表达式名称可以用于过滤或排序。
- script_fields
{
"table":"movies_rt",
"script_fields":{
"cond1":{
"script":{
"inline":"actor_2_facebook_likes =296 OR movie_facebook_likes =37000"
}
},
"cond2":{
"script":{
"inline":"IF (IN (content_rating,'TV-PG','PG'),2, IF(IN(content_rating,'TV-14','PG-13'),1,0))"
}
}
},
"limit":10,
"sort":[
{
"cond2":"desc"
},
{
"actor_1_name":"asc"
},
{
"actor_2_name":"desc"
}
],
"profile":true,
"query":{
"bool":{
"must":[
{
"match":{
"*":"star"
}
},
{
"equals":{
"cond1":1
}
}
],
"must_not":[
{
"equals":{
"content_rating":"R"
}
}
]
}
}
}默认情况下,表达式值包含在结果集的 _source 数组中。如果源是选择性的(参见源选择),可以将表达式名称添加到请求中的 _source 参数中。注意,表达式名称必须是小写。
expressions 是 script_fields 的一种替代方案,语法更简单。示例请求添加了两个表达式,并将结果存储到 add_all 和 title_len 属性中。注意,表达式名称必须是小写。
- expressions
{
"table": "test",
"query": { "match_all": {} },
"expressions":
{
"add_all": "( gid * 10 ) | crc32(title)",
"title_len": "crc32(title)"
}
}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。
命名的整数列表(每字段用户权重,用于排名)。
示例:
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。
该设置用于控制和限制 RAM 使用,决定在搜索每个表时保留多少匹配。每个找到的匹配仍会被处理,但只保留最优的 N 个匹配在内存中,最终返回给客户端。例如,假设一个表中某查询有 2,000,000 个匹配。通常你不需要检索全部匹配,而是需要扫描所有匹配,但只选择“最佳”的 500 个(例如,基于相关性、价格或其他因素排序),并以每页 20 到 100 个匹配的形式展示给最终用户。仅跟踪最佳 500 个匹配比保留全部 2,000,000 个匹配、排序后再丢弃多余的匹配要高效得多。max_matches 控制这个“最佳 N”数量。
该参数显著影响每查询的 RAM 和 CPU 使用。一般接受的值为 1,000 到 10,000,但更高的限制应谨慎使用。随意将 max_matches 增加到 1,000,000 意味着 searchd 必须为每个查询分配并初始化一个包含一百万条目的匹配缓冲区。这必然会增加每查询的 RAM 使用,有时会明显影响性能。
更多关于 max_matches 行为影响的信息,请参阅 max_matches_increase_threshold。
整数。设置 max_matches 可增加的阈值。默认值为 16384。
当启用 pseudo_sharding 并检测到 groupby 属性的唯一值数量少于此阈值时,Manticore 可能会增加 max_matches 以提高 groupby 和/或聚合的准确性。当伪分片在多个线程中执行查询,或 RT 表在磁盘分片中进行并行搜索时,可能会出现准确性损失。
如果 groupby 属性的唯一值数量少于阈值,max_matches 将被设置为该数量。否则,使用默认的 max_matches。
如果查询选项中显式设置了 max_matches,此阈值无效。
请注意,如果该阈值设置过高,会导致内存消耗增加和整体性能下降。
您还可以使用accurate_aggregation选项强制执行保证的groupby/聚合准确模式。
设置最大搜索查询时间,单位为毫秒。必须是非负整数。默认值为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。默认情况下,每个查询都会自动生成一个新的不同的种子值。
整数。分布式重试次数。
整数。分布式重试延迟,单位为毫秒。
字符串。用于使用滚动分页方法进行结果分页的滚动令牌。
pq- 优先队列,默认设置kbuffer- 为已预排序数据提供更快的排序,例如按id排序的表数据 两种情况下的结果集相同;选择其中一个选项可能仅仅是提高(或降低)性能。
限制当前查询处理使用的最大线程数。默认 - 无限制(查询可以占用全局定义的所有线程)。 对于一批查询,该选项必须附加到批次中的第一个查询,然后在创建工作队列时应用,并对整个批次生效。此选项与选项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。
高亮是在所谓的“后限制”阶段执行的,这意味着摘要生成不仅推迟到整个最终结果集准备好之后,还推迟到应用 LIMIT 子句之后。例如,对于 LIMIT 20,10 子句,HIGHLIGHT() 函数最多调用 10 次。
有几个可选的高亮选项可用于微调摘要生成,这些选项在 SQL、HTTP 和 PHP 客户端中通用。
在关键字匹配前插入的字符串。此字符串中可以使用 %SNIPPET_ID% 宏。宏的第一次出现会被替换为当前摘要内递增的摘要编号。编号默认从 1 开始,但可以通过 start_snippet_id 选项覆盖。%SNIPPET_ID% 在每个新文档开始时重置。默认值为 <b>。
在关键字匹配后插入的字符串。默认值为 </b>。
摘要的最大大小,单位为符号(代码点)。默认值为 256。默认情况下此限制按字段应用,详见 limits_per_field。
限制结果中包含的最大单词数。请注意,此限制适用于所有单词,而不仅仅是要高亮的匹配关键字。例如,如果高亮 Mary,且选中的摘要为 Mary had a little lamb,则此摘要贡献了 5 个单词,而不仅仅是 1 个。默认值为 0(无限制)。默认情况下此限制按字段应用,详见 limits_per_field。
限制结果中包含的最大摘要数。默认值为 0(无限制)。默认情况下此限制按字段应用,详见 limits_per_field。
确定 limit、limit_words 和 limit_snippets 是作为每个被高亮文档字段的单独限制,还是作为整个文档的全局限制。将此选项设置为 0 表示一个文档的所有合并高亮结果必须在指定限制内。缺点是如果高亮引擎认为某些字段的摘要更相关,可能会在一个字段中高亮多个摘要,而另一个字段中没有摘要。默认值为 1(使用每字段限制)。
在每个匹配关键字块周围选择的单词数。默认值为 5。
确定是否额外按短语边界字符拆分摘要,这些边界字符在表设置中通过 phrase_boundary 指令配置。默认值为 0(不使用边界)。
指定是否按相关性(权重递减)排序提取的摘要,或按文档中出现顺序(位置递增)排序。默认值为 0(不使用权重排序)。
忽略长度限制,直到结果包含所有关键字。默认值为 0(不强制包含所有关键字)。
设置 %SNIPPET_ID% 宏的起始值(该宏在 before_match、after_match 字符串中被检测并展开)。默认值为 1。
定义HTML剥离模式设置。默认值为index,表示将使用表设置。其他值包括none和strip,无论表设置如何,强制跳过或应用剥离;以及retain,保留HTML标记并保护其不被高亮。retain模式只能在高亮完整文档时使用,因此要求不设置片段大小限制。允许的字符串值为none、strip、index和retain。
允许在当前字段无法生成片段时(无关键字匹配或无片段符合限制)返回空字符串作为高亮结果。默认情况下,将返回原始文本的开头而不是空字符串。默认值为0(不允许空结果)。
确保片段不会跨越句子、段落或区域边界(当与启用了相应索引设置的表一起使用时)。允许的值为sentence、paragraph和zone。
在每个片段前发出包含区域名称的HTML标签。默认值为0(不发出区域名称)。
确定是否强制生成片段,即使限制允许高亮整个文本。默认值为0(不强制生成片段)。
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- Javascript
- Java
- C#
- Rust
- TypeScript
- Go
SELECT HIGHLIGHT({limit=50}) FROM books WHERE MATCH('try|gets|down|said');POST /search
{
"table": "books",
"query": {"query_string": "try|gets|down|said"},
"highlight": { "limit":50 }
}$results = $index->search('try|gets|down|said')->highlight([],['limit'=>50])->get();
foreach($results as $doc)
{
echo 'Document: '.$doc->getId();
foreach($doc->getData() as $field=>$value)
{
echo $field.': '.$value;
}
foreach($doc->getHighlight() as $field=>$snippets)
{
echo "Highlight for ".$field.":\n";
foreach($snippets as $snippet)
{
echo $snippet."\n";
}
}
}res = searchApi.search({"table":"books","query":{"match":{"*":"try"}},"highlight":{"limit":50}})res = await searchApi.search({"table":"books","query":{"match":{"*":"try"}},"highlight":{"limit":50}})res = await searchApi.search({"table":"books","query":{"query_string":"try|gets|down|said"},"highlight":{"limit":50}});searchRequest = new SearchRequest();
searchRequest.setIndex("books");
query = new HashMap<String,Object>();
query.put("match",new HashMap<String,Object>(){{
put("*","try|gets|down|said");
}});
searchRequest.setQuery(query);
highlight = new HashMap<String,Object>(){{
put("limit",50);
}};
searchRequest.setHighlight(highlight);
searchResponse = searchApi.search(searchRequest);var searchRequest = new SearchRequest("books");
searchRequest.FulltextFilter = new MatchFilter("*", "try|gets|down|said");
var highlight = new Highlight();
highlight.Limit = 50;
searchRequest.Highlight = highlight;
var searchResponse = searchApi.Search(searchRequest);let match_filter = HashMap::new();
match_filter.insert("*".to_string(), "try|gets|down|said".to_string());
let query = SearchQuery {
match: Some(serde_json::json!(match_filter).into()),
..Default::default(),
};
let highlight = Highlight {
limit: Some(50),
..Default::default(),
};
let search_req = SearchRequest {
table: "books".to_string(),
query: Some(Box::new(query)),
highlight: serde_json::json!(highlight),
..Default::default(),
};res = await searchApi.search({
index: 'test',
query: { match: { *: 'Text } },
highlight: { limit: 2}
});matchClause := map[string]interface{} {"*": "Text 1"};
query := map[string]interface{} {"match": matchClause};
searchRequest.SetQuery(query);
highlight := manticoreclient.NewHighlight()
searchRequest.SetHighlight(highlight)
res, _, _ := apiClient.SearchAPI.Search(context.Background()).SearchRequest(*searchRequest).Execute()+---------------------------------------------------------------------------+
| highlight({limit=50}) |
+---------------------------------------------------------------------------+
| ... , "It <b>gets</b> infantile pleasure ... to knock it <b>down</b>." |
| Don`t <b>try</b> to compete in childishness, <b>said</b> Bliss. |
| ... a small room. Bander <b>said</b>, "Come, half-humans, I ... |
+---------------------------------------------------------------------------+
3 rows in set (0.00 sec){
"took":2,
"timed_out":false,
"hits":
{
"total":3,
"hits":
[
{
"_id": 3,
"_score":1602,
"_source":
{
"title":"Book three",
"content":"Trevize whispered, \"It gets infantile pleasure out of display. I`d love to knock it down.\""
},
"highlight":
{
"title":
[
"Book three"
],
"content":
[
", \"It <b>gets</b> infantile pleasure ",
" to knock it <b>down</b>.\""
]
}
},
{
"_id": 4,
"_score":1573,
"_source":
{
"title":"Book four",
"content":"Don`t try to compete in childishness, said Bliss."
},
"highlight":
{
"title":
[
"Book four"
],
"content":
[
"Don`t <b>try</b> to compete in childishness, <b>said</b> Bliss."
]
}
},
{
"_id": 2,
"_score":1521,
"_source":
{
"title":"Book two",
"content":"A door opened before them, revealing a small room. Bander said, \"Come, half-humans, I want to show you how we live.\""
},
"highlight":
{
"title":
[
"Book two"
],
"content":
[
" a small room. Bander <b>said</b>, \"Come, half-humans, I"
]
}
}
]
}
}Document: 3
title: Book three
content: Trevize whispered, "It gets infantile pleasure out of display. I`d love to knock it down."
Highlight for title:
- Book four
Highlight for content:
, "It <b>gets</b> infantile pleasure
to knock it <b>down</b>."
Document: 4
title: Book four
content: Don`t try to compete in childishness, said Bliss.
Highlight for title:
- Book four
Highlight for content:
Don`t <b>try</b> to compete in childishness, <b>said</b> Bliss.
Document: 2
title: Book two
content: A door opened before them, revealing a small room. Bander said, "Come, half-humans, I want to show you how we live.
Highlight for title:
- Book two
Highlight for content:
a small room. Bander <b>said</b>, \"Come, half-humans, I{'aggregations': None,
'hits': {'hits': [{u'_id': u'4',
u'_score': 1695,
u'_source': {u'content': u'Don`t try to compete in childishness, said Bliss.',
u'title': u'Book four'},
u'highlight': {u'content': [u'Don`t <b>try</b> to compete in childishness, said Bliss.'],
u'title': [u'Book four']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{'aggregations': None,
'hits': {'hits': [{u'_id': u'4',
u'_score': 1695,
u'_source': {u'content': u'Don`t try to compete in childishness, said Bliss.',
u'title': u'Book four'},
u'highlight': {u'content': [u'Don`t <b>try</b> to compete in childishness, said Bliss.'],
u'title': [u'Book four']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{"took":0,"timed_out":false,"hits":{"total":3,"hits":[{"_id": 3,"_score":1597,"_source":{"title":"Book three","content":"Trevize whispered, \"It gets infantile pleasure out of display. I`d love to knock it down.\""},"highlight":{"title":["Book three"],"content":[", \"It <b>gets</b> infantile pleasure "," to knock it <b>down</b>.\""]}},{"_id": 4,"_score":1563,"_source":{"title":"Book four","content":"Don`t try to compete in childishness, said Bliss."},"highlight":{"title":["Book four"],"content":["Don`t <b>try</b> to compete in childishness, <b>said</b> Bliss."]}},{"_id": 5,"_score":1514,"_source":{"title":"Books two","content":"A door opened before them, revealing a small room. Bander said, \"Come, half-humans, I want to show you how we live.\""},"highlight":{"title":["Books two"],"content":[" a small room. Bander <b>said</b>, \"Come, half-humans, I"]}}]}}class SearchResponse {
took: 0
timedOut: false
hits: class SearchResponseHits {
total: 3
maxScore: null
hits: [{_id=3, _score=1597, _source={title=Book three, content=Trevize whispered, "It gets infantile pleasure out of display. I`d love to knock it down."}, highlight={title=[Book three], content=[, "It <b>gets</b> infantile pleasure , to knock it <b>down</b>."]}}, {_id=4, _score=1563, _source={title=Book four, content=Don`t try to compete in childishness, said Bliss.}, highlight={title=[Book four], content=[Don`t <b>try</b> to compete in childishness, <b>said</b> Bliss.]}}, {_id=5, _score=1514, _source={title=Books two, content=A door opened before them, revealing a small room. Bander said, "Come, half-humans, I want to show you how we live."}, highlight={title=[Books two], content=[ a small room. Bander <b>said</b>, "Come, half-humans, I]}}]
aggregations: null
}
profile: null
}class SearchResponse {
took: 0
timedOut: false
hits: class SearchResponseHits {
total: 3
maxScore: null
hits: [{_id=3, _score=1597, _source={title=Book three, content=Trevize whispered, "It gets infantile pleasure out of display. I`d love to knock it down."}, highlight={title=[Book three], content=[, "It <b>gets</b> infantile pleasure , to knock it <b>down</b>."]}}, {_id=4, _score=1563, _source={title=Book four, content=Don`t try to compete in childishness, said Bliss.}, highlight={title=[Book four], content=[Don`t <b>try</b> to compete in childishness, <b>said</b> Bliss.]}}, {_id=5, _score=1514, _source={title=Books two, content=A door opened before them, revealing a small room. Bander said, "Come, half-humans, I want to show you how we live."}, highlight={title=[Books two], content=[ a small room. Bander <b>said</b>, "Come, half-humans, I]}}]
aggregations: null
}
profile: null
}class SearchResponse {
took: 0
timedOut: false
hits: class SearchResponseHits {
total: 3
maxScore: null
hits: [{_id=3, _score=1597, _source={title=Book three, content=Trevize whispered, "It gets infantile pleasure out of display. I`d love to knock it down."}, highlight={title=[Book three], content=[, "It <b>gets</b> infantile pleasure , to knock it <b>down</b>."]}}, {_id=4, _score=1563, _source={title=Book four, content=Don`t try to compete in childishness, said Bliss.}, highlight={title=[Book four], content=[Don`t <b>try</b> to compete in childishness, <b>said</b> Bliss.]}}, {_id=5, _score=1514, _source={title=Books two, content=A door opened before them, revealing a small room. Bander said, "Come, half-humans, I want to show you how we live."}, highlight={title=[Books two], content=[ a small room. Bander <b>said</b>, "Come, half-humans, I]}}]
aggregations: null
}
profile: null
}{
"took":0,
"timed_out":false,
"hits":
{
"total":2,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text 1</b>"
]
}
},
{
"_id": 2,
"_score":1480,
"_source":
{
"content":"Text 2",
"name":"Doc 2",
"cat":2
},
"highlight":
{
"content":
[
"<b>Text 2</b>"
]
}
}]
}
}{
"took":0,
"timed_out":false,
"hits":
{
"total":2,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text 1</b>"
]
}
},
{
"_id": 2,
"_score":1480,
"_source":
{
"content":"Text 2",
"name":"Doc 2",
"cat":2
},
"highlight":
{
"content":
[
"<b>Text 2</b>"
]
}
}]
}
}HIGHLIGHT()函数可用于高亮搜索结果。语法如下:
HIGHLIGHT([options], [field_list], [query] )
默认情况下,无需参数即可使用。
- SQL
SELECT HIGHLIGHT() FROM books WHERE MATCH('before');+-----------------------------------------------------------+
| highlight() |
+-----------------------------------------------------------+
| A door opened <b>before</b> them, revealing a small room. |
+-----------------------------------------------------------+
1 row in set (0.00 sec)HIGHLIGHT()从文档存储中检索所有可用的全文字段,并针对提供的查询进行高亮。查询中支持字段语法。字段文本由field_separator分隔,可在选项中修改。
- SQL
SELECT HIGHLIGHT() FROM books WHERE MATCH('@title one');+-----------------+
| highlight() |
+-----------------+
| Book <b>one</b> |
+-----------------+
1 row in set (0.00 sec)HIGHLIGHT()的可选第一个参数是选项列表。
- SQL
SELECT HIGHLIGHT({before_match='[match]',after_match='[/match]'}) FROM books WHERE MATCH('@title one');+------------------------------------------------------------+
| highlight({before_match='[match]',after_match='[/match]'}) |
+------------------------------------------------------------+
| Book [match]one[/match] |
+------------------------------------------------------------+
1 row in set (0.00 sec)可选的第二个参数是包含单个字段或逗号分隔字段列表的字符串。如果存在此参数,则仅从文档存储中获取并高亮指定字段。第二个参数为空字符串表示“获取所有可用字段”。
- SQL
SELECT HIGHLIGHT({},'title,content') FROM books WHERE MATCH('one|robots');+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| highlight({},'title,content') |
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Book <b>one</b> | They followed Bander. The <b>robots</b> remained at a polite distance, but their presence was a constantly felt threat. |
| Bander ushered all three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander gestured the other <b>robots</b> away and entered itself. The door closed behind it. |
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
2 rows in set (0.00 sec)或者,可以使用第二个参数指定字符串属性或字段名(不带引号)。在这种情况下,提供的字符串将针对查询进行高亮,但字段语法将被忽略。
- SQL
SELECT HIGHLIGHT({}, title) FROM books WHERE MATCH('one');+---------------------+
| highlight({},title) |
+---------------------+
| Book <b>one</b> |
| Book five |
+---------------------+
2 rows in set (0.00 sec)可选的第三个参数是查询。用于针对与搜索时不同的查询进行高亮。
- SQL
SELECT HIGHLIGHT({},'title', 'five') FROM books WHERE MATCH('one');+-------------------------------+
| highlight({},'title', 'five') |
+-------------------------------+
| Book one |
| Book <b>five</b> |
+-------------------------------+
2 rows in set (0.00 sec)虽然HIGHLIGHT()设计用于存储的全文字段和字符串属性,但也可用于高亮任意文本。请注意,如果查询包含任何字段搜索操作符(例如@title hello @body world),则在此情况下忽略它们的字段部分。
- SQL
SELECT HIGHLIGHT({},TO_STRING('some text to highlight'), 'highlight') FROM books WHERE MATCH('@title one');+----------------------------------------------------------------+
| highlight({},TO_STRING('some text to highlight'), 'highlight') |
+----------------------------------------------------------------+
| some text to <b>highlight</b> |
+----------------------------------------------------------------+
1 row in set (0.00 sec)某些选项仅在生成单个字符串结果(而非片段数组)时相关。这仅适用于SQL的HIGHLIGHT()函数:
插入片段之间的字符串。默认值为...。
插入字段之间的字符串。默认值为|。
另一种高亮文本的方法是使用CALL SNIPPETS语句。它大致复制了HIGHLIGHT()的功能,但不能使用内置文档存储。不过,它可以从文件加载源文本。
要通过HTTP在JSON查询中高亮全文搜索结果,字段内容必须存储在文档存储中(默认启用)。示例中,从文档存储中获取全文字段content和title,并针对query子句中指定的查询进行高亮。
高亮片段在hits数组的highlight属性中返回。
- JSON
- PHP
- Python
- Python-asyncio
- Javascript
- Java
- C#
- Rust
- TypeScript
- Go
POST /search
{
"table": "books",
"query": { "match": { "*": "one|robots" } },
"highlight":
{
"fields": ["content"]
}
}$index->setName('books');
$results = $index->search('one|robots')->highlight(['content'])->get();
foreach($results as $doc)
{
echo 'Document: '.$doc->getId()."\n";
foreach($doc->getData() as $field=>$value)
{
echo $field.' : '.$value."\n";
}
foreach($doc->getHighlight() as $field=>$snippets)
{
echo "Highlight for ".$field.":\n";
foreach($snippets as $snippet)
{
echo "- ".$snippet."\n";
}
}
}res = searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content"]}}))res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content"]}}))res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content"]}});searchRequest = new SearchRequest();
searchRequest.setIndex("books");
query = new HashMap<String,Object>();
query.put("match",new HashMap<String,Object>(){{
put("*","one|robots");
}});
searchRequest.setQuery(query);
highlight = new HashMap<String,Object>(){{
put("fields",new String[] {"content"});
}};
searchRequest.setHighlight(highlight);
searchResponse = searchApi.search(searchRequest);var searchRequest = new SearchRequest("books");
searchRequest.FulltextFilter = new MatchFilter("*", "one|robots");
var highlight = new Highlight();
highlight.Fieldnames = new List<string> {"content"};
searchRequest.Highlight = highlight;
var searchResponse = searchApi.Search(searchRequest);let match_filter = HashMap::new();
match_filter.insert("*".to_string(), "one|robots".to_string());
let query = SearchQuery {
match: Some(serde_json::json!(match_filter).into()),
..Default::default(),
};
let highlight_fields [String; 1] = ["content".to_string()];
let highlight = Highlight {
fields: Some(serde_json::json!(highlight_fields)),
..Default::default(),
};
let search_req = SearchRequest {
table: "books".to_string(),
query: Some(Box::new(query)),
highlight: serde_json::json!(highlight),
..Default::default(),
};res = await searchApi.search({
index: 'test',
query: {
match: {
*: 'Text 1|Text 9'
}
},
highlight: {}
});matchClause := map[string]interface{} {"*": "Text 1|Text 9"};
query := map[string]interface{} {"match": matchClause};
searchRequest.SetQuery(query);
highlight := manticoreclient.NewHighlight()
searchRequest.SetHighlight(highlight)
res, _, _ := apiClient.SearchAPI.Search(context.Background()).SearchRequest(*searchRequest).Execute(){
"took": 0,
"timed_out": false,
"hits": {
"total": 1,
"hits": [
{
"_id": 1,
"_score": 2788,
"_source": {
"title": "Books one",
"content": "They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. "
},
"highlight": {
"content": [
"They followed Bander. The <b>robots</b> remained at a polite distance, ",
" three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander",
" gestured the other <b>robots</b> away and entered itself. The"
]
}
}
]
}
}Document: 1
title : Books one
content : They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it.
Highlight for content:
- They followed Bander. The <b>robots</b> remained at a polite distance,
- three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander
- gestured the other <b>robots</b> away and entered itself. The{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u'They followed Bander. The <b>robots</b> remained at a polite distance, ',
u' three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander',
u' gestured the other <b>robots</b> away and entered itself. The']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u'They followed Bander. The <b>robots</b> remained at a polite distance, ',
u' three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander',
u' gestured the other <b>robots</b> away and entered itself. The']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{"took":0,"timed_out":false,"hits":{"total":1,"hits":[{"_id": 1,"_score":2788,"_source":{"title":"Books one","content":"They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. "},"highlight":{"content":["They followed Bander. The <b>robots</b> remained at a polite distance, "," three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander"," gestured the other <b>robots</b> away and entered itself. The"]}}]}}class SearchResponse {
took: 0
timedOut: false
hits: class SearchResponseHits {
total: 1
maxScore: null
hits: [{_id=1, _score=2788, _source={title=Books one, content=They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. }, highlight={title=[Books <b>one</b>], content=[They followed Bander. The <b>robots</b> remained at a polite distance, , three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander, gestured the other <b>robots</b> away and entered itself. The]}}]
aggregations: null
}
profile: null
}class SearchResponse {
took: 0
timedOut: false
hits: class SearchResponseHits {
total: 1
maxScore: null
hits: [{_id=1, _score=2788, _source={title=Books one, content=They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. }, highlight={title=[Books <b>one</b>], content=[They followed Bander. The <b>robots</b> remained at a polite distance, , three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander, gestured the other <b>robots</b> away and entered itself. The]}}]
aggregations: null
}
profile: null
}class SearchResponse {
took: 0
timedOut: false
hits: class SearchResponseHits {
total: 1
maxScore: null
hits: [{_id=1, _score=2788, _source={title=Books one, content=They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. }, highlight={title=[Books <b>one</b>], content=[They followed Bander. The <b>robots</b> remained at a polite distance, , three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander, gestured the other <b>robots</b> away and entered itself. The]}}]
aggregations: null
}
profile: null
}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text 1</b>"
]
}
]}
}
}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text 1</b>"
]
}
]}
}
}要突出显示所有可能的字段,请将一个空对象作为 highlight 属性传递。
- JSON
- PHP
- Python
- Python-asyncio
- Javascript
- Java
- C#
- Rust
- TypeScript
- Go
POST /search
{
"table": "books",
"query": { "match": { "*": "one|robots" } },
"highlight": {}
}$index->setName('books');
$results = $index->search('one|robots')->highlight()->get();
foreach($results as $doc)
{
echo 'Document: '.$doc->getId()."\n";
foreach($doc->getData() as $field=>$value)
{
echo $field.' : '.$value."\n";
}
foreach($doc->getHighlight() as $field=>$snippets)
{
echo "Highlight for ".$field.":\n";
foreach($snippets as $snippet)
{
echo "- ".$snippet."\n";
}
}
}res = searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{}})res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{}})res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{}});searchRequest = new SearchRequest();
searchRequest.setIndex("books");
query = new HashMap<String,Object>();
query.put("match",new HashMap<String,Object>(){{
put("*","one|robots");
}});
searchRequest.setQuery(query);
highlight = new HashMap<String,Object>(){{
}};
searchRequest.setHighlight(highlight);
searchResponse = searchApi.search(searchRequest);var searchRequest = new SearchRequest("books");
searchRequest.FulltextFilter = new MatchFilter("*", "one|robots");
var highlight = new Highlight();
searchRequest.Highlight = highlight;
var searchResponse = searchApi.Search(searchRequest);let match_filter = HashMap::new();
match_filter.insert("*".to_string(), "one|robots".to_string());
let query = SearchQuery {
match: Some(serde_json::json!(match_filter).into()),
..Default::default(),
};
let highlight = Highlight::new();
let search_req = SearchRequest {
table: "books".to_string(),
query: Some(Box::new(query)),
highlight: serde_json::json!(highlight),
..Default::default(),
};res = await searchApi.search({
index: 'test',
query: {
match: {
*: 'Text 1|Doc 1'
}
},
highlight: {}
});matchClause := map[string]interface{} {"*": "Text 1|Doc 1"};
query := map[string]interface{} {"match": matchClause};
searchRequest.SetQuery(query);
highlight := manticoreclient.NewHighlight()
searchRequest.SetHighlight(highlight)
res, _, _ := apiClient.SearchAPI.Search(context.Background()).SearchRequest(*searchRequest).Execute(){
"took": 0,
"timed_out": false,
"hits": {
"total": 1,
"hits": [
{
"_id": 1,
"_score": 2788,
"_source": {
"title": "Books one",
"content": "They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. "
},
"highlight": {
"title": [
"Books <b>one</b>"
],
"content": [
"They followed Bander. The <b>robots</b> remained at a polite distance, ",
" three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander",
" gestured the other <b>robots</b> away and entered itself. The"
]
}
}
]
}
}Document: 1
title : Books one
content : They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it.
Highlight for title:
- Books <b>one</b>
Highlight for content:
- They followed Bander. The <b>robots</b> remained at a polite distance,
- three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander
- gestured the other <b>robots</b> away and entered itself. The{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u'They followed Bander. The <b>robots</b> remained at a polite distance, ',
u' three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander',
u' gestured the other <b>robots</b> away and entered itself. The'],
u'title': [u'Books <b>one</b>']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u'They followed Bander. The <b>robots</b> remained at a polite distance, ',
u' three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander',
u' gestured the other <b>robots</b> away and entered itself. The'],
u'title': [u'Books <b>one</b>']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{"took":0,"timed_out":false,"hits":{"total":1,"hits":[{"_id": 1,"_score":2788,"_source":{"title":"Books one","content":"They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. "},"highlight":{"title":["Books <b>one</b>"],"content":["They followed Bander. The <b>robots</b> remained at a polite distance, "," three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander"," gestured the other <b>robots</b> away and entered itself. The"]}}]}}class SearchResponse {
took: 0
timedOut: false
hits: class SearchResponseHits {
total: 1
maxScore: null
hits: [{_id=1, _score=2788, _source={title=Books one, content=They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. }, highlight={title=[Books <b>one</b>], content=[They followed Bander. The <b>robots</b> remained at a polite distance, , three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander, gestured the other <b>robots</b> away and entered itself. The]}}]
aggregations: null
}
profile: null
}class SearchResponse {
took: 0
timedOut: false
hits: class SearchResponseHits {
total: 1
maxScore: null
hits: [{_id=1, _score=2788, _source={title=Books one, content=They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. }, highlight={title=[Books <b>one</b>], content=[They followed Bander. The <b>robots</b> remained at a polite distance, , three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander, gestured the other <b>robots</b> away and entered itself. The]}}]
aggregations: null
}
profile: null
}class SearchResponse {
took: 0
timedOut: false
hits: class SearchResponseHits {
total: 1
maxScore: null
hits: [{_id=1, _score=2788, _source={title=Books one, content=They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. }, highlight={title=[Books <b>one</b>], content=[They followed Bander. The <b>robots</b> remained at a polite distance, , three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander, gestured the other <b>robots</b> away and entered itself. The]}}]
aggregations: null
}
profile: null
}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text 1</b>"
],
"name":
[
"<b>Doc 1</b>"
]
}
]}
}
}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text 1</b>"
],
"name":
[
"<b>Doc 1</b>"
]
}
]}
}
}除了常见的高亮选项外,通过 HTTP 的 JSON 查询还提供了几个同义词:
fields 对象包含带有选项的属性名称。它也可以是字段名称的数组(不带任何选项)。
请注意,默认情况下,高亮尝试突出显示全文查询后的结果。在一般情况下,当您不指定要高亮的字段时,高亮基于您的全文查询。然而,如果您指定了要高亮的字段,则只有当全文查询匹配所选字段时才会高亮。
encoder 可以设置为 default 或 html。当设置为 html 时,它在高亮时保留 HTML 标记。这与 html_strip_mode=retain 选项的作用类似。
- JSON
- PHP
- Python
- Python-asyncio
- Javascript
- Java
- C#
- Rust
- TypeScript
- Go
POST /search
{
"table": "books",
"query": { "match": { "content": "one|robots" } },
"highlight":
{
"fields": [ "content"],
"highlight_query": { "match": { "*":"polite distance" } }
}
}$index->setName('books');
$bool = new \Manticoresearch\Query\BoolQuery();
$bool->must(new \Manticoresearch\Query\Match(['query' => 'one|robots'], 'content'));
$results = $index->search($bool)->highlight(['content'],['highlight_query'=>['match'=>['*'=>'polite distance']]])->get();
foreach($results as $doc)
{
echo 'Document: '.$doc->getId()."\n";
foreach($doc->getData() as $field=>$value)
{
echo $field.' : '.$value."\n";
}
foreach($doc->getHighlight() as $field=>$snippets)
{
echo "Highlight for ".$field.":\n";
foreach($snippets as $snippet)
{
echo "- ".$snippet."\n";
}
}
}res = searchApi.search({"table":"books","query":{"match":{"content":"one|robots"}},"highlight":{"fields":["content"],"highlight_query":{"match":{"*":"polite distance"}}}})res = await searchApi.search({"table":"books","query":{"match":{"content":"one|robots"}},"highlight":{"fields":["content"],"highlight_query":{"match":{"*":"polite distance"}}}})res = await searchApi.search({"table":"books","query":{"match":{"content":"one|robots"}},"highlight":{"fields":["content"],"highlight_query":{"match":{"*":"polite distance"}}}});searchRequest = new SearchRequest();
searchRequest.setIndex("books");
query = new HashMap<String,Object>();
query.put("match",new HashMap<String,Object>(){{
put("*","one|robots");
}});
searchRequest.setQuery(query);
highlight = new HashMap<String,Object>(){{
put("fields",new String[] {"content","title"});
put("highlight_query",
new HashMap<String,Object>(){{
put("match", new HashMap<String,Object>(){{
put("*","polite distance");
}});
}});
}};
searchRequest.setHighlight(highlight);
searchResponse = searchApi.search(searchRequest);var searchRequest = new SearchRequest("books");
searchRequest.FulltextFilter = new MatchFilter("*", "one|robots");
var highlight = new Highlight();
highlight.Fieldnames = new List<string> {"content", "title"};
Dictionary<string, Object> match = new Dictionary<string, Object>();
match.Add("*", "polite distance");
Dictionary<string, Object> highlightQuery = new Dictionary<string, Object>();
highlightQuery.Add("match", match);
highlight.HighlightQuery = highlightQuery;
searchRequest.Highlight = highlight;
var searchResponse = searchApi.Search(searchRequest);let match_filter = HashMap::new();
match_filter.insert("*".to_string(), "one|robots".to_string());
let query = SearchQuery {
match: Some(serde_json::json!(match_filter).into()),
..Default::default(),
};
let mut highlight_match_filter = HashMap::new();
highlight_match_filter.insert("*".to_string(), "polite distance".to_string());
let highlight_query = QueryFilter {
r#match: Some(serde_json::json!(highlight_match_filter)),
..Default::default(),
};
let highlight_fields [String; 2] = ["content".to_string(), "title".to_string()];
let highlight = Highlight {
fields: Some(serde_json::json!(highlight_fields)),
highlight_query: Some(Box::new(highlight_query)),
..Default::default(),
};
let search_req = SearchRequest {
table: "books".to_string(),
query: Some(Box::new(query)),
highlight: serde_json::json!(highlight),
..Default::default(),
};
let search_res = search_api.search(search_req).await;res = await searchApi.search({
index: 'test',
query: {
match: {
*: 'Text 1'
}
},
highlight: {
fields: ['content'],
highlight_query: {
match: {*: 'Text'}
}
}
});matchClause := map[string]interface{} {"*": "Text 1"};
query := map[string]interface{} {"match": matchClause};
searchRequest.SetQuery(query);
highlight := manticoreclient.NewHighlight()
highlightField := manticoreclient.NetHighlightField("content")
highlightFields := []interface{} { highlightField }
highlight.SetFields(highlightFields)
queryMatchClause := map[string]interface{} {"*": "Text"};
highlightQuery := map[string]interface{} {"match": queryMatchClause};
highlight.SetHighlightQuery(highlightQuery)
searchRequest.SetHighlight(highlight)
res, _, _ := apiClient.SearchAPI.Search(context.Background()).SearchRequest(*searchRequest).Execute(){'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 1788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u'. The robots remained at a <b>polite distance</b>, but their presence was a']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 1788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u'. The robots remained at a <b>polite distance</b>, but their presence was a']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{"took":0,"timed_out":false,"hits":{"total":1,"hits":[{"_id": 1,"_score":1788,"_source":{"title":"Books one","content":"They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. "},"highlight":{"content":[". The robots remained at a <b>polite distance</b>, but their presence was a"]}}]}}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text</b> 1"
]
}
]}
}
}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text</b> 1"
]
}
]}
}
}pre_tags 和 post_tags 设置高亮文本片段的起始和结束标签。它们的功能类似于 before_match 和 after_match 选项。这些是可选的,默认值分别为 <b> 和 </b>。
- JSON
- PHP
- Python
- Python-asyncio
- Javascript
- Java
- C#
- Rust
- TypeScript
- Go
POST /search
{
"table": "books",
"query": { "match": { "*": "one|robots" } },
"highlight":
{
"fields": [ "content", "title" ],
"pre_tags": "before_",
"post_tags": "_after"
}
}$index->setName('books');
$bool = new \Manticoresearch\Query\BoolQuery();
$bool->must(new \Manticoresearch\Query\Match(['query' => 'one|robots'], '*'));
$results = $index->search($bool)->highlight(['content','title'],['pre_tags'=>'before_','post_tags'=>'_after'])->get();
foreach($results as $doc)
{
echo 'Document: '.$doc->getId()."\n";
foreach($doc->getData() as $field=>$value)
{
echo $field.' : '.$value."\n";
}
foreach($doc->getHighlight() as $field=>$snippets)
{
echo "Highlight for ".$field.":\n";
foreach($snippets as $snippet)
{
echo "- ".$snippet."\n";
}
}
}res = searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content","title"],"pre_tags":"before_","post_tags":"_after"}})res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content","title"],"pre_tags":"before_","post_tags":"_after"}})res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content","title"],"pre_tags":"before_","post_tags":"_after"}});searchRequest = new SearchRequest();
searchRequest.setIndex("books");
query = new HashMap<String,Object>();
query.put("match",new HashMap<String,Object>(){{
put("*","one|robots");
}});
searchRequest.setQuery(query);
highlight = new HashMap<String,Object>(){{
put("fields",new String[] {"content","title"});
put("pre_tags","before_");
put("post_tags","_after");
}};
searchRequest.setHighlight(highlight);
searchResponse = searchApi.search(searchRequest);var searchRequest = new SearchRequest("books");
searchRequest.FulltextFilter = new MatchFilter("*", "one|robots");
var highlight = new Highlight();
highlight.Fieldnames = new List<string> {"content", "title"};
highlight.PreTags = "before_";
highlight.PostTags = "_after";
searchRequest.Highlight = highlight;
var searchResponse = searchApi.Search(searchRequest);let match_filter = HashMap::new();
match_filter.insert("*".to_string(), "one|robots".to_string());
let query = SearchQuery {
match: Some(serde_json::json!(match_filter).into()),
..Default::default(),
};
let highlight_fields [String; 2] = ["content".to_string(), "title".to_string()];
let highlight = Highlight {
fields: Some(serde_json::json!(highlight_fields)),
pre_tags: Some("before_".to_string()),
post_tags: Some("_after".to_string()),
..Default::default(),
};
let search_req = SearchRequest {
table: "books".to_string(),
query: Some(Box::new(query)),
highlight: serde_json::json!(highlight),
..Default::default(),
};
let search_res = search_api.search(search_req).await;res = await searchApi.search({
index: 'test',
query: {
match: {
*: 'Text 1'
}
},
highlight: {
pre_tags: 'before_',
post_tags: '_after'
}
});matchClause := map[string]interface{} {"*": "Text 1"}
query := map[string]interface{} {"match": matchClause}
searchRequest.SetQuery(query)
highlight := manticoreclient.NewHighlight()
highlight.SetPreTags("before_")
highlight.SetPostTags("_after")
searchRequest.SetHighlight(highlight)
res, _, _ := apiClient.SearchAPI.Search(context.Background()).SearchRequest(*searchRequest).Execute()Document: 1
title : Books one
content : They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it.
Highlight for content:
- They followed Bander. The before_robots_after remained at a polite distance,
- three into the room. before_One_after of the before_robots_after followed as well. Bander
- gestured the other before_robots_after away and entered itself. The
Highlight for title:
- Books before_one_after{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u'They followed Bander. The before_robots_after remained at a polite distance, ',
u' three into the room. before_One_after of the before_robots_after followed as well. Bander',
u' gestured the other before_robots_after away and entered itself. The'],
u'title': [u'Books before_one_after']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u'They followed Bander. The before_robots_after remained at a polite distance, ',
u' three into the room. before_One_after of the before_robots_after followed as well. Bander',
u' gestured the other before_robots_after away and entered itself. The'],
u'title': [u'Books before_one_after']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{"took":0,"timed_out":false,"hits":{"total":1,"hits":[{"_id": 1,"_score":2788,"_source":{"title":"Books one","content":"They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. "},"highlight":{"content":["They followed Bander. The before_robots_after remained at a polite distance, "," three into the room. before_One_after of the before_robots_after followed as well. Bander"," gestured the other before_robots_after away and entered itself. The"],"title":["Books before_one_after"]}}]}}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"before_Text 1_after"
]
}
]}
}
}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"before_Text 1_after"
]
}
]}
}
}no_match_size 的功能类似于 allow_empty 选项。如果设置为 0,则相当于 allow_empty=1,允许在无法生成片段时返回空字符串作为高亮结果。否则,将返回字段的开头。这是可选的,默认值为 1。
- JSON
- PHP
- Python
- Python-asyncio
- Javascript
- Java
- C#
- Rust
- TypeScript
- Go
POST /search
{
"table": "books",
"query": { "match": { "*": "one|robots" } },
"highlight":
{
"fields": [ "content", "title" ],
"no_match_size": 0
}
}$index->setName('books');
$bool = new \Manticoresearch\Query\BoolQuery();
$bool->must(new \Manticoresearch\Query\Match(['query' => 'one|robots'], '*'));
$results = $index->search($bool)->highlight(['content','title'],['no_match_size'=>0])->get();
foreach($results as $doc)
{
echo 'Document: '.$doc->getId()."\n";
foreach($doc->getData() as $field=>$value)
{
echo $field.' : '.$value."\n";
}
foreach($doc->getHighlight() as $field=>$snippets)
{
echo "Highlight for ".$field.":\n";
foreach($snippets as $snippet)
{
echo "- ".$snippet."\n";
}
}
}res = searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content","title"],"no_match_size":0}})res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content","title"],"no_match_size":0}})res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content","title"],"no_match_size":0}});searchRequest = new SearchRequest();
searchRequest.setIndex("books");
query = new HashMap<String,Object>();
query.put("match",new HashMap<String,Object>(){{
put("*","one|robots");
}});
searchRequest.setQuery(query);
highlight = new HashMap<String,Object>(){{
put("fields",new String[] {"content","title"});
put("no_match_size",0);
}};
searchRequest.setHighlight(highlight);
searchResponse = searchApi.search(searchRequest);var searchRequest = new SearchRequest("books");
searchRequest.FulltextFilter = new MatchFilter("*", "one|robots");
var highlight = new Highlight();
highlight.Fieldnames = new List<string> {"content", "title"};
highlight.NoMatchSize = 0;
searchRequest.Highlight = highlight;
var searchResponse = searchApi.Search(searchRequest);let match_filter = HashMap::new();
match_filter.insert("*".to_string(), "one|robots".to_string());
let query = SearchQuery {
match: Some(serde_json::json!(match_filter).into()),
..Default::default(),
};
let highlight_fields [String; 2] = ["content".to_string(), "title".to_string()];
let highlight = Highlight {
fields: Some(serde_json::json!(highlight_fields)),
no_match_size: Some(NoMatchSize::Variant0),
..Default::default(),
};
let search_req = SearchRequest {
table: "books".to_string(),
query: Some(Box::new(query)),
highlight: serde_json::json!(highlight),
..Default::default(),
};
let search_res = search_api.search(search_req).await;res = await searchApi.search({
index: 'test',
query: {
match: {
*: 'Text 1'
}
},
highlight: {no_match_size: 0}
});matchClause := map[string]interface{} {"*": "Text 1"};
query := map[string]interface{} {"match": matchClause};
searchRequest.SetQuery(query);
highlight := manticoreclient.NewHighlight()
highlight.SetNoMatchSize(0)
searchRequest.SetHighlight(highlight)
res, _, _ := apiClient.SearchAPI.Search(context.Background()).SearchRequest(*searchRequest).Execute()Document: 1
title : Books one
content : They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it.
Highlight for content:
- They followed Bander. The <b>robots</b> remained at a polite distance,
- three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander
- gestured the other <b>robots</b> away and entered itself. The
Highlight for title:
- Books <b>one</b>{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u'They followed Bander. The <b>robots</b> remained at a polite distance, ',
u' three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander',
u' gestured the other <b>robots</b> away and entered itself. The'],
u'title': [u'Books <b>one</b>']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u'They followed Bander. The <b>robots</b> remained at a polite distance, ',
u' three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander',
u' gestured the other <b>robots</b> away and entered itself. The'],
u'title': [u'Books <b>one</b>']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{"took":0,"timed_out":false,"hits":{"total":1,"hits":[{"_id": 1,"_score":2788,"_source":{"title":"Books one","content":"They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. "},"highlight":{"content":["They followed Bander. The <b>robots</b> remained at a polite distance, "," three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander"," gestured the other <b>robots</b> away and entered itself. The"],"title":["Books <b>one</b>"]}}]}}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text 1</b>"
]
}
]}
}
}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text 1</b>"
]
}
]}
}
}- JSON
- PHP
- Python
- Python-asyncio
- Javascript
- Java
- C#
- Rust
- TypeScript
- Go
POST /search
{
"table": "books",
"query": { "match": { "*": "one|robots" } },
"highlight":
{
"fields": [ "content", "title" ],
"order": "score"
}
}$index->setName('books');
$bool = new \Manticoresearch\Query\BoolQuery();
$bool->must(new \Manticoresearch\Query\Match(['query' => 'one|robots'], '*'));
$results = $index->search($bool)->highlight(['content','title'],['order'=>"score"])->get();
foreach($results as $doc)
{
echo 'Document: '.$doc->getId()."\n";
foreach($doc->getData() as $field=>$value)
{
echo $field.' : '.$value."\n";
}
foreach($doc->getHighlight() as $field=>$snippets)
{
echo "Highlight for ".$field.":\n";
foreach($snippets as $snippet)
{
echo "- ".$snippet."\n";
}
}
}res = searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content","title"],"order":"score"}})res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content","title"],"order":"score"}})res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content","title"],"order":"score"}});searchRequest = new SearchRequest();
searchRequest.setIndex("books");
query = new HashMap<String,Object>();
query.put("match",new HashMap<String,Object>(){{
put("*","one|robots");
}});
searchRequest.setQuery(query);
highlight = new HashMap<String,Object>(){{
put("fields",new String[] {"content","title"});
put("order","score");
}};
searchRequest.setHighlight(highlight);
searchResponse = searchApi.search(searchRequest);var searchRequest = new SearchRequest("books");
searchRequest.FulltextFilter = new MatchFilter("*", "one|robots");
var highlight = new Highlight();
highlight.Fieldnames = new List<string> {"content", "title"};
highlight.Order = "score";
searchRequest.Highlight = highlight;
var searchResponse = searchApi.Search(searchRequest);let match_filter = HashMap::new();
match_filter.insert("*".to_string(), "one|robots".to_string());
let query = SearchQuery {
match: Some(serde_json::json!(match_filter).into()),
..Default::default(),
};
let highlight_fields [String; 2] = ["content".to_string(), "title".to_string()];
let highlight = Highlight {
fields: Some(serde_json::json!(highlight_fields)),
order: Some(Order::Score),
post_tags: Some("_after".to_string()),
..Default::default(),
};
let search_req = SearchRequest {
table: "books".to_string(),
query: Some(Box::new(query)),
highlight: serde_json::json!(highlight),
..Default::default(),
};
let search_res = search_api.search(search_req).await;res = await searchApi.search({
index: 'test',
query: {
match: {
*: 'Text 1'
}
},
highlight: { order: 'score' }
});matchClause := map[string]interface{} {"*": "Text 1"};
query := map[string]interface{} {"match": matchClause};
searchRequest.SetQuery(query);
highlight := manticoreclient.NewHighlight()
highlight.SetOrder("score")
searchRequest.SetHighlight(highlight)
res, _, _ := apiClient.SearchAPI.Search(context.Background()).SearchRequest(*searchRequest).Execute()Document: 1
title : Books one
content : They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it.
Highlight for content:
- three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander
- gestured the other <b>robots</b> away and entered itself. The
- They followed Bander. The <b>robots</b> remained at a polite distance,
Highlight for title:
- Books <b>one</b>{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u' three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander',
u' gestured the other <b>robots</b> away and entered itself. The',
u'They followed Bander. The <b>robots</b> remained at a polite distance, '],
u'title': [u'Books <b>one</b>']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u' three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander',
u' gestured the other <b>robots</b> away and entered itself. The',
u'They followed Bander. The <b>robots</b> remained at a polite distance, '],
u'title': [u'Books <b>one</b>']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{"took":0,"timed_out":false,"hits":{"total":1,"hits":[{"_id": 1,"_score":2788,"_source":{"title":"Books one","content":"They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. "},"highlight":{"content":[" three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander"," gestured the other <b>robots</b> away and entered itself. The","They followed Bander. The <b>robots</b> remained at a polite distance, "],"title":["Books <b>one</b>"]}}]}}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text 1</b>"
]
}
]}
}
}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text 1</b>"
]
}
]}
}
}- JSON
- PHP
- Python
- Python-asyncio
- Javascript
- Java
- C#
- Rust
- TypeScript
- Go
POST /search
{
"table": "books",
"query": { "match": { "*": "one|robots" } },
"highlight":
{
"fields": [ "content", "title" ],
"fragment_size": 100
}
}$index->setName('books');
$bool = new \Manticoresearch\Query\BoolQuery();
$bool->must(new \Manticoresearch\Query\Match(['query' => 'one|robots'], '*'));
$results = $index->search($bool)->highlight(['content','title'],['fragment_size'=>100])->get();
foreach($results as $doc)
{
echo 'Document: '.$doc->getId()."\n";
foreach($doc->getData() as $field=>$value)
{
echo $field.' : '.$value."\n";
}
foreach($doc->getHighlight() as $field=>$snippets)
{
echo "Highlight for ".$field.":\n";
foreach($snippets as $snippet)
{
echo "- ".$snippet."\n";
}
}
}res = searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content","title"],"fragment_size":100}})res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content","title"],"fragment_size":100}})res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content","title"],"fragment_size":100}});searchRequest = new SearchRequest();
searchRequest.setIndex("books");
query = new HashMap<String,Object>();
query.put("match",new HashMap<String,Object>(){{
put("*","one|robots");
}});
searchRequest.setQuery(query);
highlight = new HashMap<String,Object>(){{
put("fields",new String[] {"content","title"});
put("fragment_size",100);
}};
searchRequest.setHighlight(highlight);
searchResponse = searchApi.search(searchRequest);var searchRequest = new SearchRequest("books");
searchRequest.FulltextFilter = new MatchFilter("*", "one|robots");
var highlight = new Highlight();
highlight.Fieldnames = new List<string> {"content", "title"};
highlight.FragmentSize = 100;
searchRequest.Highlight = highlight;
var searchResponse = searchApi.Search(searchRequest);let match_filter = HashMap::new();
match_filter.insert("*".to_string(), "one|robots".to_string());
let query = SearchQuery {
match: Some(serde_json::json!(match_filter).into()),
..Default::default(),
};
let highlight_fields [String; 2] = ["content".to_string(), "title".to_string()];
let highlight = Highlight {
fields: Some(serde_json::json!(highlight_fields)),
fragment_size: Some(serde_json::json!(100)),
..Default::default(),
};
let search_req = SearchRequest {
table: "books".to_string(),
query: Some(Box::new(query)),
highlight: serde_json::json!(highlight),
..Default::default(),
};
let search_res = search_api.search(search_req).await;res = await searchApi.search({
index: 'test',
query: {
match: {
*: 'Text 1'
}
},
highlight: { fragment_size: 4}
});matchClause := map[string]interface{} {"*": "Text 1"};
query := map[string]interface{} {"match": matchClause};
searchRequest.SetQuery(query);
highlight := manticoreclient.NewHighlight()
highlight.SetFragmentSize(4)
searchRequest.SetHighlight(highlight)
res, _, _ := apiClient.SearchAPI.Search(context.Background()).SearchRequest(*searchRequest).Execute()Document: 1
title : Books one
content : They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it.
Highlight for content:
- the room. <b>One</b> of the <b>robots</b> followed as well
- Bander gestured the other <b>robots</b> away and entered
Highlight for title:
- Books <b>one</b>{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u' the room. <b>One</b> of the <b>robots</b> followed as well',
u'Bander gestured the other <b>robots</b> away and entered '],
u'title': [u'Books <b>one</b>']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u' the room. <b>One</b> of the <b>robots</b> followed as well',
u'Bander gestured the other <b>robots</b> away and entered '],
u'title': [u'Books <b>one</b>']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{"took":0,"timed_out":false,"hits":{"total":1,"hits":[{"_id": 1,"_score":2788,"_source":{"title":"Books one","content":"They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. "},"highlight":{"content":[" the room. <b>One</b> of the <b>robots</b> followed as well","Bander gestured the other <b>robots</b> away and entered "],"title":["Books <b>one</b>"]}}]}}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text</b>"
]
}
]}
}
}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text</b>"
]
}
]}
}
}number_of_fragments 限制结果中片段的最大数量。与 fragment_size 类似,它可以是全局设置或按字段设置。此项为可选,默认值为 0(无限制)。其工作方式类似于 limit_snippets 选项。
- JSON
- PHP
- Python
- Python-asyncio
- Javascript
- Java
- C#
- Rust
- TypeScript
- Go
POST /search
{
"table": "books",
"query": { "match": { "*": "one|robots" } },
"highlight":
{
"fields": [ "content", "title" ],
"number_of_fragments": 10
}
}$index->setName('books');
$bool = new \Manticoresearch\Query\BoolQuery();
$bool->must(new \Manticoresearch\Query\Match(['query' => 'one|robots'], '*'));
$results = $index->search($bool)->highlight(['content','title'],['number_of_fragments'=>10])->get();
foreach($results as $doc)
{
echo 'Document: '.$doc->getId()."\n";
foreach($doc->getData() as $field=>$value)
{
echo $field.' : '.$value."\n";
}
foreach($doc->getHighlight() as $field=>$snippets)
{
echo "Highlight for ".$field.":\n";
foreach($snippets as $snippet)
{
echo "- ".$snippet."\n";
}
}
}res =searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content","title"],"number_of_fragments":10}})res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content","title"],"number_of_fragments":10}})res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content","title"],"number_of_fragments":10}});searchRequest = new SearchRequest();
searchRequest.setIndex("books");
query = new HashMap<String,Object>();
query.put("match",new HashMap<String,Object>(){{
put("*","one|robots");
}});
searchRequest.setQuery(query);
highlight = new HashMap<String,Object>(){{
put("fields",new String[] {"content","title"});
put("number_of_fragments",10);
}};
searchRequest.setHighlight(highlight);
searchResponse = searchApi.search(searchRequest);var searchRequest = new SearchRequest("books");
searchRequest.FulltextFilter = new MatchFilter("*", "one|robots");
var highlight = new Highlight();
highlight.Fieldnames = new List<string> {"content", "title"};
highlight.NumberOfFragments = 10;
searchRequest.Highlight = highlight;
var searchResponse = searchApi.Search(searchRequest);let match_filter = HashMap::new();
match_filter.insert("*".to_string(), "one|robots".to_string());
let query = SearchQuery {
match: Some(serde_json::json!(match_filter).into()),
..Default::default(),
};
let highlight_fields [String; 2] = ["content".to_string(), "title".to_string()];
let highlight = Highlight {
fields: Some(serde_json::json!(highlight_fields)),
number_of_fragments: Some(serde_json::json!(10)),
..Default::default(),
};
let search_req = SearchRequest {
table: "books".to_string(),
query: Some(Box::new(query)),
highlight: serde_json::json!(highlight),
..Default::default(),
};
let search_res = search_api.search(search_req).await;res = await searchApi.search({
index: 'test',
query: {
match: {
*: 'Text 1'
}
},
highlight: { number_of_fragments: 1}
});matchClause := map[string]interface{} {"*": "Text 1"};
query := map[string]interface{} {"match": matchClause};
searchRequest.SetQuery(query);
highlight := manticoreclient.NewHighlight()
highlight.SetNumberOfFragments(1)
searchRequest.SetHighlight(highlight)
res, _, _ := apiClient.SearchAPI.Search(context.Background()).SearchRequest(*searchRequest).Execute()Document: 1
title : Books one
content : They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it.
Highlight for content:
- They followed Bander. The <b>robots</b> remained at a polite distance,
- three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander
- gestured the other <b>robots</b> away and entered itself. The
Highlight for title:
- Books <b>one</b>{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u'They followed Bander. The <b>robots</b> remained at a polite distance, ',
u' three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander',
u' gestured the other <b>robots</b> away and entered itself. The'],
u'title': [u'Books <b>one</b>']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u'They followed Bander. The <b>robots</b> remained at a polite distance, ',
u' three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander',
u' gestured the other <b>robots</b> away and entered itself. The'],
u'title': [u'Books <b>one</b>']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{"took":0,"timed_out":false,"hits":{"total":1,"hits":[{"_id": 1,"_score":2788,"_source":{"title":"Books one","content":"They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. "},"highlight":{"content":["They followed Bander. The <b>robots</b> remained at a polite distance, "," three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander"," gestured the other <b>robots</b> away and entered itself. The"],"title":["Books <b>one</b>"]}}]}}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text 1</b>"
]
}
]}
}
}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text 1</b>"
]
}
]}
}
}选项如 limit、limit_words 和 limit_snippets 可以设置为全局或按字段的选项。全局选项作为按字段的限制使用,除非被按字段选项覆盖。在示例中,title 字段使用默认限制设置进行高亮,而 content 字段使用不同的限制。
- JSON
- PHP
- Python
- Python-asyncio
- Javascript
- Java
- C#
- Rust
- TypeScript
- Go
POST /search
{
"table": "books",
"query": { "match": { "*": "one|robots" } },
"highlight":
{
"fields":
{
"title": {},
"content" : { "limit": 50 }
}
}
}$index->setName('books');
$bool = new \Manticoresearch\Query\BoolQuery();
$bool->must(new \Manticoresearch\Query\Match(['query' => 'one|robots'], '*'));
$results = $index->search($bool)->highlight(['content'=>['limit'=>50],'title'=>new \stdClass])->get();
foreach($results as $doc)
{
echo 'Document: '.$doc->getId()."\n";
foreach($doc->getData() as $field=>$value)
{
echo $field.' : '.$value."\n";
}
foreach($doc->getHighlight() as $field=>$snippets)
{
echo "Highlight for ".$field.":\n";
foreach($snippets as $snippet)
{
echo "- ".$snippet."\n";
}
}
}res =searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":{"title":{},"content":{"limit":50}}}})res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":{"title":{},"content":{"limit":50}}}})res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":{"title":{},"content":{"limit":50}}}});searchRequest = new SearchRequest();
searchRequest.setIndex("books");
query = new HashMap<String,Object>();
query.put("match",new HashMap<String,Object>(){{
put("*","one|robots");
}});
searchRequest.setQuery(query);
highlight = new HashMap<String,Object>(){{
put("fields",new HashMap<String,Object>(){{
put("title",new HashMap<String,Object>(){{}});
put("content",new HashMap<String,Object>(){{
put("limit",50);
}});
}}
);
}};
searchRequest.setHighlight(highlight);
searchResponse = searchApi.search(searchRequest);var searchRequest = new SearchRequest("books");
searchRequest.FulltextFilter = new MatchFilter("*", "one|robots");
var highlight = new Highlight();
var highlightField = new HighlightField("title");
highlightField.Limit = 50;
highlight.Fields = new List<Object> {highlightField};
searchRequest.Highlight = highlight;
var searchResponse = searchApi.Search(searchRequest);let match_filter = HashMap::new();
match_filter.insert("*".to_string(), "one|robots".to_string());
let query = SearchQuery {
match: Some(serde_json::json!(match_filter).into()),
..Default::default(),
};
let highlight_fields [String; 1] = ["title".to_string()];
let highlight = Highlight {
fields: Some(serde_json::json!(highlight_fields)),
limit: Some(serde_json::json!(50)),
..Default::default(),
};
let search_req = SearchRequest {
table: "books".to_string(),
query: Some(Box::new(query)),
highlight: serde_json::json!(highlight),
..Default::default(),
};
let search_res = search_api.search(search_req).await;res = await searchApi.search({
index: 'test',
query: {
match: {
*: 'Text 1'
}
},
highlight: {
fields: {
content: { limit:1 }
}
}
});matchClause := map[string]interface{} {"*": "Text 1"};
query := map[string]interface{} {"match": matchClause};
searchRequest.SetQuery(query);
highlight := manticoreclient.NewHighlight()
highlightField := manticoreclient.NetHighlightField("content")
highlightField.SetLimit(1);
highlightFields := []interface{} { highlightField }
highlight.SetFields(highlightFields)
searchRequest.SetHighlight(highlight)
res, _, _ := apiClient.SearchAPI.Search(context.Background()).SearchRequest(*searchRequest).Execute()Document: 1
title : Books one
content : They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it.
Highlight for content:
- into the room. <b>One</b> of the <b>robots</b> followed as well
Highlight for title:
- Books <b>one</b>{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u' into the room. <b>One</b> of the <b>robots</b> followed as well'],
u'title': [u'Books <b>one</b>']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u' into the room. <b>One</b> of the <b>robots</b> followed as well'],
u'title': [u'Books <b>one</b>']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{"took":0,"timed_out":false,"hits":{"total":1,"hits":[{"_id": 1,"_score":2788,"_source":{"title":"Books one","content":"They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. "},"highlight":{"title":["Books <b>one</b>"],"content":[" into the room. <b>One</b> of the <b>robots</b> followed as well"]}}]}}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text</b>"
]
}
]}
}
}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text</b>"
]
}
]}
}
}全局限制也可以通过指定 limits_per_field=0 来强制执行。设置此选项意味着所有合并的高亮结果必须在指定的限制内。缺点是,如果高亮引擎认为某些片段更相关,可能会在一个字段中获得多个高亮片段,而另一个字段则没有。
- JSON
- PHP
- Python
- Python-asyncio
- Javascript
- Java
- C#
- Rust
- TypeScript
- Go
POST /search
{
"table": "books",
"query": { "match": { "content": "and first" } },
"highlight":
{
"limits_per_field": false,
"fields":
{
"content" : { "limit": 50 }
}
}
}$index->setName('books');
$bool = new \Manticoresearch\Query\BoolQuery();
$bool->must(new \Manticoresearch\Query\Match(['query' => 'and first'], 'content'));
$results = $index->search($bool)->highlight(['content'=>['limit'=>50]],['limits_per_field'=>false])->get();
foreach($results as $doc)
{
echo 'Document: '.$doc->getId()."\n";
foreach($doc->getData() as $field=>$value)
{
echo $field.' : '.$value."\n";
}
foreach($doc->getHighlight() as $field=>$snippets)
{
echo "Highlight for ".$field.":\n";
foreach($snippets as $snippet)
{
echo "- ".$snippet."\n";
}
}
}res =searchApi.search({"table":"books","query":{"match":{"content":"and first"}},"highlight":{"fields":{"content":{"limit":50}},"limits_per_field":False}})res = await searchApi.search({"table":"books","query":{"match":{"content":"and first"}},"highlight":{"fields":{"content":{"limit":50}},"limits_per_field":False}})res = await searchApi.search({"table":"books","query":{"match":{"content":"and first"}},"highlight":{"fields":{"content":{"limit":50}},"limits_per_field":false}});searchRequest = new SearchRequest();
searchRequest.setIndex("books");
query = new HashMap<String,Object>();
query.put("match",new HashMap<String,Object>(){{
put("*","one|robots");
}});
searchRequest.setQuery(query);
highlight = new HashMap<String,Object>(){{
put("limits_per_field",0);
put("fields",new HashMap<String,Object>(){{
put("content",new HashMap<String,Object>(){{
put("limit",50);
}});
}}
);
}};
searchRequest.setHighlight(highlight);
searchResponse = searchApi.search(searchRequest);var searchRequest = new SearchRequest("books");
searchRequest.FulltextFilter = new MatchFilter("*", "one|robots");
var highlight = new Highlight();
highlight.LimitsPerField = 0;
var highlightField = new HighlightField("title");
highlight.Fields = new List<Object> {highlightField};
searchRequest.Highlight = highlight;
var searchResponse = searchApi.Search(searchRequest);let match_filter = HashMap::new();
match_filter.insert("*".to_string(), "one|robots".to_string());
let query = SearchQuery {
match: Some(serde_json::json!(match_filter).into()),
..Default::default(),
};
let highlight_fields [String; 1] = ["title".to_string()];
let highlight = Highlight {
fields: Some(serde_json::json!(highlight_fields)),
limit_per_field: Some(serde_json::json!(false)),
..Default::default(),
};
let search_req = SearchRequest {
table: "books".to_string(),
query: Some(Box::new(query)),
highlight: serde_json::json!(highlight),
..Default::default(),
};
let search_res = search_api.search(search_req).await;res = await searchApi.search({
index: 'test',
query: {
match: {
*: 'Text 1'
}
},
highlight: { limits_per_field: 0 }
});matchClause := map[string]interface{} {"*": "Text 1"};
query := map[string]interface{} {"match": matchClause};
searchRequest.SetQuery(query);
highlight := manticoreclient.NewHighlight()
highlight.SetLimitsPerField(0)
searchRequest.SetHighlight(highlight)
res, _, _ := apiClient.SearchAPI.Search(context.Background()).SearchRequest(*searchRequest).Execute()Document: 1
title : Books one
content : They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it.
Highlight for content:
- gestured the other robots away <b>and</b> entered itself. The door closed{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 1597,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u' gestured the other robots away <b>and</b> entered itself. The door closed']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 1597,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u' gestured the other robots away <b>and</b> entered itself. The door closed']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{"took":0,"timed_out":false,"hits":{"total":1,"hits":[{"_id": 1,"_score":1597,"_source":{"title":"Books one","content":"They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. "},"highlight":{"content":[" gestured the other robots away <b>and</b> entered itself. The door closed"]}}]}}CALL SNIPPETS 语句使用指定的表设置,从提供的数据和查询中构建片段。它无法访问内置的文档存储,因此建议使用 HIGHLIGHT() 函数。
语法如下:
CALL SNIPPETS(data, table, query[, opt_value AS opt_name[, ...]])
data 作为提取片段的来源。它可以是单个字符串,也可以是用花括号括起来的字符串列表。
table 指定提供文本处理设置以生成片段的表名。
query 是用于构建摘要的全文查询。
opt_value 和 opt_name 表示摘要生成选项。
- SQL
CALL SNIPPETS(('this is my document text','this is my another text'), 'forum', 'is text', 5 AS around, 200 AS limit);+----------------------------------------+
| snippet |
+----------------------------------------+
| this <b>is</b> my document <b>text</b> |
| this <b>is</b> my another <b>text</b> |
+----------------------------------------+
2 rows in set (0.02 sec)大多数选项与HIGHLIGHT() 函数中的相同。然而,有几个选项只能与 CALL SNIPPETS 一起使用。
以下选项可用于突出显示存储在单独文件中的文本:
启用此选项时,将第一个参数视为文件名,而不是用于提取摘要的数据。服务器端将加载指定的文件以获取数据。当启用此标志时,每个请求将使用最多 max_threads_per_query 个工作线程来并行处理工作。默认值为 0(无限制)。要在远程代理之间分配摘要生成,请在仅包含一个(!)本地代理和多个远程代理的分布式表中调用摘要生成。snippets_file_prefix 选项用于生成最终文件名。例如,当 searchd 配置为 snippets_file_prefix = /var/data_ 并且提供了文件名 text.txt 时,摘要将从 /var/data_text.txt 的内容生成。
此选项仅适用于带有远程代理的分布式摘要生成。摘要生成的源文件可以分布在不同的代理上,主服务器将合并所有无错误的结果。例如,如果分布式表的一个代理有 file1.txt,另一个代理有 file2.txt,并且你使用 CALL SNIPPETS 处理这两个文件,searchd 将合并代理结果,因此你将获得来自 file1.txt 和 file2.txt 的结果。默认值为 0。
如果同时启用了 load_files 选项,则如果任何文件在任何地方不可用,请求将返回错误。否则(如果未启用 load_files),对于所有缺失的文件将返回空字符串。searchd 不会将此标志传递给代理,因此如果文件不存在,代理不会生成严重错误。如果你想确保所有源文件都被加载,请将 load_files_scattered 和 load_files 都设置为 1。如果某些代理上缺少某些源文件不是关键问题,只需将 load_files_scattered 设置为 1。
- SQL
CALL SNIPPETS(('data/doc1.txt','data/doc2.txt'), 'forum', 'is text', 1 AS load_files);+----------------------------------------+
| snippet |
+----------------------------------------+
| this <b>is</b> my document <b>text</b> |
| this <b>is</b> my another <b>text</b> |
+----------------------------------------+
2 rows in set (0.02 sec)