Получить документы, содержащие только допустимые теги (в точности равные)

Для каждого запроса поиска я разрешил список тэгов. Например,

["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 условие работает с нетривиальными случаями, а другие условия здесь рассматриваются как простые случаи.