Manticore 的数据类型可以分为两类:全文字段和属性。
Manticore 中的字段名必须遵循以下规则:
- 可以包含字母(a-z, A-Z)、数字(0-9)和连字符(-)
- 必须以字母开头
- 数字只能出现在字母之后
- 下划线(
_)是唯一允许的特殊字符
- 字段名不区分大小写
例如:
- 有效的字段名:
title、product_id、user_name_2
- 无效的字段名:
2title、-price、user@name
全文字段:
- 可以使用自然语言处理算法进行索引,因此可以搜索关键词
- 不能用于排序或分组
- 可以检索原始文档内容
- 原始文档内容可用于高亮显示
全文字段由数据类型 text 表示。所有其他数据类型都称为“属性”。
属性是与每个文档关联的非全文值,可用于在搜索期间执行非全文过滤、排序和分组。
通常,不仅需要根据匹配的文档 ID 及其排名来处理全文搜索结果,还需要基于许多其他每文档值进行处理。例如,可能需要按日期和相关性对新闻搜索结果进行排序,或在指定价格范围内搜索产品,或将博客搜索限制在选定用户发布的帖子中,或按月份对结果进行分组。为了实现这一点,Manticore 不仅支持全文字段,还允许为每个文档添加额外的属性。这些属性可用于过滤、排序或分组全文匹配项,或仅按属性进行搜索。
与全文字段不同,属性不进行全文索引。它们存储在表中,但无法像全文那样进行搜索。
属性的一个很好的例子是论坛帖子表。假设只有标题和内容字段需要进行全文搜索——但有时还需要将搜索限制在特定作者或子论坛(即,仅搜索那些具有特定 author_id 或 forum_id 值的行);或按 post_date 列对匹配项进行排序;或按 post_date 的月份对匹配的帖子进行分组并计算每组的匹配计数。
CREATE TABLE forum(title text, content text, author_id int, forum_id int, post_date timestamp);
POST /cli -d "CREATE TABLE forum(title text, content text, author_id int, forum_id int, post_date timestamp)"
$index = new \Manticoresearch\Index($client);
$index->setName('forum');
$index->create([
'title'=>['type'=>'text'],
'content'=>['type'=>'text'],
'author_id'=>['type'=>'int'],
'forum_id'=>['type'=>'int'],
'post_date'=>['type'=>'timestamp']
]);
utilsApi.sql('CREATE TABLE forum(title text, content text, author_id int, forum_id int, post_date timestamp)')
await utilsApi.sql('CREATE TABLE forum(title text, content text, author_id int, forum_id int, post_date timestamp)')
res = await utilsApi.sql('CREATE TABLE forum(title text, content text, author_id int, forum_id int, post_date timestamp)');
utilsApi.sql("CREATE TABLE forum(title text, content text, author_id int, forum_id int, post_date timestamp)");
utilsApi.Sql("CREATE TABLE forum(title text, content text, author_id int, forum_id int, post_date timestamp)");
utils_api.sql("CREATE TABLE forum(title text, content text, author_id int, forum_id int, post_date timestamp)", Some(true)).await;
table forum
{
type = rt
path = forum
# when configuring fields via config, they are indexed (and not stored) by default
rt_field = title
rt_field = content
# this option needs to be specified for the field to be stored
stored_fields = title, content
rt_attr_uint = author_id
rt_attr_uint = forum_id
rt_attr_timestamp = post_date
}
此示例展示了按 author_id、forum_id 过滤并按 post_date 排序的全文查询。
select * from forum where author_id=123 and forum_id in (1,3,7) order by post_date desc
POST /search
{
"table": "forum",
"query":
{
"match_all": {},
"bool":
{
"must":
[
{ "equals": { "author_id": 123 } },
{ "in": { "forum_id": [1,3,7] } }
]
}
},
"sort": [ { "post_date": "desc" } ]
}
$client->search([
'table' => 'forum',
'query' =>
[
'match_all' => [],
'bool' => [
'must' => [
'equals' => ['author_id' => 123],
'in' => [
'forum_id' => [
1,3,7
]
]
]
]
],
'sort' => [
['post_date' => 'desc']
]
]);
searchApi.search({"table":"forum","query":{"match_all":{},"bool":{"must":[{"equals":{"author_id":123}},{"in":{"forum_id":[1,3,7]}}]}},"sort":[{"post_date":"desc"}]})
await searchApi.search({"table":"forum","query":{"match_all":{},"bool":{"must":[{"equals":{"author_id":123}},{"in":{"forum_id":[1,3,7]}}]}},"sort":[{"post_date":"desc"}]})
res = await searchApi.search({"table":"forum","query":{"match_all":{},"bool":{"must":[{"equals":{"author_id":123}},{"in":{"forum_id":[1,3,7]}}]}},"sort":[{"post_date":"desc"}]});
HashMap<String,Object> filters = new HashMap<String,Object>(){{
put("must", new HashMap<String,Object>(){{
put("equals",new HashMap<String,Integer>(){{
put("author_id",123);
}});
put("in",
new HashMap<String,Object>(){{
put("forum_id",new int[] {1,3,7});
}});
}});
}};
Map<String,Object> query = new HashMap<String,Object>();
query.put("match_all",null);
query.put("bool",filters);
SearchRequest searchRequest = new SearchRequest();
searchRequest.setIndex("forum");
searchRequest.setQuery(query);
searchRequest.setSort(new ArrayList<Object>(){{
add(new HashMap<String,String>(){{ put("post_date","desc");}});
}});
SearchResponse searchResponse = searchApi.search(searchRequest);
object query = new { match_all=null };
var searchRequest = new SearchRequest("forum", query);
var boolFilter = new BoolFilter();
boolFilter.Must = new List<Object> {
new EqualsFilter("author_id", 123),
new InFilter("forum_id", new List<Object> {1,3,7})
};
searchRequest.AttrFilter = boolFilter;
searchRequest.Sort = new List<Object> { new SortOrder("post_date", SortOrder.OrderEnum.Desc) };
var searchResponse = searchApi.Search(searchRequest);
let query = SearchQuery::new();
let mut sort = HashMap::new();
sort.insert("post_date".to_string(), serde_json::json!("desc"));
let search_request = SearchRequest {
table: "forum".to_string(),
query: Some(Box::new(query)),
sort: serde_json::json!(sort)
..Default::default()
};
let search_res = search_api.search(search_req).await;
Manticore 支持两种类型的属性存储:
从它们的名称可以理解,它们以不同的方式存储数据。传统的 行式存储:
- 以未压缩的方式存储属性
- 同一文档的所有属性存储在彼此靠近的一行中
- 行按顺序存储
- 访问属性基本上是通过将行 ID 乘以步幅(单个向量的长度)并从计算出的内存位置获取请求的属性来实现。这提供了非常低的随机访问延迟。
- 属性必须位于内存中才能获得可接受的性能,否则由于存储的行式特性,Manticore 可能不得不从磁盘读取太多不需要的数据,这在许多情况下是次优的。
使用 列式存储:
- 每个属性都独立于所有其他属性存储在其单独的“列”中
- 存储被分成 65536 个条目的块
- 块以压缩形式存储。这通常允许仅存储几个不同的值,而不是像行式存储那样存储所有值。高压缩比允许更快地从磁盘读取,并大大降低内存需求
- 当数据被索引时,每个块独立选择存储方案。例如,如果一个块中的所有值都相同,则采用“常量”存储,整个块只存储一个值。如果每个块的唯一值少于 256 个,则采用“表”存储,存储值表的索引而不是值本身
- 如果明确请求的值不在块中,可以提前拒绝在块中的搜索。
列式存储旨在处理无法放入 RAM 的大数据量,因此建议如下:
- 如果您有足够的内存容纳所有属性,您将从行式存储中受益
- 否则,列式存储仍然可以为您提供不错的性能,同时内存占用低得多,这将允许您在表中存储更多的文档
传统的行式存储是默认方式,因此如果您希望所有内容都以行式存储,创建表时无需进行任何操作。
要启用列式存储,您需要:
- 在 CREATE TABLE 中指定
engine='columnar',使表的所有属性变为列式。然后,如果您希望保留特定属性为行式,需要在声明该属性时添加 engine='rowwise'。例如:create table tbl(title text, type int, price float engine='rowwise') engine='columnar'
- 在
CREATE TABLE 中为特定属性指定 engine='columnar',使其变为列式。例如:create table tbl(title text, type int, price float engine='columnar');
或
create table tbl(title text, type int, price float engine='columnar') engine='rowwise';
- 在 plain 模式 下,您需要在 columnar_attrs 中列出希望为列式的属性。
以下是 Manticore Search 支持的数据类型列表:
文档标识符是一个必需的属性,必须是唯一的 64 位无符号整数。创建表时可以显式指定文档 ID,但即使未指定,文档 ID 也始终会被启用。文档 ID 无法更新。
创建表时,您可以显式指定 ID,但无论您使用何种数据类型,其行为始终如上所述——以无符号 64 位存储,但以有符号 64 位整数形式暴露。
mysql> CREATE TABLE tbl(id bigint, content text);
DESC tbl;
+---------+--------+----------------+
| Field | Type | Properties |
+---------+--------+----------------+
| id | bigint | |
| content | text | indexed stored |
+---------+--------+----------------+
2 rows in set (0.00 sec)
您也可以完全省略指定 ID,它将自动启用。
mysql> CREATE TABLE tbl(content text);
DESC tbl;
+---------+--------+----------------+
| Field | Type | Properties |
+---------+--------+----------------+
| id | bigint | |
| content | text | indexed stored |
+---------+--------+----------------+
2 rows in set (0.00 sec)
处理文档 ID 时,重要的是要知道它们在内部以无符号 64 位整数存储,但根据接口的不同,处理方式也有所不同:
MySQL/SQL 接口:
- 大于 2^63-1 的 ID 将显示为负数。
- 过滤此类大 ID 时,必须使用其有符号表示。
- 使用 UINT64() 函数查看实际的无符号值。
JSON/HTTP 接口:
- ID 始终以其原始无符号值显示,无论大小。
- 过滤时可以使用有符号或无符号表示。
- 插入操作接受完整的无符号 64 位范围。
例如,让我们创建一个表并插入一些接近 2^63 的值:
mysql> create table t(id_text string);
Query OK, 0 rows affected (0.01 sec)
mysql> insert into t values(9223372036854775807, '2 ^ 63 - 1'),(9223372036854775808, '2 ^ 63');
Query OK, 2 rows affected (0.00 sec)
一些 ID 在结果中显示为负数,因为它们超过了 2^63-1。然而,使用 UINT64(id) 可以揭示它们的实际无符号值:
mysql> select *, uint64(id) from t;
+----------------------+------------+---------------------+
| id | id_text | uint64(id) |
+----------------------+------------+---------------------+
| 9223372036854775807 | 2 ^ 63 - 1 | 9223372036854775807 |
| -9223372036854775808 | 2 ^ 63 | 9223372036854775808 |
+----------------------+------------+---------------------+
2 rows in set (0.00 sec)
--- 2 out of 2 results in 0ms ---
对于查询 ID 小于 2^63 的文档,可以直接使用无符号值:
mysql> select * from t where id = 9223372036854775807;
+---------------------+------------+
| id | id_text |
+---------------------+------------+
| 9223372036854775807 | 2 ^ 63 - 1 |
+---------------------+------------+
1 row in set (0.00 sec)
--- 1 out of 1 results in 0ms ---
但是,对于从 2^63 开始的 ID,需要使用有符号值:
mysql> select * from t where id = -9223372036854775808;
+----------------------+---------+
| id | id_text |
+----------------------+---------+
| -9223372036854775808 | 2 ^ 63 |
+----------------------+---------+
1 row in set (0.00 sec)
--- 1 out of 1 results in 0ms ---
如果使用无符号值,将会出错:
mysql> select * from t where id = 9223372036854775808;
ERROR 1064 (42000): number 9223372036854775808 is out of range [-9223372036854775808..9223372036854775807]
超出 64 位范围的值将触发类似错误:
mysql> select * from t where id = -9223372036854775809;
ERROR 1064 (42000): number -9223372036854775809 is out of range [-9223372036854775808..9223372036854775807]
MySQL/SQL 和 JSON/HTTP 接口之间的行为差异在处理非常大的文档 ID 时变得更加明显。以下是一个全面的示例:
MySQL/SQL 接口:
mysql> drop table if exists t; create table t; insert into t values(17581446260360033510);
Query OK, 0 rows affected (0.01 sec)
mysql> select * from t;
+---------------------+
| id |
+---------------------+
| -865297813349518106 |
+---------------------+
mysql> select *, uint64(id) from t;
+---------------------+----------------------+
| id | uint64(id) |
+---------------------+----------------------+
| -865297813349518106 | 17581446260360033510 |
+---------------------+----------------------+
mysql> select * from t where id = -865297813349518106;
+---------------------+
| id |
+---------------------+
| -865297813349518106 |
+---------------------+
mysql> select * from t where id = 17581446260360033510;
ERROR 1064 (42000): number 17581446260360033510 is out of range [-9223372036854775808..9223372036854775807]
JSON/HTTP 接口:
# Search returns the original unsigned value
curl -s 0:9308/search -d '{"table": "t"}'
{
"took": 0,
"timed_out": false,
"hits": {
"total": 1,
"total_relation": "eq",
"hits": [
{
"_id": 17581446260360033510,
"_score": 1,
"_source": {}
}
]
}
}
# Both signed and unsigned values work for filtering
curl -s 0:9308/search -d '{"table": "t", "query": {"equals": {"id": 17581446260360033510}}}'
curl -s 0:9308/search -d '{"table": "t", "query": {"equals": {"id": -865297813349518106}}}'
# Insert with maximum unsigned 64-bit value
curl -s 0:9308/insert -d '{"table": "t", "id": 18446744073709551615, "doc": {}}'
这意味着在处理大文档 ID 时:
- MySQL 接口 要求查询时使用有符号表示,但可以通过
UINT64() 显示无符号值
- JSON 接口 始终使用无符号值显示,并接受两种表示进行过滤
通用语法:
string|text [stored|attribute] [indexed]
属性:
indexed - 全文索引(可用于全文查询)
stored - 存储在文档存储中(存储在磁盘上,而非 RAM 中,惰性读取)
attribute - 使其成为字符串属性(可以按其排序/分组)
指定至少一个属性会覆盖所有默认属性(见下文),即,如果您决定使用自定义属性组合,需要列出所有您想要的属性。
未指定任何属性:
string 和 text 是别名,但如果您不指定任何属性,它们默认表示不同的含义:
- 仅
string 默认表示 attribute(详见 下文)。
- 仅
text 默认表示 stored + indexed(详见 下文)。
文本(仅 text 或 text/string indexed)数据类型构成表的全文部分。文本字段被索引,可以用于搜索关键词。
文本通过分析器管道处理,将文本转换为单词,应用词形变换等。最终,从该文本构建出全文表(一种特殊的数据结构,能够快速搜索关键词)。
全文字段只能在 MATCH() 子句中使用,不能用于排序或聚合。单词存储在倒排索引中,并带有它们所属字段的引用和字段中的位置。这允许在每个字段内搜索单词,并使用邻近等高级运算符。默认情况下,字段的原始文本既被索引,也被存储在文档存储中。这意味着原始文本可以随查询结果返回,并用于 搜索结果高亮。
CREATE TABLE products(title text);
POST /cli -d "CREATE TABLE products(title text)"
$index = new \Manticoresearch\Index($client);
$index->setName('products');
$index->create([
'title'=>['type'=>'text']
]);
utilsApi.sql('CREATE TABLE products(title text)')
await utilsApi.sql('CREATE TABLE products(title text)')
res = await utilsApi.sql('CREATE TABLE products(title text)');
utilsApi.sql("CREATE TABLE products(title text)");
utilsApi.Sql("CREATE TABLE products(title text)");
utils_api.sql("CREATE TABLE products(title text)", Some(true)).await;
table products
{
type = rt
path = products
# when configuring fields via config, they are indexed (and not stored) by default
rt_field = title
# this option needs to be specified for the field to be stored
stored_fields = title
}
CREATE TABLE products(title text indexed);
POST /cli -d "CREATE TABLE products(title text indexed)"
$index = new \Manticoresearch\Index($client);
$index->setName('products');
$index->create([
'title'=>['type'=>'text','options'=>['indexed']]
]);
utilsApi.sql('CREATE TABLE products(title text indexed)')
await utilsApi.sql('CREATE TABLE products(title text indexed)')
res = await utilsApi.sql('CREATE TABLE products(title text indexed)');
utilsApi.sql("CREATE TABLE products(title text indexed)");
utilsApi.Sql("CREATE TABLE products(title text indexed)");
utils_api.sql("CREATE TABLE products(title text indexed)", Some(true)).await;
table products
{
type = rt
path = products
# when configuring fields via config, they are indexed (and not stored) by default
rt_field = title
}
字段是有名称的,您可以将搜索限制在单个字段(例如仅搜索"title")或字段子集(例如仅"title"和"abstract")。您最多可以拥有256个全文字段。
select * from products where match('@title first');
POST /search
{
"table": "products",
"query":
{
"match": { "title": "first" }
}
}
$index->setName('products')->search('@title')->get();
searchApi.search({"table":"products","query":{"match":{"title":"first"}}})
await searchApi.search({"table":"products","query":{"match":{"title":"first"}}})
res = await searchApi.search({"table":"products","query":{"match":{"title":"first"}}});
utilsApi.sql("CREATE TABLE products(title text indexed)");
utilsApi.Sql("CREATE TABLE products(title text indexed)");
utils_api.sql("CREATE TABLE products(title text indexed)", Some(true)).await;
与全文字段不同,字符串属性(仅string或string/text attribute)按接收时的原样存储,不能用于全文搜索。相反,它们会在结果中返回,可以在WHERE子句中用于比较过滤或REGEX,并且可以用于排序和聚合。通常,不建议在字符串属性中存储大文本,而是将字符串属性用于元数据,如名称、标题、标签、键。
如果您还想索引字符串属性,可以同时指定为string attribute indexed。这将允许全文搜索并作为属性使用。
CREATE TABLE products(title text, keys string);
POST /cli -d "CREATE TABLE products(title text, keys string)"
$index = new \Manticoresearch\Index($client);
$index->setName('products');
$index->create([
'title'=>['type'=>'text'],
'keys'=>['type'=>'string']
]);
utilsApi.sql('CREATE TABLE products(title text, keys string)')
await utilsApi.sql('CREATE TABLE products(title text, keys string)')
res = await utilsApi.sql('CREATE TABLE products(title text, keys string)');
utilsApi.sql("CREATE TABLE products(title text, keys string)");
utilsApi.Sql("CREATE TABLE products(title text, keys string)");
utils_api.sql("CREATE TABLE products(title text, keys string)", Some(true)).await;
table products
{
type = rt
path = products
rt_field = title
stored_fields = title
rt_attr_string = keys
}
Manticore没有专门的二进制数据类型字段,但您可以通过使用base64编码和text stored或string stored字段类型(它们是同义词)来安全地存储它。如果您不对二进制数据进行编码,部分数据可能会丢失——例如,如果遇到空字节,Manticore会修剪字符串的末尾。
这是一个示例,我们使用base64编码ls命令,将其存储在Manticore中,然后解码以验证MD5校验和保持不变:
# md5sum /bin/ls
43d1b8a7ccda411118e2caba685f4329 /bin/ls
# encoded_data=`base64 -i /bin/ls `
# mysql -P9306 -h0 -e "drop table if exists test; create table test(data text stored); insert into test(data) values('$encoded_data')"
# mysql -P9306 -h0 -NB -e "select data from test" | base64 -d > /tmp/ls | md5sum
43d1b8a7ccda411118e2caba685f4329 -
CREATE TABLE products(title text, price int);
POST /cli -d "CREATE TABLE products(title text, price int)"
$index = new \Manticoresearch\Index($client);
$index->setName('products');
$index->create([
'title'=>['type'=>'text'],
'price'=>['type'=>'int']
]);
utilsApi.sql('CREATE TABLE products(title text, price int)')
await utilsApi.sql('CREATE TABLE products(title text, price int)')
res = await utilsApi.sql('CREATE TABLE products(title text, price int)');
utilsApi.sql("CREATE TABLE products(title text, price int)");
utilsApi.Sql("CREATE TABLE products(title text, price int)");
utils_api.sql("CREATE TABLE products(title text, price int)", Some(true)).await;
table products
{
type = rt
path = products
rt_field = title
stored_fields = title
rt_attr_uint = type
}
整数可以通过指定位数以比32位更短的尺寸存储。例如,如果我们想存储一个我们知道不会大于8的数值,类型可以定义为bit(3)。位计数整数比全尺寸整数性能更慢,但它们需要更少的RAM。它们以32位块保存,因此为了节省空间,它们应该分组在属性定义的末尾(否则,在两个全尺寸整数之间的位计数整数也将占用32位)。
CREATE TABLE products(title text, flags bit(3), tags bit(2) );
POST /cli -d "CREATE TABLE products(title text, flags bit(3), tags bit(2))"
$index = new \Manticoresearch\Index($client);
$index->setName('products');
$index->create([
'title'=>['type'=>'text'],
'flags'=>['type'=>'bit(3)'],
'tags'=>['type'=>'bit(2)']
]);
utilsApi.sql('CREATE TABLE products(title text, flags bit(3), tags bit(2) ')
await utilsApi.sql('CREATE TABLE products(title text, flags bit(3), tags bit(2) ')
res = await utilsApi.sql('CREATE TABLE products(title text, flags bit(3), tags bit(2) ');
utilsApi.sql("CREATE TABLE products(title text, flags bit(3), tags bit(2)");
utilsApi.Sql("CREATE TABLE products(title text, flags bit(3), tags bit(2)");
utils_api.sql("CREATE TABLE products(title text, flags bit(3), tags bit(2)", Some(true)).await;
table products
{
type = rt
path = products
rt_field = title
stored_fields = title
rt_attr_uint = flags:3
rt_attr_uint = tags:2
}
CREATE TABLE products(title text, price bigint );
POST /cli -d "CREATE TABLE products(title text, price bigint)"
$index = new \Manticoresearch\Index($client);
$index->setName('products');
$index->create([
'title'=>['type'=>'text'],
'price'=>['type'=>'bigint']
]);
utilsApi.sql('CREATE TABLE products(title text, price bigint )')
await utilsApi.sql('CREATE TABLE products(title text, price bigint )')
res = await utilsApi.sql('CREATE TABLE products(title text, price bigint )');
utilsApi.sql("CREATE TABLE products(title text, price bigint )");
utilsApi.Sql("CREATE TABLE products(title text, price bigint )");
utils_api.sql("CREATE TABLE products(title text, price bigint )", Some(true)).await;
table products
{
type = rt
path = products
rt_field = title
stored_fields = title
rt_attr_bigint = type
}
CREATE TABLE products(title text, sold bool );
POST /cli -d "CREATE TABLE products(title text, sold bool)"
$index = new \Manticoresearch\Index($client);
$index->setName('products');
$index->create([
'title'=>['type'=>'text'],
'sold'=>['type'=>'bool']
]);
utilsApi.sql('CREATE TABLE products(title text, sold bool )')
await utilsApi.sql('CREATE TABLE products(title text, sold bool )')
res = await utilsApi.sql('CREATE TABLE products(title text, sold bool )');
utilsApi.sql("CREATE TABLE products(title text, sold bool )");
utilsApi.Sql("CREATE TABLE products(title text, sold bool )");
utils_api.sql("CREATE TABLE products(title text, sold bool )", Some(true)).await;
table products
{
type = rt
path = products
rt_field = title
stored_fields = title
rt_attr_bool = sold
}
时间戳类型表示Unix时间戳,以32位整数存储。与基本整数不同,时间戳类型允许使用时间和日期函数。从字符串值转换遵循以下规则:
- 不带分隔符的数字,且长度至少为10个字符,按原样转换为时间戳。
%Y-%m-%dT%H:%M:%E*S%Z
%Y-%m-%d'T'%H:%M:%S%Z
%Y-%m-%dT%H:%M:%E*S
%Y-%m-%dT%H:%M:%s
%Y-%m-%dT%H:%M
%Y-%m-%dT%H
%Y-%m-%d
%Y-%m
%Y
这些转换格式的含义详见strptime手册,除%E*S表示毫秒外。
注意,纯表中不支持时间戳的自动转换。
CREATE TABLE products(title text, date timestamp);
POST /cli -d "CREATE TABLE products(title text, date timestamp)"
$index = new \Manticoresearch\Index($client);
$index->setName('products');
$index->create([
'title'=>['type'=>'text'],
'date'=>['type'=>'timestamp']
]);
utilsApi.sql('CREATE TABLE products(title text, date timestamp)')
await utilsApi.sql('CREATE TABLE products(title text, date timestamp)')
res = await utilsApi.sql('CREATE TABLE products(title text, date timestamp)');
utilsApi.sql("CREATE TABLE products(title text, date timestamp)");
utilsApi.Sql("CREATE TABLE products(title text, date timestamp)");
utils_api.sql("CREATE TABLE products(title text, date timestamp)", Some(true)).await;
table products
{
type = rt
path = products
rt_field = title
stored_fields = title
rt_attr_timestamp = date
}
CREATE TABLE products(title text, coeff float);
POST /cli -d "CREATE TABLE products(title text, coeff float)"
$index = new \Manticoresearch\Index($client);
$index->setName('products');
$index->create([
'title'=>['type'=>'text'],
'coeff'=>['type'=>'float']
]);
utilsApi.sql('CREATE TABLE products(title text, coeff float)')
await utilsApi.sql('CREATE TABLE products(title text, coeff float)')
res = await utilsApi.sql('CREATE TABLE products(title text, coeff float)');
utilsApi.sql("CREATE TABLE products(title text, coeff float)");
utilsApi.Sql("CREATE TABLE products(title text, coeff float)");
utils_api.sql("CREATE TABLE products(title text, coeff float)", Some(true)).await;
table products
{
type = rt
path = products
rt_field = title
stored_fields = title
rt_attr_float = coeff
}
与整数类型不同,不建议直接比较两个浮点数是否相等,因为可能存在舍入误差。更可靠的做法是使用近似比较,通过检查绝对误差范围。
select abs(a-b)<=0.00001 from products
POST /search
{
"table": "products",
"query": { "match_all": {} } },
"expressions": { "eps": "abs(a-b)" }
}
$index->setName('products')->search('')->expression('eps','abs(a-b)')->get();
searchApi.search({"table":"products","query":{"match_all":{}},"expressions":{"eps":"abs(a-b)"}})
await searchApi.search({"table":"products","query":{"match_all":{}},"expressions":{"eps":"abs(a-b)"}})
res = await searchApi.search({"table":"products","query":{"match_all":{}},"expressions":{"eps":"abs(a-b)"}});
searchRequest = new SearchRequest();
searchRequest.setIndex("forum");
query = new HashMap<String,Object>();
query.put("match_all",null);
searchRequest.setQuery(query);
Object expressions = new HashMap<String,Object>(){{
put("ebs","abs(a-b)");
}};
searchRequest.setExpressions(expressions);
searchResponse = searchApi.search(searchRequest);
object query = new { match_all=null };
var searchRequest = new SearchRequest("forum", query);
searchRequest.Expressions = new List<Object>{
new Dictionary<string, string> { {"ebs", "abs(a-b)"} }
};
var searchResponse = searchApi.Search(searchRequest);
let query = SearchQuery::new();
let mut expr = HashMap::new();
expr.insert("ebs".to_string(), serde_json::json!("abs(a-b)"));
let expressions: [HashMap; 1] = [expr];
let search_request = SearchRequest {
table: "forum".to_string(),
query: Some(Box::new(query)),
expressions: serde_json::json!(expressions),
..Default::default(),
};
let search_res = search_api.search(search_req).await;
另一个替代方案,也可用于执行IN(attr,val1,val2,val3),是通过选择一个乘数因子将浮点数转换为整数进行比较操作。以下例子展示了如何将IN(attr,2.0,2.5,3.5)修改为整型值。
select in(ceil(attr*100),200,250,350) from products
POST /search
{
"table": "products",
"query": { "match_all": {} } },
"expressions": { "inc": "in(ceil(attr*100),200,250,350)" }
}
$index->setName('products')->search('')->expression('inc','in(ceil(attr*100),200,250,350)')->get();
searchApi.search({"table":"products","query":{"match_all":{}}},"expressions":{"inc":"in(ceil(attr*100),200,250,350)"}})
await searchApi.search({"table":"products","query":{"match_all":{}}},"expressions":{"inc":"in(ceil(attr*100),200,250,350)"}})
res = await searchApi.search({"table":"products","query":{"match_all":{}}},"expressions":{"inc":"in(ceil(attr*100),200,250,350)"}});
searchRequest = new SearchRequest();
searchRequest.setIndex("forum");
query = new HashMap<String,Object>();
query.put("match_all",null);
searchRequest.setQuery(query);
Object expressions = new HashMap<String,Object>(){{
put("inc","in(ceil(attr*100),200,250,350)");
}};
searchRequest.setExpressions(expressions);
searchResponse = searchApi.search(searchRequest);
object query = new { match_all=null };
var searchRequest = new SearchRequest("forum", query);
searchRequest.Expressions = new List<Object> {
new Dictionary<string, string> { {"ebs", "in(ceil(attr*100),200,250,350)"} }
};
var searchResponse = searchApi.Search(searchRequest);
let query = SearchQuery::new();
let mut expr = HashMap::new();
expr.insert("ebs".to_string(), serde_json::json!("in(ceil(attr*100),200,250,350)"));
let expressions: [HashMap; 1] = [expr];
let search_request = SearchRequest {
table: "forum".to_string(),
query: Some(Box::new(query)),
expressions: serde_json::json!(expressions),
..Default::default(),
};
let search_res = search_api.search(search_req).await;
浮点值在Manticore中以精确的方式显示,以确保它们反映存储的确切值。这种方法是为了防止精度丢失,特别是在地理坐标等情况下,四舍五入到6位小数会导致不准确。
现在,Manticore首先输出一个带有6位数字的数字,然后解析并将其与原始值进行比较。如果不匹配,则会添加更多位数,直到匹配为止。
例如,如果浮点值插入为19.45,Manticore将显示为19.450001以准确表示存储的值。
insert into t(id, f) values(1, 19.45)
--------------
Query OK, 1 row affected (0.02 sec)
--------------
select * from t
--------------
+------+-----------+
| id | f |
+------+-----------+
| 1 | 19.450001 |
+------+-----------+
1 row in set (0.00 sec)
--- 1 out of 1 results in 0ms ---
此数据类型允许存储JSON对象,特别适用于处理无模式数据。在定义JSON值时,请确保对象的开闭花括号{和}包含,或数组的开闭方括号[和]包含。虽然JSON不支持列存储,但它可以存储在传统的行存储中。值得注意的是,这两种存储类型可以在同一表中结合使用。
CREATE TABLE products(title text, data json);
POST /cli -d "CREATE TABLE products(title text, data json)"
$index = new \Manticoresearch\Index($client);
$index->setName('products');
$index->create([
'title'=>['type'=>'text'],
'data'=>['type'=>'json']
]);
utilsApi.sql('CREATE TABLE products(title text, data json)')
await utilsApi.sql('CREATE TABLE products(title text, data json)')
res = await utilsApi.sql('CREATE TABLE products(title text, data json)');
utilsApi.sql("CREATE TABLE products(title text, data json)");
utilsApi.Sql("CREATE TABLE products(title text, data json)");
utils_api.sql("CREATE TABLE products(title text, data json)", Some(true)).await;
table products
{
type = rt
path = products
rt_field = title
stored_fields = title
rt_attr_json = data
}
select indexof(x>2 for x in data.intarray) from products
POST /search
{
"table": "products",
"query": { "match_all": {} } },
"expressions": { "idx": "indexof(x>2 for x in data.intarray)" }
}
$index->setName('products')->search('')->expression('idx','indexof(x>2 for x in data.intarray)')->get();
searchApi.search({"table":"products","query":{"match_all":{}},"expressions":{"idx":"indexof(x>2 for x in data.intarray)"}})
await searchApi.search({"table":"products","query":{"match_all":{}},"expressions":{"idx":"indexof(x>2 for x in data.intarray)"}})
res = await searchApi.search({"table":"products","query":{"match_all":{}},"expressions":{"idx":"indexof(x>2 for x in data.intarray)"}});
searchRequest = new SearchRequest();
searchRequest.setIndex("forum");
query = new HashMap<String,Object>();
query.put("match_all",null);
searchRequest.setQuery(query);
Object expressions = new HashMap<String,Object>(){{
put("idx","indexof(x>2 for x in data.intarray)");
}};
searchRequest.setExpressions(expressions);
searchResponse = searchApi.search(searchRequest);
object query = new { match_all=null };
var searchRequest = new SearchRequest("forum", query);
searchRequest.Expressions = new List<Object> {
new Dictionary<string, string> { {"idx", "indexof(x>2 for x in data.intarray)"} }
};
var searchResponse = searchApi.Search(searchRequest);
let query = SearchQuery::new();
let mut expr = HashMap::new();
expr.insert("idx".to_string(), serde_json::json!("indexof(x>2 for x in data.intarray)"));
let expressions: [HashMap; 1] = [expr];
let search_request = SearchRequest {
table: "forum".to_string(),
query: Some(Box::new(query)),
expressions: serde_json::json!(expressions),
..Default::default(),
};
let search_res = search_api.search(search_req).await;
文本属性与字符串相同,因此无法在全文匹配表达式中使用。但是,可以使用字符串函数,如REGEX()。
select regex(data.name, 'est') as c from products where c>0
POST /search
{
"table": "products",
"query":
{
"match_all": {},
"range": { "c": { "gt": 0 } } }
},
"expressions": { "c": "regex(data.name, 'est')" }
}
$index->setName('products')->search('')->expression('idx',"regex(data.name, 'est')")->filter('c','gt',0)->get();
searchApi.search({"table":"products","query":{"match_all":{},"range":{"c":{"gt":0}}}},"expressions":{"c":"regex(data.name, 'est')"}})
await searchApi.search({"table":"products","query":{"match_all":{},"range":{"c":{"gt":0}}}},"expressions":{"c":"regex(data.name, 'est')"}})
res = await searchApi.search({"table":"products","query":{"match_all":{},"range":{"c":{"gt":0}}}},"expressions":{"c":"regex(data.name, 'est')"}}});
searchRequest = new SearchRequest();
searchRequest.setIndex("forum");
query = new HashMap<String,Object>();
query.put("match_all",null);
query.put("range", new HashMap<String,Object>(){{
put("c", new HashMap<String,Object>(){{
put("gt",0);
}});
}});
searchRequest.setQuery(query);
Object expressions = new HashMap<String,Object>(){{
put("idx","indexof(x>2 for x in data.intarray)");
}};
searchRequest.setExpressions(expressions);
searchResponse = searchApi.search(searchRequest);
object query = new { match_all=null };
var searchRequest = new SearchRequest("forum", query);
var rangeFilter = new RangeFilter("c");
rangeFilter.Gt = 0;
searchRequest.AttrFilter = rangeFilter;
searchRequest.Expressions = new List<Object> {
new Dictionary<string, string> { {"idx", "indexof(x>2 for x in data.intarray)"} }
};
var searchResponse = searchApi.Search(searchRequest);
let query = SearchQuery::new();
let mut expr = HashMap::new();
expr.insert("idx".to_string(), serde_json::json!("indexof(x>2 for x in data.intarray)"));
let expressions: [HashMap; 1] = [expr];
let search_request = SearchRequest {
table: "forum".to_string(),
query: Some(Box::new(query)),
expressions: serde_json::json!(expressions),
..Default::default(),
};
let search_res = search_api.search(search_req).await;
在处理浮点值的情况下,可能需要强制执行数据类型以确保某些情况下的正确功能。例如,当处理浮点值时,必须使用DOUBLE()以确保正确的排序。
select * from products order by double(data.myfloat) desc
POST /search
{
"table": "products",
"query": { "match_all": {} } },
"sort": [ { "double(data.myfloat)": { "order": "desc"} } ]
}
$index->setName('products')->search('')->sort('double(data.myfloat)','desc')->get();
searchApi.search({"table":"products","query":{"match_all":{}}},"sort":[{"double(data.myfloat)":{"order":"desc"}}]})
await searchApi.search({"table":"products","query":{"match_all":{}}},"sort":[{"double(data.myfloat)":{"order":"desc"}}]})
res = await searchApi.search({"table":"products","query":{"match_all":{}}},"sort":[{"double(data.myfloat)":{"order":"desc"}}]});
searchRequest = new SearchRequest();
searchRequest.setIndex("forum");
query = new HashMap<String,Object>();
query.put("match_all",null);
searchRequest.setQuery(query);
searchRequest.setSort(new ArrayList<Object>(){{
add(new HashMap<String,String>(){{ put("double(data.myfloat)",new HashMap<String,String>(){{ put("order","desc");}});}});
}});
searchResponse = searchApi.search(searchRequest);
object query = new { match_all=null };
var searchRequest = new SearchRequest("forum", query);
searchRequest.Sort = new List<Object> {
new SortOrder("double(data.myfloat)", SortOrder.OrderEnum.Desc)
};
var searchResponse = searchApi.Search(searchRequest);
let query = SearchQuery::new();
let mut sort = HashMap::new();
sort.insert("double(data.myfloat)".to_string(), serde_json::json!("desc"));
let search_request = SearchRequest {
table: "forum".to_string(),
query: Some(Box::new(query)),
sort: serde_json::json!(sort)
..Default::default()
};
let search_res = search_api.search(search_req).await;
浮点向量属性允许存储可变长度的浮点数列表,主要用于机器学习应用和相似性搜索。此类型与多值整数(MVA)(MVAs)在几个重要方面不同:
- 保留值的确切顺序(与MVAs不同,MVAs可能会重新排序)
- 保留重复值(与MVAs不同,MVAs会去重)
- 插入时无需额外处理(与MVAs不同,MVAs会排序和去重)
重要: float_vector 数据类型与 自动模式 机制不兼容。
- 目前仅支持实时表(不支持普通表)
- 其他函数或表达式中不支持
- 不能在常规过滤器或排序中使用
当你配置 float_vector 属性并设置 KNN 参数时,你将启用向量相似性搜索功能。这允许你执行 k-最近邻搜索,根据向量距离查找相似文档。
你可以做到:
- 执行 KNN(k-最近邻)向量搜索以查找相似文档
- 构建语义搜索、推荐和 AI 驱动的功能
- 使用自动嵌入从文本中自动生成向量
你不能做到:
UPDATE float_vector 值(必须使用 REPLACE 代替)
- 在常规过滤器或排序中使用浮点向量
- 除了通过向量搜索操作,不能通过
float_vector 值进行过滤
当你创建带有 float_vector 属性的表以进行 KNN 搜索时,可以指定以下参数:
必填参数:
KNN_TYPE:目前仅支持 'hnsw'
KNN_DIMS:向量的维度数(手动插入向量时必需,使用 MODEL_NAME 时可省略)
HNSW_SIMILARITY:距离函数 - 'l2'、'ip'(内积)或 'cosine'
可选参数:
HNSW_M:图中的最大连接数(默认:16)
HNSW_EF_CONSTRUCTION:构建时间/准确性权衡(默认:200)
自动嵌入参数(当使用 MODEL_NAME 时):
MODEL_NAME:使用的嵌入模型(例如,'sentence-transformers/all-MiniLM-L6-v2'、'openai/text-embedding-ada-002')
FROM:用于生成嵌入的字段名列表(逗号分隔),或空字符串 '' 表示使用所有文本/字符串字段
API_KEY:基于 API 的模型(OpenAI、Voyage、Jina)的 API 密钥
有关设置浮点向量并在搜索中使用它们的详细信息,请参阅KNN搜索。
处理浮点向量最简便的方式是使用自动嵌入。此功能使用机器学习模型自动从您的文本数据生成嵌入,从而消除手动计算和插入向量的需要。
- 简化的工作流程:只需插入文本,嵌入会自动生成
- 无需手动计算向量:无需运行单独的嵌入模型
- 一致的嵌入:相同的模型确保一致的向量表示
- 多模型支持:可选择 sentence-transformers、Qwen 嵌入模型、OpenAI、Voyage 和 Jina 模型
- 灵活的字段选择:控制用于生成嵌入的字段
在创建带有自动嵌入的表时,指定以下附加参数:
MODEL_NAME:用于自动向量生成的嵌入模型
FROM:用于生成嵌入的字段(空字符串表示所有文本/字符串字段)
支持的嵌入模型:
- Sentence Transformers:任何 适合的 BERT 基础 Hugging Face 模型(例如,
sentence-transformers/all-MiniLM-L6-v2)——无需 API 密钥。Manticore 在创建表时下载模型。
- Qwen 本地嵌入:Qwen 嵌入模型,如
Qwen/Qwen3-Embedding-0.6B——不需要 API 密钥。Manticore 在您创建表时下载模型。
- OpenAI:OpenAI 嵌入模型,如
openai/text-embedding-ada-002 - 需要 API_KEY='<OPENAI_API_KEY>' 参数
- Voyage:Voyage AI 嵌入模型 - 需要
API_KEY='<VOYAGE_API_KEY>' 参数
- Jina:Jina AI 嵌入模型 - 需要
API_KEY='<JINA_API_KEY>' 参数
使用 sentence-transformers 模型(无需 API 密钥)
CREATE TABLE products (
title TEXT,
description TEXT,
embedding_vector FLOAT_VECTOR KNN_TYPE='hnsw' HNSW_SIMILARITY='l2'
MODEL_NAME='sentence-transformers/all-MiniLM-L6-v2' FROM='title'
);
使用 Qwen 本地嵌入(不需要 API 密钥)
CREATE TABLE products_qwen (
title TEXT,
description TEXT,
embedding_vector FLOAT_VECTOR KNN_TYPE='hnsw' HNSW_SIMILARITY='l2'
MODEL_NAME='Qwen/Qwen3-Embedding-0.6B' FROM='title'
);
使用 OpenAI 模型(需要 API_KEY 参数)
CREATE TABLE products_openai (
title TEXT,
content TEXT,
embedding_vector FLOAT_VECTOR KNN_TYPE='hnsw' HNSW_SIMILARITY='cosine'
MODEL_NAME='openai/text-embedding-ada-002' FROM='title,content' API_KEY='<OPENAI_API_KEY>'
);
使用所有文本字段进行嵌入(FROM 为空)
CREATE TABLE products_all_fields (
title TEXT,
description TEXT,
tags TEXT,
embedding_vector FLOAT_VECTOR KNN_TYPE='hnsw' HNSW_SIMILARITY='l2'
MODEL_NAME='sentence-transformers/all-MiniLM-L6-v2' FROM=''
);
FROM 参数控制用于生成嵌入的字段:
- 特定字段:
FROM='title' - 仅使用标题字段
- 多个字段:
FROM='title,description' - 将标题和描述字段连接并使用
- 所有文本字段:
FROM=''(空)- 表中的所有 text(全文字段)和 string(字符串属性)字段用于嵌入生成
- 空向量:仍然可以使用
() 插入空向量以排除文档从向量搜索
使用自动嵌入时,不要在 INSERT 语句中指定向量字段。嵌入会自动从指定的文本字段生成:
-- Insert text data - embeddings generated automatically
INSERT INTO products (title, description) VALUES
('smartphone', 'latest mobile device with camera'),
('laptop computer', 'portable workstation for developers');
-- Insert with empty vector (excluded from vector search)
INSERT INTO products (title, description, embedding_vector) VALUES
('no-vector item', 'this item has no embedding', ());
或者,你可以手动插入预计算的向量数据。这需要你使用外部工具或模型自行计算向量,然后将其插入到 Manticore 中。
重要提示: 当使用 HNSW_SIMILARITY='cosine' 时,插入时向量会自动归一化为单位向量(数学长度/幅度为 1.0 的向量)。这种归一化保留了向量的方向,同时标准化了其长度,这是高效余弦相似性计算所必需的。这意味着存储的值将与你的原始输入值不同。
CREATE TABLE products (
title TEXT,
image_vector FLOAT_VECTOR KNN_TYPE='hnsw' KNN_DIMS='4' HNSW_SIMILARITY='l2'
);
INSERT INTO products VALUES
(1, 'yellow bag', (0.653448,0.192478,0.017971,0.339821)),
(2, 'white bag', (-0.148894,0.748278,0.091892,-0.095406));
你也可以创建没有 KNN 配置的 float_vector 属性。在这种模式下,向量被存储但不能用于向量搜索操作。
你可以做到:
- 存储向量数据
UPDATE float_vector 值(与 KNN 不同,此时可以使用 UPDATE)
你不能做到:
- 执行 KNN 搜索或向量相似性搜索
- 使用向量进行任何搜索操作
- 通过
float_vector 值进行过滤
- 在配置KNN之前临时存储向量数据
- 用于后续与KNN一起使用的暂存数据
- 存储不需要搜索功能的向量
CREATE TABLE products(title text, image_vector float_vector);
POST /cli -d "CREATE TABLE products(title text, image_vector float_vector)"
$index = new \Manticoresearch\Index($client);
$index->setName('products');
$index->create([
'title'=>['type'=>'text'],
'image_vector'=>['type'=>'float_vector']
]);
utilsApi.sql('CREATE TABLE products(title text, image_vector float_vector)')
await utilsApi.sql('CREATE TABLE products(title text, image_vector float_vector)')
res = await utilsApi.sql('CREATE TABLE products(title text, image_vector float_vector)');
utilsApi.sql("CREATE TABLE products(title text, image_vector float_vector)");
utilsApi.Sql("CREATE TABLE products(title text, image_vector float_vector)");
utils_api.sql("CREATE TABLE products(title text, image_vector float_vector)", Some(true)).await;
table products
{
type = rt
path = products
rt_field = title
stored_fields = title
rt_attr_float_vector = image_vector
}
多值属性允许存储可变长度的32位无符号整数列表。这在存储一对多的数值时非常有用,例如标签、产品类别和属性。
CREATE TABLE products(title text, product_codes multi);
或
CREATE TABLE products(title text, product_codes mva);
POST /cli -d "CREATE TABLE products(title text, product_codes multi)"
$index = new \Manticoresearch\Index($client);
$index->setName('products');
$index->create([
'title'=>['type'=>'text'],
'product_codes'=>['type'=>'multi']
]);
utilsApi.sql('CREATE TABLE products(title text, product_codes multi)')
await utilsApi.sql('CREATE TABLE products(title text, product_codes multi)')
res = await utilsApi.sql('CREATE TABLE products(title text, product_codes multi)');
utilsApi.sql("CREATE TABLE products(title text, product_codes multi)");
utilsApi.Sql("CREATE TABLE products(title text, product_codes multi)");
utils_api.sql("CREATE TABLE products(title text, product_codes multi)", Some(true)).await;
table products
{
type = rt
path = products
rt_field = title
stored_fields = title
rt_attr_multi = product_codes
}
它支持过滤和聚合,但不支持排序。过滤可以通过至少一个元素通过(使用 ANY())或所有元素 (ALL()) 来完成。
select * from products where any(product_codes)=3
POST /search
{
"table": "products",
"query":
{
"match_all": {},
"equals" : { "any(product_codes)": 3 }
}
}
$index->setName('products')->search('')->filter('any(product_codes)','equals',3)->get();
searchApi.search({"table":"products","query":{"match_all":{},"equals":{"any(product_codes)":3}}}})
await searchApi.search({"table":"products","query":{"match_all":{},"equals":{"any(product_codes)":3}}}})
res = await searchApi.search({"table":"products","query":{"match_all":{},"equals":{"any(product_codes)":3}}}})'
searchRequest = new SearchRequest();
searchRequest.setIndex("forum");
query = new HashMap<String,Object>();
query.put("match_all",null);
query.put("equals",new HashMap<String,Integer>(){{
put("any(product_codes)",3);
}});
searchRequest.setQuery(query);
searchResponse = searchApi.search(searchRequest);
object query = new { match_all=null };
var searchRequest = new SearchRequest("forum", query);
searchRequest.AttrFilter = new EqualsFilter("any(product_codes)", 3);
var searchResponse = searchApi.Search(searchRequest);
let query = SearchQuery::new();
let search_request = SearchRequest {
table: "forum".to_string(),
query: Some(Box::new(query)),
..Default::default(),
};
let search_res = search_api.search(search_req).await;
可以提取诸如最小或最大元素以及列表长度等信息。以下示例展示了如何按多值属性的最小元素进行排序。
select least(product_codes) l from products order by l asc
POST /search
{
"table": "products",
"query":
{
"match_all": {},
"sort": [ { "product_codes":{ "order":"asc", "mode":"min" } } ]
}
}
$index->setName('products')->search('')->sort('product_codes','asc','min')->get();
searchApi.search({"table":"products","query":{"match_all":{},"sort":[{"product_codes":{"order":"asc","mode":"min"}}]}})
await searchApi.search({"table":"products","query":{"match_all":{},"sort":[{"product_codes":{"order":"asc","mode":"min"}}]}})
res = await searchApi.search({"table":"products","query":{"match_all":{},"sort":[{"product_codes":{"order":"asc","mode":"min"}}]}});
searchRequest = new SearchRequest();
searchRequest.setIndex("forum");
query = new HashMap<String,Object>();
query.put("match_all",null);
searchRequest.setQuery(query);
searchRequest.setSort(new ArrayList<Object>(){{
add(new HashMap<String,String>(){{ put("product_codes",new HashMap<String,String>(){{ put("order","asc");put("mode","min");}});}});
}});
searchResponse = searchApi.search(searchRequest);
object query = new { match_all=null };
var searchRequest = new SearchRequest("forum", query);
searchRequest.Sort = new List<Object> {
new SortMVA("product_codes", SortOrder.OrderEnum.Asc, SortMVA.ModeEnum.Min)
};
searchResponse = searchApi.Search(searchRequest);
let query = SearchQuery::new();
let mut sort_opts = HashMap::new();
sort_opts.insert("order".to_string(), serde_json::json!("asc"));
sort_opts.insert("mode".to_string(), serde_json::json!("min"));
sort_expr.insert("product_codes".to_string(), serde_json::json!(sort_opts));
let sort: [HashMap; 1] = [sort_expr];
let search_req = SearchRequest {
table: "forum".to_string(),
query: Some(Box::new(query)),
sort: serde_json::json!(sort),
..Default::default(),
};
let search_res = search_api.search(search_req).await;
当按多值属性分组时,一个文档将贡献给与该文档关联的不同值数量相同的多个组。例如,如果一个集合恰好包含一个文档,其'product_codes'多值属性的值为5、7和11,那么按'product_codes'分组将产生3个组,COUNT(*)等于1,GROUPBY()键值分别为5、7和11。另外请注意,按多值属性分组可能导致结果集中出现重复文档,因为每个文档可以参与多个组。
insert into products values ( 1, 'doc one', (5,7,11) );
select id, count(*), groupby() from products group by product_codes;
Query OK, 1 row affected (0.00 sec)
+------+----------+-----------+
| id | count(*) | groupby() |
+------+----------+-----------+
| 1 | 1 | 11 |
| 1 | 1 | 7 |
| 1 | 1 | 5 |
+------+----------+-----------+
3 rows in set (0.00 sec)
作为多值属性值插入的数字顺序不会被保留。值在内部以排序集合的形式存储。
insert into product values (1,'first',(4,2,1,3));
select * from products;
POST /insert
{
"table":"products",
"id":1,
"doc":
{
"title":"first",
"product_codes":[4,2,1,3]
}
}
POST /search
{
"table": "products",
"query": { "match_all": {} }
}
$index->addDocument([
"title"=>"first",
"product_codes"=>[4,2,1,3]
]);
$index->search('')-get();
indexApi.insert({"table":"products","id":1,"doc":{"title":"first","product_codes":[4,2,1,3]}})
searchApi.search({"table":"products","query":{"match_all":{}}})
await indexApi.insert({"table":"products","id":1,"doc":{"title":"first","product_codes":[4,2,1,3]}})
await searchApi.search({"table":"products","query":{"match_all":{}}})
await indexApi.insert({"table":"products","id":1,"doc":{"title":"first","product_codes":[4,2,1,3]}});
res = await searchApi.search({"table":"products","query":{"match_all":{}}});
InsertDocumentRequest newdoc = new InsertDocumentRequest();
HashMap<String,Object> doc = new HashMap<String,Object>(){{
put("title","first");
put("product_codes",new int[] {4,2,1,3});
}};
newdoc.index("products").id(1L).setDoc(doc);
sqlresult = indexApi.insert(newdoc);
Map<String,Object> query = new HashMap<String,Object>();
query.put("match_all",null);
SearchRequest searchRequest = new SearchRequest();
searchRequest.setIndex("products");
searchRequest.setQuery(query);
SearchResponse searchResponse = searchApi.search(searchRequest);
System.out.println(searchResponse.toString() );
Dictionary<string, Object> doc = new Dictionary<string, Object>();
doc.Add("title", "first");
doc.Add("product_codes", new List<Object> {4,2,1,3});
InsertDocumentRequest newdoc = new InsertDocumentRequest(index: "products", id: 1, doc: doc);
var sqlresult = indexApi.Insert(newdoc);
object query = new { match_all=null };
var searchRequest = new SearchRequest("products", query);
var searchResponse = searchApi.Search(searchRequest);
Console.WriteLine(searchResponse.ToString())
let mut doc = HashMap::new();
doc.insert("title".to_string(), serde_json::json!("first"));
doc.insert("product_codes".to_string(), serde_json::json!([4,2,1,3]));
let insert_req = InsertDocumentRequest::new("products".to_string(), serde_json::json!(doc));
let insert_res = index_api.insert(insert_req).await;
let query = SearchQuery::new();
let search_req = SearchRequest {
table: "forum".to_string(),
query: Some(Box::new(query)),
..Default::default(),
};
let search_res = search_api.search(search_req).await;
println!("{:?}", search_res);
Query OK, 1 row affected (0.00 sec)
+------+---------------+-------+
| id | product_codes | title |
+------+---------------+-------+
| 1 | 1,2,3,4 | first |
+------+---------------+-------+
1 row in set (0.01 sec)
{
"table":"products",
"_id":1,
"created":true,
"result":"created",
"status":201
}
{
"took":0,
"timed_out":false,
"hits":{
"total":1,
"hits":[
{
"_id": 1,
"_score":1,
"_source":{
"product_codes":[
1,
2,
3,
4
],
"title":"first"
}
}
]
}
}
Array
(
[_index] => products
[_id] => 1
[created] => 1
[result] => created
[status] => 201
)
Array
(
[took] => 0
[timed_out] =>
[hits] => Array
(
[total] => 1
[hits] => Array
(
[0] => Array
(
[_id] => 1
[_score] => 1
[_source] => Array
(
[product_codes] => Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => 4
)
[title] => first
)
)
)
)
)
{'created': True,
'found': None,
'id': 1,
'table': 'products',
'result': 'created'}
{'hits': {'hits': [{u'_id': u'1',
u'_score': 1,
u'_source': {u'product_codes': [1, 2, 3, 4],
u'title': u'first'}}],
'total': 1},
'profile': None,
'timed_out': False,
'took': 29}
{'created': True,
'found': None,
'id': 1,
'table': 'products',
'result': 'created'}
{'hits': {'hits': [{u'_id': u'1',
u'_score': 1,
u'_source': {u'product_codes': [1, 2, 3, 4],
u'title': u'first'}}],
'total': 1},
'profile': None,
'timed_out': False,
'took': 29}
{"took":0,"timed_out":false,"hits":{"total":1,"hits":[{"_id": 1,"_score":1,"_source":{"product_codes":[1,2,3,4],"title":"first"}}]}}
class SearchResponse {
took: 0
timedOut: false
hits: class SearchResponseHits {
total: 1
hits: [{_id=1, _score=1, _source={product_codes=[1, 2, 3, 4], title=first}}]
aggregations: null
}
profile: null
}
class SearchResponse {
took: 0
timedOut: false
hits: class SearchResponseHits {
total: 1
hits: [{_id=1, _score=1, _source={product_codes=[1, 2, 3, 4], title=first}}]
aggregations: null
}
profile: null
}
class SearchResponse {
took: 0
timedOut: false
hits: class SearchResponseHits {
total: 1
hits: [{_id=1, _score=1, _source={product_codes=[1, 2, 3, 4], title=first}}]
aggregations: null
}
profile: null
}
一种允许存储可变长度64位有符号整数列表的数据类型。其功能与多值整数相同。
CREATE TABLE products(title text, values multi64);
或
CREATE TABLE products(title text, values mva64);
POST /cli -d "CREATE TABLE products(title text, values multi64)"
$index = new \Manticoresearch\Index($client);
$index->setName('products');
$index->create([
'title'=>['type'=>'text'],
'values'=>['type'=>'multi64']
]);
utilsApi.sql('CREATE TABLE products(title text, values multi64))')
await utilsApi.sql('CREATE TABLE products(title text, values multi64))')
res = await utilsApi.sql('CREATE TABLE products(title text, values multi64))');
utilsApi.sql("CREATE TABLE products(title text, values multi64))");
utilsApi.Sql("CREATE TABLE products(title text, values multi64))");
utils_api.sql("CREATE TABLE products(title text, values multi64))", Some(true)).await;
table products
{
type = rt
path = products
rt_field = title
stored_fields = title
rt_attr_multi_64 = values
}
当使用列式存储时,可以为属性指定以下特性。
默认情况下,Manticore 列式存储以列式方式存储所有属性,同时也在特殊的文档存储中按行存储。这使得像 SELECT * FROM ... 这样的查询能够快速执行,尤其是在一次获取大量记录时。但是,如果您确定不需要此功能或希望节省磁盘空间,可以在创建表时通过指定 fast_fetch='0' 来禁用它,或者(如果您在配置中定义表)使用 columnar_no_fast_fetch,如下例所示。
create table t(a int, b int fast_fetch='0') engine='columnar'; desc t;
source min {
type = mysql
sql_host = localhost
sql_user = test
sql_pass =
sql_db = test
sql_query = select 1, 1 a, 1 b
sql_attr_uint = a
sql_attr_uint = b
}
table tbl {
path = tbl/col
source = min
columnar_attrs = *
columnar_no_fast_fetch = b
}
+-------+--------+---------------------+
| Field | Type | Properties |
+-------+--------+---------------------+
| id | bigint | columnar fast_fetch |
| a | uint | columnar fast_fetch |
| b | uint | columnar |
+-------+--------+---------------------+
3 rows in set (0.00 sec)
+-------+--------+---------------------+
| Field | Type | Properties |
+-------+--------+---------------------+
| id | bigint | columnar fast_fetch |
| a | uint | columnar fast_fetch |
| b | uint | columnar |
+-------+--------+---------------------+