ElasticSearch - агрегація даних

    
У статті ми розглянемо, як правильно реалізовувати агрегацію даних, навіщо це може знадобитися, і присмачити це купою робочих прикладів.
 
Для всіх, кому цікаво як зробити свої запити в ES цікавіше і подивитися на звичайній пошук з іншого боку, прошу під кат.
 
 
 У попередній статті користувачі розділилися порівну між статтею за простішою темі і за більш складною, тому я вибрав не дуже складну тему, але досить свіжу, яка додалася в ES відносно недавно (v1.0) і несе досить цікавий функціонал.
 
 

Aggregation module

Цей модуль прийшов в ES на зміну Facets, причому в наполегливій формі, Facets тепер вважаються застарілими і будуть видалені в найближчі релізи. Хоча агрегати і були додані в v1.0.0RC1, а зараз вже> 1.2, я все ж не рекомендую використовувати Facets.
Навіщо ж знадобилося змінювати робочий інструмент?
Напевно, головною фішкою агрегатів є їх вкладеність. Наведу загальний синтаксис запиту:
 
"aggregations" : {
    "<aggregation_name>" : {
        "<aggregation_type>" : {
            <aggregation_body>
        }
        [,"aggregations" : { [<sub_aggregation>]+ } ]?
    }
    [,"<aggregation_name_2>" : { ... } ]*
}

 
Як видно зі структури, агрегатів може бути як завгодно багато, і у кожного елемента може бути вкладений елемент без обмежень по глибині.
Використовуючи вкладеність, ми можемо отримати дуже цікаві статистичні дані (приклад в кінці статті).
 
 
Типи агрегатів
Типів агрегатів дуже багато , але всі їх можна об'єднати в 2 головних типи:
 
  — Bucketing (Узагальнення)
Для простоти розуміння, це можна порівняти з усім знайомим інструментів «GROUP BY». Звичайно, це досить спрощене порівняння, але принцип роботи схожий. Цей тип на основі фільтрів узагальнює документи, по якомусь певному ознакою, хороший приклад це terms aggregation .
 
  — Metric (Метричні)
Це агрегати, які вираховують-які значення за певним набором документів. Наприклад sum aggregation
 
Думаю, для початок теорії вистачить, всім, кого цікавить більш фундаментальна інформація з цього модулю, можуть ознайомиться з нею за цим посиланням .
 
 

Простий приклад

Бажаючим спробувати все власноруч пропоную використовувати цей дамп
 Структура і дані для тіста Дамп нахабним чином взятий з цієї прекрасної статті
 
curl -XPUT "http://localhost:9200/sports/" -d'
{
   "mappings": {
      "athlete": {
         "properties": {
            "birthdate": {
               "type": "date",
               "format": "dateOptionalTime"
            },
            "location": {
               "type": "geo_point"
            },
            "name": {
               "type": "string"
            },
            "rating": {
               "type": "integer"
            },
            "sport": {
               "type": "string"
            }
         }
      }
   }
}'

