Получить документы, содержащие только допустимые теги (в точности равные)
Для каждого запроса поиска я разрешил список тэгов. Например,
["search", "open_source", "freeware", "linux"]
И я хочу получить документы со всеми тегами в этом списке. Я хочу получить:
{
"tags": ["search", "freeware"]
}
и исключить
{
"tags": ["search", "windows"]
}
потому что список не содержит тега windows
.
В документации Elasticsearch есть пример для equals:
https://www.elastic.co/guide/en/elasticsearch/guide/current/_finding_multiple_exact_values.html
Во-первых, мы включаем поле, которое поддерживает количество тегов:
{ "tags" : ["search"], "tag_count" : 1 }
{ "tags" : ["search", "open_source"], "tag_count" : 2 }
Во-вторых, мы получаем требуемый tag_count
GET /my_index/my_type/_search
{
"query": {
"filtered" : {
"filter" : {
"bool" : {
"must" : [
{ "term" : { "tags" : "search" } },
{ "term" : { "tags" : "open_source" } },
{ "term" : { "tag_count" : 2 } }
]
}
}
}
}
}
Проблема в том, что я не знаю tag_count
.
Также я попытался написать запрос с script_field
tags_count
, записать каждый разрешенный тег в запросе терминов и установить minimal_should_match
в tags_count
, но я не могу установить переменную script в minimal_should_match
.
Что я могу исследовать?
Ответы
Ответ 1
Поэтому я признаю, что это не отличное решение, но, возможно, это вдохновит другие лучшие решения?
Указанные части записей, которые вы ищете, выглядят так, как у вас в вашем посте с полями tag_count:
"tags" : ["search"],
"tag_count" : 1
или
"tags" : ["search", "open_source"],
"tag_count" : 2
И у вас есть запрос вроде:
["search", "open_source", "freeware"]
Затем вы можете программно генерировать запрос типа:
{
"query" : {
"bool" : {
"should" : [
{
"bool" : {
"should" : [
{ "term" : { "tags" : "search" } },
{ "term" : { "tags" : "open_source" } },
{ "term" : { "tags" : "freeware" } },
{ "term" : { "tag_count" : 1 } },
],
"minimum_should_match" : 2
}
},
{
"bool" : {
"should" : [
{ "term" : { "tags" : "search" } },
{ "term" : { "tags" : "open_source" } },
{ "term" : { "tags" : "freeware" } },
{ "term" : { "tag_count" : 2 } },
],
"minimum_should_match" : 3
}
},
{
"bool" : {
"should" : [
{ "term" : { "tags" : "search" } },
{ "term" : { "tags" : "open_source" } },
{ "term" : { "tags" : "freeware" } },
{ "term" : { "tag_count" : 3 } },
],
"minimum_should_match" : 4
}
}
],
"minimum_should_match" : 1
}
}
}
Число вложенных запросов bool будет соответствовать запросу числа тегов запроса (не очень важно по ряду причин, но с меньшими запросами/меньшими индексами, возможно, может уйти от этого?). В основном каждое предложение будет обрабатывать каждый возможный случай tag_count, а minimum_should_match будет tag_count + 1 (так что сопоставьте tag_count и соответствующее количество тегов - количество тегов_ tag_count).
Ответ 2
Если размер индекса средний и размерность тега довольно низкая, я бы просто использовал агрегацию terms
для получения отдельных тегов и создания фильтров must
и must not
для фильтрации документов, содержащих теги, которые вы не разрешаете ". Есть много способов кэшировать список всех тегов в базе данных в памяти, такой как Redis, вот несколько из них, которые пришли мне на ум:
- У вас есть время на ожидание нескольких минут или часов, заново сгенерируйте список, если срок действия кеша истек.
- Проведите фоновый процесс, обновляя список через регулярные промежутки времени.
- Обновить список при вставке новых документов, а также удалить doc файлы.
Более эффективный и 100% точный метод может выглядеть так:
- Запросить все документы, у которых есть запрошенные теги, но исключить документы с известными другими тегами (как с первым решением)
- Пройдите список возвращаемых документов
- Если в документе содержится тег, который не разрешен, значит, он не был в кэше известных тегов и поэтому должен быть добавлен туда, исключить этот документ из набора результатов
- Теги в Redis могут иметь TTL, например, один день или одну неделю, таким образом старые теги автоматически обрезаются, и вы получаете более простые запросы ES.
Таким образом, вам не нужен процесс резервного копирования, чтобы поддерживать список тегов или использовать возможно тяжелую агрегацию terms
при попадании на все документы и всегда получать правильный набор результатов и довольно эффективные запросы.
Это не сработает, если последующие агрегаты будут использоваться, поскольку ES может вернуть ложные документы, которые обрезаны на стороне клиента. Однако это можно обнаружить, добавив агрегацию terms
и подтвердите, что у нее нет неожиданных тегов. Если это необходимо добавить в кеш тега, добавляется в фильтр must_not
, и запрос должен быть повторно выполнен. Это не идеально, если новые теги создаются часто.
Ответ 3
Почему бы не использовать bool с добавленными окнами в условие не должно. Я надеюсь, что вы ищете.
Ответ 4
@Sergey Shuvalov, еще один способ избежать этого без использования скриптов - это создать другое поле, значение которого содержит все отсортированные теги, разделенные запятой (например, или вы можете выбрать, какой разделитель вам подходит).
Так, например, если у вас есть такой документ:
{
"tags": ["search", "open_source", "freeware", "linux"]
}
Вы создали бы другое поле alltags
, которое содержит те же теги, но отсортированные в лексикографическом порядке и разделенные запятыми, например:
{
"tags": ["search", "open_source", "freeware", "linux"]
"alltags": "freeware,linux,open_source,search"
}
Это новое поле alltags
будет not_analyzed
и, следовательно, имеет следующее отображение:
{
"mappings": {
"doc": {
"properties": {
"tags": {
"type": "string"
},
"alltags": {
"type": "string",
"index": "not_analyzed"
}
}
}
}
}
Затем вы можете отправить простой запрос term
, подобный приведенному ниже, вам просто нужно убедиться, что теги также отсортированы, и вы получите соответствующие документы.
{
"query": {
"term": {
"alltags": "freeware,linux,open_source,search"
}
}
}
Если у вас длинный список тегов, вы также можете решить создать MD5 или SHA1 из отсортированного списка тегов и сохранить это значение только в поле alltags
и использовать то же значение во время поиска. Суть в том, что вам нужно создать какую-то "подпись" для вашего списка тегов и знать, что эта подпись всегда будет одинаковой с тем же набором тегов. Предел - небо!
Ответ 5
Как я уже говорил, я сочетаю два приятных ответа. И это то, что у меня есть:
"query" : {
"bool":{
"should":[
{"term":{"tag_count":1}},
{
"bool":{
"should":[
{"term":{"tags":"search"}},
{"term":{"tags":"open_source"}},
{"term":{"tags":"freeware"}}
],
"filter":{"term":{"tag_count":2}},
"minimum_should_match":2
}
},
{
"bool":{
"should":[
{"term":{"tags":"search"}},
{"term":{"tags":"open_source"}},
{"term":{"tags":"freeware"}}
],
"filter":{"term":{"tag_count":3}},
"minimum_should_match":3
}
},
{
"script": {
"script": "tags.containsAll(doc['tags'].values)",
"params": {"tags":["search", "open_source", "freeware"]}
}
}
],
"filter":{ "terms" : {"tags" :["search", "open_source", "freeware"]}}
}
}
script условие работает с нетривиальными случаями, а другие условия здесь рассматриваются как простые случаи.