Я новичок в ElasticSearch и пытаюсь сгруппировать объекты, полученные в результате поиска, по иерархическим категориям.

Заранее прошу прощения за объем вопроса, но я хотел предоставить достаточно примеров и информации, чтобы сделать необходимость как можно более ясной.

Чего я пытаюсь достичь

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

Вот упрощенное сопоставление для документа, содержащего только минимум данных:

{
  "mappings": {
    "_doc": {
      "properties": {
        "categoriesList": {
          "properties": {
            "depth": {
              "type": "long"
            },
            "title": {
              "type": "text",
              "fields": {
                "keyword": {
                  "type": "keyword",
                  "ignore_above": 256
                }
              }
            }
          }
        }
      }
    }
  }
}

Вот упрощенный образец документа:

{
  "_index": "x",
  "_type": "_doc",
  "_id": "wY0w5GYBOIOl7fi31c_b",
  "_score": 22.72073,
  "_source": {
    "categoriesList": [
      {
        "title": "category_lvl_2_2",
        "depth": 2
      },
      {
        "title": "category_lvl_2",
        "depth": 2,
      },
      {
        "title": "category_lvl_1",
        "depth": 1
      }
    ]
  }
}

Теперь я пытаюсь получить иерархические сегменты категорий на основе их глубины, т.е. я хочу иметь сегмент, содержащий все заголовки категорий глубины 1 для всех обращений, а затем еще один сегмент (или вложенный сегмент с заголовками). только категорий глубины 2 для всех обращений и т. д. Что-то вроде:

"aggregations": {
    "depth": {
      "doc_count_error_upper_bound": 0,
      "sum_other_doc_count": 0,
      "buckets": [
        {
          "key": 1,
          "doc_count": 47,
          "name": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": [
              {
                "key": "category_lvl_1",
                "doc_count": 47,
                "depth_1": {
                  "doc_count": 47
                }
              }
            ]
          }
        },
        {
          "key": 2,
          "doc_count": 47,
          "name": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": [
              {
                "key": "category_lvl_2_1",
                "doc_count": 47
              },
              {
                "key": "category_lvl_2_2",
                "doc_count": 33
              }
            ]
          }
        }
      ]
    }
  }

Что я пробовал

Сначала я попытался просто создать вложенные агрегаты следующим образом:

  "aggs": {
    "depth": {
      "terms": {
        "field": "categoriesList.depth"
      },
      "aggs": {
        "name": {
          "terms": {
            "field": "categoriesList.title.keyword"
          },
        }
      }
    }
  }

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

  "aggregations": {
    "depth": {
      "doc_count_error_upper_bound": 0,
      "sum_other_doc_count": 0,
      "buckets": [
        {
          "key": 1,
          "doc_count": 47,
          "name": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": [
              {
                "key": "category_lvl_1",
                "doc_count": 47
              },
              {
                "key": "category_lvl_2_1",
                "doc_count": 33
              },
              {
                "key": "category_lvl_2_2",
                "doc_count": 15
              }
            ]
          }
        },
        {
          "key": 2,
          "doc_count": 47,
          "name": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": [
              {
                "key": "category_lvl_1",
                "doc_count": 47
              },
              {
                "key": "category_lvl_2_1",
                "doc_count": 33
              },
              {
                "key": "category_lvl_2_2",
                "doc_count": 15
              }
            ]
          }
        }
      ]
    }
  }

Затем я попытался проверить, будет ли работать отфильтрованная агрегация, попытавшись отфильтровать одну подсегмент по значению глубины 1:

  "aggs": {
    "depth": {
      "terms": {
        "field": "categoriesList.depth"
      },
      "aggs": {
        "name": {
          "terms": {
            "field": "categoriesList.title.keyword"
          },
          "aggs": {
            "depth_1": {
              "filter": {
                "term": {
                  "categoriesList.depth": 1
                }
              }
            }
          }
        }
      }
    }
  }

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

Вопрос

С моим текущим пониманием ES то, что я вижу, имеет смысл: он просматривает каждый документ из поиска, а затем создает сегменты на основе глубины категории, но поскольку каждый документ имеет по крайней мере одну категорию с каждой глубиной, весь список категорий добавляется в ведро.

Возможно ли то, что я пытаюсь сделать с помощью ES? У меня такое ощущение, что это не сработает, потому что я в основном пытаюсь разделить и отфильтровать свойства, используемые начальным запросом сегментирования, а не работать со свойствами документа.

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

Благодарность!

0
Ouamer 13 Ноя 2018 в 19:18

1 ответ

Лучший ответ

Основываясь на комментарии sramalingam24, я сделал следующее, чтобы заставить его работать:

Создать индекс с отображением, определяющим вложенные типы

Я изменил отображение, чтобы сообщить ES, что свойство CategoriesList было вложенным объектом. Для этого я создал новый индекс со следующим отображением:

{
  "mappings": {
    "_doc": {
      "properties": {
        "categoriesList": {
          "type": "nested",
          "properties": {
            "depth": {
              "type": "long"
            },
            "title": {
              "type": "text",
              "fields": {
                "keyword": {
                  "type": "keyword",
                  "ignore_above": 256
                }
              }
            }
          }
        }
      }
    }
  }
}

Переиндексировать в новый индекс

Затем я переиндексирую из старого индекса в новый.

{
  "source": {
    "index": "old_index"
  },
  "dest": {
    "index": "index_with_nested_mapping"
  }
}

Используйте вложенную агрегацию

Затем я использовал вложенную агрегацию, подобную этой:

{
  "aggs": {
    "categories": {
      "nested": {
        "path": "categoriesList"
      },
      "aggs": {
        "depth": {
          "terms": {
            "field": "categoriesList.depth"
          },
          "aggs": {
            "sub-categories": {
              "terms": {
                "field": "categoriesList.title.keyword"
              }
            }
          }
        }
      }
    }
  }
}

Что дало мне желаемые результаты:

{
  "aggregations": {
    "categories": {
      "doc_count": 96,
      "depth": {
        "doc_count_error_upper_bound": 0,
        "sum_other_doc_count": 0,
        "buckets": [
          {
            "key": 2,
            "doc_count": 49,
            "sub-categories": {
              "doc_count_error_upper_bound": 0,
              "sum_other_doc_count": 0,
              "buckets": [
                {
                  "key": "category_lvl_2_1",
                  "doc_count": 33
                },
                {
                  "key": "category_lvl_2_2",
                  "doc_count": 15
                }
              ]
            }
          },
          {
            "key": 1,
            "doc_count": 47,
            "sub-categories": {
              "doc_count_error_upper_bound": 0,
              "sum_other_doc_count": 0,
              "buckets": [
                {
                  "key": "category_lvl_1",
                  "doc_count": 47
                }
              ]
            }
          }
        ]
      }
    }
  }
}
1
Ouamer 15 Ноя 2018 в 15:29