Агрегация лучших совпадений
Агрегатор метрик top_hits отслеживает наиболее релевантный документируемый документ. Этот агрегатор предназначен для использования в качестве под-агрегатора, чтобы можно было агрегировать верхние совпадающие документы в каждом ведре.
Мы не рекомендуем использовать top_hits в качестве агрегации верхнего уровня. Если вы хотите сгруппировать результаты поиска, используйте параметр collapse вместо этого.
Агрегатор top_hits эффективно используется для группировки наборов результатов по определённым полям с помощью агрегатора ведер. Один или несколько агрегаторов ведер определяют, по каким свойствам набор результатов будет разделен на сегменты.
Параметры
-
from— Смещение от первого результата, который вы хотите получить. -
size— Максимальное количество верхних совпадающих результатов, которые необходимо вернуть в каждом ведре. По умолчанию возвращаются три самых подходящих результата. -
sort— Способ сортировки верхних совпадающих результатов. По умолчанию результаты сортируются по оценке основного запроса.
Поддерживаемые функции по каждому результату
Агрегация top_hits возвращает обычные результаты поиска, поэтому поддерживается множество функций по каждому результату:
Если вам только необходимы docvalue_fields, size и sort, то Агрегация верхних метрик может быть более эффективным выбором, чем агрегация Top Hits.
top_hits не поддерживает параметр rescore. Переоценка запроса применяется только к результатам поиска, а не к результатам агрегации. Чтобы изменить оценки, используемые агрегациями, используйте запрос function_score или script_score.
Пример
В следующем примере мы группируем продажи по типам и для каждого типа отображаем последнюю продажу. Для каждой продажи в исходных данных включаются только поля «дата» и «цена».
resp = client.search(
index="sales",
size="0",
aggs={
"top_tags": {
"terms": {
"field": "type",
"size": 3
},
"aggs": {
"top_sales_hits": {
"top_hits": {
"sort": [
{
"date": {
"order": "desc"
}
}
],
"_source": {
"includes": [
"date",
"price"
]
},
"size": 1
}
}
}
}
},
)
print(resp) response = client.search(
index: 'sales',
size: 0,
body: {
aggregations: {
top_tags: {
terms: {
field: 'type',
size: 3
},
aggregations: {
top_sales_hits: {
top_hits: {
sort: [
{
date: {
order: 'desc'
}
}
],
_source: {
includes: [
'date',
'price'
]
},
size: 1
}
}
}
}
}
}
)
puts response const response = await client.search({
index: "sales",
size: 0,
aggs: {
top_tags: {
terms: {
field: "type",
size: 3,
},
aggs: {
top_sales_hits: {
top_hits: {
sort: [
{
date: {
order: "desc",
},
},
],
_source: {
includes: ["date", "price"],
},
size: 1,
},
},
},
},
},
});
console.log(response); POST /sales/_search?size=0
{
"aggs": {
"top_tags": {
"terms": {
"field": "type",
"size": 3
},
"aggs": {
"top_sales_hits": {
"top_hits": {
"sort": [
{
"date": {
"order": "desc"
}
}
],
"_source": {
"includes": [ "date", "price" ]
},
"size": 1
}
}
}
}
}
} Возможный ответ:
{
...
"aggregations": {
"top_tags": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "hat",
"doc_count": 3,
"top_sales_hits": {
"hits": {
"total" : {
"value": 3,
"relation": "eq"
},
"max_score": null,
"hits": [
{
"_index": "sales",
"_id": "AVnNBmauCQpcRyxw6ChK",
"_source": {
"date": "2015/03/01 00:00:00",
"price": 200
},
"sort": [
1425168000000
],
"_score": null
}
]
}
}
},
{
"key": "t-shirt",
"doc_count": 3,
"top_sales_hits": {
"hits": {
"total" : {
"value": 3,
"relation": "eq"
},
"max_score": null,
"hits": [
{
"_index": "sales",
"_id": "AVnNBmauCQpcRyxw6ChL",
"_source": {
"date": "2015/03/01 00:00:00",
"price": 175
},
"sort": [
1425168000000
],
"_score": null
}
]
}
}
},
{
"key": "bag",
"doc_count": 1,
"top_sales_hits": {
"hits": {
"total" : {
"value": 1,
"relation": "eq"
},
"max_score": null,
"hits": [
{
"_index": "sales",
"_id": "AVnNBmatCQpcRyxw6ChH",
"_source": {
"date": "2015/01/01 00:00:00",
"price": 150
},
"sort": [
1420070400000
],
"_score": null
}
]
}
}
}
]
}
}
} Пример сжатия полей
Сжатие или группировка полей — это функция, которая логически группирует набор результатов в группы и для каждой группы возвращает лучшие документы. Порядок групп определяется релевантностью первого документа в группе. В Elasticsearch это можно реализовать с помощью агрегатора ведер, который содержит агрегатор top_hits в качестве под-агрегатора.
В примере ниже мы ищем по веб-страницам, проиндексированным с помощью программы сканирования. Для каждой веб-страницы мы храним тело страницы и домен, к которому она принадлежит. Определяя агрегатор terms по полю domain, мы группируем набор результатов веб-страниц по доменам. Агрегатор top_hits определяется как под-агрегатор, чтобы собирать лучшие совпадающие результаты в каждом ведре.
Также определен агрегатор max, который используется функцией сортировки агрегатора terms для возврата ведер в порядке релевантности самого релевантного документа в ведре.
resp = client.search(
index="sales",
query={
"match": {
"body": "elections"
}
},
aggs={
"top_sites": {
"terms": {
"field": "domain",
"order": {
"top_hit": "desc"
}
},
"aggs": {
"top_tags_hits": {
"top_hits": {}
},
"top_hit": {
"max": {
"script": {
"source": "_score"
}
}
}
}
}
},
)
print(resp) response = client.search(
index: 'sales',
body: {
query: {
match: {
body: 'elections'
}
},
aggregations: {
top_sites: {
terms: {
field: 'domain',
order: {
top_hit: 'desc'
}
},
aggregations: {
top_tags_hits: {
top_hits: {}
},
top_hit: {
max: {
script: {
source: '_score'
}
}
}
}
}
}
}
)
puts response const response = await client.search({
index: "sales",
query: {
match: {
body: "elections",
},
},
aggs: {
top_sites: {
terms: {
field: "domain",
order: {
top_hit: "desc",
},
},
aggs: {
top_tags_hits: {
top_hits: {},
},
top_hit: {
max: {
script: {
source: "_score",
},
},
},
},
},
},
});
console.log(response); POST /sales/_search
{
"query": {
"match": {
"body": "elections"
}
},
"aggs": {
"top_sites": {
"terms": {
"field": "domain",
"order": {
"top_hit": "desc"
}
},
"aggs": {
"top_tags_hits": {
"top_hits": {}
},
"top_hit" : {
"max": {
"script": {
"source": "_score"
}
}
}
}
}
}
} В настоящее время необходим агрегатор max (или min), чтобы убедиться, что ведра из агрегатора terms упорядочены в соответствии с оценкой наиболее релевантной веб-страницы в каждом домене. К сожалению, агрегатор top_hits пока не может использоваться в опции order агрегатора terms.
Поддержка top_hits в агрегаторе nested или reverse_nested
Если агрегатор top_hits заключён в агрегатор nested или reverse_nested, то возвращаются вложенные результаты. Вложенные результаты — это, по сути, скрытые мини-документы, которые являются частью обычных документов, где в схеме настроено поле типа nested. Агрегатор top_hits может отобразить эти документы, если он заключен в агрегатор nested или reverse_nested. Дополнительные сведения о вложенных типах см. в сопоставлении типа nested.
Если тип nested настроен, один документ фактически индексируется как несколько документов Lucene, и они разделяют один и тот же идентификатор. Для определения идентичности вложенного результата требуется больше, чем только идентификатор, поэтому вложенные результаты также включают свой вложенный идентификатор. Вложенный идентификатор хранится в поле _nested в результате поиска и включает поле массива и смещение в поле массива, к которому принадлежит вложенный результат. Смещение нулевое.
Давайте посмотрим, как это работает на реальном примере. Рассмотрим следующую схему:
resp = client.indices.create(
index="sales",
mappings={
"properties": {
"tags": {
"type": "keyword"
},
"comments": {
"type": "nested",
"properties": {
"username": {
"type": "keyword"
},
"comment": {
"type": "text"
}
}
}
}
},
)
print(resp) response = client.indices.create(
index: 'sales',
body: {
mappings: {
properties: {
tags: {
type: 'keyword'
},
comments: {
type: 'nested',
properties: {
username: {
type: 'keyword'
},
comment: {
type: 'text'
}
}
}
}
}
}
)
puts response const response = await client.indices.create({
index: "sales",
mappings: {
properties: {
tags: {
type: "keyword",
},
comments: {
type: "nested",
properties: {
username: {
type: "keyword",
},
comment: {
type: "text",
},
},
},
},
},
});
console.log(response); PUT /sales
{
"mappings": {
"properties": {
"tags": { "type": "keyword" },
"comments": {
"type": "nested",
"properties": {
"username": { "type": "keyword" },
"comment": { "type": "text" }
}
}
}
}
} |
|
И некоторые документы:
resp = client.index(
index="sales",
id="1",
refresh=True,
document={
"tags": [
"car",
"auto"
],
"comments": [
{
"username": "baddriver007",
"comment": "This car could have better brakes"
},
{
"username": "dr_who",
"comment": "Where's the autopilot? Can't find it"
},
{
"username": "ilovemotorbikes",
"comment": "This car has two extra wheels"
}
]
},
)
print(resp) response = client.index(
index: 'sales',
id: 1,
refresh: true,
body: {
tags: [
'car',
'auto'
],
comments: [
{
username: 'baddriver007',
comment: 'This car could have better brakes'
},
{
username: 'dr_who',
comment: "Where's the autopilot? Can't find it"
},
{
username: 'ilovemotorbikes',
comment: 'This car has two extra wheels'
}
]
}
)
puts response const response = await client.index({
index: "sales",
id: 1,
refresh: "true",
document: {
tags: ["car", "auto"],
comments: [
{
username: "baddriver007",
comment: "This car could have better brakes",
},
{
username: "dr_who",
comment: "Where's the autopilot? Can't find it",
},
{
username: "ilovemotorbikes",
comment: "This car has two extra wheels",
},
],
},
});
console.log(response); PUT /sales/_doc/1?refresh
{
"tags": [ "car", "auto" ],
"comments": [
{ "username": "baddriver007", "comment": "This car could have better brakes" },
{ "username": "dr_who", "comment": "Where's the autopilot? Can't find it" },
{ "username": "ilovemotorbikes", "comment": "This car has two extra wheels" }
]
} Теперь можно выполнить следующую агрегацию top_hits (внутри агрегации nested):
resp = client.search(
index="sales",
query={
"term": {
"tags": "car"
}
},
aggs={
"by_sale": {
"nested": {
"path": "comments"
},
"aggs": {
"by_user": {
"terms": {
"field": "comments.username",
"size": 1
},
"aggs": {
"by_nested": {
"top_hits": {}
}
}
}
}
}
},
)
print(resp) response = client.search(
index: 'sales',
body: {
query: {
term: {
tags: 'car'
}
},
aggregations: {
by_sale: {
nested: {
path: 'comments'
},
aggregations: {
by_user: {
terms: {
field: 'comments.username',
size: 1
},
aggregations: {
by_nested: {
top_hits: {}
}
}
}
}
}
}
}
)
puts response const response = await client.search({
index: "sales",
query: {
term: {
tags: "car",
},
},
aggs: {
by_sale: {
nested: {
path: "comments",
},
aggs: {
by_user: {
terms: {
field: "comments.username",
size: 1,
},
aggs: {
by_nested: {
top_hits: {},
},
},
},
},
},
},
});
console.log(response); POST /sales/_search
{
"query": {
"term": { "tags": "car" }
},
"aggs": {
"by_sale": {
"nested": {
"path": "comments"
},
"aggs": {
"by_user": {
"terms": {
"field": "comments.username",
"size": 1
},
"aggs": {
"by_nested": {
"top_hits": {}
}
}
}
}
}
}
} Фрагмент ответа top hits с вложенным результатом, находящимся в первой ячейке поля массива comments:
{
...
"aggregations": {
"by_sale": {
"by_user": {
"buckets": [
{
"key": "baddriver007",
"doc_count": 1,
"by_nested": {
"hits": {
"total" : {
"value": 1,
"relation": "eq"
},
"max_score": 0.3616575,
"hits": [
{
"_index": "sales",
"_id": "1",
"_nested": {
"field": "comments",
"offset": 0
},
"_score": 0.3616575,
"_source": {
"comment": "This car could have better brakes",
"username": "baddriver007"
}
}
]
}
}
}
...
]
}
}
}
} | Имя поля массива, содержащего вложенный результат | |
| Позиция вложенного результата в содержащем массиве | |
| Источник вложенного результата |
Если запрашивается _source, то возвращается только часть источника вложенного объекта, а не весь источник документа. Доступ к сохранённым полям вложенного внутреннего объекта также возможен через агрегатор top_hits, находящегося в агрегаторе nested или reverse_nested.
Только вложенные результаты будут иметь поле _nested в результате, у невложенных (обычных) результатов этого поля не будет.
Информация в _nested также может быть использована для парсинга исходного источника в другом месте, если _source не включено.
Если в схеме определено несколько уровней вложенных типов объектов, то информация _nested также может быть иерархической для выражения идентичности вложенных результатов, расположенных на глубине двух уровней и более.
В примере ниже вложенный результат находится в первой ячейке поля nested_grand_child_field, которое, в свою очередь, находится во второй ячейке поля nested_child_field:
...
"hits": {
"total" : {
"value": 2565,
"relation": "eq"
},
"max_score": 1,
"hits": [
{
"_index": "a",
"_id": "1",
"_score": 1,
"_nested" : {
"field" : "nested_child_field",
"offset" : 1,
"_nested" : {
"field" : "nested_grand_child_field",
"offset" : 0
}
}
"_source": ...
},
...
]
}
... Использование в агрегациях конвейера
top_hits может использоваться в агрегациях конвейера, которые потребляют одно значение на каждый бакет, например, bucket_selector, которая применяет фильтрацию на каждый бакет, аналогично использованию оператора HAVING в SQL. Это требует установки size в 1 и указания правильного пути для значения, которое будет передано в обертку агрегатора. Последнее может быть _source, _sort или _score значением. Например:
resp = client.search(
index="sales",
size="0",
aggs={
"top_tags": {
"terms": {
"field": "type",
"size": 3
},
"aggs": {
"top_sales_hits": {
"top_hits": {
"sort": [
{
"date": {
"order": "desc"
}
}
],
"_source": {
"includes": [
"date",
"price"
]
},
"size": 1
}
},
"having.top_salary": {
"bucket_selector": {
"buckets_path": {
"tp": "top_sales_hits[_source.price]"
},
"script": "params.tp < 180"
}
}
}
}
},
)
print(resp) const response = await client.search({
index: "sales",
size: 0,
aggs: {
top_tags: {
terms: {
field: "type",
size: 3,
},
aggs: {
top_sales_hits: {
top_hits: {
sort: [
{
date: {
order: "desc",
},
},
],
_source: {
includes: ["date", "price"],
},
size: 1,
},
},
"having.top_salary": {
bucket_selector: {
buckets_path: {
tp: "top_sales_hits[_source.price]",
},
script: "params.tp < 180",
},
},
},
},
},
});
console.log(response); POST /sales/_search?size=0
{
"aggs": {
"top_tags": {
"terms": {
"field": "type",
"size": 3
},
"aggs": {
"top_sales_hits": {
"top_hits": {
"sort": [
{
"date": {
"order": "desc"
}
}
],
"_source": {
"includes": [ "date", "price" ]
},
"size": 1
}
},
"having.top_salary": {
"bucket_selector": {
"buckets_path": {
"tp": "top_sales_hits[_source.price]"
},
"script": "params.tp < 180"
}
}
}
}
}
} bucket_path использует имя top_hits top_sales_hits и ключевое слово для поля, предоставляющего агрегированное значение, а именно поле _source price в приведённом примере. Другие варианты включают top_sales_hits[_sort], для фильтрации по значению сортировки date выше, и top_sales_hits[_score], для фильтрации по оценке лучшего совпадения.
© 2023-2025 Elasticsearch
As of September 2024, Elasticsearch is available under a choice of three licenses: the Server Side Public License (SSPL), the Elastic License, or the AGPLv3 (OSI approved).
Elasticsearch and the Elasticsearch logo are trademarks of Elasticsearch B.V., registered in the U.S. and in other countries.
https://www.elastic.co/guide/en/elasticsearch/reference/8.17/search-aggregations-metrics-top-hits-aggregation.html