Сгруппировать по последней записи фильтра в Elastic Search

У меня есть сценарий, похожий на следующий:

Индекс, содержащий купленные товары в магазине, где у каждого товара есть order_id. И мне нужно сгруппировать по цвету только последний элемент каждого заказа.

Структура данных:

{
    "order_id": 1,
    "product_id":235233
    "color": "Blue",
    "purchase_date": "2020-08-21T05:53:43.362Z"
},
{
    "order_id": 1,
    "product_id":2352662
    "color": "Black",
    "purchase_date": "2020-08-23T05:53:43.362Z"
},
{
    "order_id": 2,
    "product_id":855477
    "color": "Blue",
    "purchase_date": "2020-08-22T05:53:43.362Z"
},
{
    "order_id": 2,
    "product_id":322352
    "color": "Red",
    "purchase_date": "2020-08-24T05:53:43.362Z"
},
{
    "order_id": 3,
    "product_id":3225235
    "color": "Red",
    "purchase_date": "2020-08-25T05:53:43.362Z"
}

Ожидаемый результат

Черный: 1 (цвет последнего товара order_id 1)

Красный: 2 (цвет последних товаров order_id 2, 3)

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

POST /items/_search?search_type=count
{
    "aggs": {
        "group": {
            "terms": {
                "field": "order_id"
            },
            "aggs": {
                "group_items": {
                    "top_hits": {
                        "size": 1,
                          "sort": [
                            {
                                "purchase_date": {
                                    "order": "desc"
                                }
                            }
                        ]
                    }
                }
            }
        }
    }
}

И следующее дает мне количество элементов для каждого цвета для всех элементов заказа, а не только последнего в каждом заказе.

GET /items/_search?search_type=count
{
 "size":0,
  "aggs": {
    "colors": {
       "terms": {
        "field": "color.keyword"
        }
     }
  }
}

@ESCoder спасибо, надеюсь теперь понятно

Yahya Hussein 21.12.2020 11:44
0
1
375
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

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

{
  "size": 0,
  "aggs": {
    "group": {
      "terms": {
        "field": "color.keyword",
        "order": {
          "by_latest_purchase": "desc"
        }
      },
      "aggs": {
        "by_latest_purchase": {
          "max": {
            "field": "purchase_date"
          }
        }
      }
    }
  }
}

но вы все равно получите blue b / c, это цвет, который существует в ваших документах, и я не знаю, можно ли его отфильтровать.


Если вы сомневаетесь (или ничего не помогает), заскриптованные агрегации метрик спешат на помощь:

{
  "size": 0, 
  "aggs": {
    "by_color": {
      "scripted_metric": {
        "init_script": "state.by_order_id = [:]",
        "map_script": """
          def color = doc['color.keyword'].value;
          def date = doc['purchase_date'].value.millis;
          def order_id = doc['order_id'].value;
          
          def current_group = ['color':color, 'date': date];
          
          if (state.by_order_id.containsKey(order_id)) {
            def max_group = state.by_order_id[order_id];
            if (date > max_group.date) {
              // we've found a new maximum
              state.by_order_id[order_id] = current_group
            }
          } else {
            state.by_order_id[order_id] = current_group;
          }
        """,
        "combine_script": """
          def colors_vs_count = [:];
          
          for (def group : state.by_order_id.entrySet()) {
            def order_id = group.getKey();
            def color = group.getValue()['color'];
            if (colors_vs_count.containsKey(color)) {
              colors_vs_count[color]++;
            } else {
              colors_vs_count[color] = 1;
            }
          }
          
          return colors_vs_count;
        """,
        "reduce_script": "return states"
      }
    }
  }
}

уступая:

...
"aggregations" : {
  "by_color" : {
    "value" : [
      {
        "Red" : 2,
        "Black" : 1
      }
    ]
  }
}

Вот сокращенная версия скрипта в формате JSON:

{"size":0,"aggs":{"by_color":{"scripted_metric":{"init_script":"state.by_order_id = [:]","map_script":"          def color = doc['color.keyword'].value;\n          def date = doc['purchase_date'].value.millis;\n          def order_id = doc['order_id'].value;\n          \n          def current_group = ['color':color, 'date': date];\n          \n          if (state.by_order_id.containsKey(order_id)) {\n            def max_group = state.by_order_id[order_id];\n            if (date > max_group.date) {\n              state.by_order_id[order_id] = current_group\n            }\n          } else {\n            state.by_order_id[order_id] = current_group;\n          }","combine_script":"          def colors_vs_count = [:];\n          \n          for (def group : state.by_order_id.entrySet()) {\n            def order_id = group.getKey();\n            def color = group.getValue()['color'];\n            if (colors_vs_count.containsKey(color)) {\n              colors_vs_count[color]++;\n            } else {\n              colors_vs_count[color] = 1;\n            }\n          }\n          \n          return colors_vs_count;","reduce_script":"return states"}}}}

спасибо, а вроде скрипт будет нормально работать только на одном осколке?

Yahya Hussein 23.12.2020 10:38

Я думаю, что массив by_color>value будет содержать столько объектов/словарей, сколько у вас есть осколков. Подробнее об этом здесь. Сообщите мне, если сценарий не работает для нескольких осколков.

Joe - ElasticsearchBook.com 26.12.2020 23:52

Альтернативным подходом к проблеме может быть создание и ведение отдельного индекса (latest_by_order), который отслеживает последний документ для каждого заказа. Этого можно добиться с помощью преобразований (см. документы).

Такое преобразование можно создать с помощью следующей команды:

PUT _transform/latest_by_order
{
  "source": {
    "index": "items"
  },
  "dest": {
    "index": "latest_by_order"
  },
  "latest": {
    "unique_key": ["order_id"],
    "sort": "purchase_date"
  }
}

Затем можно провести вторичный анализ поверх нового (преобразованного) индекса. Следующий запрос:

GET latest_by_order/_search
{
  "size": 0,
  "aggs": {
    "count_by_color": {
      "terms": {
        "field": "color.keyword"
      }
    }
  }
}

даст следующий ответ:

{
  "took" : 2,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 3,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "count_by_color" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 0,
      "buckets" : [
        {
          "key" : "Red",
          "doc_count" : 2
        },
        {
          "key" : "Black",
          "doc_count" : 1
        }
      ]
    }
  }
}

Другие вопросы по теме