curl -XPOST "http://localhost:9200/sports/_bulk" -d'
{"index":{"_index":"sports","_type":"athlete"}}
{"name":"Michael", "birthdate":"1989-10-1", "sport":"Baseball", "rating": ["5", "4"],  "location":"46.22,-68.45"}
{"index":{"_index":"sports","_type":"athlete"}}
{"name":"Bob", "birthdate":"1989-11-2", "sport":"Baseball", "rating": ["3", "4"],  "location":"45.21,-68.35"}
{"index":{"_index":"sports","_type":"athlete"}}
{"name":"Jim", "birthdate":"1988-10-3", "sport":"Baseball", "rating": ["3", "2"],  "location":"45.16,-63.58" }
{"index":{"_index":"sports","_type":"athlete"}}
{"name":"Joe", "birthdate":"1992-5-20", "sport":"Baseball", "rating": ["4", "3"],  "location":"45.22,-68.53"}
{"index":{"_index":"sports","_type":"athlete"}}
{"name":"Tim", "birthdate":"1992-2-28", "sport":"Baseball", "rating": ["3", "3"],  "location":"46.22,-68.85"}
{"index":{"_index":"sports","_type":"athlete"}}
{"name":"Alfred", "birthdate":"1990-9-9", "sport":"Baseball", "rating": ["2", "2"],  "location":"45.12,-68.35"}
{"index":{"_index":"sports","_type":"athlete"}}
{"name":"Jeff", "birthdate":"1990-4-1", "sport":"Baseball", "rating": ["2", "3"], "location":"46.12,-68.55"}
{"index":{"_index":"sports","_type":"athlete"}}
{"name":"Will", "birthdate":"1988-3-1", "sport":"Baseball", "rating": ["4", "4"], "location":"46.25,-68.55" }
{"index":{"_index":"sports","_type":"athlete"}}
{"name":"Mick", "birthdate":"1989-10-1", "sport":"Baseball", "rating": ["3", "4"],  "location":"46.22,-68.45"}
{"index":{"_index":"sports","_type":"athlete"}}
{"name":"Pong", "birthdate":"1989-11-2", "sport":"Baseball", "rating": ["1", "3"],  "location":"45.21,-68.35"}
{"index":{"_index":"sports","_type":"athlete"}}
{"name":"Ray", "birthdate":"1988-10-3", "sport":"Baseball", "rating": ["2", "2"],  "location":"45.16,-63.58" }
{"index":{"_index":"sports","_type":"athlete"}}
{"name":"Ping", "birthdate":"1992-5-20", "sport":"Baseball", "rating": ["4", "3"],  "location":"45.22,-68.53"}
{"index":{"_index":"sports","_type":"athlete"}}
{"name":"Duke", "birthdate":"1992-2-28", "sport":"Baseball", "rating": ["5", "2"],  "location":"46.22,-68.85"}
{"index":{"_index":"sports","_type":"athlete"}}
{"name":"Hal", "birthdate":"1990-9-9", "sport":"Baseball", "rating": ["4", "2"],  "location":"45.12,-68.35"}
{"index":{"_index":"sports","_type":"athlete"}}
{"name":"Charge", "birthdate":"1990-4-1", "sport":"Baseball", "rating": ["3", "2"], "location":"46.12,-68.55"}
{"index":{"_index":"sports","_type":"athlete"}}
{"name":"Barry", "birthdate":"1988-3-1", "sport":"Baseball", "rating": ["5", "2"], "location":"46.25,-68.55" }
{"index":{"_index":"sports","_type":"athlete"}}
{"name":"Bank", "birthdate":"1988-3-1", "sport":"Golf", "rating": ["6", "4"], "location":"46.25,-68.55" }
{"index":{"_index":"sports","_type":"athlete"}}
{"name":"Bingo", "birthdate":"1988-3-1", "sport":"Golf", "rating": ["10", "7"], "location":"46.25,-68.55" }
{"index":{"_index":"sports","_type":"athlete"}}
{"name":"James", "birthdate":"1988-3-1", "sport":"Basketball", "rating": ["10", "8"], "location":"46.25,-68.55" }
{"index":{"_index":"sports","_type":"athlete"}}
{"name":"Wayne", "birthdate":"1988-3-1", "sport":"Hockey", "rating": ["10", "10"], "location":"46.25,-68.55" }
{"index":{"_index":"sports","_type":"athlete"}}
{"name":"Brady", "birthdate":"1988-3-1", "sport":"Football", "rating": ["10", "10"], "location":"46.25,-68.55" }
{"index":{"_index":"sports","_type":"athlete"}}
{"name":"Lewis", "birthdate":"1988-3-1", "sport":"Football", "rating": ["10", "10"], "location":"46.25,-68.55" }'

 
 
Давайте згрупуємо спортсменів з їхнього вигляду спорту і дізнаємося скільки їх у кожному спорті:
 
curl -XPOST "http://localhost:9200/sports/athlete/_search?pretty" -d'
{
   "size": 0, 
   "aggregations": {
      "the_name": {
         "terms": {
            "field": "sport"
         }
      }
   }
}'

 
Тут ми використовуємо агрегат «terms», який групує документа по полю «sport».
 
"size" : 0
(0 замінюється на Integer.MAX_VALUE автоматично) говорить про те, що нам потрібні всі документи без винятку, в нашому випадку не важлива швидкість, але треба враховувати, що більш точний результат вимагає більше часу.
 
Відповідь:
 
{
  ...
  "aggregations" : {
    "the_name" : {
      "buckets" : [ {
        "key" : "baseball",
        "doc_count" : 16
      }, {
        "key" : "golf",
        "doc_count" : 2
      }, {
        "key" : "basketball",
        "doc_count" : 1
      }, {
        "key" : "football",
        "doc_count" : 1
      }, {
        "key" : "hockey",
        "doc_count" : 1
      } ]
    }
  }
}

 
Відмінно, бейсболістів найбільше.
Давайте відсортуємо спортсменів за середнім значенням їх рейтингу, від більшого до меншого:
 
curl -XPOST "http://localhost:9200/sports/athlete/_search?pretty" -d'
{
   "size": 0, 
   "aggregations": {
      "the_name": {
         "terms": {
            "field": "name",
            "order": {
               "rating_avg": "desc"
            }
         },
         "aggregations": {
            "rating_avg": {
               "avg": {
                  "field": "rating"
               }
            }
         }
      }
   }
}'

Тут добре видно, що таке вкладений агрегат і як він може допомогти нам вибрати документи максимально гнучко.
Спочатку ми вказуємо, що потрібно згрупувати спортсменів по імені, потім впорядкувати за «rating_avg», який вираховується в під агрегаті «avg», по полю «rating». Відмітьте, як елегантно ES працює з масивами (
"rating" : [10, 9]
) і з легкістю вираховує середнє значення.
 
