Объединения таблиц в Manticore Search позволяют комбинировать документы из двух таблиц, сопоставляя связанные столбцы. Эта функциональность обеспечивает более сложные запросы и улучшенный поиск данных по нескольким таблицам.
SELECT
select_expr [, select_expr] ...
FROM tbl_name
{INNER | LEFT} JOIN tbl2_name
ON join_condition
[...other select options]
join_condition: {
left_table.attr = right_table.attr
| left_table.json_attr.string_id = string(right_table.json_attr.string_id)
| left_table.json_attr.int_id = int(right_table.json_attr.int_id)
| [..фильтры по атрибутам правой таблицы]
}
Для получения дополнительной информации о параметрах select обратитесь к разделу SELECT.
При объединении по значению из JSON-атрибута необходимо явно указать тип значения с помощью функции int() или string().
SELECT ... ON left_table.json_attr.string_id = string(right_table.json_attr.string_id)
SELECT ... ON left_table.json_attr.int_id = int(right_table.json_attr.int_id)
POST /search
{
"table": "table_name",
"query": {
<optional full-text query against the left table>
},
"join": [
{
"type": "inner" | "left",
"table": "joined_table_name",
"query": {
<optional full-text query against the right table>
},
"on": [
{
"left": {
"table": "left_table_name",
"field": "field_name",
"type": "<common field's type when joining using json attributes>"
},
"operator": "eq",
"right": {
"table": "right_table_name",
"field": "field_name"
}
}
]
}
],
"options": {
...
}
}
on.type: {
int
| string
}
Обратите внимание, что в разделе операнда left есть поле type, которое следует использовать при объединении двух таблиц с использованием json-атрибутов. Допустимые значения — string и int.
Manticore Search поддерживает два типа объединений:
- INNER JOIN: Возвращает только строки, где есть совпадение в обеих таблицах. Например, запрос выполняет INNER JOIN между таблицами
ordersиcustomers, включая только заказы, у которых есть соответствующие клиенты.
- SQL
- JSON
SELECT product, customers.email, customers.name, customers.address
FROM orders
INNER JOIN customers
ON customers.id = orders.customer_id
WHERE MATCH('maple', customers)
ORDER BY customers.email ASC;POST /search
{
"table": "orders",
"join": [
{
"type": "inner",
"table": "customers",
"query": {
"query_string": "maple"
},
"on": [
{
"left": {
"table": "orders",
"field": "customer_id"
},
"operator": "eq",
"right": {
"table": "customers",
"field": "id"
}
}
]
}
],
"_source": ["product", "customers.email", "customers.name", "customers.address"],
"sort": [{"customers.email": "asc"}]
}+---------+-------------------+----------------+-------------------+
| product | customers.email | customers.name | customers.address |
+---------+-------------------+----------------+-------------------+
| Laptop | alice@example.com | Alice Johnson | 123 Maple St |
| Tablet | alice@example.com | Alice Johnson | 123 Maple St |
+---------+-------------------+----------------+-------------------+
2 rows in set (0.00 sec){
"took": 0,
"timed_out": false,
"hits": {
"total": 2,
"total_relation": "eq",
"hits": [
{
"_id": 1,
"_score": 1,
"_source": {
"product": "Laptop",
"customers.email": "alice@example.com",
"customers.name": "Alice Johnson",
"customers.address": "123 Maple St"
}
},
{
"_id": 3,
"_score": 1,
"_source": {
"product": "Tablet",
"customers.email": "alice@example.com",
"customers.name": "Alice Johnson",
"customers.address": "123 Maple St"
}
}
]
}
}- LEFT JOIN: Возвращает все строки из левой таблицы и совпадающие строки из правой таблицы. Если совпадения нет, для столбцов правой таблицы возвращаются значения NULL. Например, этот запрос извлекает всех клиентов вместе с их соответствующими заказами с помощью LEFT JOIN. Если соответствующего заказа нет, появятся значения NULL. Результаты сортируются по электронной почте клиента, и выбираются только имя клиента и количество заказов.
- SQL
- JSON
SELECT
name, orders.quantity
FROM customers
LEFT JOIN orders
ON orders.customer_id = customers.id
ORDER BY email ASC;POST /search
{
"table": "customers",
"_source": ["name", "orders.quantity"],
"join": [
{
"type": "left",
"table": "orders",
"on": [
{
"left": {
"table": "orders",
"field": "customer_id"
},
"operator": "eq",
"right": {
"table": "customers",
"field": "id"
}
}
]
}
],
"sort": [{"email": "asc"}]
}+---------------+-----------------+-------------------+
| name | orders.quantity | @int_attr_email |
+---------------+-----------------+-------------------+
| Alice Johnson | 1 | alice@example.com |
| Alice Johnson | 1 | alice@example.com |
| Bob Smith | 2 | bob@example.com |
| Carol White | 1 | carol@example.com |
| John Smith | NULL | john@example.com |
+---------------+-----------------+-------------------+
5 rows in set (0.00 sec){
"took": 0,
"timed_out": false,
"hits": {
"total": 5,
"total_relation": "eq",
"hits": [
{
"_id": 1,
"_score": 1,
"_source": {
"name": "Alice Johnson",
"address": "123 Maple St",
"email": "alice@example.com",
"orders.id": 3,
"orders.customer_id": 1,
"orders.quantity": 1,
"orders.order_date": "2023-01-03",
"orders.tags": [
101,
104
],
"orders.details": {
"price": 450,
"warranty": "1 year"
},
"orders.product": "Tablet"
}
},
{
"_id": 1,
"_score": 1,
"_source": {
"name": "Alice Johnson",
"address": "123 Maple St",
"email": "alice@example.com",
"orders.id": 1,
"orders.customer_id": 1,
"orders.quantity": 1,
"orders.order_date": "2023-01-01",
"orders.tags": [
101,
102
],
"orders.details": {
"price": 1200,
"warranty": "2 years"
},
"orders.product": "Laptop"
}
},
{
"_id": 2,
"_score": 1,
"_source": {
"name": "Bob Smith",
"address": "456 Oak St",
"email": "bob@example.com",
"orders.id": 2,
"orders.customer_id": 2,
"orders.quantity": 2,
"orders.order_date": "2023-01-02",
"orders.tags": [
103
],
"orders.details": {
"price": 800,
"warranty": "1 year"
},
"orders.product": "Phone"
}
},
{
"_id": 3,
"_score": 1,
"_source": {
"name": "Carol White",
"address": "789 Pine St",
"email": "carol@example.com",
"orders.id": 4,
"orders.customer_id": 3,
"orders.quantity": 1,
"orders.order_date": "2023-01-04",
"orders.tags": [
105
],
"orders.details": {
"price": 300,
"warranty": "1 year"
},
"orders.product": "Monitor"
}
},
{
"_id": 4,
"_score": 1,
"_source": {
"name": "John Smith",
"address": "15 Barclays St",
"email": "john@example.com",
"orders.id": 0,
"orders.customer_id": 0,
"orders.quantity": 0,
"orders.order_date": "",
"orders.tags": [],
"orders.details": null,
"orders.product": ""
}
}
]
}
}Одна из мощных возможностей объединений таблиц в Manticore Search — это возможность выполнять полнотекстовый поиск одновременно по левой и правой таблицам. Это позволяет создавать сложные запросы, фильтрующие данные на основе текстового содержимого в нескольких таблицах.
Вы можете использовать отдельные функции MATCH() для каждой таблицы в вашем JOIN-запросе. Запрос фильтрует результаты на основе текстового содержимого в обеих таблицах.
- SQL
- JSON
SELECT t1.f, t2.f
FROM t1
LEFT JOIN t2 ON t1.id = t2.id
WHERE MATCH('hello', t1) AND MATCH('goodbye', t2);POST /search
{
"table": "t1",
"query": {
"query_string": "hello"
},
"join": [
{
"type": "left",
"table": "t2",
"query": {
"query_string": "goodbye"
},
"on": [
{
"left": {
"table": "t1",
"field": "id"
},
"operator": "eq",
"right": {
"table": "t2",
"field": "id"
}
}
]
}
],
"_source": ["f", "t2.f"]
}+-------------+---------------+
| f | t2.f |
+-------------+---------------+
| hello world | goodbye world |
+-------------+---------------+
1 row in set (0.00 sec){
"took": 1,
"timed_out": false,
"hits": {
"total": 1,
"total_relation": "eq",
"hits": [
{
"_id": 2,
"_score": 1680,
"t2._score": 1680,
"_source": {
"f": "hello world",
"t2.f": "goodbye world"
}
}
]
}
}В JSON API запросах полнотекстовый поиск по таблицам структурируется иначе, чем в SQL:
Запрос основной таблицы: Поле "query" на корневом уровне применяется к основной таблице (указанной в "table").
Запрос объединённой таблицы: Каждое определение объединения может включать собственное поле "query", которое применяется конкретно к этой объединённой таблице.
- JSON
POST /search
{
"table": "t1",
"query": {
"query_string": "hello"
},
"join": [
{
"type": "left",
"table": "t2",
"query": {
"match": {
"*": "goodbye"
}
},
"on": [
{
"left": {
"table": "t1",
"field": "id"
},
"operator": "eq",
"right": {
"table": "t2",
"field": "id"
}
}
]
}
]
}{
"took": 1,
"timed_out": false,
"hits": {
"total": 1,
"total_relation": "eq",
"hits": [
{
"_id": 1,
"_score": 1680,
"t2._score": 1680,
"_source": {
"f": "hello world",
"t2.id": 1,
"t2.f": "goodbye world"
}
}
]
}
}1. Запрос только к основной таблице: Возвращает все совпадающие строки из основной таблицы. Для несопоставленных записей объединённой таблицы (LEFT JOIN) SQL возвращает значения NULL, а JSON API — значения по умолчанию (0 для чисел, пустые строки для текста).
- SQL
- JSON
SELECT * FROM t1
LEFT JOIN t2 ON t1.id = t2.id
WHERE MATCH('database', t1);POST /search
{
"table": "t1",
"query": {
"query_string": "database"
},
"join": [
{
"type": "left",
"table": "t2",
"on": [
{
"left": {
"table": "t1",
"field": "id"
},
"operator": "eq",
"right": {
"table": "t2",
"field": "id"
}
}
]
}
]
}+------+-----------------+-------+------+
| id | f | t2.id | t2.f |
+------+-----------------+-------+------+
| 3 | database search | NULL | NULL |
+------+-----------------+-------+------+
1 row in set (0.00 sec){
"took": 0,
"timed_out": false,
"hits": {
"total": 1,
"total_relation": "eq",
"hits": [
{
"_id": 3,
"_score": 1680,
"t2._score": 0,
"_source": {
"f": "database search",
"t2.id": 0,
"t2.f": ""
}
}
]
}
}2. Запрос к объединённой таблице действует как фильтр: Когда у объединённой таблицы есть запрос, возвращаются только записи, удовлетворяющие как условию объединения, так и условию запроса.
- JSON
POST /search
{
"table": "t1",
"query": {
"query_string": "database"
},
"join": [
{
"type": "left",
"table": "t2",
"query": {
"query_string": "nonexistent"
},
"on": [
{
"left": {
"table": "t1",
"field": "id"
},
"operator": "eq",
"right": {
"table": "t2",
"field": "id"
}
}
]
}
]
}{
"took": 0,
"timed_out": false,
"hits": {
"total": 0,
"total_relation": "eq",
"hits": []
}
}3. Тип JOIN влияет на фильтрацию: INNER JOIN требует выполнения условий объединения и запроса, тогда как LEFT JOIN возвращает совпадающие строки левой таблицы даже при несоответствии условий правой таблицы.
При использовании полнотекстового поиска с объединениями учитывайте следующие моменты:
-
Поиск по конкретным таблицам:
- SQL: Каждая функция
MATCH()должна указывать, в какой таблице искать:MATCH('term', table_name) - JSON: Используйте корневое поле
"query"для основной таблицы и поле"query"внутри каждого определения объединения для объединённых таблиц
- SQL: Каждая функция
-
Гибкость синтаксиса запроса: JSON API поддерживает синтаксис как
"query_string", так и"match"для полнотекстовых запросов -
Влияние на производительность: Полнотекстовый поиск по обеим таблицам может повлиять на производительность запроса, особенно при больших объёмах данных. Рекомендуется использовать соответствующие индексы и размеры пакетов.
-
Обработка NULL/значений по умолчанию: При LEFT JOIN, если нет совпадающей записи в правой таблице, оптимизатор запроса решает, сначала ли оценивать полнотекстовые условия или условия фильтрации, исходя из производительности. SQL возвращает значения NULL, а JSON API — значения по умолчанию (0 для чисел, пустые строки для текста).
-
Поведение фильтрации: Запросы к объединённым таблицам действуют как фильтры — они ограничивают результаты записями, удовлетворяющими и условиям объединения, и условиям запроса.
-
Поддержка полнотекстовых операторов: Все операторы полнотекстового поиска поддерживаются в JOIN-запросах, включая фразы, близость, поиск по полям, NEAR, кворум и расширенные операторы.
-
Вычисление оценки релевантности: Каждая таблица поддерживает собственный балл релевантности, доступный через
table_name.weight()в SQL илиtable_name._scoreв JSON-ответах.
Опираясь на предыдущие примеры, рассмотрим более продвинутый сценарий, где мы комбинируем объединения таблиц с фасетированием и полнотекстовым поиском по нескольким таблицам. Это демонстрирует всю мощь возможностей JOIN в Manticore с комплексной фильтрацией и агрегацией.
Этот запрос демонстрирует полнотекстовый поиск по таблицам customers и orders, объединённый с фильтрацией по диапазону и фасетным поиском. Он ищет клиентов с именами "Alice" или "Bob" и их заказы, содержащие "laptop", "phone" или "tablet" с ценами выше $500. Результаты упорядочены по ID заказа и фасетированы по условиям гарантии.
- SQL
- JSON
SELECT orders.product, name, orders.details.price, orders.tags
FROM customers
LEFT JOIN orders ON customers.id = orders.customer_id
WHERE orders.details.price > 500
AND MATCH('laptop | phone | tablet', orders)
AND MATCH('alice | bob', customers)
ORDER BY orders.id ASC
FACET orders.details.warranty;POST /search
{
"table": "customers",
"query": {
"bool": {
"must": [
{
"range": {
"orders.details.price": {
"gt": 500
}
},
"query_string": "alice | bob"
]
}
},
"join": [
{
"type": "left",
"table": "orders",
"query": {
"query_string": "laptop | phone | tablet"
},
"on": [
{
"left": {
"table": "customers",
"field": "id"
},
"operator": "eq",
"right": {
"table": "orders",
"field": "customer_id"
}
}
]
}
],
"_source": ["orders.product", "name", "orders.details.price", "orders.tags"],
"sort": [{"orders.id": "asc"}],
"aggs": {
"warranty_facet": {
"terms": {
"field": "orders.details.warranty"
}
}
}
}+-----------------+---------------+----------------------+-------------+
| orders.product | name | orders.details.price | orders.tags |
+-----------------+---------------+----------------------+-------------+
| Laptop Computer | Alice Johnson | 1200 | 101,102 |
| Smart Phone | Bob Smith | 800 | 103 |
+-----------------+---------------+----------------------+-------------+
2 rows in set (0.00 sec)
+-------------------------+----------+
| orders.details.warranty | count(*) |
+-------------------------+----------+
| 2 years | 1 |
| 1 year | 1 |
+-------------------------+----------+
2 rows in set (0.00 sec){
"took": 0,
"timed_out": false,
"hits": {
"total": 3,
"total_relation": "eq",
"hits": [
{
"_id": 1,
"_score": 1,
"orders._score": 1565,
"_source": {
"name": "Alice Johnson",
"orders.tags": [
101,
102
],
"orders.product": "Laptop Computer"
}
},
{
"_id": 2,
"_score": 1,
"orders._score": 1565,
"_source": {
"name": "Bob Smith",
"orders.tags": [
103
],
"orders.product": "Smart Phone"
}
},
{
"_id": 1,
"_score": 1,
"orders._score": 1565,
"_source": {
"name": "Alice Johnson",
"orders.tags": [
101,
104
],
"orders.product": "Tablet Device"
}
}
]
},
"aggregations": {
"warranty_facet": {
"buckets": [
{
"key": "2 years",
"doc_count": 1
},
{
"key": "1 year",
"doc_count": 2
}
]
}
}
}Для запросов в объединении можно задать отдельные параметры: для левой и правой таблиц. Синтаксис — OPTION(<table_name>) для SQL-запросов и один или несколько подобъектов в "options" для JSON-запросов.
Вот пример, как задать разные веса полей для полнотекстового запроса по правой таблице. Чтобы получить веса совпадений через SQL, используйте выражение <table_name>.weight().
В JSON-запросах этот вес представлен как <table_name>._score.
- SQL
- JSON
SELECT product, customers.email, customers.name, customers.address, customers.weight()
FROM orders
INNER JOIN customers
ON customers.id = orders.customer_id
WHERE MATCH('maple', customers)
OPTION(customers) field_weights=(address=1500);POST /search
{
"table": "orders",
"options": {
"customers": {
"field_weights": {
"address": 1500
}
}
},
"join": [
{
"type": "inner",
"table": "customers",
"query": {
"query_string": "maple"
},
"on": [
{
"left": {
"table": "orders",
"field": "customer_id"
},
"operator": "eq",
"right": {
"table": "customers",
"field": "id"
}
}
]
}
],
"_source": ["product", "customers.email", "customers.name", "customers.address"]
}+---------+-------------------+----------------+-------------------+--------------------+
| product | customers.email | customers.name | customers.address | customers.weight() |
+---------+-------------------+----------------+-------------------+--------------------+
| Laptop | alice@example.com | Alice Johnson | 123 Maple St | 1500680 |
| Tablet | alice@example.com | Alice Johnson | 123 Maple St | 1500680 |
+---------+-------------------+----------------+-------------------+--------------------+
2 rows in set (0.00 sec){
"took": 0,
"timed_out": false,
"hits": {
"total": 2,
"total_relation": "eq",
"hits": [
{
"_id": 1,
"_score": 1,
"customers._score": 15000680,
"_source": {
"product": "Laptop",
"customers.email": "alice@example.com",
"customers.name": "Alice Johnson",
"customers.address": "123 Maple St"
}
},
{
"_id": 3,
"_score": 1,
"customers._score": 15000680,
"_source": {
"product": "Tablet",
"customers.email": "alice@example.com",
"customers.name": "Alice Johnson",
"customers.address": "123 Maple St"
}
}
]
}
}При выполнении объединений таблиц Manticore Search обрабатывает результаты пакетами для оптимизации производительности и использования ресурсов. Вот как это работает:
-
Как работает пакетная обработка:
- Сначала выполняется запрос к левой таблице, и результаты накапливаются в пакет.
- Этот пакет затем используется как входные данные для запроса к правой таблице, который выполняется как единая операция.
- Такой подход минимизирует количество запросов к правой таблице, повышая эффективность.
-
Настройка размера пакета:
- Размер пакета можно настроить с помощью опции поиска
join_batch_size. - Также он настраивается в разделе
searchdконфигурационного файла. - Размер пакета по умолчанию —
1000, но вы можете увеличить или уменьшить его в зависимости от задачи. - Установка
join_batch_size=0полностью отключает пакетную обработку, что может быть полезно для отладки или специфических сценариев.
- Размер пакета можно настроить с помощью опции поиска
-
Особенности производительности:
- Больший размер пакета может улучшить производительность за счёт уменьшения количества запросов к правой таблице.
- Однако большие пакеты могут потреблять больше памяти, особенно при сложных запросах или больших данных.
- Экспериментируйте с разными размерами пакетов, чтобы найти оптимальный баланс между производительностью и использованием ресурсов.
Для дальнейшей оптимизации операций объединения Manticore Search использует механизм кэширования запросов, выполняемых по правой таблице. Вот что важно знать:
-
Как работает кэширование:
- Каждый запрос к правой таблице определяется условиями
JOIN ON. - Если одни и те же условия
JOIN ONповторяются в нескольких запросах, результаты кэшируются и переиспользуются. - Это позволяет избежать избыточных запросов и ускорить последующие операции объединения.
- Каждый запрос к правой таблице определяется условиями
-
Настройка размера кэша:
- Размер кэша объединений можно настроить с помощью опции join_cache_size в разделе
searchdконфигурационного файла. - Размер кэша по умолчанию —
20MB, но вы можете изменить его в зависимости от нагрузки и доступной памяти. - Установка
join_cache_size=0полностью отключает кэширование.
- Размер кэша объединений можно настроить с помощью опции join_cache_size в разделе
-
Особенности использования памяти:
- Каждый поток поддерживает собственный кэш, поэтому общий объём памяти зависит от количества потоков и размера кэша.
- Убедитесь, что на сервере достаточно памяти для кэша, особенно при высокой конкуренции запросов.
Распределённые таблицы, состоящие только из локальных таблиц, поддерживаются как слева, так и справа в запросах с объединением. Однако распределённые таблицы, включающие удалённые таблицы, не поддерживаются.
При использовании JOIN в Manticore Search учитывайте следующие моменты:
-
Выбор полей: При выборе полей из двух таблиц в JOIN не используйте префикс для полей левой таблицы, но используйте префикс для полей правой таблицы. Например:
SELECT field_name, right_table.field_name FROM ... -
Условия JOIN: Всегда явно указывайте имена таблиц в условиях JOIN:
JOIN ON table_name.some_field = another_table_name.some_field -
Выражения с JOIN: При использовании выражений, объединяющих поля из обеих таблиц, задавайте псевдоним для результата выражения:
SELECT *, (nums2.n + 3) AS x, x * n FROM nums LEFT JOIN nums2 ON nums2.id = nums.num2_id -
Фильтрация по псевдонимам выражений: Нельзя использовать псевдонимы выражений, включающих поля из обеих таблиц, в условии WHERE.
-
JSON-атрибуты: При объединении по JSON-атрибутам необходимо явно приводить значения к нужному типу:
-- Правильно: SELECT * FROM t1 LEFT JOIN t2 ON int(t1.json_attr.id) = t2.json_attr.id -- Неправильно: SELECT * FROM t1 LEFT JOIN t2 ON t1.json_attr.id = t2.json_attr.id -
Обработка NULL: Можно использовать условия IS NULL и IS NOT NULL для объединённых полей:
SELECT * FROM t1 LEFT JOIN t2 ON t1.id = t2.id WHERE t2.name IS NULL SELECT * FROM t1 LEFT JOIN t2 ON t1.id = t2.id WHERE t2.name IS NOT NULL -
Использование ANY с MVA: При использовании функции
ANY()с мультизначными атрибутами в JOIN задавайте псевдоним для мультизначного атрибута из присоединённой таблицы:SELECT *, t2.m AS alias FROM t LEFT JOIN t2 ON t.id = t2.t_id WHERE ANY(alias) IN (3, 5)
Следуя этим рекомендациям, вы сможете эффективно использовать JOIN в Manticore Search для объединения данных из нескольких индексов и выполнения сложных запросов.
Manticore позволяет использовать произвольные арифметические выражения как через SQL, так и через HTTP, включая значения атрибутов, внутренние атрибуты (ID документа и вес релевантности), арифметические операции, несколько встроенных функций и пользовательские функции. Ниже приведён полный справочный список для быстрого доступа.
+, -, *, /, %, DIV, MOD
Доступны стандартные арифметические операторы. Арифметические вычисления с использованием этих операторов могут выполняться в трёх различных режимах:
- с использованием чисел с плавающей запятой одинарной точности, 32-битных по стандарту IEEE 754 (по умолчанию),
- с использованием знаковых 32-битных целых чисел,
- с использованием знаковых 64-битных целых чисел.
Парсер выражений автоматически переключается в целочисленный режим, если ни одна операция не приводит к значению с плавающей точкой. В противном случае используется режим с плавающей точкой по умолчанию. Например, a+b будет вычислено с использованием 32-битных целых чисел, если оба аргумента 32-битные целые; или с использованием 64-битных целых, если оба аргумента целые, но один из них 64-битный; или в формате с плавающей точкой в остальных случаях. Однако a/b или sqrt(a) всегда будут вычисляться в формате с плавающей точкой, так как эти операции возвращают нецелочисленный результат. Чтобы избежать этого, можно использовать IDIV(a,b) или форму DIV b. Кроме того, a*b не будет автоматически повышать разрядность до 64-бит при аргументах 32-бит. Чтобы обеспечить 64-битный результат, используйте BIGINT(), но учтите, что если присутствуют операции с нецелочисленными значениями, BIGINT() будет просто проигнорирован.
<, > <=, >=, =, <>
Операторы сравнения возвращают 1.0, когда условие истинно, и 0.0 в противном случае. Например, (a=b)+3 вычисляется как 4, когда атрибут a равен атрибуту b, и как 3, когда a не равен. В отличие от MySQL, сравнения на равенство (т.е. операторы = и <>) включают небольшой порог равенства (по умолчанию 1e-6). Если разница между сравниваемыми значениями находится в пределах порога, они считаются равными.
Операторы BETWEEN и IN в случае многозначных атрибутов возвращают true, если хотя бы одно значение соответствует условию (аналогично ANY()). Оператор IN не поддерживает JSON-атрибуты. Оператор IS (NOT) NULL поддерживается только для JSON-атрибутов.
AND, OR, NOT
Логические операторы (AND, OR, NOT) ведут себя ожидаемо. Они являются левосторонне ассоциативными и имеют самый низкий приоритет по сравнению с другими операторами. NOT имеет более высокий приоритет, чем AND и OR, но всё же ниже, чем любой другой оператор. AND и OR имеют одинаковый приоритет, поэтому рекомендуется использовать скобки, чтобы избежать путаницы в сложных выражениях.
&, |
Эти операторы выполняют побитовое И и ИЛИ соответственно. Операнды должны быть целочисленных типов.
- ABS()
- ALL()
- ANY()
- ATAN2()
- BIGINT()
- BITDOT()
- BM25F()
- CEIL()
- CONCAT()
- CONTAINS()
- COS()
- CRC32()
- DATE_HISTOGRAM()
- DATE_RANGE()
- DAY()
- DOUBLE()
- EXP()
- FIBONACCI()
- FLOOR()
- GEODIST()
- GEOPOLY2D()
- GREATEST()
- HOUR()
- HISTOGRAM()
- IDIV()
- IF()
- IN()
- INDEXOF()
- INTEGER()
- INTERVAL()
- LAST_INSERT_ID()
- LEAST()
- LENGTH()
- LN()
- LOG10()
- LOG2()
- MAX()
- MIN()
- MINUTE()
- MIN_TOP_SORTVAL()
- MIN_TOP_WEIGHT()
- MONTH()
- NOW()
- PACKEDFACTORS()
- POLY2D()
- POW()
- RAND()
- RANGE()
- REGEX()
- REMAP()
- SECOND()
- SIN()
- SINT()
- SQRT()
- SUBSTRING_INDEX()
- TO_STRING()
- UINT()
- YEAR()
- YEARMONTH()
- YEARMONTHDAY()
- WEIGHT()
В интерфейсе HTTP JSON поддерживаются выражения через script_fields и expressions.
{
"table": "test",
"query": {
"match_all": {}
}, "script_fields": {
"add_all": {
"script": {
"inline": "( gid * 10 ) | crc32(title)"
}
},
"title_len": {
"script": {
"inline": "crc32(title)"
}
}
}
}
В этом примере создаются два выражения: add_all и title_len. Первое выражение вычисляет ( gid * 10 ) | crc32(title) и сохраняет результат в атрибут add_all. Второе выражение вычисляет crc32(title) и сохраняет результат в атрибут title_len.
В настоящее время поддерживаются только inline выражения. Значение свойства inline (выражение для вычисления) имеет такой же синтаксис, как и SQL-выражения.
Имя выражения может использоваться при фильтрации или сортировке.
- script_fields
{
"table":"movies_rt",
"script_fields":{
"cond1":{
"script":{
"inline":"actor_2_facebook_likes =296 OR movie_facebook_likes =37000"
}
},
"cond2":{
"script":{
"inline":"IF (IN (content_rating,'TV-PG','PG'),2, IF(IN(content_rating,'TV-14','PG-13'),1,0))"
}
}
},
"limit":10,
"sort":[
{
"cond2":"desc"
},
{
"actor_1_name":"asc"
},
{
"actor_2_name":"desc"
}
],
"profile":true,
"query":{
"bool":{
"must":[
{
"match":{
"*":"star"
}
},
{
"equals":{
"cond1":1
}
}
],
"must_not":[
{
"equals":{
"content_rating":"R"
}
}
]
}
}
}По умолчанию значения выражений включаются в массив _source набора результатов. Если источник выборочный (см. Выбор источника), имя выражения можно добавить в параметр _source в запросе. Обратите внимание, что имена выражений должны быть в нижнем регистре.
expressions является альтернативой script_fields с более простым синтаксисом. В примере запроса добавляются два выражения, и результаты сохраняются в атрибуты add_all и title_len. Обратите внимание, что имена выражений должны быть в нижнем регистре.
- expressions
{
"table": "test",
"query": { "match_all": {} },
"expressions":
{
"add_all": "( gid * 10 ) | crc32(title)",
"title_len": "crc32(title)"
}
}SQL SELECT оператор и HTTP /search эндпоинт поддерживают ряд опций, которые можно использовать для тонкой настройки поведения поиска.
SQL:
SELECT ... [OPTION <optionname>=<value> [ , ... ]] [/*+ [NO_][ColumnarScan|DocidIndex|SecondaryIndex(<attribute>[,...])]] /*]
HTTP:
POST /search
{
"table" : "table_name",
"options":
{
"optionname": "value",
"optionname2": <value2>
}
}
- SQL
- JSON
SELECT * FROM test WHERE MATCH('@title hello @body world')
OPTION ranker=bm25, max_matches=3000,
field_weights=(title=10, body=3), agent_query_timeout=10000POST /search
{
"table" : "test",
"query": {
"match": {
"title": "hello"
},
"match": {
"body": "world"
}
},
"options":
{
"ranker": "bm25",
"max_matches": 3000,
"field_weights": {
"title": 10,
"body": 3
},
"agent_query_timeout": 10000
}
}+------+-------+-------+
| id | title | body |
+------+-------+-------+
| 1 | hello | world |
+------+-------+-------+
1 row in set (0.00 sec){
"took": 0,
"timed_out": false,
"hits": {
"total": 1,
"total_relation": "eq",
"hits": [
{
"_id": 1,
"_score": 10500,
"_source": {
"title": "hello",
"body": "world"
}
}
]
}
}Поддерживаемые опции:
Целое число. Включает или отключает гарантированную точность агрегатов при выполнении запросов groupby в нескольких потоках. По умолчанию 0.
При выполнении запроса groupby он может выполняться параллельно на простой таблице с несколькими псевдо-шардами (если включен pseudo_sharding). Аналогичный подход работает с RT таблицами. Каждый шард/чанк выполняет запрос, но количество групп ограничено max_matches. Если наборы результатов из разных шардов/чанков содержат разные группы, подсчёты групп и агрегаты могут быть неточными. Обратите внимание, что Manticore пытается увеличить max_matches до max_matches_increase_threshold на основе количества уникальных значений атрибута groupby (получаемых из вторичных индексов). Если это удаётся, потери точности не будет.
Однако, если количество уникальных значений атрибута groupby велико, дальнейшее увеличение max_matches может быть не лучшей стратегией, так как это может привести к снижению производительности и увеличению использования памяти. Установка accurate_aggregation в 1 заставляет groupby-запросы выполняться в одном потоке, что решает проблему точности. Обратите внимание, что выполнение в одном потоке принудительно только тогда, когда max_matches нельзя установить достаточно высоким; в противном случае запросы с accurate_aggregation=1 всё равно будут выполняться в нескольких потоках.
В целом, установка accurate_aggregation в 1 обеспечивает точность подсчёта групп и агрегатов в RT таблицах и простых таблицах с pseudo_sharding=1. Недостаток в том, что запросы будут выполняться медленнее, так как будут вынуждены работать в одном потоке.
Однако, если у нас есть RT таблица и простая таблица с одинаковыми данными, и мы выполняем запрос с accurate_aggregation=1, мы всё равно можем получить разные результаты. Это происходит потому, что демон может выбрать разные настройки max_matches для RT и простой таблицы из-за настройки max_matches_increase_threshold.
Целое число. Максимальное время в миллисекундах ожидания завершения удалённых запросов, см. этот раздел.
0 или 1 (по умолчанию 1). boolean_simplify=1 включает упрощение запроса для ускорения его выполнения.
Эту опцию также можно установить глобально в конфигурации searchd, чтобы изменить поведение по умолчанию для всех запросов. Опция на уровне запроса переопределит глобальную настройку.
Строка, пользовательский комментарий, который копируется в файл журнала запросов.
Целое число. Указывает максимальное количество обрабатываемых совпадений. Если не задано, Manticore автоматически выберет подходящее значение.
N = 0: Отключает ограничение на количество совпадений.N > 0: Инструктирует Manticore прекратить обработку результатов, как только будет найденоNсовпадающих документов.- Не задано: Manticore самостоятельно определяет порог.
Когда Manticore не может определить точное количество совпадающих документов, поле total_relation в метаинформации запроса покажет gte, что означает Больше или равно. Это указывает, что фактическое количество совпадений как минимум равно указанному total_found (в SQL) или hits.total (в JSON). Когда количество точное, total_relation будет показывать eq.
Примечание: использование cutoff в агрегатных запросах не рекомендуется, так как это может привести к неточным или неполным результатам.
- Example
Использование cutoff в агрегатных запросах может привести к неправильным или вводящим в заблуждение результатам, как показано в следующем примере:
drop table if exists t
--------------
Query OK, 0 rows affected (0.02 sec)
--------------
create table t(a int)
--------------
Query OK, 0 rows affected (0.04 sec)
--------------
insert into t(a) values(1),(2),(3),(1),(2),(3)
--------------
Query OK, 6 rows affected (0.00 sec)
--------------
select avg(a) from t option cutoff=1 facet a
--------------
+----------+
| avg(a) |
+----------+
| 1.000000 |
+----------+
1 row in set (0.00 sec)
--- 1 out of 1 results in 0ms ---
+------+----------+
| a | count(*) |
+------+----------+
| 1 | 1 |
+------+----------+
1 row in set (0.00 sec)
--- 1 out of 1 results in 0ms ---Сравните с тем же запросом без cutoff:
--------------
select avg(a) from t facet a
--------------
+----------+
| avg(a) |
+----------+
| 2.000000 |
+----------+
1 row in set (0.00 sec)
--- 1 out of 1 results in 0ms ---
+------+----------+
| a | count(*) |
+------+----------+
| 1 | 2 |
| 2 | 2 |
| 3 | 2 |
+------+----------+
3 rows in set (0.00 sec)
--- 3 out of 3 results in 0ms ---Целое число. По умолчанию 3500. Эта опция задаёт порог, ниже которого подсчёты, возвращаемые count distinct, гарантированно точны в пределах простой таблицы.
Допустимые значения варьируются от 500 до 15500. Значения вне этого диапазона будут ограничены.
Если эта опция установлена в 0, включается алгоритм, обеспечивающий точные подсчёты. Этот алгоритм собирает пары {group, value}, сортирует их и периодически устраняет дубликаты. Результат — точные подсчёты в пределах простой таблицы. Однако этот подход не подходит для наборов данных с высокой кардинальностью из-за высокого потребления памяти и медленного выполнения запросов.
Если distinct_precision_threshold установлен в значение больше 0, Manticore использует другой алгоритм. Он загружает подсчёты в хеш-таблицу и возвращает размер таблицы. Если хеш-таблица становится слишком большой, её содержимое переносится в структуру данных HyperLogLog. В этот момент подсчёты становятся приближенными, так как HyperLogLog — вероятностный алгоритм. Этот подход поддерживает фиксированное максимальное использование памяти на группу, но с компромиссом в точности подсчётов.
Точность HyperLogLog и порог для перехода от хеш-таблицы к HyperLogLog выводятся из настройки distinct_precision_threshold. Важно использовать эту опцию с осторожностью, так как удвоение её значения также удвоит максимальное количество памяти, необходимое для подсчётов. Максимальное использование памяти можно примерно оценить по формуле: 64 * max_matches * distinct_precision_threshold, хотя на практике подсчёты часто используют меньше памяти, чем в худшем случае.
0 или 1 (по умолчанию 0). Расширяет ключевые слова точными формами и/или звёздочками, когда это возможно. Подробнее см. expand_keywords.
Именованный список целых чисел (веса пользователя для ранжирования по полям).
Пример:
SELECT ... OPTION field_weights=(title=10, body=3)
Использовать глобальную статистику (частоты) из файла global_idf для вычисления IDF.
Кавычками, через запятую перечисленные флаги вычисления IDF. Известные флаги:
normalized: вариант BM25, idf = log((N-n+1)/n), согласно Робертсону и др.plain: простой вариант, idf = log(N/n), согласно Спарку-Джонсуtfidf_normalized: дополнительно делить IDF на количество слов в запросе, чтобыTF*IDFпопадал в диапазон [0, 1]tfidf_unnormalized: не делить дополнительно IDF на количество слов в запросе, где N — размер коллекции, а n — количество совпадающих документов
Исторически стандартный IDF (обратная частота документа) в Manticore эквивалентен OPTION idf='normalized,tfidf_normalized', и эти нормализации могут вызывать несколько нежелательных эффектов.
Во-первых, idf=normalized приводит к штрафованию ключевых слов. Например, если вы ищете the | something и the встречается более чем в 50% документов, то документы с обоими ключевыми словами the и something получат меньший вес, чем документы только с ключевым словом something. Использование OPTION idf=plain этого избегает. Простой IDF варьируется в диапазоне [0, log(N)], и ключевые слова никогда не штрафуются; в то время как нормализованный IDF варьируется в диапазоне [-log(N), log(N)], и слишком частые ключевые слова штрафуются.
Во-вторых, idf=tfidf_normalized приводит к дрейфу IDF между запросами. Исторически IDF также делился на количество ключевых слов в запросе, что обеспечивало, что сумма sum(tf*idf) по всем ключевым словам оставалась в диапазоне [0,1]. Однако это означало, что запросы вроде word1 и word1 | nonmatchingword2 присваивали разные веса одному и тому же набору результатов, так как IDF для word1 и nonmatchingword2 делились на 2. Использование OPTION idf='tfidf_unnormalized' решает эту проблему. Учтите, что факторы ранжирования BM25, BM25A, BM25F() будут соответственно скорректированы при отключении этой нормализации.
Флаги IDF могут комбинироваться; plain и normalized взаимоисключающие; tfidf_unnormalized и tfidf_normalized также взаимоисключающие; и неуказанные флаги в таких взаимоисключающих группах по умолчанию принимают свои исходные настройки. Это означает, что OPTION idf=plain эквивалентно полному указанию OPTION idf='plain,tfidf_normalized'.
Задает режим сегментации Jieba для запроса.
При использовании китайской сегментации Jieba иногда полезно использовать разные режимы сегментации для токенизации документов и запроса. Полный список режимов см. в jieba_mode.
Именованный список целых чисел. Веса пользователя для ранжирования по таблицам.
0 или 1, автоматически суммирует DF по всем локальным частям распределённой таблицы, обеспечивая согласованный (и точный) IDF для локально шардированной таблицы. По умолчанию включено для дисковых чанков RT-таблицы. Термины запроса с подстановочными знаками игнорируются.
0 или 1 (по умолчанию 0). Установка low_priority=1 выполняет запрос с пониженным приоритетом, перепланируя его задачи в 10 раз реже, чем запросы с нормальным приоритетом.
Целое число. Максимальное количество совпадений на запрос.
Максимальное количество совпадений, которое сервер хранит в ОЗУ для каждой таблицы и может вернуть клиенту. По умолчанию 1000.
Введено для контроля и ограничения использования ОЗУ, настройка max_matches определяет, сколько совпадений будет храниться в памяти при поиске по каждой таблице. Каждое найденное совпадение всё равно обрабатывается, но в памяти сохраняются только лучшие N из них, которые в итоге возвращаются клиенту. Например, если в таблице для запроса найдено 2 000 000 совпадений, редко нужно получить их все. Вместо этого нужно просканировать все, но выбрать только "лучшие" 500, например, по какому-то критерию (например, по релевантности, цене или другим факторам) и показать эти 500 совпадений пользователю постранично по 20-100 совпадений. Отслеживание только лучших 500 совпадений гораздо эффективнее по памяти и CPU, чем хранение всех 2 000 000, их сортировка и затем отбрасывание всего, кроме первых 20 для страницы результатов. max_matches контролирует N в этом количестве "лучших N".
Этот параметр существенно влияет на использование ОЗУ и CPU на запрос. Значения от 1000 до 10 000 обычно приемлемы, но более высокие лимиты следует использовать с осторожностью. Бездумное увеличение max_matches до 1 000 000 означает, что searchd должен выделить и инициализировать буфер для совпадений на миллион записей для каждого запроса. Это неизбежно увеличит использование ОЗУ на запрос и в некоторых случаях заметно повлияет на производительность.
Дополнительную информацию о влиянии на поведение опции max_matches см. в max_matches_increase_threshold.
Целое число. Устанавливает порог, до которого может быть увеличен max_matches. По умолчанию 16384.
Manticore может увеличить max_matches для повышения точности groupby и/или агрегаций при включённом pseudo_sharding, если обнаружит, что количество уникальных значений атрибута groupby меньше этого порога. Потеря точности может возникать, когда pseudo-sharding выполняет запрос в нескольких потоках или когда RT-таблица проводит параллельные поиски в дисковых чанках.
Если количество уникальных значений атрибута groupby меньше порога, max_matches будет установлено в это число. В противном случае будет использоваться значение max_matches по умолчанию.
Если max_matches было явно задано в опциях запроса, этот порог не действует.
Учтите, что слишком высокий порог приведёт к увеличенному потреблению памяти и общему снижению производительности.
Вы также можете включить режим гарантированной точности groupby/aggregate с помощью опции accurate_aggregation.
Устанавливает максимальное время выполнения поискового запроса в миллисекундах. Должно быть неотрицательным целым числом. Значение по умолчанию — 0, что означает «не ограничивать». Локальные поисковые запросы будут остановлены после истечения указанного времени. Обратите внимание, что если вы выполняете поиск, который обращается к нескольким локальным таблицам, это ограничение применяется к каждой таблице отдельно. Имейте в виду, что это может немного увеличить время отклика запроса из-за накладных расходов, связанных с постоянным отслеживанием времени остановки запроса.
Целое число. Максимальное предсказанное время поиска; см. predicted_time_costs.
none позволяет заменять все термины запроса их точными формами, если таблица была построена с включённым index_exact_words. Это полезно для предотвращения стемминга или лемматизации терминов запроса.
0 или 1 разрешает использование отдельного отрицания в запросе. Значение по умолчанию — 0. См. также соответствующую глобальную настройку.
- SQL
MySQL [(none)]> select * from tbl where match('-donald');
ERROR 1064 (42000): index t: query error: query is non-computable (single NOT operator)
MySQL [(none)]> select * from t where match('-donald') option not_terms_only_allowed=1;
+---------------------+-----------+
| id | field |
+---------------------+-----------+
| 1658178727135150081 | smth else |
+---------------------+-----------+Выберите из следующих вариантов:
proximity_bm25bm25nonewordcountproximitymatchanyfieldmasksph04exprexport
Для получения подробной информации о каждом ранжировщике смотрите Ранжирование результатов поиска.
Позволяет указать конкретное целочисленное значение зерна для запроса ORDER BY RAND(), например: ... OPTION rand_seed=1234. По умолчанию для каждого запроса автоматически генерируется новое и уникальное значение зерна.
Целое число. Количество повторных попыток в распределённом режиме.
Целое число. Задержка между повторными попытками в распределённом режиме, в миллисекундах.
Строка. Токен прокрутки для постраничного вывода результатов с использованием подхода Scroll pagination.
pq— очередь с приоритетом, установлена по умолчаниюkbuffer— обеспечивает более быструю сортировку для уже предварительно отсортированных данных, например, данных таблицы, отсортированных по id Набор результатов одинаков в обоих случаях; выбор одного из вариантов может просто улучшить (или ухудшить) производительность.
Ограничивает максимальное количество потоков, используемых для обработки текущего запроса. По умолчанию — без ограничений (запрос может использовать все потоки, определённые глобально). Для пакета запросов опция должна быть добавлена к самому первому запросу в пакете, после чего применяется при создании рабочей очереди и действует для всего пакета. Эта опция имеет то же значение, что и опция max_threads_per_query, но применяется только к текущему запросу или пакету запросов.
Заключённая в кавычки строка с разделением двоеточиями в формате название библиотеки:название плагина:необязательная строка настроек. Для каждого поиска при вызове полнотекстового поиска каждой задействованной таблицей создаётся фильтр токенов во время выполнения запроса, что позволяет реализовать пользовательский токенизатор, генерирующий токены согласно пользовательским правилам.
SELECT * FROM index WHERE MATCH ('yes@no') OPTION token_filter='mylib.so:blend:@'
Ограничивает максимальное количество расширенных ключевых слов для одного подстановочного знака, значение по умолчанию 0 означает отсутствие ограничения. Для дополнительной информации смотрите expansion_limit.
В редких случаях встроенный анализатор запросов Manticore может неправильно понять запрос и определить, следует ли использовать индекс docid, вторичные индексы или колонковое сканирование. Чтобы переопределить решения оптимизатора запросов, вы можете использовать следующие подсказки в вашем запросе:
/*+ DocidIndex(id) */— принудительно использовать индекс docid,/*+ NO_DocidIndex(id) */— указать оптимизатору игнорировать его/*+ SecondaryIndex(<attr_name1>[, <attr_nameN>]) */— принудительно использовать вторичный индекс (если доступен),/*+ NO_SecondaryIndex(id) */— указать оптимизатору игнорировать его/*+ ColumnarScan(<attr_name1>[, <attr_nameN>]) */— принудительно использовать колонковое сканирование (если атрибут колонковый),/*+ NO_ColumnarScan(id) */— указать оптимизатору игнорировать его
Обратите внимание, что при выполнении полнотекстового запроса с фильтрами оптимизатор запросов выбирает между пересечением результатов полнотекстового дерева с результатами фильтра или использованием стандартного подхода match-then-filter. Указание любой подсказки заставит демон использовать путь кода, который выполняет пересечение результатов полнотекстового дерева с результатами фильтра.
Для получения дополнительной информации о работе оптимизатора запросов смотрите страницу Оптимизатор на основе стоимости.
- SQL
SELECT * FROM students where age > 21 /*+ SecondaryIndex(age) */При использовании клиента MySQL/MariaDB убедитесь, что включён флаг --comments для активации подсказок в ваших запросах.
- mysql
mysql -P9306 -h0 --commentsПодсветка позволяет получать выделенные фрагменты текста (называемые сниппетами) из документов, содержащих совпадающие ключевые слова.
Функция SQL HIGHLIGHT(), свойство "highlight" в JSON-запросах через HTTP и функция highlight() в PHP-клиенте используют встроенное хранилище документов для получения оригинального содержимого поля (включено по умолчанию).
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- Javascript
- Java
- C#
- Rust
- TypeScript
- Go
SELECT HIGHLIGHT() FROM books WHERE MATCH('try');POST /search
{
"table": "books",
"query": { "match": { "*" : "try" } },
"highlight": {}
}$results = $index->search('try')->highlight()->get();
foreach($results as $doc)
{
echo 'Document: '.$doc->getId();
foreach($doc->getData() as $field=>$value)
{
echo $field.': '.$value;
}
foreach($doc->getHighlight() as $field=>$snippets)
{
echo "Highlight for ".$field.":\n";
foreach($snippets as $snippet)
{
echo "- ".$snippet."\n";
}
}
}
res = searchApi.search({"table":"books","query":{"match":{"*":"try"}},"highlight":{}})res = await searchApi.search({"table":"books","query":{"match":{"*":"try"}},"highlight":{}})res = await searchApi.search({"table":"books","query":{"match":{"*":"try"}},"highlight":{}});searchRequest = new SearchRequest();
searchRequest.setIndex("books");
query = new HashMap<String,Object>();
query.put("match",new HashMap<String,Object>(){{
put("*","try|gets|down|said");
}});
searchRequest.setQuery(query);
highlight = new HashMap<String,Object>(){{
}};
searchRequest.setHighlight(highlight);
searchResponse = searchApi.search(searchRequest);var searchRequest = new SearchRequest("books");
searchRequest.FulltextFilter = new MatchFilter("*", "try|gets|down|said");
var highlight = new Highlight();
searchRequest.Highlight = highlight;
var searchResponse = searchApi.Search(searchRequest);let match_filter = HashMap::new();
match_filter.insert("*".to_string(), "try|gets|down|said".to_string());
let query = SearchQuery {
match: Some(serde_json::json!(match_filter).into()),
..Default::default(),
};
let highlight = Highlight::new();
let search_req = SearchRequest {
table: "books".to_string(),
query: Some(Box::new(query)),
highlight: serde_json::json!(highlight),
..Default::default(),
};res = await searchApi.search({
index: 'test',
query: {
match: {
*: 'Text 1'
}
},
highlight: {}
});matchClause := map[string]interface{} {"*": "Text 1"};
query := map[string]interface{} {"match": matchClause};
searchRequest.SetQuery(query);
highlight := manticoreclient.NewHighlight()
searchRequest.SetHighlight(highlight)
res, _, _ := apiClient.SearchAPI.Search(context.Background()).SearchRequest(*searchRequest).Execute()+----------------------------------------------------------+
| highlight() |
+----------------------------------------------------------+
| Don`t <b>try</b> to compete in childishness, said Bliss. |
+----------------------------------------------------------+
1 row in set (0.00 sec){
"took":1,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[
{
"_id": 4,
"_score":1704,
"_source":
{
"title":"Book four",
"content":"Don`t try to compete in childishness, said Bliss."
},
"highlight":
{
"title": ["Book four"],
"content": ["Don`t <b>try</b> to compete in childishness, said Bliss."]
}
}
]
}
}Document: 14
title: Book four
content: Don`t try to compete in childishness, said Bliss.
Highlight for title:
- Book four
Highlight for content:
- Don`t <b>try</b> to compete in childishness, said Bliss.
{'aggregations': None,
'hits': {'hits': [{u'_id': u'4',
u'_score': 1695,
u'_source': {u'content': u'Don`t try to compete in childishness, said Bliss.',
u'title': u'Book four'},
u'highlight': {u'content': [u'Don`t <b>try</b> to compete in childishness, said Bliss.'],
u'title': [u'Book four']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{'aggregations': None,
'hits': {'hits': [{u'_id': u'4',
u'_score': 1695,
u'_source': {u'content': u'Don`t try to compete in childishness, said Bliss.',
u'title': u'Book four'},
u'highlight': {u'content': [u'Don`t <b>try</b> to compete in childishness, said Bliss.'],
u'title': [u'Book four']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{"took":0,"timed_out":false,"hits":{"total":1,"hits":[{"_id": 4,"_score":1695,"_source":{"title":"Book four","content":"Don`t try to compete in childishness, said Bliss."},"highlight":{"title":["Book four"],"content":["Don`t <b>try</b> to compete in childishness, said Bliss."]}}]}}class SearchResponse {
took: 0
timedOut: false
hits: class SearchResponseHits {
total: 3
maxScore: null
hits: [{_id=3, _score=1597, _source={title=Book three, content=Trevize whispered, "It gets infantile pleasure out of display. I`d love to knock it down."}, highlight={title=[Book three], content=[, "It <b>gets</b> infantile pleasure , to knock it <b>down</b>."]}}, {_id=4, _score=1563, _source={title=Book four, content=Don`t try to compete in childishness, said Bliss.}, highlight={title=[Book four], content=[Don`t <b>try</b> to compete in childishness, <b>said</b> Bliss.]}}, {_id=5, _score=1514, _source={title=Books two, content=A door opened before them, revealing a small room. Bander said, "Come, half-humans, I want to show you how we live."}, highlight={title=[Books two], content=[ a small room. Bander <b>said</b>, "Come, half-humans, I]}}]
aggregations: null
}
profile: null
}class SearchResponse {
took: 0
timedOut: false
hits: class SearchResponseHits {
total: 3
maxScore: null
hits: [{_id=3, _score=1597, _source={title=Book three, content=Trevize whispered, "It gets infantile pleasure out of display. I`d love to knock it down."}, highlight={title=[Book three], content=[, "It <b>gets</b> infantile pleasure , to knock it <b>down</b>."]}}, {_id=4, _score=1563, _source={title=Book four, content=Don`t try to compete in childishness, said Bliss.}, highlight={title=[Book four], content=[Don`t <b>try</b> to compete in childishness, <b>said</b> Bliss.]}}, {_id=5, _score=1514, _source={title=Books two, content=A door opened before them, revealing a small room. Bander said, "Come, half-humans, I want to show you how we live."}, highlight={title=[Books two], content=[ a small room. Bander <b>said</b>, "Come, half-humans, I]}}]
aggregations: null
}
profile: null
}class SearchResponse {
took: 0
timedOut: false
hits: class SearchResponseHits {
total: 3
maxScore: null
hits: [{_id=3, _score=1597, _source={title=Book three, content=Trevize whispered, "It gets infantile pleasure out of display. I`d love to knock it down."}, highlight={title=[Book three], content=[, "It <b>gets</b> infantile pleasure , to knock it <b>down</b>."]}}, {_id=4, _score=1563, _source={title=Book four, content=Don`t try to compete in childishness, said Bliss.}, highlight={title=[Book four], content=[Don`t <b>try</b> to compete in childishness, <b>said</b> Bliss.]}}, {_id=5, _score=1514, _source={title=Books two, content=A door opened before them, revealing a small room. Bander said, "Come, half-humans, I want to show you how we live."}, highlight={title=[Books two], content=[ a small room. Bander <b>said</b>, "Come, half-humans, I]}}]
aggregations: null
}
profile: null
}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1"
},
"highlight":
{
"content":
[
"<b>Text 1</b>"
]
}
]}
}
}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1"
},
"highlight":
{
"content":
[
"<b>Text 1</b>"
]
}
]}
}
}При использовании SQL для подсветки результатов поиска вы получите сниппеты из различных полей, объединённые в одну строку из-за ограничений протокола MySQL. Вы можете настроить разделители конкатенации с помощью опций field_separator и snippet_separator, как описано ниже.
При выполнении JSON-запросов через HTTP или использовании PHP-клиента таких ограничений нет, и результирующий набор включает массив полей, содержащих массивы сниппетов (без разделителей).
Имейте в виду, что опции генерации сниппетов, такие как limit, limit_words и limit_snippets, по умолчанию применяются к каждому полю отдельно. Вы можете изменить это поведение с помощью опции limits_per_field, но это может привести к нежелательным результатам. Например, в одном поле могут быть совпадающие ключевые слова, но сниппеты из этого поля не будут включены в результат, потому что они не получили такой высокий рейтинг, как сниппеты из других полей в механизме подсветки.
Алгоритм подсветки в настоящее время отдаёт приоритет лучшим сниппетам (с более точными совпадениями фраз), а затем сниппетам с ключевыми словами, ещё не включёнными в результат. В целом, он стремится выделить лучшее совпадение для запроса и выделить все ключевые слова запроса в пределах установленных ограничений. Если в текущем поле совпадений не найдено, начало документа будет обрезано согласно ограничениям и возвращено по умолчанию. Чтобы вернуть пустую строку вместо этого, установите опцию allow_empty в 1.
Подсветка выполняется на так называемом этапе post limit, что означает, что генерация сниппетов откладывается не только до подготовки всего итогового набора результатов, но и после применения оператора LIMIT. Например, с оператором LIMIT 20,10 функция HIGHLIGHT() будет вызвана максимум 10 раз.
Существует несколько дополнительных опций подсветки, которые можно использовать для тонкой настройки генерации сниппетов, общих для SQL, HTTP и PHP клиентов.
Строка, вставляемая перед совпадением ключевого слова. В этой строке можно использовать макрос %SNIPPET_ID%. Первое вхождение макроса заменяется на увеличивающийся номер сниппета внутри текущего сниппета. Нумерация начинается с 1 по умолчанию, но может быть переопределена опцией start_snippet_id. %SNIPPET_ID% сбрасывается в начале каждого нового документа. Значение по умолчанию — <b>.
Строка, вставляемая после совпадения ключевого слова. Значение по умолчанию — </b>.
Максимальный размер сниппета в символах (кодовых точках). Значение по умолчанию — 256. Применяется по умолчанию к каждому полю отдельно, см. limits_per_field.
Ограничивает максимальное количество слов, которые могут быть включены в результат. Обратите внимание, что это ограничение применяется ко всем словам, а не только к совпадающим ключевым словам для подсветки. Например, если подсвечивается Mary и выбран сниппет Mary had a little lamb, он учитывается как 5 слов, а не 1. Значение по умолчанию — 0 (без ограничений). Применяется по умолчанию к каждому полю отдельно, см. limits_per_field.
Ограничивает максимальное количество сниппетов, которые могут быть включены в результат. Значение по умолчанию — 0 (без ограничений). Применяется по умолчанию к каждому полю отдельно, см. limits_per_field.
Определяет, применяются ли limit, limit_words и limit_snippets как индивидуальные ограничения для каждого поля документа, подсвечиваемого, или как глобальные ограничения для всего документа. Установка этой опции в 0 означает, что все объединённые результаты подсветки для одного документа должны укладываться в указанные ограничения. Недостаток в том, что вы можете получить несколько подсвеченных сниппетов в одном поле и ни одного в другом, если механизм подсветки решит, что они более релевантны. Значение по умолчанию — 1 (использовать ограничения по полям).
Количество слов, выбираемых вокруг каждого блока совпадающих ключевых слов. Значение по умолчанию — 5.
Определяет, следует ли дополнительно разбивать сниппеты по символам границ фраз, как настроено в параметрах таблицы с помощью директивы phrase_boundary. Значение по умолчанию — 0 (не использовать границы).
Указывает, сортировать ли извлечённые сниппеты по релевантности (по убыванию веса) или по порядку появления в документе (по возрастанию позиции). Значение по умолчанию — 0 (не использовать сортировку по весу).
Игнорирует ограничение по длине до тех пор, пока результат не будет включать все ключевые слова. Значение по умолчанию — 0 (не принуждать к включению всех ключевых слов).
Устанавливает начальное значение макроса %SNIPPET_ID% (который обнаруживается и расширяется в строках before_match, after_match). Значение по умолчанию — 1.
Определяет режим удаления HTML. По умолчанию index, что означает использование настроек таблицы. Другие значения включают none и strip, которые принудительно пропускают или применяют удаление независимо от настроек таблицы; и retain, который сохраняет HTML-разметку и защищает её от подсветки. Режим retain можно использовать только при подсветке полных документов, поэтому требуется отсутствие ограничений на размер фрагментов. Допустимые строковые значения: none, strip, index и retain.
Разрешает возвращать пустую строку в качестве результата подсветки, если не удалось сгенерировать фрагменты в текущем поле (нет совпадений по ключевым словам или фрагменты не помещаются в лимит). По умолчанию возвращается начало исходного текста вместо пустой строки. Значение по умолчанию 0 (не разрешать пустой результат).
Обеспечивает, чтобы фрагменты не пересекали границы предложения, абзаца или зоны (при использовании с таблицей, у которой включены соответствующие настройки индексации). Допустимые значения: sentence, paragraph и zone.
Вставляет HTML-тег с именем окружающей зоны перед каждым фрагментом. Значение по умолчанию 0 (не вставлять имена зон).
Определяет, следует ли принудительно генерировать фрагменты, даже если лимиты позволяют подсветить весь текст. Значение по умолчанию 0 (не принуждать генерацию фрагментов).
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- Javascript
- Java
- C#
- Rust
- TypeScript
- Go
SELECT HIGHLIGHT({limit=50}) FROM books WHERE MATCH('try|gets|down|said');POST /search
{
"table": "books",
"query": {"query_string": "try|gets|down|said"},
"highlight": { "limit":50 }
}$results = $index->search('try|gets|down|said')->highlight([],['limit'=>50])->get();
foreach($results as $doc)
{
echo 'Document: '.$doc->getId();
foreach($doc->getData() as $field=>$value)
{
echo $field.': '.$value;
}
foreach($doc->getHighlight() as $field=>$snippets)
{
echo "Highlight for ".$field.":\n";
foreach($snippets as $snippet)
{
echo $snippet."\n";
}
}
}res = searchApi.search({"table":"books","query":{"match":{"*":"try"}},"highlight":{"limit":50}})res = await searchApi.search({"table":"books","query":{"match":{"*":"try"}},"highlight":{"limit":50}})res = await searchApi.search({"table":"books","query":{"query_string":"try|gets|down|said"},"highlight":{"limit":50}});searchRequest = new SearchRequest();
searchRequest.setIndex("books");
query = new HashMap<String,Object>();
query.put("match",new HashMap<String,Object>(){{
put("*","try|gets|down|said");
}});
searchRequest.setQuery(query);
highlight = new HashMap<String,Object>(){{
put("limit",50);
}};
searchRequest.setHighlight(highlight);
searchResponse = searchApi.search(searchRequest);var searchRequest = new SearchRequest("books");
searchRequest.FulltextFilter = new MatchFilter("*", "try|gets|down|said");
var highlight = new Highlight();
highlight.Limit = 50;
searchRequest.Highlight = highlight;
var searchResponse = searchApi.Search(searchRequest);let match_filter = HashMap::new();
match_filter.insert("*".to_string(), "try|gets|down|said".to_string());
let query = SearchQuery {
match: Some(serde_json::json!(match_filter).into()),
..Default::default(),
};
let highlight = Highlight {
limit: Some(50),
..Default::default(),
};
let search_req = SearchRequest {
table: "books".to_string(),
query: Some(Box::new(query)),
highlight: serde_json::json!(highlight),
..Default::default(),
};res = await searchApi.search({
index: 'test',
query: { match: { *: 'Text } },
highlight: { limit: 2}
});matchClause := map[string]interface{} {"*": "Text 1"};
query := map[string]interface{} {"match": matchClause};
searchRequest.SetQuery(query);
highlight := manticoreclient.NewHighlight()
searchRequest.SetHighlight(highlight)
res, _, _ := apiClient.SearchAPI.Search(context.Background()).SearchRequest(*searchRequest).Execute()+---------------------------------------------------------------------------+
| highlight({limit=50}) |
+---------------------------------------------------------------------------+
| ... , "It <b>gets</b> infantile pleasure ... to knock it <b>down</b>." |
| Don`t <b>try</b> to compete in childishness, <b>said</b> Bliss. |
| ... a small room. Bander <b>said</b>, "Come, half-humans, I ... |
+---------------------------------------------------------------------------+
3 rows in set (0.00 sec){
"took":2,
"timed_out":false,
"hits":
{
"total":3,
"hits":
[
{
"_id": 3,
"_score":1602,
"_source":
{
"title":"Book three",
"content":"Trevize whispered, \"It gets infantile pleasure out of display. I`d love to knock it down.\""
},
"highlight":
{
"title":
[
"Book three"
],
"content":
[
", \"It <b>gets</b> infantile pleasure ",
" to knock it <b>down</b>.\""
]
}
},
{
"_id": 4,
"_score":1573,
"_source":
{
"title":"Book four",
"content":"Don`t try to compete in childishness, said Bliss."
},
"highlight":
{
"title":
[
"Book four"
],
"content":
[
"Don`t <b>try</b> to compete in childishness, <b>said</b> Bliss."
]
}
},
{
"_id": 2,
"_score":1521,
"_source":
{
"title":"Book two",
"content":"A door opened before them, revealing a small room. Bander said, \"Come, half-humans, I want to show you how we live.\""
},
"highlight":
{
"title":
[
"Book two"
],
"content":
[
" a small room. Bander <b>said</b>, \"Come, half-humans, I"
]
}
}
]
}
}Document: 3
title: Book three
content: Trevize whispered, "It gets infantile pleasure out of display. I`d love to knock it down."
Highlight for title:
- Book four
Highlight for content:
, "It <b>gets</b> infantile pleasure
to knock it <b>down</b>."
Document: 4
title: Book four
content: Don`t try to compete in childishness, said Bliss.
Highlight for title:
- Book four
Highlight for content:
Don`t <b>try</b> to compete in childishness, <b>said</b> Bliss.
Document: 2
title: Book two
content: A door opened before them, revealing a small room. Bander said, "Come, half-humans, I want to show you how we live.
Highlight for title:
- Book two
Highlight for content:
a small room. Bander <b>said</b>, \"Come, half-humans, I{'aggregations': None,
'hits': {'hits': [{u'_id': u'4',
u'_score': 1695,
u'_source': {u'content': u'Don`t try to compete in childishness, said Bliss.',
u'title': u'Book four'},
u'highlight': {u'content': [u'Don`t <b>try</b> to compete in childishness, said Bliss.'],
u'title': [u'Book four']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{'aggregations': None,
'hits': {'hits': [{u'_id': u'4',
u'_score': 1695,
u'_source': {u'content': u'Don`t try to compete in childishness, said Bliss.',
u'title': u'Book four'},
u'highlight': {u'content': [u'Don`t <b>try</b> to compete in childishness, said Bliss.'],
u'title': [u'Book four']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{"took":0,"timed_out":false,"hits":{"total":3,"hits":[{"_id": 3,"_score":1597,"_source":{"title":"Book three","content":"Trevize whispered, \"It gets infantile pleasure out of display. I`d love to knock it down.\""},"highlight":{"title":["Book three"],"content":[", \"It <b>gets</b> infantile pleasure "," to knock it <b>down</b>.\""]}},{"_id": 4,"_score":1563,"_source":{"title":"Book four","content":"Don`t try to compete in childishness, said Bliss."},"highlight":{"title":["Book four"],"content":["Don`t <b>try</b> to compete in childishness, <b>said</b> Bliss."]}},{"_id": 5,"_score":1514,"_source":{"title":"Books two","content":"A door opened before them, revealing a small room. Bander said, \"Come, half-humans, I want to show you how we live.\""},"highlight":{"title":["Books two"],"content":[" a small room. Bander <b>said</b>, \"Come, half-humans, I"]}}]}}class SearchResponse {
took: 0
timedOut: false
hits: class SearchResponseHits {
total: 3
maxScore: null
hits: [{_id=3, _score=1597, _source={title=Book three, content=Trevize whispered, "It gets infantile pleasure out of display. I`d love to knock it down."}, highlight={title=[Book three], content=[, "It <b>gets</b> infantile pleasure , to knock it <b>down</b>."]}}, {_id=4, _score=1563, _source={title=Book four, content=Don`t try to compete in childishness, said Bliss.}, highlight={title=[Book four], content=[Don`t <b>try</b> to compete in childishness, <b>said</b> Bliss.]}}, {_id=5, _score=1514, _source={title=Books two, content=A door opened before them, revealing a small room. Bander said, "Come, half-humans, I want to show you how we live."}, highlight={title=[Books two], content=[ a small room. Bander <b>said</b>, "Come, half-humans, I]}}]
aggregations: null
}
profile: null
}class SearchResponse {
took: 0
timedOut: false
hits: class SearchResponseHits {
total: 3
maxScore: null
hits: [{_id=3, _score=1597, _source={title=Book three, content=Trevize whispered, "It gets infantile pleasure out of display. I`d love to knock it down."}, highlight={title=[Book three], content=[, "It <b>gets</b> infantile pleasure , to knock it <b>down</b>."]}}, {_id=4, _score=1563, _source={title=Book four, content=Don`t try to compete in childishness, said Bliss.}, highlight={title=[Book four], content=[Don`t <b>try</b> to compete in childishness, <b>said</b> Bliss.]}}, {_id=5, _score=1514, _source={title=Books two, content=A door opened before them, revealing a small room. Bander said, "Come, half-humans, I want to show you how we live."}, highlight={title=[Books two], content=[ a small room. Bander <b>said</b>, "Come, half-humans, I]}}]
aggregations: null
}
profile: null
}class SearchResponse {
took: 0
timedOut: false
hits: class SearchResponseHits {
total: 3
maxScore: null
hits: [{_id=3, _score=1597, _source={title=Book three, content=Trevize whispered, "It gets infantile pleasure out of display. I`d love to knock it down."}, highlight={title=[Book three], content=[, "It <b>gets</b> infantile pleasure , to knock it <b>down</b>."]}}, {_id=4, _score=1563, _source={title=Book four, content=Don`t try to compete in childishness, said Bliss.}, highlight={title=[Book four], content=[Don`t <b>try</b> to compete in childishness, <b>said</b> Bliss.]}}, {_id=5, _score=1514, _source={title=Books two, content=A door opened before them, revealing a small room. Bander said, "Come, half-humans, I want to show you how we live."}, highlight={title=[Books two], content=[ a small room. Bander <b>said</b>, "Come, half-humans, I]}}]
aggregations: null
}
profile: null
}{
"took":0,
"timed_out":false,
"hits":
{
"total":2,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text 1</b>"
]
}
},
{
"_id": 2,
"_score":1480,
"_source":
{
"content":"Text 2",
"name":"Doc 2",
"cat":2
},
"highlight":
{
"content":
[
"<b>Text 2</b>"
]
}
}]
}
}{
"took":0,
"timed_out":false,
"hits":
{
"total":2,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text 1</b>"
]
}
},
{
"_id": 2,
"_score":1480,
"_source":
{
"content":"Text 2",
"name":"Doc 2",
"cat":2
},
"highlight":
{
"content":
[
"<b>Text 2</b>"
]
}
}]
}
}Функция HIGHLIGHT() может использоваться для подсветки результатов поиска. Вот синтаксис:
HIGHLIGHT([options], [field_list], [query] )
По умолчанию работает без аргументов.
- SQL
SELECT HIGHLIGHT() FROM books WHERE MATCH('before');+-----------------------------------------------------------+
| highlight() |
+-----------------------------------------------------------+
| A door opened <b>before</b> them, revealing a small room. |
+-----------------------------------------------------------+
1 row in set (0.00 sec)HIGHLIGHT() извлекает все доступные полнотекстовые поля из хранилища документов и подсвечивает их по заданному запросу. Поддерживается синтаксис полей в запросах. Текст полей разделяется с помощью field_separator, который можно изменить в опциях.
- SQL
SELECT HIGHLIGHT() FROM books WHERE MATCH('@title one');+-----------------+
| highlight() |
+-----------------+
| Book <b>one</b> |
+-----------------+
1 row in set (0.00 sec)Необязательный первый аргумент в HIGHLIGHT() — список опций.
- SQL
SELECT HIGHLIGHT({before_match='[match]',after_match='[/match]'}) FROM books WHERE MATCH('@title one');+------------------------------------------------------------+
| highlight({before_match='[match]',after_match='[/match]'}) |
+------------------------------------------------------------+
| Book [match]one[/match] |
+------------------------------------------------------------+
1 row in set (0.00 sec)Необязательный второй аргумент — строка, содержащая одно поле или список полей, разделённых запятыми. Если этот аргумент присутствует, будут извлечены и подсвечены только указанные поля из хранилища документов. Пустая строка во втором аргументе означает «извлечь все доступные поля».
- SQL
SELECT HIGHLIGHT({},'title,content') FROM books WHERE MATCH('one|robots');+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| highlight({},'title,content') |
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Book <b>one</b> | They followed Bander. The <b>robots</b> remained at a polite distance, but their presence was a constantly felt threat. |
| Bander ushered all three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander gestured the other <b>robots</b> away and entered itself. The door closed behind it. |
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
2 rows in set (0.00 sec)В качестве альтернативы можно использовать второй аргумент для указания строкового атрибута или имени поля без кавычек. В этом случае переданная строка будет подсвечена по заданному запросу, но синтаксис полей игнорируется.
- SQL
SELECT HIGHLIGHT({}, title) FROM books WHERE MATCH('one');+---------------------+
| highlight({},title) |
+---------------------+
| Book <b>one</b> |
| Book five |
+---------------------+
2 rows in set (0.00 sec)Необязательный третий аргумент — запрос. Он используется для подсветки результатов поиска по запросу, отличному от того, что использовался для поиска.
- SQL
SELECT HIGHLIGHT({},'title', 'five') FROM books WHERE MATCH('one');+-------------------------------+
| highlight({},'title', 'five') |
+-------------------------------+
| Book one |
| Book <b>five</b> |
+-------------------------------+
2 rows in set (0.00 sec)Хотя HIGHLIGHT() предназначена для работы с сохранёнными полнотекстовыми полями и строковыми атрибутами, её также можно использовать для подсветки произвольного текста. Учтите, что если запрос содержит операторы поиска по полям (например, @title hello @body world), часть с полями в этом случае игнорируется.
- SQL
SELECT HIGHLIGHT({},TO_STRING('some text to highlight'), 'highlight') FROM books WHERE MATCH('@title one');+----------------------------------------------------------------+
| highlight({},TO_STRING('some text to highlight'), 'highlight') |
+----------------------------------------------------------------+
| some text to <b>highlight</b> |
+----------------------------------------------------------------+
1 row in set (0.00 sec)Некоторые опции актуальны только при генерации одной строки в качестве результата (а не массива фрагментов). Это относится исключительно к SQL-функции HIGHLIGHT():
Строка, вставляемая между фрагментами. Значение по умолчанию ....
Строка, вставляемая между полями. Значение по умолчанию |.
Другой способ подсветки текста — использовать оператор CALL SNIPPETS. Он в основном дублирует функциональность HIGHLIGHT(), но не может использовать встроенное хранилище документов. Однако может загружать исходный текст из файлов.
Для подсветки результатов полнотекстового поиска в JSON-запросах через HTTP содержимое полей должно храниться в хранилище документов (включено по умолчанию). В примере полнотекстовые поля content и title извлекаются из хранилища документов и подсвечиваются по запросу, указанному в разделе query.
Подсвеченные фрагменты возвращаются в свойстве highlight массива hits.
- JSON
- PHP
- Python
- Python-asyncio
- Javascript
- Java
- C#
- Rust
- TypeScript
- Go
POST /search
{
"table": "books",
"query": { "match": { "*": "one|robots" } },
"highlight":
{
"fields": ["content"]
}
}$index->setName('books');
$results = $index->search('one|robots')->highlight(['content'])->get();
foreach($results as $doc)
{
echo 'Document: '.$doc->getId()."\n";
foreach($doc->getData() as $field=>$value)
{
echo $field.' : '.$value."\n";
}
foreach($doc->getHighlight() as $field=>$snippets)
{
echo "Highlight for ".$field.":\n";
foreach($snippets as $snippet)
{
echo "- ".$snippet."\n";
}
}
}res = searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content"]}}))res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content"]}}))res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content"]}});searchRequest = new SearchRequest();
searchRequest.setIndex("books");
query = new HashMap<String,Object>();
query.put("match",new HashMap<String,Object>(){{
put("*","one|robots");
}});
searchRequest.setQuery(query);
highlight = new HashMap<String,Object>(){{
put("fields",new String[] {"content"});
}};
searchRequest.setHighlight(highlight);
searchResponse = searchApi.search(searchRequest);var searchRequest = new SearchRequest("books");
searchRequest.FulltextFilter = new MatchFilter("*", "one|robots");
var highlight = new Highlight();
highlight.Fieldnames = new List<string> {"content"};
searchRequest.Highlight = highlight;
var searchResponse = searchApi.Search(searchRequest);let match_filter = HashMap::new();
match_filter.insert("*".to_string(), "one|robots".to_string());
let query = SearchQuery {
match: Some(serde_json::json!(match_filter).into()),
..Default::default(),
};
let highlight_fields [String; 1] = ["content".to_string()];
let highlight = Highlight {
fields: Some(serde_json::json!(highlight_fields)),
..Default::default(),
};
let search_req = SearchRequest {
table: "books".to_string(),
query: Some(Box::new(query)),
highlight: serde_json::json!(highlight),
..Default::default(),
};res = await searchApi.search({
index: 'test',
query: {
match: {
*: 'Text 1|Text 9'
}
},
highlight: {}
});matchClause := map[string]interface{} {"*": "Text 1|Text 9"};
query := map[string]interface{} {"match": matchClause};
searchRequest.SetQuery(query);
highlight := manticoreclient.NewHighlight()
searchRequest.SetHighlight(highlight)
res, _, _ := apiClient.SearchAPI.Search(context.Background()).SearchRequest(*searchRequest).Execute(){
"took": 0,
"timed_out": false,
"hits": {
"total": 1,
"hits": [
{
"_id": 1,
"_score": 2788,
"_source": {
"title": "Books one",
"content": "They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. "
},
"highlight": {
"content": [
"They followed Bander. The <b>robots</b> remained at a polite distance, ",
" three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander",
" gestured the other <b>robots</b> away and entered itself. The"
]
}
}
]
}
}Document: 1
title : Books one
content : They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it.
Highlight for content:
- They followed Bander. The <b>robots</b> remained at a polite distance,
- three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander
- gestured the other <b>robots</b> away and entered itself. The{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u'They followed Bander. The <b>robots</b> remained at a polite distance, ',
u' three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander',
u' gestured the other <b>robots</b> away and entered itself. The']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u'They followed Bander. The <b>robots</b> remained at a polite distance, ',
u' three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander',
u' gestured the other <b>robots</b> away and entered itself. The']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{"took":0,"timed_out":false,"hits":{"total":1,"hits":[{"_id": 1,"_score":2788,"_source":{"title":"Books one","content":"They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. "},"highlight":{"content":["They followed Bander. The <b>robots</b> remained at a polite distance, "," three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander"," gestured the other <b>robots</b> away and entered itself. The"]}}]}}class SearchResponse {
took: 0
timedOut: false
hits: class SearchResponseHits {
total: 1
maxScore: null
hits: [{_id=1, _score=2788, _source={title=Books one, content=They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. }, highlight={title=[Books <b>one</b>], content=[They followed Bander. The <b>robots</b> remained at a polite distance, , three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander, gestured the other <b>robots</b> away and entered itself. The]}}]
aggregations: null
}
profile: null
}class SearchResponse {
took: 0
timedOut: false
hits: class SearchResponseHits {
total: 1
maxScore: null
hits: [{_id=1, _score=2788, _source={title=Books one, content=They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. }, highlight={title=[Books <b>one</b>], content=[They followed Bander. The <b>robots</b> remained at a polite distance, , three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander, gestured the other <b>robots</b> away and entered itself. The]}}]
aggregations: null
}
profile: null
}class SearchResponse {
took: 0
timedOut: false
hits: class SearchResponseHits {
total: 1
maxScore: null
hits: [{_id=1, _score=2788, _source={title=Books one, content=They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. }, highlight={title=[Books <b>one</b>], content=[They followed Bander. The <b>robots</b> remained at a polite distance, , three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander, gestured the other <b>robots</b> away and entered itself. The]}}]
aggregations: null
}
profile: null
}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text 1</b>"
]
}
]}
}
}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text 1</b>"
]
}
]}
}
}Чтобы выделить все возможные поля, передайте пустой объект в качестве свойства highlight.
- JSON
- PHP
- Python
- Python-asyncio
- Javascript
- Java
- C#
- Rust
- TypeScript
- Go
POST /search
{
"table": "books",
"query": { "match": { "*": "one|robots" } },
"highlight": {}
}$index->setName('books');
$results = $index->search('one|robots')->highlight()->get();
foreach($results as $doc)
{
echo 'Document: '.$doc->getId()."\n";
foreach($doc->getData() as $field=>$value)
{
echo $field.' : '.$value."\n";
}
foreach($doc->getHighlight() as $field=>$snippets)
{
echo "Highlight for ".$field.":\n";
foreach($snippets as $snippet)
{
echo "- ".$snippet."\n";
}
}
}res = searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{}})res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{}})res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{}});searchRequest = new SearchRequest();
searchRequest.setIndex("books");
query = new HashMap<String,Object>();
query.put("match",new HashMap<String,Object>(){{
put("*","one|robots");
}});
searchRequest.setQuery(query);
highlight = new HashMap<String,Object>(){{
}};
searchRequest.setHighlight(highlight);
searchResponse = searchApi.search(searchRequest);var searchRequest = new SearchRequest("books");
searchRequest.FulltextFilter = new MatchFilter("*", "one|robots");
var highlight = new Highlight();
searchRequest.Highlight = highlight;
var searchResponse = searchApi.Search(searchRequest);let match_filter = HashMap::new();
match_filter.insert("*".to_string(), "one|robots".to_string());
let query = SearchQuery {
match: Some(serde_json::json!(match_filter).into()),
..Default::default(),
};
let highlight = Highlight::new();
let search_req = SearchRequest {
table: "books".to_string(),
query: Some(Box::new(query)),
highlight: serde_json::json!(highlight),
..Default::default(),
};res = await searchApi.search({
index: 'test',
query: {
match: {
*: 'Text 1|Doc 1'
}
},
highlight: {}
});matchClause := map[string]interface{} {"*": "Text 1|Doc 1"};
query := map[string]interface{} {"match": matchClause};
searchRequest.SetQuery(query);
highlight := manticoreclient.NewHighlight()
searchRequest.SetHighlight(highlight)
res, _, _ := apiClient.SearchAPI.Search(context.Background()).SearchRequest(*searchRequest).Execute(){
"took": 0,
"timed_out": false,
"hits": {
"total": 1,
"hits": [
{
"_id": 1,
"_score": 2788,
"_source": {
"title": "Books one",
"content": "They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. "
},
"highlight": {
"title": [
"Books <b>one</b>"
],
"content": [
"They followed Bander. The <b>robots</b> remained at a polite distance, ",
" three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander",
" gestured the other <b>robots</b> away and entered itself. The"
]
}
}
]
}
}Document: 1
title : Books one
content : They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it.
Highlight for title:
- Books <b>one</b>
Highlight for content:
- They followed Bander. The <b>robots</b> remained at a polite distance,
- three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander
- gestured the other <b>robots</b> away and entered itself. The{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u'They followed Bander. The <b>robots</b> remained at a polite distance, ',
u' three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander',
u' gestured the other <b>robots</b> away and entered itself. The'],
u'title': [u'Books <b>one</b>']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u'They followed Bander. The <b>robots</b> remained at a polite distance, ',
u' three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander',
u' gestured the other <b>robots</b> away and entered itself. The'],
u'title': [u'Books <b>one</b>']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{"took":0,"timed_out":false,"hits":{"total":1,"hits":[{"_id": 1,"_score":2788,"_source":{"title":"Books one","content":"They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. "},"highlight":{"title":["Books <b>one</b>"],"content":["They followed Bander. The <b>robots</b> remained at a polite distance, "," three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander"," gestured the other <b>robots</b> away and entered itself. The"]}}]}}class SearchResponse {
took: 0
timedOut: false
hits: class SearchResponseHits {
total: 1
maxScore: null
hits: [{_id=1, _score=2788, _source={title=Books one, content=They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. }, highlight={title=[Books <b>one</b>], content=[They followed Bander. The <b>robots</b> remained at a polite distance, , three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander, gestured the other <b>robots</b> away and entered itself. The]}}]
aggregations: null
}
profile: null
}class SearchResponse {
took: 0
timedOut: false
hits: class SearchResponseHits {
total: 1
maxScore: null
hits: [{_id=1, _score=2788, _source={title=Books one, content=They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. }, highlight={title=[Books <b>one</b>], content=[They followed Bander. The <b>robots</b> remained at a polite distance, , three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander, gestured the other <b>robots</b> away and entered itself. The]}}]
aggregations: null
}
profile: null
}class SearchResponse {
took: 0
timedOut: false
hits: class SearchResponseHits {
total: 1
maxScore: null
hits: [{_id=1, _score=2788, _source={title=Books one, content=They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. }, highlight={title=[Books <b>one</b>], content=[They followed Bander. The <b>robots</b> remained at a polite distance, , three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander, gestured the other <b>robots</b> away and entered itself. The]}}]
aggregations: null
}
profile: null
}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text 1</b>"
],
"name":
[
"<b>Doc 1</b>"
]
}
]}
}
}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text 1</b>"
],
"name":
[
"<b>Doc 1</b>"
]
}
]}
}
}В дополнение к общим опциям подсветки, для JSON-запросов через HTTP доступны несколько синонимов:
Объект fields содержит имена атрибутов с опциями. Он также может быть массивом имен полей (без опций).
Обратите внимание, что по умолчанию подсветка пытается выделить результаты, соответствующие полнотекстовому запросу. В общем случае, если вы не указываете поля для подсветки, подсветка основана на вашем полнотекстовом запросе. Однако, если вы указываете поля для подсветки, выделение происходит только если полнотекстовый запрос совпадает с выбранными полями.
encoder может быть установлен в default или html. При установке в html сохраняется HTML-разметка при подсветке. Это работает аналогично опции html_strip_mode=retain.
Опция highlight_query позволяет выделять текст по запросу, отличному от вашего поискового запроса. Синтаксис такой же, как в основном query.
- JSON
- PHP
- Python
- Python-asyncio
- Javascript
- Java
- C#
- Rust
- TypeScript
- Go
POST /search
{
"table": "books",
"query": { "match": { "content": "one|robots" } },
"highlight":
{
"fields": [ "content"],
"highlight_query": { "match": { "*":"polite distance" } }
}
}$index->setName('books');
$bool = new \Manticoresearch\Query\BoolQuery();
$bool->must(new \Manticoresearch\Query\Match(['query' => 'one|robots'], 'content'));
$results = $index->search($bool)->highlight(['content'],['highlight_query'=>['match'=>['*'=>'polite distance']]])->get();
foreach($results as $doc)
{
echo 'Document: '.$doc->getId()."\n";
foreach($doc->getData() as $field=>$value)
{
echo $field.' : '.$value."\n";
}
foreach($doc->getHighlight() as $field=>$snippets)
{
echo "Highlight for ".$field.":\n";
foreach($snippets as $snippet)
{
echo "- ".$snippet."\n";
}
}
}res = searchApi.search({"table":"books","query":{"match":{"content":"one|robots"}},"highlight":{"fields":["content"],"highlight_query":{"match":{"*":"polite distance"}}}})res = await searchApi.search({"table":"books","query":{"match":{"content":"one|robots"}},"highlight":{"fields":["content"],"highlight_query":{"match":{"*":"polite distance"}}}})res = await searchApi.search({"table":"books","query":{"match":{"content":"one|robots"}},"highlight":{"fields":["content"],"highlight_query":{"match":{"*":"polite distance"}}}});searchRequest = new SearchRequest();
searchRequest.setIndex("books");
query = new HashMap<String,Object>();
query.put("match",new HashMap<String,Object>(){{
put("*","one|robots");
}});
searchRequest.setQuery(query);
highlight = new HashMap<String,Object>(){{
put("fields",new String[] {"content","title"});
put("highlight_query",
new HashMap<String,Object>(){{
put("match", new HashMap<String,Object>(){{
put("*","polite distance");
}});
}});
}};
searchRequest.setHighlight(highlight);
searchResponse = searchApi.search(searchRequest);var searchRequest = new SearchRequest("books");
searchRequest.FulltextFilter = new MatchFilter("*", "one|robots");
var highlight = new Highlight();
highlight.Fieldnames = new List<string> {"content", "title"};
Dictionary<string, Object> match = new Dictionary<string, Object>();
match.Add("*", "polite distance");
Dictionary<string, Object> highlightQuery = new Dictionary<string, Object>();
highlightQuery.Add("match", match);
highlight.HighlightQuery = highlightQuery;
searchRequest.Highlight = highlight;
var searchResponse = searchApi.Search(searchRequest);let match_filter = HashMap::new();
match_filter.insert("*".to_string(), "one|robots".to_string());
let query = SearchQuery {
match: Some(serde_json::json!(match_filter).into()),
..Default::default(),
};
let mut highlight_match_filter = HashMap::new();
highlight_match_filter.insert("*".to_string(), "polite distance".to_string());
let highlight_query = QueryFilter {
r#match: Some(serde_json::json!(highlight_match_filter)),
..Default::default(),
};
let highlight_fields [String; 2] = ["content".to_string(), "title".to_string()];
let highlight = Highlight {
fields: Some(serde_json::json!(highlight_fields)),
highlight_query: Some(Box::new(highlight_query)),
..Default::default(),
};
let search_req = SearchRequest {
table: "books".to_string(),
query: Some(Box::new(query)),
highlight: serde_json::json!(highlight),
..Default::default(),
};
let search_res = search_api.search(search_req).await;res = await searchApi.search({
index: 'test',
query: {
match: {
*: 'Text 1'
}
},
highlight: {
fields: ['content'],
highlight_query: {
match: {*: 'Text'}
}
}
});matchClause := map[string]interface{} {"*": "Text 1"};
query := map[string]interface{} {"match": matchClause};
searchRequest.SetQuery(query);
highlight := manticoreclient.NewHighlight()
highlightField := manticoreclient.NetHighlightField("content")
highlightFields := []interface{} { highlightField }
highlight.SetFields(highlightFields)
queryMatchClause := map[string]interface{} {"*": "Text"};
highlightQuery := map[string]interface{} {"match": queryMatchClause};
highlight.SetHighlightQuery(highlightQuery)
searchRequest.SetHighlight(highlight)
res, _, _ := apiClient.SearchAPI.Search(context.Background()).SearchRequest(*searchRequest).Execute(){'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 1788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u'. The robots remained at a <b>polite distance</b>, but their presence was a']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 1788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u'. The robots remained at a <b>polite distance</b>, but their presence was a']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{"took":0,"timed_out":false,"hits":{"total":1,"hits":[{"_id": 1,"_score":1788,"_source":{"title":"Books one","content":"They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. "},"highlight":{"content":[". The robots remained at a <b>polite distance</b>, but their presence was a"]}}]}}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text</b> 1"
]
}
]}
}
}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text</b> 1"
]
}
]}
}
}pre_tags и post_tags задают открывающие и закрывающие теги для выделенных фрагментов текста. Они работают аналогично опциям before_match и after_match. Эти параметры необязательны, значения по умолчанию — <b> и </b>.
- JSON
- PHP
- Python
- Python-asyncio
- Javascript
- Java
- C#
- Rust
- TypeScript
- Go
POST /search
{
"table": "books",
"query": { "match": { "*": "one|robots" } },
"highlight":
{
"fields": [ "content", "title" ],
"pre_tags": "before_",
"post_tags": "_after"
}
}$index->setName('books');
$bool = new \Manticoresearch\Query\BoolQuery();
$bool->must(new \Manticoresearch\Query\Match(['query' => 'one|robots'], '*'));
$results = $index->search($bool)->highlight(['content','title'],['pre_tags'=>'before_','post_tags'=>'_after'])->get();
foreach($results as $doc)
{
echo 'Document: '.$doc->getId()."\n";
foreach($doc->getData() as $field=>$value)
{
echo $field.' : '.$value."\n";
}
foreach($doc->getHighlight() as $field=>$snippets)
{
echo "Highlight for ".$field.":\n";
foreach($snippets as $snippet)
{
echo "- ".$snippet."\n";
}
}
}res = searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content","title"],"pre_tags":"before_","post_tags":"_after"}})res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content","title"],"pre_tags":"before_","post_tags":"_after"}})res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content","title"],"pre_tags":"before_","post_tags":"_after"}});searchRequest = new SearchRequest();
searchRequest.setIndex("books");
query = new HashMap<String,Object>();
query.put("match",new HashMap<String,Object>(){{
put("*","one|robots");
}});
searchRequest.setQuery(query);
highlight = new HashMap<String,Object>(){{
put("fields",new String[] {"content","title"});
put("pre_tags","before_");
put("post_tags","_after");
}};
searchRequest.setHighlight(highlight);
searchResponse = searchApi.search(searchRequest);var searchRequest = new SearchRequest("books");
searchRequest.FulltextFilter = new MatchFilter("*", "one|robots");
var highlight = new Highlight();
highlight.Fieldnames = new List<string> {"content", "title"};
highlight.PreTags = "before_";
highlight.PostTags = "_after";
searchRequest.Highlight = highlight;
var searchResponse = searchApi.Search(searchRequest);let match_filter = HashMap::new();
match_filter.insert("*".to_string(), "one|robots".to_string());
let query = SearchQuery {
match: Some(serde_json::json!(match_filter).into()),
..Default::default(),
};
let highlight_fields [String; 2] = ["content".to_string(), "title".to_string()];
let highlight = Highlight {
fields: Some(serde_json::json!(highlight_fields)),
pre_tags: Some("before_".to_string()),
post_tags: Some("_after".to_string()),
..Default::default(),
};
let search_req = SearchRequest {
table: "books".to_string(),
query: Some(Box::new(query)),
highlight: serde_json::json!(highlight),
..Default::default(),
};
let search_res = search_api.search(search_req).await;res = await searchApi.search({
index: 'test',
query: {
match: {
*: 'Text 1'
}
},
highlight: {
pre_tags: 'before_',
post_tags: '_after'
}
});matchClause := map[string]interface{} {"*": "Text 1"}
query := map[string]interface{} {"match": matchClause}
searchRequest.SetQuery(query)
highlight := manticoreclient.NewHighlight()
highlight.SetPreTags("before_")
highlight.SetPostTags("_after")
searchRequest.SetHighlight(highlight)
res, _, _ := apiClient.SearchAPI.Search(context.Background()).SearchRequest(*searchRequest).Execute()Document: 1
title : Books one
content : They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it.
Highlight for content:
- They followed Bander. The before_robots_after remained at a polite distance,
- three into the room. before_One_after of the before_robots_after followed as well. Bander
- gestured the other before_robots_after away and entered itself. The
Highlight for title:
- Books before_one_after{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u'They followed Bander. The before_robots_after remained at a polite distance, ',
u' three into the room. before_One_after of the before_robots_after followed as well. Bander',
u' gestured the other before_robots_after away and entered itself. The'],
u'title': [u'Books before_one_after']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u'They followed Bander. The before_robots_after remained at a polite distance, ',
u' three into the room. before_One_after of the before_robots_after followed as well. Bander',
u' gestured the other before_robots_after away and entered itself. The'],
u'title': [u'Books before_one_after']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{"took":0,"timed_out":false,"hits":{"total":1,"hits":[{"_id": 1,"_score":2788,"_source":{"title":"Books one","content":"They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. "},"highlight":{"content":["They followed Bander. The before_robots_after remained at a polite distance, "," three into the room. before_One_after of the before_robots_after followed as well. Bander"," gestured the other before_robots_after away and entered itself. The"],"title":["Books before_one_after"]}}]}}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"before_Text 1_after"
]
}
]}
}
}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"before_Text 1_after"
]
}
]}
}
}no_match_size работает аналогично опции allow_empty. Если установлено в 0, это эквивалентно allow_empty=1, позволяя возвращать пустую строку в качестве результата подсветки, если не удалось сгенерировать фрагмент. В противном случае возвращается начало поля. Параметр необязательный, значение по умолчанию — 1.
- JSON
- PHP
- Python
- Python-asyncio
- Javascript
- Java
- C#
- Rust
- TypeScript
- Go
POST /search
{
"table": "books",
"query": { "match": { "*": "one|robots" } },
"highlight":
{
"fields": [ "content", "title" ],
"no_match_size": 0
}
}$index->setName('books');
$bool = new \Manticoresearch\Query\BoolQuery();
$bool->must(new \Manticoresearch\Query\Match(['query' => 'one|robots'], '*'));
$results = $index->search($bool)->highlight(['content','title'],['no_match_size'=>0])->get();
foreach($results as $doc)
{
echo 'Document: '.$doc->getId()."\n";
foreach($doc->getData() as $field=>$value)
{
echo $field.' : '.$value."\n";
}
foreach($doc->getHighlight() as $field=>$snippets)
{
echo "Highlight for ".$field.":\n";
foreach($snippets as $snippet)
{
echo "- ".$snippet."\n";
}
}
}res = searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content","title"],"no_match_size":0}})res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content","title"],"no_match_size":0}})res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content","title"],"no_match_size":0}});searchRequest = new SearchRequest();
searchRequest.setIndex("books");
query = new HashMap<String,Object>();
query.put("match",new HashMap<String,Object>(){{
put("*","one|robots");
}});
searchRequest.setQuery(query);
highlight = new HashMap<String,Object>(){{
put("fields",new String[] {"content","title"});
put("no_match_size",0);
}};
searchRequest.setHighlight(highlight);
searchResponse = searchApi.search(searchRequest);var searchRequest = new SearchRequest("books");
searchRequest.FulltextFilter = new MatchFilter("*", "one|robots");
var highlight = new Highlight();
highlight.Fieldnames = new List<string> {"content", "title"};
highlight.NoMatchSize = 0;
searchRequest.Highlight = highlight;
var searchResponse = searchApi.Search(searchRequest);let match_filter = HashMap::new();
match_filter.insert("*".to_string(), "one|robots".to_string());
let query = SearchQuery {
match: Some(serde_json::json!(match_filter).into()),
..Default::default(),
};
let highlight_fields [String; 2] = ["content".to_string(), "title".to_string()];
let highlight = Highlight {
fields: Some(serde_json::json!(highlight_fields)),
no_match_size: Some(NoMatchSize::Variant0),
..Default::default(),
};
let search_req = SearchRequest {
table: "books".to_string(),
query: Some(Box::new(query)),
highlight: serde_json::json!(highlight),
..Default::default(),
};
let search_res = search_api.search(search_req).await;res = await searchApi.search({
index: 'test',
query: {
match: {
*: 'Text 1'
}
},
highlight: {no_match_size: 0}
});matchClause := map[string]interface{} {"*": "Text 1"};
query := map[string]interface{} {"match": matchClause};
searchRequest.SetQuery(query);
highlight := manticoreclient.NewHighlight()
highlight.SetNoMatchSize(0)
searchRequest.SetHighlight(highlight)
res, _, _ := apiClient.SearchAPI.Search(context.Background()).SearchRequest(*searchRequest).Execute()Document: 1
title : Books one
content : They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it.
Highlight for content:
- They followed Bander. The <b>robots</b> remained at a polite distance,
- three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander
- gestured the other <b>robots</b> away and entered itself. The
Highlight for title:
- Books <b>one</b>{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u'They followed Bander. The <b>robots</b> remained at a polite distance, ',
u' three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander',
u' gestured the other <b>robots</b> away and entered itself. The'],
u'title': [u'Books <b>one</b>']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u'They followed Bander. The <b>robots</b> remained at a polite distance, ',
u' three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander',
u' gestured the other <b>robots</b> away and entered itself. The'],
u'title': [u'Books <b>one</b>']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{"took":0,"timed_out":false,"hits":{"total":1,"hits":[{"_id": 1,"_score":2788,"_source":{"title":"Books one","content":"They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. "},"highlight":{"content":["They followed Bander. The <b>robots</b> remained at a polite distance, "," three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander"," gestured the other <b>robots</b> away and entered itself. The"],"title":["Books <b>one</b>"]}}]}}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text 1</b>"
]
}
]}
}
}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text 1</b>"
]
}
]}
}
}order задаёт порядок сортировки извлечённых фрагментов. Если установлено значение "score", фрагменты сортируются по релевантности. Это опционально и работает аналогично опции weight_order.
- JSON
- PHP
- Python
- Python-asyncio
- Javascript
- Java
- C#
- Rust
- TypeScript
- Go
POST /search
{
"table": "books",
"query": { "match": { "*": "one|robots" } },
"highlight":
{
"fields": [ "content", "title" ],
"order": "score"
}
}$index->setName('books');
$bool = new \Manticoresearch\Query\BoolQuery();
$bool->must(new \Manticoresearch\Query\Match(['query' => 'one|robots'], '*'));
$results = $index->search($bool)->highlight(['content','title'],['order'=>"score"])->get();
foreach($results as $doc)
{
echo 'Document: '.$doc->getId()."\n";
foreach($doc->getData() as $field=>$value)
{
echo $field.' : '.$value."\n";
}
foreach($doc->getHighlight() as $field=>$snippets)
{
echo "Highlight for ".$field.":\n";
foreach($snippets as $snippet)
{
echo "- ".$snippet."\n";
}
}
}res = searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content","title"],"order":"score"}})res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content","title"],"order":"score"}})res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content","title"],"order":"score"}});searchRequest = new SearchRequest();
searchRequest.setIndex("books");
query = new HashMap<String,Object>();
query.put("match",new HashMap<String,Object>(){{
put("*","one|robots");
}});
searchRequest.setQuery(query);
highlight = new HashMap<String,Object>(){{
put("fields",new String[] {"content","title"});
put("order","score");
}};
searchRequest.setHighlight(highlight);
searchResponse = searchApi.search(searchRequest);var searchRequest = new SearchRequest("books");
searchRequest.FulltextFilter = new MatchFilter("*", "one|robots");
var highlight = new Highlight();
highlight.Fieldnames = new List<string> {"content", "title"};
highlight.Order = "score";
searchRequest.Highlight = highlight;
var searchResponse = searchApi.Search(searchRequest);let match_filter = HashMap::new();
match_filter.insert("*".to_string(), "one|robots".to_string());
let query = SearchQuery {
match: Some(serde_json::json!(match_filter).into()),
..Default::default(),
};
let highlight_fields [String; 2] = ["content".to_string(), "title".to_string()];
let highlight = Highlight {
fields: Some(serde_json::json!(highlight_fields)),
order: Some(Order::Score),
post_tags: Some("_after".to_string()),
..Default::default(),
};
let search_req = SearchRequest {
table: "books".to_string(),
query: Some(Box::new(query)),
highlight: serde_json::json!(highlight),
..Default::default(),
};
let search_res = search_api.search(search_req).await;res = await searchApi.search({
index: 'test',
query: {
match: {
*: 'Text 1'
}
},
highlight: { order: 'score' }
});matchClause := map[string]interface{} {"*": "Text 1"};
query := map[string]interface{} {"match": matchClause};
searchRequest.SetQuery(query);
highlight := manticoreclient.NewHighlight()
highlight.SetOrder("score")
searchRequest.SetHighlight(highlight)
res, _, _ := apiClient.SearchAPI.Search(context.Background()).SearchRequest(*searchRequest).Execute()Document: 1
title : Books one
content : They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it.
Highlight for content:
- three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander
- gestured the other <b>robots</b> away and entered itself. The
- They followed Bander. The <b>robots</b> remained at a polite distance,
Highlight for title:
- Books <b>one</b>{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u' three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander',
u' gestured the other <b>robots</b> away and entered itself. The',
u'They followed Bander. The <b>robots</b> remained at a polite distance, '],
u'title': [u'Books <b>one</b>']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u' three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander',
u' gestured the other <b>robots</b> away and entered itself. The',
u'They followed Bander. The <b>robots</b> remained at a polite distance, '],
u'title': [u'Books <b>one</b>']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{"took":0,"timed_out":false,"hits":{"total":1,"hits":[{"_id": 1,"_score":2788,"_source":{"title":"Books one","content":"They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. "},"highlight":{"content":[" three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander"," gestured the other <b>robots</b> away and entered itself. The","They followed Bander. The <b>robots</b> remained at a polite distance, "],"title":["Books <b>one</b>"]}}]}}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text 1</b>"
]
}
]}
}
}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text 1</b>"
]
}
]}
}
}fragment_size задаёт максимальный размер фрагмента в символах. Может быть глобальным или для каждого поля отдельно. Опции для поля переопределяют глобальные. Это опционально, значение по умолчанию — 256. Работает аналогично опции limit.
- JSON
- PHP
- Python
- Python-asyncio
- Javascript
- Java
- C#
- Rust
- TypeScript
- Go
POST /search
{
"table": "books",
"query": { "match": { "*": "one|robots" } },
"highlight":
{
"fields": [ "content", "title" ],
"fragment_size": 100
}
}$index->setName('books');
$bool = new \Manticoresearch\Query\BoolQuery();
$bool->must(new \Manticoresearch\Query\Match(['query' => 'one|robots'], '*'));
$results = $index->search($bool)->highlight(['content','title'],['fragment_size'=>100])->get();
foreach($results as $doc)
{
echo 'Document: '.$doc->getId()."\n";
foreach($doc->getData() as $field=>$value)
{
echo $field.' : '.$value."\n";
}
foreach($doc->getHighlight() as $field=>$snippets)
{
echo "Highlight for ".$field.":\n";
foreach($snippets as $snippet)
{
echo "- ".$snippet."\n";
}
}
}res = searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content","title"],"fragment_size":100}})res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content","title"],"fragment_size":100}})res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content","title"],"fragment_size":100}});searchRequest = new SearchRequest();
searchRequest.setIndex("books");
query = new HashMap<String,Object>();
query.put("match",new HashMap<String,Object>(){{
put("*","one|robots");
}});
searchRequest.setQuery(query);
highlight = new HashMap<String,Object>(){{
put("fields",new String[] {"content","title"});
put("fragment_size",100);
}};
searchRequest.setHighlight(highlight);
searchResponse = searchApi.search(searchRequest);var searchRequest = new SearchRequest("books");
searchRequest.FulltextFilter = new MatchFilter("*", "one|robots");
var highlight = new Highlight();
highlight.Fieldnames = new List<string> {"content", "title"};
highlight.FragmentSize = 100;
searchRequest.Highlight = highlight;
var searchResponse = searchApi.Search(searchRequest);let match_filter = HashMap::new();
match_filter.insert("*".to_string(), "one|robots".to_string());
let query = SearchQuery {
match: Some(serde_json::json!(match_filter).into()),
..Default::default(),
};
let highlight_fields [String; 2] = ["content".to_string(), "title".to_string()];
let highlight = Highlight {
fields: Some(serde_json::json!(highlight_fields)),
fragment_size: Some(serde_json::json!(100)),
..Default::default(),
};
let search_req = SearchRequest {
table: "books".to_string(),
query: Some(Box::new(query)),
highlight: serde_json::json!(highlight),
..Default::default(),
};
let search_res = search_api.search(search_req).await;res = await searchApi.search({
index: 'test',
query: {
match: {
*: 'Text 1'
}
},
highlight: { fragment_size: 4}
});matchClause := map[string]interface{} {"*": "Text 1"};
query := map[string]interface{} {"match": matchClause};
searchRequest.SetQuery(query);
highlight := manticoreclient.NewHighlight()
highlight.SetFragmentSize(4)
searchRequest.SetHighlight(highlight)
res, _, _ := apiClient.SearchAPI.Search(context.Background()).SearchRequest(*searchRequest).Execute()Document: 1
title : Books one
content : They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it.
Highlight for content:
- the room. <b>One</b> of the <b>robots</b> followed as well
- Bander gestured the other <b>robots</b> away and entered
Highlight for title:
- Books <b>one</b>{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u' the room. <b>One</b> of the <b>robots</b> followed as well',
u'Bander gestured the other <b>robots</b> away and entered '],
u'title': [u'Books <b>one</b>']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u' the room. <b>One</b> of the <b>robots</b> followed as well',
u'Bander gestured the other <b>robots</b> away and entered '],
u'title': [u'Books <b>one</b>']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{"took":0,"timed_out":false,"hits":{"total":1,"hits":[{"_id": 1,"_score":2788,"_source":{"title":"Books one","content":"They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. "},"highlight":{"content":[" the room. <b>One</b> of the <b>robots</b> followed as well","Bander gestured the other <b>robots</b> away and entered "],"title":["Books <b>one</b>"]}}]}}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text</b>"
]
}
]}
}
}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text</b>"
]
}
]}
}
}number_of_fragments ограничивает максимальное количество фрагментов в результате. Как и fragment_size, может быть глобальным или для каждого поля. Опционально, значение по умолчанию — 0 (без ограничений). Работает аналогично опции limit_snippets.
- JSON
- PHP
- Python
- Python-asyncio
- Javascript
- Java
- C#
- Rust
- TypeScript
- Go
POST /search
{
"table": "books",
"query": { "match": { "*": "one|robots" } },
"highlight":
{
"fields": [ "content", "title" ],
"number_of_fragments": 10
}
}$index->setName('books');
$bool = new \Manticoresearch\Query\BoolQuery();
$bool->must(new \Manticoresearch\Query\Match(['query' => 'one|robots'], '*'));
$results = $index->search($bool)->highlight(['content','title'],['number_of_fragments'=>10])->get();
foreach($results as $doc)
{
echo 'Document: '.$doc->getId()."\n";
foreach($doc->getData() as $field=>$value)
{
echo $field.' : '.$value."\n";
}
foreach($doc->getHighlight() as $field=>$snippets)
{
echo "Highlight for ".$field.":\n";
foreach($snippets as $snippet)
{
echo "- ".$snippet."\n";
}
}
}res =searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content","title"],"number_of_fragments":10}})res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content","title"],"number_of_fragments":10}})res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":["content","title"],"number_of_fragments":10}});searchRequest = new SearchRequest();
searchRequest.setIndex("books");
query = new HashMap<String,Object>();
query.put("match",new HashMap<String,Object>(){{
put("*","one|robots");
}});
searchRequest.setQuery(query);
highlight = new HashMap<String,Object>(){{
put("fields",new String[] {"content","title"});
put("number_of_fragments",10);
}};
searchRequest.setHighlight(highlight);
searchResponse = searchApi.search(searchRequest);var searchRequest = new SearchRequest("books");
searchRequest.FulltextFilter = new MatchFilter("*", "one|robots");
var highlight = new Highlight();
highlight.Fieldnames = new List<string> {"content", "title"};
highlight.NumberOfFragments = 10;
searchRequest.Highlight = highlight;
var searchResponse = searchApi.Search(searchRequest);let match_filter = HashMap::new();
match_filter.insert("*".to_string(), "one|robots".to_string());
let query = SearchQuery {
match: Some(serde_json::json!(match_filter).into()),
..Default::default(),
};
let highlight_fields [String; 2] = ["content".to_string(), "title".to_string()];
let highlight = Highlight {
fields: Some(serde_json::json!(highlight_fields)),
number_of_fragments: Some(serde_json::json!(10)),
..Default::default(),
};
let search_req = SearchRequest {
table: "books".to_string(),
query: Some(Box::new(query)),
highlight: serde_json::json!(highlight),
..Default::default(),
};
let search_res = search_api.search(search_req).await;res = await searchApi.search({
index: 'test',
query: {
match: {
*: 'Text 1'
}
},
highlight: { number_of_fragments: 1}
});matchClause := map[string]interface{} {"*": "Text 1"};
query := map[string]interface{} {"match": matchClause};
searchRequest.SetQuery(query);
highlight := manticoreclient.NewHighlight()
highlight.SetNumberOfFragments(1)
searchRequest.SetHighlight(highlight)
res, _, _ := apiClient.SearchAPI.Search(context.Background()).SearchRequest(*searchRequest).Execute()Document: 1
title : Books one
content : They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it.
Highlight for content:
- They followed Bander. The <b>robots</b> remained at a polite distance,
- three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander
- gestured the other <b>robots</b> away and entered itself. The
Highlight for title:
- Books <b>one</b>{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u'They followed Bander. The <b>robots</b> remained at a polite distance, ',
u' three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander',
u' gestured the other <b>robots</b> away and entered itself. The'],
u'title': [u'Books <b>one</b>']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u'They followed Bander. The <b>robots</b> remained at a polite distance, ',
u' three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander',
u' gestured the other <b>robots</b> away and entered itself. The'],
u'title': [u'Books <b>one</b>']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{"took":0,"timed_out":false,"hits":{"total":1,"hits":[{"_id": 1,"_score":2788,"_source":{"title":"Books one","content":"They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. "},"highlight":{"content":["They followed Bander. The <b>robots</b> remained at a polite distance, "," three into the room. <b>One</b> of the <b>robots</b> followed as well. Bander"," gestured the other <b>robots</b> away and entered itself. The"],"title":["Books <b>one</b>"]}}]}}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text 1</b>"
]
}
]}
}
}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text 1</b>"
]
}
]}
}
}Опции limit, limit_words и limit_snippets могут задаваться глобально или для каждого поля. Глобальные опции используются как лимиты для полей, если не переопределены опциями для конкретного поля. В примере поле title подсвечивается с настройками по умолчанию, а поле content использует другие лимиты.
- JSON
- PHP
- Python
- Python-asyncio
- Javascript
- Java
- C#
- Rust
- TypeScript
- Go
POST /search
{
"table": "books",
"query": { "match": { "*": "one|robots" } },
"highlight":
{
"fields":
{
"title": {},
"content" : { "limit": 50 }
}
}
}$index->setName('books');
$bool = new \Manticoresearch\Query\BoolQuery();
$bool->must(new \Manticoresearch\Query\Match(['query' => 'one|robots'], '*'));
$results = $index->search($bool)->highlight(['content'=>['limit'=>50],'title'=>new \stdClass])->get();
foreach($results as $doc)
{
echo 'Document: '.$doc->getId()."\n";
foreach($doc->getData() as $field=>$value)
{
echo $field.' : '.$value."\n";
}
foreach($doc->getHighlight() as $field=>$snippets)
{
echo "Highlight for ".$field.":\n";
foreach($snippets as $snippet)
{
echo "- ".$snippet."\n";
}
}
}res =searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":{"title":{},"content":{"limit":50}}}})res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":{"title":{},"content":{"limit":50}}}})res = await searchApi.search({"table":"books","query":{"match":{"*":"one|robots"}},"highlight":{"fields":{"title":{},"content":{"limit":50}}}});searchRequest = new SearchRequest();
searchRequest.setIndex("books");
query = new HashMap<String,Object>();
query.put("match",new HashMap<String,Object>(){{
put("*","one|robots");
}});
searchRequest.setQuery(query);
highlight = new HashMap<String,Object>(){{
put("fields",new HashMap<String,Object>(){{
put("title",new HashMap<String,Object>(){{}});
put("content",new HashMap<String,Object>(){{
put("limit",50);
}});
}}
);
}};
searchRequest.setHighlight(highlight);
searchResponse = searchApi.search(searchRequest);var searchRequest = new SearchRequest("books");
searchRequest.FulltextFilter = new MatchFilter("*", "one|robots");
var highlight = new Highlight();
var highlightField = new HighlightField("title");
highlightField.Limit = 50;
highlight.Fields = new List<Object> {highlightField};
searchRequest.Highlight = highlight;
var searchResponse = searchApi.Search(searchRequest);let match_filter = HashMap::new();
match_filter.insert("*".to_string(), "one|robots".to_string());
let query = SearchQuery {
match: Some(serde_json::json!(match_filter).into()),
..Default::default(),
};
let highlight_fields [String; 1] = ["title".to_string()];
let highlight = Highlight {
fields: Some(serde_json::json!(highlight_fields)),
limit: Some(serde_json::json!(50)),
..Default::default(),
};
let search_req = SearchRequest {
table: "books".to_string(),
query: Some(Box::new(query)),
highlight: serde_json::json!(highlight),
..Default::default(),
};
let search_res = search_api.search(search_req).await;res = await searchApi.search({
index: 'test',
query: {
match: {
*: 'Text 1'
}
},
highlight: {
fields: {
content: { limit:1 }
}
}
});matchClause := map[string]interface{} {"*": "Text 1"};
query := map[string]interface{} {"match": matchClause};
searchRequest.SetQuery(query);
highlight := manticoreclient.NewHighlight()
highlightField := manticoreclient.NetHighlightField("content")
highlightField.SetLimit(1);
highlightFields := []interface{} { highlightField }
highlight.SetFields(highlightFields)
searchRequest.SetHighlight(highlight)
res, _, _ := apiClient.SearchAPI.Search(context.Background()).SearchRequest(*searchRequest).Execute()Document: 1
title : Books one
content : They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it.
Highlight for content:
- into the room. <b>One</b> of the <b>robots</b> followed as well
Highlight for title:
- Books <b>one</b>{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u' into the room. <b>One</b> of the <b>robots</b> followed as well'],
u'title': [u'Books <b>one</b>']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 2788,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u' into the room. <b>One</b> of the <b>robots</b> followed as well'],
u'title': [u'Books <b>one</b>']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{"took":0,"timed_out":false,"hits":{"total":1,"hits":[{"_id": 1,"_score":2788,"_source":{"title":"Books one","content":"They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. "},"highlight":{"title":["Books <b>one</b>"],"content":[" into the room. <b>One</b> of the <b>robots</b> followed as well"]}}]}}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text</b>"
]
}
]}
}
}{
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"hits":
[{
"_id": 1,
"_score":1480,
"_source":
{
"content":"Text 1",
"name":"Doc 1",
"cat":1
},
"highlight":
{
"content":
[
"<b>Text</b>"
]
}
]}
}
}Глобальные лимиты также можно задать, указав limits_per_field=0. Эта опция означает, что все объединённые результаты подсветки должны укладываться в заданные лимиты. Недостаток в том, что в одном поле может быть несколько подсвеченных фрагментов, а в другом — ни одного, если движок подсветки решит, что они более релевантны.
- JSON
- PHP
- Python
- Python-asyncio
- Javascript
- Java
- C#
- Rust
- TypeScript
- Go
POST /search
{
"table": "books",
"query": { "match": { "content": "and first" } },
"highlight":
{
"limits_per_field": false,
"fields":
{
"content" : { "limit": 50 }
}
}
}$index->setName('books');
$bool = new \Manticoresearch\Query\BoolQuery();
$bool->must(new \Manticoresearch\Query\Match(['query' => 'and first'], 'content'));
$results = $index->search($bool)->highlight(['content'=>['limit'=>50]],['limits_per_field'=>false])->get();
foreach($results as $doc)
{
echo 'Document: '.$doc->getId()."\n";
foreach($doc->getData() as $field=>$value)
{
echo $field.' : '.$value."\n";
}
foreach($doc->getHighlight() as $field=>$snippets)
{
echo "Highlight for ".$field.":\n";
foreach($snippets as $snippet)
{
echo "- ".$snippet."\n";
}
}
}res =searchApi.search({"table":"books","query":{"match":{"content":"and first"}},"highlight":{"fields":{"content":{"limit":50}},"limits_per_field":False}})res = await searchApi.search({"table":"books","query":{"match":{"content":"and first"}},"highlight":{"fields":{"content":{"limit":50}},"limits_per_field":False}})res = await searchApi.search({"table":"books","query":{"match":{"content":"and first"}},"highlight":{"fields":{"content":{"limit":50}},"limits_per_field":false}});searchRequest = new SearchRequest();
searchRequest.setIndex("books");
query = new HashMap<String,Object>();
query.put("match",new HashMap<String,Object>(){{
put("*","one|robots");
}});
searchRequest.setQuery(query);
highlight = new HashMap<String,Object>(){{
put("limits_per_field",0);
put("fields",new HashMap<String,Object>(){{
put("content",new HashMap<String,Object>(){{
put("limit",50);
}});
}}
);
}};
searchRequest.setHighlight(highlight);
searchResponse = searchApi.search(searchRequest);var searchRequest = new SearchRequest("books");
searchRequest.FulltextFilter = new MatchFilter("*", "one|robots");
var highlight = new Highlight();
highlight.LimitsPerField = 0;
var highlightField = new HighlightField("title");
highlight.Fields = new List<Object> {highlightField};
searchRequest.Highlight = highlight;
var searchResponse = searchApi.Search(searchRequest);let match_filter = HashMap::new();
match_filter.insert("*".to_string(), "one|robots".to_string());
let query = SearchQuery {
match: Some(serde_json::json!(match_filter).into()),
..Default::default(),
};
let highlight_fields [String; 1] = ["title".to_string()];
let highlight = Highlight {
fields: Some(serde_json::json!(highlight_fields)),
limit_per_field: Some(serde_json::json!(false)),
..Default::default(),
};
let search_req = SearchRequest {
table: "books".to_string(),
query: Some(Box::new(query)),
highlight: serde_json::json!(highlight),
..Default::default(),
};
let search_res = search_api.search(search_req).await;res = await searchApi.search({
index: 'test',
query: {
match: {
*: 'Text 1'
}
},
highlight: { limits_per_field: 0 }
});matchClause := map[string]interface{} {"*": "Text 1"};
query := map[string]interface{} {"match": matchClause};
searchRequest.SetQuery(query);
highlight := manticoreclient.NewHighlight()
highlight.SetLimitsPerField(0)
searchRequest.SetHighlight(highlight)
res, _, _ := apiClient.SearchAPI.Search(context.Background()).SearchRequest(*searchRequest).Execute()Document: 1
title : Books one
content : They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it.
Highlight for content:
- gestured the other robots away <b>and</b> entered itself. The door closed{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 1597,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u' gestured the other robots away <b>and</b> entered itself. The door closed']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{'aggregations': None,
'hits': {'hits': [{u'_id': u'1',
u'_score': 1597,
u'_source': {u'content': u'They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. ',
u'title': u'Books one'},
u'highlight': {u'content': [u' gestured the other robots away <b>and</b> entered itself. The door closed']}}],
'max_score': None,
'total': 1},
'profile': None,
'timed_out': False,
'took': 0}{"took":0,"timed_out":false,"hits":{"total":1,"hits":[{"_id": 1,"_score":1597,"_source":{"title":"Books one","content":"They followed Bander. The robots remained at a polite distance, but their presence was a constantly felt threat. Bander ushered all three into the room. One of the robots followed as well. Bander gestured the other robots away and entered itself. The door closed behind it. "},"highlight":{"content":[" gestured the other robots away <b>and</b> entered itself. The door closed"]}}]}}Оператор CALL SNIPPETS формирует фрагмент из предоставленных данных и запроса с использованием настроек указанной таблицы. Он не может получить доступ к встроенному хранилищу документов, поэтому рекомендуется использовать функцию HIGHLIGHT().
Синтаксис:
CALL SNIPPETS(data, table, query[, opt_value AS opt_name[, ...]])
data — источник, из которого извлекается фрагмент. Это может быть одна строка или список строк в фигурных скобках.
table — имя таблицы, которая задаёт настройки обработки текста для генерации фрагмента.
query — это полнотекстовый запрос, используемый для создания сниппетов.
opt_value и opt_name представляют собой опции генерации сниппетов.
- SQL
CALL SNIPPETS(('this is my document text','this is my another text'), 'forum', 'is text', 5 AS around, 200 AS limit);+----------------------------------------+
| snippet |
+----------------------------------------+
| this <b>is</b> my document <b>text</b> |
| this <b>is</b> my another <b>text</b> |
+----------------------------------------+
2 rows in set (0.02 sec)Большинство опций совпадает с опциями в функции HIGHLIGHT(). Однако есть несколько опций, которые можно использовать только с CALL SNIPPETS.
Следующие опции можно использовать для подсветки текста, хранящегося в отдельных файлах:
Эта опция, при включении, рассматривает первый аргумент как имена файлов, а не как данные для извлечения сниппетов. Указанные файлы на стороне сервера будут загружены для получения данных. Для параллелизации работы при включенном флаге будет использовано до max_threads_per_query рабочих потоков на запрос. По умолчанию 0 (без ограничений). Чтобы распределить генерацию сниппетов между удалёнными агентами, вызовите генерацию сниппетов в распределённой таблице, содержащей только одного(!) локального агента и нескольких удалённых. Опция snippets_file_prefix используется для генерации итогового имени файла. Например, если searchd настроен с snippets_file_prefix = /var/data_ и в качестве имени файла передан text.txt, сниппеты будут сгенерированы из содержимого /var/data_text.txt.
Эта опция работает только с распределённой генерацией сниппетов с удалёнными агентами. Исходные файлы для генерации сниппетов могут быть распределены между разными агентами, а основной сервер объединит все корректные результаты. Например, если у одного агента распределённой таблицы есть file1.txt, у другого агента — file2.txt, и вы используете CALL SNIPPETS с обоими этими файлами, searchd объединит результаты агентов, и вы получите результаты из обоих файлов file1.txt и file2.txt. По умолчанию 0.
Если опция load_files также включена, запрос вернёт ошибку, если какой-либо из файлов недоступен где-либо. В противном случае (если load_files не включена) для всех отсутствующих файлов будут возвращены пустые строки. Searchd не передаёт этот флаг агентам, поэтому агенты не генерируют критическую ошибку, если файл не существует. Если вы хотите быть уверены, что все исходные файлы загружены, установите обе опции load_files_scattered и load_files в 1. Если отсутствие некоторых исходных файлов на некоторых агентах не критично, установите только load_files_scattered в 1.
- SQL
CALL SNIPPETS(('data/doc1.txt','data/doc2.txt'), 'forum', 'is text', 1 AS load_files);+----------------------------------------+
| snippet |
+----------------------------------------+
| this <b>is</b> my document <b>text</b> |
| this <b>is</b> my another <b>text</b> |
+----------------------------------------+
2 rows in set (0.02 sec)