Типы данных Manticore можно разделить на две категории: полнотекстовые поля и атрибуты.
Имена полей в Manticore должны соответствовать следующим правилам:
- Могут содержать буквы (a-z, A-Z), цифры (0-9) и дефисы (-)
- Должны начинаться с буквы
- Цифры могут появляться только после букв
- Подчеркивание (
_) — единственный разрешенный специальный символ - Имена полей нечувствительны к регистру
Например:
- Допустимые имена полей:
title,product_id,user_name_2 - Недопустимые имена полей:
2title,-price,user@name
Полнотекстовые поля:
- могут быть проиндексированы с помощью алгоритмов обработки естественного языка, поэтому по ним можно искать ключевые слова
- не могут использоваться для сортировки или группировки
- исходное содержимое документа может быть извлечено
- исходное содержимое документа может использоваться для подсветки
Полнотекстовые поля представлены типом данных text. Все остальные типы данных называются "атрибутами".
Атрибуты — это неполнотекстовые значения, связанные с каждым документом, которые можно использовать для выполнения неполнотекстовой фильтрации, сортировки и группировки во время поиска.
Часто требуется обрабатывать результаты полнотекстового поиска не только на основе совпадения ID документа и его релевантности, но и на основе ряда других значений для каждого документа. Например, может потребоваться отсортировать результаты поиска новостей по дате, а затем по релевантности, или выполнить поиск товаров в указанном диапазоне цен, или ограничить поиск по блогу записями, сделанными выбранными пользователями, или сгруппировать результаты по месяцам. Для эффективного выполнения этих задач Manticore позволяет добавлять к каждому документу не только полнотекстовые поля, но и дополнительные атрибуты. Эти атрибуты можно использовать для фильтрации, сортировки или группировки полнотекстовых совпадений или для поиска только по атрибутам.
Атрибуты, в отличие от полнотекстовых полей, не индексируются полнотекстово. Они хранятся в таблице, но по ним нельзя выполнять полнотекстовый поиск.
Хорошим примером атрибутов может служить таблица сообщений форума. Предположим, что только поля title и content должны быть доступны для полнотекстового поиска — но иногда также требуется ограничить поиск определенным автором или подфорумом (т.е. искать только те строки, которые имеют определенные значения author_id или forum_id); или отсортировать совпадения по столбцу post_date; или сгруппировать совпадающие сообщения по месяцу из post_date и вычислить количество совпадений для каждой группы.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- Javascript
- Java
- C#
- Rust
- config
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.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
select * from forum where author_id=123 and forum_id in (1,3,7) order by post_date descPOST /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 поддерживает два типа хранения атрибутов:
- построчное — традиционное хранилище, доступное в Manticore Search из коробки
- колоночное — предоставляемое Manticore Columnar Library
Как следует из их названий, они хранят данные по-разному. Традиционное построчное хранилище:
- хранит атрибуты без сжатия
- все атрибуты одного документа хранятся в одной строке рядом друг с другом
- строки хранятся одна за другой
- доступ к атрибутам осуществляется путем умножения ID строки на шаг (длину одного вектора) и получения запрашиваемого атрибута из вычисленного места в памяти. Это обеспечивает очень низкую задержку при случайном доступе.
- атрибуты должны находиться в памяти для приемлемой производительности, иначе из-за построчной природы хранилища Manticore может вынужденно считывать с диска слишком много ненужных данных, что во многих случаях неоптимально.
В колоночном хранилище:
- каждый атрибут хранится независимо от всех других атрибутов в своей отдельной "колонке"
- хранилище разбито на блоки по 65536 записей
- блоки хранятся в сжатом виде. Это часто позволяет хранить всего несколько различных значений вместо хранения всех из них, как в построчном хранилище. Высокая степень сжатия позволяет быстрее читать с диска и значительно снижает требования к памяти
- при индексации данных схема хранения выбирается для каждого блока независимо. Например, если все значения в блоке одинаковы, он получает хранилище "const", и для всего блока хранится только одно значение. Если в блоке менее 256 уникальных значений, он получает хранилище "table" и хранит индексы к таблице значений вместо самих значений
- поиск в блоке может быть досрочно прекращен, если ясно, что запрашиваемое значение отсутствует в блоке.
Колоночное хранилище было разработано для обработки больших объемов данных, которые не помещаются в оперативную память, поэтому рекомендации следующие:
- если у вас достаточно памяти для всех ваших атрибутов, вы получите выгоду от построчного хранилища
- в противном случае колоночное хранилище все еще может обеспечить достойную производительность при значительно меньшем потреблении памяти, что позволит вам хранить гораздо больше документов в вашей таблице
Традиционное построчное хранение является стандартным, поэтому если вы хотите, чтобы всё хранилось построчно, вам не нужно ничего делать при создании таблицы.
Чтобы включить колоночное хранение, вам необходимо:
- указать
engine='columnar'в CREATE TABLE, чтобы сделать все атрибуты таблицы колоночными. Затем, если вы хотите оставить конкретный атрибут построчным, вам нужно добавитьengine='rowwise'при его объявлении. Например:create table tbl(title text, type int, price float engine='rowwise') engine='columnar' - указать
engine='columnar'для конкретного атрибута вCREATE TABLE, чтобы сделать его колоночным. Например: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 mode вам нужно перечислить атрибуты, которые должны быть колоночными, в columnar_attrs.
Ниже приведен список типов данных, поддерживаемых Manticore Search:
Идентификатор документа — это обязательный атрибут, который должен быть уникальным 64-битным целым числом без знака. Идентификаторы документов могут быть явно указаны при создании таблицы, но они всегда включены, даже если не указаны. Идентификаторы документов не могут быть обновлены.
Когда вы создаете таблицу, вы можете явно указать 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)
При работе с идентификаторами документов важно знать, что они хранятся внутри как 64-битные целые числа без знака, но обрабатываются по-разному в зависимости от интерфейса:
Интерфейс MySQL/SQL:
- ID больше 2^63-1 будут отображаться как отрицательные числа.
- При фильтрации по таким большим 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 ---
Однако для ID, начиная с 2^63, вам нужно использовать знаковое значение:
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 становятся более заметными с очень большими идентификаторами документов. Вот комплексный пример:
Интерфейс 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": {}}'
Это означает, что при работе с большими идентификаторами документов:
- Интерфейс MySQL требует использования знакового представления для запросов, но может отображать беззнаковое значение с помощью
UINT64() - Интерфейс JSON последовательно использует беззнаковые значения для отображения и принимает оба представления для фильтрации
Общий синтаксис:
string|text [stored|attribute] [indexed]
Свойства:
indexed— полнотекстовый индексируемый (может использоваться в полнотекстовых запросах)stored— хранится в docstore (хранится на диске, не в оперативной памяти, ленивое чтение)attribute— делает его строковым атрибутом (можно сортировать/группировать по нему)
Указание хотя бы одного свойства переопределяет все стандартные (см. ниже), т.е. если вы решите использовать пользовательскую комбинацию свойств, вам нужно перечислить все свойства, которые вы хотите.
Свойства не указаны:
string и text являются псевдонимами, но если вы не указываете никаких свойств, они по умолчанию означают разное:
- просто
stringпо умолчанию означаетattribute(подробности ниже). - просто
textпо умолчанию означаетstored+indexed(подробности ниже).
Тип данных text (просто text или text/string indexed) формирует полнотекстовую часть таблицы. Текстовые поля индексируются, и по ним можно искать ключевые слова.
Текст проходит через конвейер анализатора, который преобразует текст в слова, применяет морфологические преобразования и т.д. В конечном итоге из этого текста строится полнотекстовая таблица (специальная структура данных, позволяющая быстро искать ключевое слово).
Полнотекстовые поля могут использоваться только в предложении MATCH() и не могут использоваться для сортировки или агрегации. Слова хранятся в инвертированном индексе вместе со ссылками на поля, к которым они принадлежат, и позициями в поле. Это позволяет искать слово внутри каждого поля и использовать расширенные операторы, такие как близость. По умолчанию исходный текст полей как индексируется, так и хранится в хранилище документов. Это означает, что исходный текст может быть возвращен с результатами запроса и использован в подсветке результатов поиска.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
- config
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
}Это поведение может быть переопределено путем явного указания, что текст только индексируется.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
- config
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 полнотекстовых полей.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
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. Это позволит выполнять полнотекстовый поиск и работать как атрибут.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
- config
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 обрезает конец строки, если встречает нулевой байт.
Вот пример, где мы кодируем команду ls с помощью base64, сохраняем её в Manticore, а затем декодируем, чтобы убедиться, что контрольная сумма MD5 осталась неизменной:
- Example
# 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 -Целочисленный тип позволяет хранить 32-битные беззнаковые целые значения.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
- config
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). Целые числа с битовым счетчиком работают медленнее, чем полноразмерные, но требуют меньше оперативной памяти. Они сохраняются в 32-битных блоках, поэтому для экономии места их следует группировать в конце определений атрибутов (иначе целое число с битовым счетчиком между двумя полноразмерными целыми числами также займет 32 бита).
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
- config
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
}Большие целые числа (bigint) — это 64-битные знаковые целые числа.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
- config
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
}Объявляет булевый атрибут. Это эквивалентно целочисленному атрибуту с битовой длиной 1.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
- config
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
}Тип timestamp представляет собой Unix-метки времени, которые хранятся как 32-битные целые числа. В отличие от базовых целых чисел, тип timestamp позволяет использовать функции времени и даты. Конвертация из строковых значений подчиняется следующим правилам:
- Числа без разделителей, длиной не менее 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, который обозначает миллисекунды.
Обратите внимание, что автоматическое преобразование меток времени не поддерживается в простых таблицах.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
- config
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
}Вещественные числа хранятся как 32-битные числа с плавающей точкой формата IEEE 754 одинарной точности.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
- config
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
}В отличие от целочисленных типов, сравнение двух чисел с плавающей точкой на равенство не рекомендуется из-за возможных ошибок округления. Более надежный подход — использовать сравнение с допуском (почти равно), проверяя абсолютную погрешность.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
select abs(a-b)<=0.00001 from productsPOST /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) для работы с целочисленными значениями.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
select in(ceil(attr*100),200,250,350) from productsPOST /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, чтобы точно представить сохраненное значение.
- Example
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 не поддерживается колоночным хранилищем, он может храниться в традиционном построчном хранилище. Стоит отметить, что оба типа хранилища могут быть объединены в одной таблице.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
- config
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
}Свойства JSON могут использоваться в большинстве операций. Также существуют специальные функции, такие как ALL(), ANY(), GREATEST(), LEAST() и INDEXOF(), которые позволяют обходить массивы свойств.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
select indexof(x>2 for x in data.intarray) from productsPOST /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().
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
select regex(data.name, 'est') as c from products where c>0POST /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;В случае свойств JSON может потребоваться приведение типа данных для корректной работы в определенных ситуациях. Например, при работе со значениями с плавающей запятой необходимо использовать DOUBLE() для правильной сортировки.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
select * from products order by double(data.myfloat) descPOST /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) несколькими важными способами:
- Сохраняет точный порядок значений (в отличие от MVA, которые могут переупорядочивать)
- Сохраняет повторяющиеся значения (в отличие от MVA, которые удаляют дубликаты)
- Не требует дополнительной обработки при вставке (в отличие от MVA, которые сортируют и удаляют дубликаты)
Важно: Тип данных float_vector несовместим с механизмом Авто-схемы.
- В настоящее время поддерживается только в real-time таблицах (не в plain таблицах)
- Не поддерживается в других функциях или выражениях
- Нельзя использовать в обычных фильтрах или сортировке
Когда вы настраиваете атрибут float_vector с параметрами KNN, вы включаете возможности поиска по векторному сходству. Это позволяет выполнять поиск k-ближайших соседей для нахождения похожих документов на основе векторного расстояния.
Что можно делать:
- Выполнять KNN-поиск (k-ближайших соседей) для нахождения похожих документов
- Создавать семантический поиск, рекомендательные системы и функции на основе ИИ
- Использовать автоэмбеддинги для автоматической генерации векторов из текста
Что нельзя делать:
UPDATEзначенияfloat_vector(вместо этого необходимо использоватьREPLACE)- Использовать векторы типа float в обычных фильтрах или сортировке
- Фильтровать по значениям
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-ключ для моделей на основе API (OpenAI, Voyage, Jina)
Для получения более подробной информации о настройке векторов с плавающей запятой и их использовании в поиске см. KNN поиск.
Самый удобный способ работы с векторами с плавающей запятой — использование автоматических эмбеддингов. Эта функция автоматически генерирует эмбеддинги из ваших текстовых данных с использованием моделей машинного обучения, устраняя необходимость вручную вычислять и вставлять векторы.
- Упрощенный рабочий процесс: Просто вставляйте текст, эмбеддинги генерируются автоматически
- Без ручного вычисления векторов: Не нужно запускать отдельные модели эмбеддингов
- Согласованные эмбеддинги: Одна и та же модель обеспечивает согласованные векторные представления
- Поддержка множества моделей: Выбирайте из моделей эмбеддингов sentence-transformers, Qwen, OpenAI, Voyage и Jina
- Гибкий выбор полей: Контролируйте, какие поля используются для генерации эмбеддингов
При создании таблицы с автоматическими эмбеддингами укажите эти дополнительные параметры:
MODEL_NAME: Модель эмбеддинга для автоматической генерации векторовFROM: Какие поля использовать для генерации эмбеддингов (пустая строка означает все текстовые/строковые поля)
Поддерживаемые модели эмбеддингов:
- Sentence Transformers: Любая подходящая модель Hugging Face на основе BERT (например,
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>'
- SQL
Использование модели 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'- используется только поле title - Несколько полей:
FROM='title,description'- оба поля 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). Эта нормализация сохраняет направление вектора, стандартизируя его длину, что необходимо для эффективных вычислений косинусного сходства. Это означает, что сохраненные значения будут отличаться от ваших исходных входных значений.
- SQL
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));Вы также можете создавать атрибуты float_vector без конфигурации KNN. В этом режиме векторы хранятся, но не могут использоваться для операций векторного поиска.
Что можно делать:
- Хранить векторные данные
UPDATEзначенияfloat_vector(в отличие от KNN, где необходимо использоватьREPLACE)
Что нельзя делать:
- Выполнять KNN-поиск или поиск по векторному сходству
- Использовать векторы для любых операций поиска
- Фильтровать по значениям
float_vector
- Временное хранение векторных данных перед настройкой KNN
- Промежуточное хранение данных, которые позже будут использоваться с KNN
- Хранение векторов, не требующих возможностей поиска
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
- config
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-битных целых чисел без знака переменной длины. Это может быть полезно для хранения числовых значений "один ко многим", таких как теги, категории продуктов и свойства.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
- config
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()) прошли.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
select * from products where any(product_codes)=3POST /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;Информацию, такую как наименьший или наибольший элемент и длина списка, можно извлечь. Пример показывает упорядочивание по наименьшему элементу многозначного атрибута.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
select least(product_codes) l from products order by l ascPOST /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 соответственно. Также обратите внимание, что группировка по многозначным атрибутам может привести к дублированию документов в результирующем наборе, поскольку каждый документ может участвовать во многих группах.
- SQL
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)Порядок чисел, вставленных в качестве значений многозначных атрибутов, не сохраняется. Значения хранятся внутри как отсортированный набор.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
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-битных знаковых целых чисел переменной длины. Имеет ту же функциональность, что и многозначное целое.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
- config
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, как показано в следующем примере.
- RT mode
- Plain mode
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 |
+-------+--------+---------------------+Типы данных Manticore можно разделить на две категории: полнотекстовые поля и атрибуты.
Имена полей в Manticore должны соответствовать следующим правилам:
- Могут содержать буквы (a-z, A-Z), цифры (0-9) и дефисы (-)
- Должны начинаться с буквы
- Цифры могут появляться только после букв
- Подчеркивание (
_) — единственный разрешенный специальный символ - Имена полей нечувствительны к регистру
Например:
- Допустимые имена полей:
title,product_id,user_name_2 - Недопустимые имена полей:
2title,-price,user@name
Полнотекстовые поля:
- могут быть проиндексированы с помощью алгоритмов обработки естественного языка, поэтому по ним можно искать ключевые слова
- не могут использоваться для сортировки или группировки
- исходное содержимое документа может быть извлечено
- исходное содержимое документа может использоваться для подсветки
Полнотекстовые поля представлены типом данных text. Все остальные типы данных называются "атрибутами".
Атрибуты — это неполнотекстовые значения, связанные с каждым документом, которые можно использовать для выполнения неполнотекстовой фильтрации, сортировки и группировки во время поиска.
Часто требуется обрабатывать результаты полнотекстового поиска не только на основе совпадения ID документа и его релевантности, но и на основе ряда других значений для каждого документа. Например, может потребоваться отсортировать результаты поиска новостей по дате, а затем по релевантности, или выполнить поиск товаров в указанном диапазоне цен, или ограничить поиск по блогу записями, сделанными выбранными пользователями, или сгруппировать результаты по месяцам. Для эффективного выполнения этих задач Manticore позволяет добавлять к каждому документу не только полнотекстовые поля, но и дополнительные атрибуты. Эти атрибуты можно использовать для фильтрации, сортировки или группировки полнотекстовых совпадений или для поиска только по атрибутам.
Атрибуты, в отличие от полнотекстовых полей, не индексируются полнотекстово. Они хранятся в таблице, но по ним нельзя выполнять полнотекстовый поиск.
Хорошим примером атрибутов может служить таблица сообщений форума. Предположим, что только поля title и content должны быть доступны для полнотекстового поиска — но иногда также требуется ограничить поиск определенным автором или подфорумом (т.е. искать только те строки, которые имеют определенные значения author_id или forum_id); или отсортировать совпадения по столбцу post_date; или сгруппировать совпадающие сообщения по месяцу из post_date и вычислить количество совпадений для каждой группы.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- Javascript
- Java
- C#
- Rust
- config
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.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
select * from forum where author_id=123 and forum_id in (1,3,7) order by post_date descPOST /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 поддерживает два типа хранения атрибутов:
- построчное — традиционное хранилище, доступное в Manticore Search из коробки
- колоночное — предоставляемое Manticore Columnar Library
Как следует из их названий, они хранят данные по-разному. Традиционное построчное хранилище:
- хранит атрибуты без сжатия
- все атрибуты одного документа хранятся в одной строке рядом друг с другом
- строки хранятся одна за другой
- доступ к атрибутам осуществляется путем умножения ID строки на шаг (длину одного вектора) и получения запрашиваемого атрибута из вычисленного места в памяти. Это обеспечивает очень низкую задержку при случайном доступе.
- атрибуты должны находиться в памяти для приемлемой производительности, иначе из-за построчной природы хранилища Manticore может вынужденно считывать с диска слишком много ненужных данных, что во многих случаях неоптимально.
В колоночном хранилище:
- каждый атрибут хранится независимо от всех других атрибутов в своей отдельной "колонке"
- хранилище разбито на блоки по 65536 записей
- блоки хранятся в сжатом виде. Это часто позволяет хранить всего несколько различных значений вместо хранения всех из них, как в построчном хранилище. Высокая степень сжатия позволяет быстрее читать с диска и значительно снижает требования к памяти
- при индексации данных схема хранения выбирается для каждого блока независимо. Например, если все значения в блоке одинаковы, он получает хранилище "const", и для всего блока хранится только одно значение. Если в блоке менее 256 уникальных значений, он получает хранилище "table" и хранит индексы к таблице значений вместо самих значений
- поиск в блоке может быть досрочно прекращен, если ясно, что запрашиваемое значение отсутствует в блоке.
Колоночное хранилище было разработано для обработки больших объемов данных, которые не помещаются в оперативную память, поэтому рекомендации следующие:
- если у вас достаточно памяти для всех ваших атрибутов, вы получите выгоду от построчного хранилища
- в противном случае колоночное хранилище все еще может обеспечить достойную производительность при значительно меньшем потреблении памяти, что позволит вам хранить гораздо больше документов в вашей таблице
Традиционное построчное хранение является стандартным, поэтому если вы хотите, чтобы всё хранилось построчно, вам не нужно ничего делать при создании таблицы.
Чтобы включить колоночное хранение, вам необходимо:
- указать
engine='columnar'в CREATE TABLE, чтобы сделать все атрибуты таблицы колоночными. Затем, если вы хотите оставить конкретный атрибут построчным, вам нужно добавитьengine='rowwise'при его объявлении. Например:create table tbl(title text, type int, price float engine='rowwise') engine='columnar' - указать
engine='columnar'для конкретного атрибута вCREATE TABLE, чтобы сделать его колоночным. Например: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 mode вам нужно перечислить атрибуты, которые должны быть колоночными, в columnar_attrs.
Ниже приведен список типов данных, поддерживаемых Manticore Search:
Идентификатор документа — это обязательный атрибут, который должен быть уникальным 64-битным целым числом без знака. Идентификаторы документов могут быть явно указаны при создании таблицы, но они всегда включены, даже если не указаны. Идентификаторы документов не могут быть обновлены.
Когда вы создаете таблицу, вы можете явно указать 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)
При работе с идентификаторами документов важно знать, что они хранятся внутри как 64-битные целые числа без знака, но обрабатываются по-разному в зависимости от интерфейса:
Интерфейс MySQL/SQL:
- ID больше 2^63-1 будут отображаться как отрицательные числа.
- При фильтрации по таким большим 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 ---
Однако для ID, начиная с 2^63, вам нужно использовать знаковое значение:
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 становятся более заметными с очень большими идентификаторами документов. Вот комплексный пример:
Интерфейс 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": {}}'
Это означает, что при работе с большими идентификаторами документов:
- Интерфейс MySQL требует использования знакового представления для запросов, но может отображать беззнаковое значение с помощью
UINT64() - Интерфейс JSON последовательно использует беззнаковые значения для отображения и принимает оба представления для фильтрации
Общий синтаксис:
string|text [stored|attribute] [indexed]
Свойства:
indexed— полнотекстовый индексируемый (может использоваться в полнотекстовых запросах)stored— хранится в docstore (хранится на диске, не в оперативной памяти, ленивое чтение)attribute— делает его строковым атрибутом (можно сортировать/группировать по нему)
Указание хотя бы одного свойства переопределяет все стандартные (см. ниже), т.е. если вы решите использовать пользовательскую комбинацию свойств, вам нужно перечислить все свойства, которые вы хотите.
Свойства не указаны:
string и text являются псевдонимами, но если вы не указываете никаких свойств, они по умолчанию означают разное:
- просто
stringпо умолчанию означаетattribute(подробности ниже). - просто
textпо умолчанию означаетstored+indexed(подробности ниже).
Тип данных text (просто text или text/string indexed) формирует полнотекстовую часть таблицы. Текстовые поля индексируются, и по ним можно искать ключевые слова.
Текст проходит через конвейер анализатора, который преобразует текст в слова, применяет морфологические преобразования и т.д. В конечном итоге из этого текста строится полнотекстовая таблица (специальная структура данных, позволяющая быстро искать ключевое слово).
Полнотекстовые поля могут использоваться только в предложении MATCH() и не могут использоваться для сортировки или агрегации. Слова хранятся в инвертированном индексе вместе со ссылками на поля, к которым они принадлежат, и позициями в поле. Это позволяет искать слово внутри каждого поля и использовать расширенные операторы, такие как близость. По умолчанию исходный текст полей как индексируется, так и хранится в хранилище документов. Это означает, что исходный текст может быть возвращен с результатами запроса и использован в подсветке результатов поиска.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
- config
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
}Это поведение может быть переопределено путем явного указания, что текст только индексируется.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
- config
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 полнотекстовых полей.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
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. Это позволит выполнять полнотекстовый поиск и работать как атрибут.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
- config
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 обрезает конец строки, если встречает нулевой байт.
Вот пример, где мы кодируем команду ls с помощью base64, сохраняем её в Manticore, а затем декодируем, чтобы убедиться, что контрольная сумма MD5 осталась неизменной:
- Example
# 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 -Целочисленный тип позволяет хранить 32-битные беззнаковые целые значения.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
- config
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). Целые числа с битовым счетчиком работают медленнее, чем полноразмерные, но требуют меньше оперативной памяти. Они сохраняются в 32-битных блоках, поэтому для экономии места их следует группировать в конце определений атрибутов (иначе целое число с битовым счетчиком между двумя полноразмерными целыми числами также займет 32 бита).
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
- config
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
}Большие целые числа (bigint) — это 64-битные знаковые целые числа.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
- config
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
}Объявляет булевый атрибут. Это эквивалентно целочисленному атрибуту с битовой длиной 1.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
- config
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
}Тип timestamp представляет собой Unix-метки времени, которые хранятся как 32-битные целые числа. В отличие от базовых целых чисел, тип timestamp позволяет использовать функции времени и даты. Конвертация из строковых значений подчиняется следующим правилам:
- Числа без разделителей, длиной не менее 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, который обозначает миллисекунды.
Обратите внимание, что автоматическое преобразование меток времени не поддерживается в простых таблицах.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
- config
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
}Вещественные числа хранятся как 32-битные числа с плавающей точкой формата IEEE 754 одинарной точности.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
- config
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
}В отличие от целочисленных типов, сравнение двух чисел с плавающей точкой на равенство не рекомендуется из-за возможных ошибок округления. Более надежный подход — использовать сравнение с допуском (почти равно), проверяя абсолютную погрешность.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
select abs(a-b)<=0.00001 from productsPOST /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) для работы с целочисленными значениями.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
select in(ceil(attr*100),200,250,350) from productsPOST /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, чтобы точно представить сохраненное значение.
- Example
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 не поддерживается колоночным хранилищем, он может храниться в традиционном построчном хранилище. Стоит отметить, что оба типа хранилища могут быть объединены в одной таблице.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
- config
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
}Свойства JSON могут использоваться в большинстве операций. Также существуют специальные функции, такие как ALL(), ANY(), GREATEST(), LEAST() и INDEXOF(), которые позволяют обходить массивы свойств.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
select indexof(x>2 for x in data.intarray) from productsPOST /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().
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
select regex(data.name, 'est') as c from products where c>0POST /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;В случае свойств JSON может потребоваться приведение типа данных для корректной работы в определенных ситуациях. Например, при работе со значениями с плавающей запятой необходимо использовать DOUBLE() для правильной сортировки.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
select * from products order by double(data.myfloat) descPOST /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) несколькими важными способами:
- Сохраняет точный порядок значений (в отличие от MVA, которые могут переупорядочивать)
- Сохраняет повторяющиеся значения (в отличие от MVA, которые удаляют дубликаты)
- Не требует дополнительной обработки при вставке (в отличие от MVA, которые сортируют и удаляют дубликаты)
Важно: Тип данных float_vector несовместим с механизмом Авто-схемы.
- В настоящее время поддерживается только в real-time таблицах (не в plain таблицах)
- Не поддерживается в других функциях или выражениях
- Нельзя использовать в обычных фильтрах или сортировке
Когда вы настраиваете атрибут float_vector с параметрами KNN, вы включаете возможности поиска по векторному сходству. Это позволяет выполнять поиск k-ближайших соседей для нахождения похожих документов на основе векторного расстояния.
Что можно делать:
- Выполнять KNN-поиск (k-ближайших соседей) для нахождения похожих документов
- Создавать семантический поиск, рекомендательные системы и функции на основе ИИ
- Использовать автоэмбеддинги для автоматической генерации векторов из текста
Что нельзя делать:
UPDATEзначенияfloat_vector(вместо этого необходимо использоватьREPLACE)- Использовать векторы типа float в обычных фильтрах или сортировке
- Фильтровать по значениям
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-ключ для моделей на основе API (OpenAI, Voyage, Jina)
Для получения более подробной информации о настройке векторов с плавающей запятой и их использовании в поиске см. KNN поиск.
Самый удобный способ работы с векторами с плавающей запятой — использование автоматических эмбеддингов. Эта функция автоматически генерирует эмбеддинги из ваших текстовых данных с использованием моделей машинного обучения, устраняя необходимость вручную вычислять и вставлять векторы.
- Упрощенный рабочий процесс: Просто вставляйте текст, эмбеддинги генерируются автоматически
- Без ручного вычисления векторов: Не нужно запускать отдельные модели эмбеддингов
- Согласованные эмбеддинги: Одна и та же модель обеспечивает согласованные векторные представления
- Поддержка множества моделей: Выбирайте из моделей эмбеддингов sentence-transformers, Qwen, OpenAI, Voyage и Jina
- Гибкий выбор полей: Контролируйте, какие поля используются для генерации эмбеддингов
При создании таблицы с автоматическими эмбеддингами укажите эти дополнительные параметры:
MODEL_NAME: Модель эмбеддинга для автоматической генерации векторовFROM: Какие поля использовать для генерации эмбеддингов (пустая строка означает все текстовые/строковые поля)
Поддерживаемые модели эмбеддингов:
- Sentence Transformers: Любая подходящая модель Hugging Face на основе BERT (например,
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>'
- SQL
Использование модели 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'- используется только поле title - Несколько полей:
FROM='title,description'- оба поля 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). Эта нормализация сохраняет направление вектора, стандартизируя его длину, что необходимо для эффективных вычислений косинусного сходства. Это означает, что сохраненные значения будут отличаться от ваших исходных входных значений.
- SQL
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));Вы также можете создавать атрибуты float_vector без конфигурации KNN. В этом режиме векторы хранятся, но не могут использоваться для операций векторного поиска.
Что можно делать:
- Хранить векторные данные
UPDATEзначенияfloat_vector(в отличие от KNN, где необходимо использоватьREPLACE)
Что нельзя делать:
- Выполнять KNN-поиск или поиск по векторному сходству
- Использовать векторы для любых операций поиска
- Фильтровать по значениям
float_vector
- Временное хранение векторных данных перед настройкой KNN
- Промежуточное хранение данных, которые позже будут использоваться с KNN
- Хранение векторов, не требующих возможностей поиска
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
- config
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-битных целых чисел без знака переменной длины. Это может быть полезно для хранения числовых значений "один ко многим", таких как теги, категории продуктов и свойства.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
- config
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()) прошли.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
select * from products where any(product_codes)=3POST /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;Информацию, такую как наименьший или наибольший элемент и длина списка, можно извлечь. Пример показывает упорядочивание по наименьшему элементу многозначного атрибута.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
select least(product_codes) l from products order by l ascPOST /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 соответственно. Также обратите внимание, что группировка по многозначным атрибутам может привести к дублированию документов в результирующем наборе, поскольку каждый документ может участвовать во многих группах.
- SQL
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)Порядок чисел, вставленных в качестве значений многозначных атрибутов, не сохраняется. Значения хранятся внутри как отсортированный набор.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
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-битных знаковых целых чисел переменной длины. Имеет ту же функциональность, что и многозначное целое.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
- config
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, как показано в следующем примере.
- RT mode
- Plain mode
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 |
+-------+--------+---------------------+В Manticore Search есть два способа управления таблицами:
Режим реального времени не требует определения таблицы в файле конфигурации. Однако директива data_dir в разделе searchd является обязательной. Файлы таблиц хранятся внутри data_dir.
Репликация доступна только в этом режиме.
Вы можете использовать SQL-команды, такие как CREATE TABLE, ALTER TABLE и DROP TABLE, для создания и изменения схемы таблицы, а также для её удаления. Этот режим особенно полезен для таблиц реального времени и перколяционных таблиц.
Имена таблиц при создании преобразуются в нижний регистр.
В этом режиме вы можете указать схему таблицы в файле конфигурации. Manticore читает эту схему при запуске и создаёт таблицу, если она ещё не существует. Этот режим особенно полезен для обычных таблиц, которые используют данные из внешнего хранилища.
Чтобы удалить таблицу, исключите её из файла конфигурации или удалите настройку пути и отправьте серверу сигнал HUP или перезапустите его.
В этом режиме имена таблиц чувствительны к регистру.
Все типы таблиц поддерживаются в этом режиме.
| Тип таблицы | Режим RT | Режим Plain |
|---|---|---|
| Real-time | поддерживается | поддерживается |
| Plain | не поддерживается | поддерживается |
| Percolate | поддерживается | поддерживается |
| Distributed | поддерживается | поддерживается |
| Template | не поддерживается | поддерживается |
Таблица реального времени — это основной тип таблицы в Manticore. Она позволяет добавлять, обновлять и удалять документы, и вы можете видеть эти изменения сразу. Вы можете настроить таблицу реального времени в конфигурационном файле или использовать команды, такие как CREATE, UPDATE, DELETE или ALTER.
Внутренне таблица реального времени состоит из одной или нескольких обычных таблиц, называемых чанками. Существует два вида чанков:
- несколько дисковых чанков — они сохраняются на диске и структурированы как обычная таблица.
- один оперативный чанк — он хранится в памяти и собирает все изменения.
Размер оперативного чанка контролируется настройкой rt_mem_limit. Как только этот лимит достигается, оперативный чанк переносится на диск в качестве дискового чанка. Если дисковых чанков становится слишком много, Manticore объединяет некоторые из них для повышения производительности.
Вы можете создать новую таблицу реального времени двумя способами: с помощью команды CREATE TABLE или через _mapping endpoint HTTP JSON API.
Вы можете использовать эту команду как через SQL, так и через HTTP протоколы:
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- Javascript
- Java
- C#
- Rust
- Typescript
- Go
- CONFIG
CREATE TABLE products(title text, price float) morphology='stem_en';POST /cli -d "CREATE TABLE products(title text, price float) morphology='stem_en'"$index = new \Manticoresearch\Index($client);
$index->setName('products');
$index->create([
'title'=>['type'=>'text'],
'price'=>['type'=>'float'],
]);utilsApi.sql('CREATE TABLE forum(title text, price float)')await utilsApi.sql('CREATE TABLE forum(title text, price float)')res = await utilsApi.sql('CREATE TABLE forum(title text, price float)');utilsApi.sql("CREATE TABLE forum(title text, price float)");utilsApi.Sql("CREATE TABLE forum(title text, price float)");utils_api.sql("CREATE TABLE forum(title text, price float)", Some(true)).await;res = await utilsApi.sql('CREATE TABLE forum(title text, price float)');utilsAPI.Sql(context.Background()).Body("CREATE TABLE forum(title text, price float)").Execute()table products {
type = rt
path = tbl
rt_field = title
rt_attr_uint = price
stored_fields = title
}Query OK, 0 rows affected (0.00 sec){
"total":0,
"error":"",
"warning":""
}ПРИМЕЧАНИЕ: API
_mappingтребует наличия Manticore Buddy. Если оно не работает, убедитесь, что Buddy установлен.
В качестве альтернативы вы можете создать новую таблицу через endpoint _mapping. Этот endpoint позволяет определить структуру таблицы, подобную Elasticsearch, для преобразования в таблицу Manticore.
Тело вашего запроса должно иметь следующую структуру:
"properties"
{
"FIELD_NAME_1":
{
"type": "FIELD_TYPE_1"
},
"FIELD_NAME_2":
{
"type": "FIELD_TYPE_2"
},
...
"FIELD_NAME_N":
{
"type": "FIELD_TYPE_M"
}
}
При создании таблицы типы данных Elasticsearch будут сопоставлены с типами Manticore в соответствии со следующими правилами:
- aggregate_metric => json
- binary => string
- boolean => bool
- byte => int
- completion => string
- date => timestamp
- date_nanos => bigint
- date_range => json
- dense_vector => json
- flattened => json
- flat_object => json
- float => float
- float_range => json
- geo_point => json
- geo_shape => json
- half_float => float
- histogram => json
- integer => int
- integer_range => json
- ip => string
- ip_range => json
- keyword => string
- knn_vector => float_vector
- long => bigint
- long_range => json
- match_only_text => text
- object => json
- point => json
- scaled_float => float
- search_as_you_type => text
- shape => json
- short => int
- text => text
- unsigned_long => int
- version => string
- JSON
POST /your_table_name/_mapping -d '
{
"properties": {
"price": {
"type": "float"
},
"title": {
"type": "text"
}
}
}
'{
"total":0,
"error":"",
"warning":""
}Вы можете создать копию таблицы реального времени, с данными или без них. Обратите внимание, что если таблица большая, копирование с данными может занять некоторое время. Копирование работает в синхронном режиме, но если соединение прервано, оно продолжится в фоновом режиме.
CREATE TABLE [IF NOT EXISTS] table_name LIKE old_table_name [WITH DATA]
ПРИМЕЧАНИЕ: Копирование таблицы требует наличия Manticore Buddy. Если оно не работает, убедитесь, что Buddy установлен.
- SQL
- Example (WITH DATA)
create table products LIKE old_products;create table products LIKE old_products WITH DATA;- Добавлять документы.
- Обновлять атрибуты и полнотекстовые поля с помощью процесса Update.
- Удалять документы.
- Очищать таблицу.
- Изменять схему онлайн с помощью команды
ALTER, как описано в Изменение схемы онлайн. - Определять таблицу в конфигурационном файле, как подробно описано в Определение таблицы.
- Использовать функцию UUID для автоматического назначения ID.
- Загружать данные с помощью функции indexer.
- Подключать её к источникам для удобного индексирования из внешнего хранилища.
- Обновлять killlist_target, так как он автоматически управляется таблицей реального времени.
В следующей таблице приведены различные расширения файлов и их описание в таблице реального времени:
| Расширение | Описание |
|---|---|
.lock |
Файл блокировки, который гарантирует, что только один процесс может получить доступ к таблице одновременно. |
.ram |
RAM-чанк таблицы, хранящийся в памяти и используемый как аккумулятор изменений. |
.meta |
Заголовки таблицы реального времени, определяющие её структуру и настройки. |
.*.sp* |
Дисковые чанки, которые хранятся на диске в том же формате, что и обычные таблицы. Они создаются, когда размер RAM-чанка превышает rt_mem_limit. |
Для получения дополнительной информации о структуре дисковых чанков смотрите структуру файлов обычной таблицы.