Відповідь:
 
{
  ...
  "aggregations" : {
    "the_name" : {
      "buckets" : [ {
        "key" : "brady",
        "doc_count" : 1,
        "rating_avg" : {
          "value" : 10.0
        }
      }, {
        "key" : "wayne",
        "doc_count" : 1,
        "rating_avg" : {
          "value" : 10.0
        }
      }, {
        "key" : "james",
        "doc_count" : 1,
        "rating_avg" : {
          "value" : 9.0
        }
      }, {
        "key" : "bingo",
        "doc_count" : 1,
        "rating_avg" : {
          "value" : 8.5
        }
      },
      ... {} ...
      {
        "key" : "duke",
        "doc_count" : 1,
        "rating_avg" : {
          "value" : 3.5
        }
      }, {
        "key" : "bob",
        "doc_count" : 1,
        "rating_avg" : {
          "value" : 3.5
        }
      } ]
    }
  }
}

 
Ще одна прекрасна можливість агрегатів це використання
"script"
. Наприклад:
 
curl -XPOST "http://localhost:9200/sports/athlete/_search?pretty" -d'
{
   "size": 0,
   "aggregations": {
      "age_ranges": {
         "range": {
            "script": "DateTime.now().year - doc[\"birthdate\"].date.year",
            "ranges": [
               {
                  "from": 22,
                  "to": 25
               }
            ]
         }
      }
   }
}'

 Починаючи з версії 1.2.0 виконання скриптів за замовчуванням вимкнено. Ви можете його включити , за умови що у користувачів немає прямого доступу до ES (Сподіваюся, що це так, інакше раджу вам негайно закрити цей доступ заради безпеки ваших даних).
 
 

Агрегація у всій красі або щось складніше

 
Давайте знайдемо всіх спортсменів, які знаходяться в радіусі 20 миль від точки
"46.12,-68.55"

Згрупуємо їх з виду спорту і виведемо детальну статистику по рейтингу спортсменів у цьому виді спорту.
Звучить непогано, а ось і приклад.
 
curl -XPOST "http://localhost:9200/sports/athlete/_search?pretty" -d'
{
   "size": 0,
   "aggregations": {
      "baseball_player_ring": {
         "geo_distance": {
            "field": "location",
            "origin": "46.12,-68.55",
            "unit": "mi",
            "ranges": [
               {
                  "from": 0,
                  "to": 20
               }
            ]
         },
         "aggregations": {
            "sport": {
         		"terms": {
              		   "field": "sport"
         		},
              	        "aggregations": {
                           "rating_stats": {
                               "stats": {
                                   "field": "rating"
                               }
                            }
                       }
                    }
      		}
         }
      }
   }
}'

 
Відповідь:
 
{
  ...
  "aggregations" : {
    "baseball_player_ring" : {
      "buckets" : [ {
        "key" : "*-20.0",
        "from" : 0.0,
        "to" : 20.0,
        "doc_count" : 13,
        "sport" : {
          "buckets" : [ {
            "key" : "baseball",
            "doc_count" : 8,
            "rating_stats" : {
              "count" : 14,
              "min" : 2.0,
              "max" : 5.0,
              "avg" : 3.357142857142857,
              "sum" : 47.0
            }
          }, {
            "key" : "golf",
            "doc_count" : 2,
            "rating_stats" : {
              "count" : 4,
              "min" : 4.0,
              "max" : 10.0,
              "avg" : 6.75,
              "sum" : 27.0
            }
          }, {
            "key" : "basketball",
            "doc_count" : 1,
            "rating_stats" : {
              "count" : 2,
              "min" : 8.0,
              "max" : 10.0,
              "avg" : 9.0,
              "sum" : 18.0
            }
          }, {
            "key" : "football",
            "doc_count" : 1,
            "rating_stats" : {
              "count" : 1,
              "min" : 10.0,
              "max" : 10.0,
              "avg" : 10.0,
              "sum" : 10.0
            }
          }, {
            "key" : "hockey",
            "doc_count" : 1,
            "rating_stats" : {
              "count" : 1,
              "min" : 10.0,
              "max" : 10.0,
              "avg" : 10.0,
              "sum" : 10.0
            }
          } ]
        }
      } ]
    }
  }
}

 
 

Висновок

Сподіваюся, я зміг донести загальні можливості цього прекрасного модуля. Всім, кого це тема зацікавила, я раджу ознайомитися з усім списком фільтрів по цим посиланням .
Радий будь-яким корисним зауважень і доповнень по темі.
 
Так само можна прочитати мою попередню статтю по ES — ElasticSearch і пошук навпаки. Percolate API
І взяти участь у голосування внизу статті.
 
 
  

    
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.