WHERE 是一个 SQL 子句,既适用于全文匹配,也适用于额外的过滤。可用的操作符如下:
支持 MATCH('query'),并映射到全文查询。
支持 {col_name | expr_alias} [NOT] IN @uservar 条件语法。有关全局用户变量的描述,请参阅 SET 语法。
如果您更喜欢 HTTP JSON 接口,也可以应用过滤。它看起来可能比 SQL 更复杂,但推荐用于需要以编程方式准备查询的情况,例如当用户在您的应用中填写表单时。
下面是一个包含多个过滤器的 bool 查询示例。
此全文查询匹配所有在任意字段中包含 product 的文档。这些文档的价格必须大于或等于 500 (gte) 并且小于或等于 1000 (lte)。所有这些文档的修订版本必须不小于 15 (lt)。
- JSON
POST /search
{
"table": "test1",
"query": {
"bool": {
"must": [
{ "match" : { "_all" : "product" } },
{ "range": { "price": { "gte": 500, "lte": 1000 } } }
],
"must_not": {
"range": { "revision": { "lt": 15 } }
}
}
}
}bool 查询基于其他查询和/或过滤器的布尔组合来匹配文档。查询和过滤器必须在 must、should 或 must_not 部分指定,并且可以嵌套。
- JSON
POST /search
{
"table":"test1",
"query": {
"bool": {
"must": [
{ "match": {"_all":"keyword"} },
{ "range": { "revision": { "gte": 14 } } }
]
}
}
}在 must 部分指定的查询和过滤器是必须匹配文档的。如果指定了多个全文查询或过滤器,则必须全部匹配。这相当于 SQL 中的 AND 查询。注意,如果您想匹配数组(多值属性),可以多次指定该属性。只有当数组中找到所有查询值时,结果才为正,例如:
"must": [
{"equals" : { "product_codes": 5 }},
{"equals" : { "product_codes": 6 }}
]
另请注意,从性能角度来看,使用以下方式可能更好:
{"in" : { "all(product_codes)": [5,6] }}
(详见下文)。
在 should 部分指定的查询和过滤器应当匹配文档。如果在 must 或 must_not 中指定了某些查询,则忽略 should 查询。另一方面,如果除了 should 外没有其他查询,则至少有一个 should 查询必须匹配文档,文档才匹配 bool 查询。这相当于 OR 查询。注意,如果您想匹配数组(多值属性),可以多次指定该属性,例如:
"should": [
{"equals" : { "product_codes": 7 }},
{"equals" : { "product_codes": 8 }}
]
另请注意,从性能角度来看,使用以下方式可能更好:
{"in" : { "any(product_codes)": [7,8] }}
(详见下文)。
在 must_not 部分指定的查询和过滤器必须不匹配文档。如果在 must_not 下指定了多个查询,则只要没有一个匹配,文档即匹配。
- JSON
POST /search
{
"table":"t",
"query": {
"bool": {
"should": [
{
"equals": {
"b": 1
}
},
{
"equals": {
"b": 3
}
}
],
"must": [
{
"equals": {
"a": 1
}
}
],
"must_not": {
"equals": {
"b": 2
}
}
}
}
}bool 查询可以嵌套在另一个 bool 查询中,以构造更复杂的查询。要创建嵌套布尔查询,只需在 must、should 或 must_not 位置使用另一个 bool。以下查询:
a = 2 and (a = 10 or b = 0)
应在 JSON 中表示为:
- JSON
a = 2 and (a = 10 or b = 0)
POST /search
{
"table":"t",
"query": {
"bool": {
"must": [
{
"equals": {
"a": 2
}
},
{
"bool": {
"should": [
{
"equals": {
"a": 10
}
},
{
"equals": {
"b": 0
}
}
]
}
}
]
}
}
}更复杂的查询:
(a = 1 and b = 1) or (a = 10 and b = 2) or (b = 0)
- JSON
(a = 1 and b = 1) or (a = 10 and b = 2) or (b = 0)
POST /search
{
"table":"t",
"query": {
"bool": {
"should": [
{
"bool": {
"must": [
{
"equals": {
"a": 1
}
},
{
"equals": {
"b": 1
}
}
]
}
},
{
"bool": {
"must": [
{
"equals": {
"a": 10
}
},
{
"equals": {
"b": 2
}
}
]
}
},
{
"bool": {
"must": [
{
"equals": {
"b": 0
}
}
]
}
}
]
}
}
}SQL 格式的查询(query_string)也可以用于 bool 查询。
- JSON
POST /search
{
"table": "test1",
"query": {
"bool": {
"must": [
{ "query_string" : "product" },
{ "query_string" : "good" }
]
}
}
}等值过滤器是最简单的过滤器,适用于整数、浮点数和字符串属性。
- JSON
POST /search
{
"table":"test1",
"query": {
"equals": { "price": 500 }
}
}equals 过滤器可以应用于多值属性,您可以使用:
any(),如果属性中至少有一个值等于查询值,则结果为正;all(),如果属性只有一个值且等于查询值,则结果为正
- JSON
POST /search
{
"table":"test1",
"query": {
"equals": { "any(price)": 100 }
}
}集合过滤器检查属性值是否等于指定集合中的任意值。
集合过滤器支持整数、字符串和多值属性。
- JSON
POST /search
{
"table":"test1",
"query": {
"in": {
"price": [1,10,100]
}
}
}应用于多值属性时,您可以使用:
any()(等同于无函数),如果属性值与查询值之间至少有一个匹配,则结果为正;all(),如果所有属性值都在查询集合中,则结果为正
- JSON
POST /search
{
"table":"test1",
"query": {
"in": {
"all(price)": [1,10]
}
}
}范围过滤器匹配属性值在指定范围内的文档。
范围过滤器支持以下属性:
gte:大于或等于gt:大于lte:小于或等于lt:小于
- JSON
POST /search
{
"table":"test1",
"query": {
"range": {
"price": {
"gte": 500,
"lte": 1000
}
}
}
}geo_distance 过滤器用于过滤距离某地理位置在特定距离范围内的文档。
指定定位点,单位为度。距离从此点计算。
指定包含纬度和经度的属性。
指定距离计算函数。可以是adaptive或haversine。adaptive更快且更精确,更多细节请参见GEODIST()。可选,默认值为adaptive。
指定距离针脚位置的最大距离。所有在此距离内的文档都匹配。距离可以用各种单位指定。如果未指定单位,则距离默认为米。以下是支持的距离单位列表:
- 米:
m或meters - 千米:
km或kilometers - 厘米:
cm或centimeters - 毫米:
mm或millimeters - 英里:
mi或miles - 码:
yd或yards - 英尺:
ft或feet - 英寸:
in或inch - 海里:
NM、nmi或nauticalmiles
location_anchor 和 location_source 属性接受以下纬度/经度格式:
- 带有lat和lon键的对象:
{ "lat": "attr_lat", "lon": "attr_lon" } - 具有以下结构的字符串:
"attr_lat, attr_lon" - 按以下顺序排列纬度和经度的数组:
[attr_lon, attr_lat]
纬度和经度以度为单位指定。
- Basic example
- Advanced example
POST /search
{
"table":"test",
"query": {
"geo_distance": {
"location_anchor": {"lat":49, "lon":15},
"location_source": {"attr_lat, attr_lon"},
"distance_type": "adaptive",
"distance":"100 km"
}
}
}geo_distance 可以作为bool查询中的过滤器,与匹配或其他属性过滤器一起使用。
POST /search
{
"table": "geodemo",
"query": {
"bool": {
"must": [
{
"match": {
"*": "station"
}
},
{
"equals": {
"state_code": "ENG"
}
},
{
"geo_distance": {
"distance_type": "adaptive",
"location_anchor": {
"lat": 52.396,
"lon": -1.774
},
"location_source": "latitude_deg,longitude_deg",
"distance": "10000 m"
}
}
]
}
}
}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