Использование результатов агрегации для фильтрации документов в другом индексе - PullRequest
1 голос
/ 06 января 2020

У меня есть 2 индекса:

  • users
  • navigations

Допустим, users выглядит так:

{
  "properties": {
    "cookies": {"type": "keyword"},
    "name": {"type": "text"}
  }
}

И navigations выглядит следующим образом:

{
  "properties": {
    "url": {"type": "keyword"},
    "cookie_id": {"type": "keyword"}
  }
}

Как вы можете заметить, users и navigations могут быть объединены полями cookie_id и cookies.

На самом деле мои индексы имеют больше полей, но только они необходимы для демонстрации моей проблемы.

Я храню users и navigations в 2 разных индексах вместо использования join mapping или nested mapping, потому что у меня будет navigations намного больше, чем у пользователей, и в большинстве случаев использования поиска я буду искать только users, поэтому я не хочу вести список navigations за users. Я предпочитаю хранить их отдельно (у меня также есть некоторые другие ограничения, которые определяют мой выбор для двух отдельных индексов, таких как согласование данных и т. Д. c ...).

Я хотел бы сделать запрос / агрегация, как это: "дать мне всех пользователей с name Fabien, которые 5 раз переходили на url http://example.com"

У меня был следующий запрос / агрегация до сих пор (поисковый запрос выполняется по моим 2 индексам):

POST / users, navigations / _search

{
  "query": {
    "bool": {
      "must": [
        {"match": {"name": "Fabien"}}
      ]
    }
  },
  "aggregations": {
    "all_navs": {
        "global": {},
        "aggregations": {
            "cookies": {
                "terms": {"field": "cookie_id"},
                "aggregations": {
                    "page_visited": {
                        "filter": {
                            "bool": {
                                "must": [
                                    {"term": {"url": "http://example.com"} }
                                ]
                            }                           
                        },
                        "aggregations": {
                            "number_page_visited": {
                                "value_count": {"field": "type"}
                            }
                        }
                    },
                    "count_filter": {
                        "bucket_selector": {
                            "buckets_path": {
                                "count": "page_visited>number_page_visited"
                            },
                            "script": "params.count > 5"
                        }
                    }
                }
            }
        }
    }
  }
}

С помощью этого запроса я могу отфильтровать users с помощью name = Fabien, и Я могу получить значение cookie_id из navigations, где есть как минимум 5 документов с url = http://example.com.

Но я не могу понять, как использовать cookie_id s из моего агрегата для фильтрации мой users.

Есть идеи?

Спасибо!

1 Ответ

1 голос
/ 11 января 2020

Решение с двумя отдельными индексами

Так как эластичный поиск не является реляционной базой данных, вы не сможете получить свои результаты за один запрос. Это строгое ограничение эластичного поиска, но оно также является основной причиной его высокой производительности.

По сути, эластичный поиск скомпилирует ваш запрос в запрос Lucene и выполнит сканирование индексов с использованием запроса Lucene. Не существует механизма, в котором какой-либо параметр в запросе (например, значение поля user_id) зависит от результата другого запроса (например, найти все значения id из users, где имя "Fabien") .

Сначала вам нужно выполнить внешнее соединение:

  • , сначала получите все документы из индекса users, имя которого равно Fabien. Если количество документов не ограничено, вам придется выполнить поиск с прокруткой или использовать search_after

  • секунду, получить все документы из index navigation, где user_id находится в наборе документов, возвращенных из первого запроса, и где удовлетворены другие критерии.

Этот подход может быть медленным, и у вас нет гарантий что при выполнении второго запроса индекс пользователей не обновлялся.

Решение с сопоставлением соединений

На самом деле, если вы используете сопоставление типов соединений , вам не нужно используйте агрегаты для вашего варианта использования.

Обратите внимание, что поле соединения имеет несколько ограничений и не рекомендуется в качестве решения по умолчанию для моделирования отношений один ко многим.

Вот рабочий пример, который должен работать для Ваше требование.

Сопоставление: содержит поле пользователя и навигационное поле, а также поле соединения.

PUT /user_navigation
{
    "mappings": {
        "properties": {
            "cookies": {
                "type": "keyword"
            },
            "name": {
                "type": "keyword"
            },
            "join_field": {
                "type": "join",
                "relations": {
                    "user": "navigation"
                }

            }
        }
    }
}

Добавьте несколько документов тестирования. Два родительских документа имеют name: Fabien, но только у одного есть двое детей с cookies: http://example.com. У другого документа есть два потомка с cookies: http://example.com, но он не назван с Fabien.

POST user_navigation/_doc/_bulk
{ "index" : { "_index" : "user_navigation", "_id" : "1" } }
{ "name" : "Fabien", "join_field": "user" }
{ "index" : { "_index" : "user_navigation", "_id" : "2" } }
{ "name" : "Fabien", "join_field": "user" }
{ "index" : { "_index" : "user_navigation", "_id" : "3" } }
{ "name" : "Autre", "join_field": "user" }
{ "index" : { "_index" : "user_navigation", "routing": "1" } }
{ "cookies": "http://example.com", "join_field": { "name": "navigation",  "parent": "1"  }}
{ "index" : { "_index" : "user_navigation", "routing": "1"} }
{ "cookies": "http://example.com", "join_field": { "name": "navigation",  "parent": "1"  }}
{ "index" : { "_index" : "user_navigation", "routing": "2"} }
{ "cookies": "http://example.com", "join_field": { "name": "navigation",  "parent": "2"  }}
{ "index" : { "_index" : "user_navigation", "routing": "2"} }
{ "cookies": "other_url", "join_field": { "name": "navigation",  "parent": "3"  }}
{ "index" : { "_index" : "user_navigation", "routing": "3"} }
{ "cookies": "http://example.com", "join_field": { "name": "navigation",  "parent": "3"  }}
{ "index" : { "_index" : "user_navigation", "routing": "3"} }
{ "cookies": "http://example.com", "join_field": { "name": "navigation",  "parent": "3"  }}

В следующем запросе используется запрос has_child , и будет возвращен только документ с name: Fabien и такой, что у него есть как минимум два дочерних документа с cookies: http://example.com.

GET user_navigation/_doc/_search
{
    "query": {
        "bool": {
            "must": [
                {
                    "term": {
                        "name": "Fabien"
                    }
                },
                {
                    "has_child": {
                        "type": "navigation",
                        "query": {
                            "term": {
                                "cookies": "http://example.com"
                            }
                        },
                        "min_children": 2,
                        "inner_hits": {}
                    }
                }
            ]
        }
    }
}

Ответ будет содержать только документ с идентификатором 1.

"min_children" Параметр позволяет изменить минимальный количество дочерних документов, которые должны выполнить запрос.

"inner_hits": {} позволяет получить дочерние документы в ответе.

...