Агрегирование ElasticSearch по вложенному полю с переменным вложением (или по определенному полю json)

У меня есть следующая структура GET /index-*/_mapping:

    "top_field" : {
      "properties" : {
        "dict_key1" : {
          "properties" : {
            "field1" : {...},
            "field2" : {...},
            "field3" : {
              "type" : "text",
              "fields" : {
                "keyword" : {
                  "type" : "keyword",
                  "ignore_above" : 256
                }
              }
            },
            "field4" : {...}
          },
        "dict_key2" : {
          "properties" : {
            "field1" : {...},
            "field2" : {...},
            "field3" : {
              "type" : "text",
              "fields" : {
                "keyword" : {
                  "type" : "keyword",
                  "ignore_above" : 256
                }
              }
            },
            "field4" : {...}
          },
        "dict_key3": ...
        }

Другими словами, top_field хранит json.

Я хотел бы агрегировать более 'field3.keyword' независимо от dict_key*. Что-то вроде top_field.*.field3.keyword.

Однако я не могу заставить его работать, используя агрегацию терминов, с вложенностью или без нее. Я также пытался просто использовать разные dict_key*, что было бы почти так же хорошо, но я тоже не могу заставить это работать.

Как это сделать?


person AOK    schedule 22.11.2020    source источник


Ответы (1)


TL; DR У меня была такая же проблема некоторое время назад (Агрегация терминов с вложенным подстановочным путем), и оказалось, что это не напрямую возможно из-за способа выполнения поиска и доступа к путям.


Однако есть обходной путь по сценарию:

{
  "size": 0,
  "aggs": {
    "terms_emulator": {
      "scripted_metric": {
        "init_script": "state.keyword_counts = [:]",
        "map_script": """
          def source = params._source['top_field'];
          for (def key : source.keySet()) {
            if (!source[key].containsKey('field3')) continue;
            
            def field3_kw = source[key]['field3'];
        
            if (state.keyword_counts.containsKey(field3_kw)) { 
              state.keyword_counts[field3_kw] += 1;
            } else {
              state.keyword_counts[field3_kw] = 1;
            }
          }
        """,
        "combine_script": "state",
        "reduce_script": "states[0]"
      }
    }
  }
}

дает что-то вроде

"aggregations" : {
  "terms_emulator" : {
    "value" : {
      "keyword_counts" : {
        "world" : 1,
        "hello" : 2
      }
    }
  }
}

Хотя это прекрасно работает, я бы не советовал использовать скрипты в продакшене. Вы могли бы реструктурировать свои данные таким образом, чтобы был возможен прямой поиск. Например:

{
  "top_field": {
    "entries": [
      {
        "group_name": "dict_key1",
        "key_value_pairs": {
          "field3": "hello"
        }
      },
      {
        "group_name": "dict_key2",
        "key_value_pairs": {
          "field3": "world"
        }
      }
    ]
  }
}

и сделать entries вложенным. Может быть, даже отказаться от top_field, так как это кажется излишним, и начать напрямую с entries.

person Joe Sorocin    schedule 22.11.2020
comment
Спасибо, я посмотрю на это и дам вам знать. К сожалению, top_field нельзя исключить, поскольку оно является частью набора других полей, которые также используются при агрегировании. Однако нет ли способа просто получить доступ к значениям пар {dict_key* : values} в виде списка, и тогда это будет по существу вложенная агрегация. Используя ваш пост, на который вы ссылаетесь: что-то вроде "path": "nested_parent.values()" или какой-то другой способ взаимодействия с ним. - person AOK; 22.11.2020
comment
И теперь я могу подтвердить, что это действительно работает. Я немного подожду, прежде чем принять, так как может быть решение, не связанное со сценариями. - person AOK; 22.11.2020
comment
Попался. Да нет, такого интерфейса нет — по крайней мере, вне контекста скрипта. Кроме того, всякий раз, когда контекст сталкивается с типом данных nested, он может получить доступ только к «текущей» итерации. Вот почему нам пришлось использовать params._source, чтобы получить доступ ко всем атрибутам документа верхнего уровня, то есть "nested_parent.values()"... - person Joe Sorocin; 23.11.2020
comment
В конце концов, я изменил структуру данных и обучил всех, кто работает над нашей системой, правильному оформлению документов в ES. Кажется, что массивы словарей предпочтительнее словаря словарей. - person AOK; 23.11.2020
comment
Хороший. Они, безусловно, предпочтительны. Удачи в остальной реализации! - person Joe Sorocin; 23.11.2020
comment
Привет, @AOK! Я только что выпустил свой Справочник по Elasticsearch, и я думаю, он будет вам полезен! - person Joe Sorocin; 18.03.2021