Получить количество пользователей, которые сделали определенное количество покупок в ElasticSearch - PullRequest
0 голосов
/ 13 мая 2019

Каждый пользователь в моем магазине может совершать несколько покупок, и информация о такой покупке хранится в ElasticSearch 6.5.

Я хочу подсчитать, сколько пользователей совершило определенное количество покупок в моем магазине - один или три .Наш поиск основывался бы на документах с именем user_purchases, выглядящих так:

{
    "user_id" : 1,
    "total_value" : 111.56,
    "total_products" : 2
}

Если бы мы делали это с помощью SQL, мы бы сделали что-то вроде:

SELECT COUNT(u.id) FROM users u WHERE u.id IN (SELECT up.user_id FROM user_purchases up WHERE COUNT(up.id) = 1 GROUP BY up.user_id);

SELECT COUNT(u.id) FROM users u WHERE u.id IN (SELECT up.user_id FROM user_purchases up WHERE COUNT(up.id) = 3 GROUP BY up.user_id);

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

Кажется, было бы лучше сделать три отдельных запроса для каждого - один или три - дело и, возможно, использование безболезненного + ctx-скриптинга, но я просто не знаю, с чего начать.Я работал с ним, немного выполняя менее сложные действия: «если он есть, то делайте это», но здесь у меня уйдет очень много времени, чтобы разобраться.

Если кто-то может помочь и привести пример запросаиспользуя поиск или API многозадачного поиска (или любой другой, который будет работать), я был бы очень признателен!

Ответы [ 2 ]

1 голос
/ 13 мая 2019

Если вы запустите агрегацию terms в поле user_id, вы получите список групп пользователей, совершивших наибольшее количество покупок, до тех, кто ' Мы сделали только одну покупку. Затем вы можете отфильтровать все ненужные сегменты, используя bucket_selector конвейерную агрегацию :

{
  "size": 0,
  "aggs": {
    "users": {
      "terms": {
        "field": "user_id"
      },
      "aggs": {
        "1_3": {
          "bucket_selector": {
            "buckets_path": {
              "nb_purchases": "_count"
            },
            "script": "params.nb_purchases == 1 || params.nb_purchases == 3"
          }
        }
      }
    }
  }
}
0 голосов
/ 16 мая 2019

Вот как мне удалось выполнить то, что мне нужно.Допустим, у нас есть индекс под названием visitor_carts с такими документами:

{
    "visitor_id" : 1,
    "total_value" : 111,
    "total_products" : 2
}

{
    "visitor_id" : 1,
    "total_value" : 199.99,
    "total_products" : 1
}

{
    "visitor_id" : 1,
    "total_value" : 890.56,
    "total_products" : 2
}

{
    "visitor_id" : 2,
    "total_value" : 223.56,
    "total_products" : 2
}

{
    "visitor_id" : 3,
    "total_value" : 4.56,
    "total_products" : 2
}

Существует решение - оно называется агрегированная метрическая агрегация .При этом вы можете создавать практически все, что захотите, недостатком является то, что вам нужно ознакомиться с безболезненным сценарием.Документация в этом отношении довольно сложна для понимания, к тому же кажется, что конкретные версии не очень хорошо поддерживаются, поскольку то, что находится в безболезненной документации, не работает с моей версией ElasticSearch 6.5 (хотя это должно быть в соответствии с указанной документацией).Так что предупреждающее слово - если это не сработает, продолжайте искать больше примеров.Я нашел примеры использования здесь очень полезными.В любом случае, вот рабочее решение:

POST visitor_carts/_search
{
  "query" : {
    "match_all" : {}
  },
  "aggs": {
    "purchases": {
      "scripted_metric": {
        "init_script" : "state['visitorPurchases'] = [:]",
        "map_script" : "if (state['visitorPurchases'].containsKey(doc['visitor_id'].value)) {state['visitorPurchases'][doc['visitor_id'].value]++} else {state['visitorPurchases'][doc['visitor_id'].value] = 1}",
        "combine_script": "def combine = [:]; for (visitor in state['visitorPurchases'].entrySet()) {if (combine.containsKey(visitor.getValue().toString())) {combine[visitor.getValue().toString()]++} else {combine[visitor.getValue().toString()] = 1}} return combine",
        "reduce_script": "def reduce = [:]; for (shard in states) { for (count in shard.entrySet()) {if (reduce.containsKey(count.getKey())) {reduce[count.getKey()] += count.getValue()} else {reduce[count.getKey()] = count.getValue()}}} return reduce"
      }
    }
  }
}

В map_script оно просматривает все документы, соответствующие запросу, и подсчитывает количество случаев каждого visitor_id.Затем в combine_script он берет то, что map_script подготовил ранее, и группирует результат по количеству появлений.Поскольку combine_script работает для каждого сегмента, нам нужно, чтобы reduce_script объединял все наборы результатов из каждого сегмента и возвращал его следующим образом:

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 5,
    "max_score" : 0.0,
    "hits" : [ ]
  },
  "aggregations" : {
    "purchases" : {
      "value" : {
        "1" : 2,
        "3" : 1
      }
    }
  }
}

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

Я слишком новичок в ElasticSearch, чтобы понять, насколько эффективно это решение.является.Он хорошо работает с несколькими тысячами документов, на которых я его протестировал, но я понятия не имею, как он будет вести себя с миллионами / миллиардами записей.Если кому-то захочется это проверить - будь моим гостем:)

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