2016-06-24 10 views
8

Ich spiele mit ES um zu verstehen, ob es die meisten meiner Szenarien abdecken kann. Ich bin an dem Punkt, an dem ich feststecke, wie ich ein bestimmtes Ergebnis erreichen kann, das in SQL ziemlich einfach ist.Elasticsearch SQL wie Unterabfrage-Aggregation

Dies ist das Beispiel

In elastisch ich einen Index mit diesen Dokumenten habe

{ "Id": 1, "Fruit": "Banana", "BoughtInStore"="Jungle", "BoughtDate"=20160101, "BestBeforeDate": 20160102, "BiteBy":"John"} 
{ "Id": 2, "Fruit": "Banana", "BoughtInStore"="Jungle", "BoughtDate"=20160102, "BestBeforeDate": 20160104, "BiteBy":"Mat"} 
{ "Id": 3, "Fruit": "Banana", "BoughtInStore"="Jungle", "BoughtDate"=20160103, "BestBeforeDate": 20160105, "BiteBy":"Mark"} 
{ "Id": 4, "Fruit": "Banana", "BoughtInStore"="Jungle", "BoughtDate"=20160104, "BestBeforeDate": 20160201, "BiteBy":"Simon"} 
{ "Id": 5, "Fruit": "Orange", "BoughtInStore"="Jungle", "BoughtDate"=20160112, "BestBeforeDate": 20160112, "BiteBy":"John"} 
{ "Id": 6, "Fruit": "Orange", "BoughtInStore"="Jungle", "BoughtDate"=20160114, "BestBeforeDate": 20160116, "BiteBy":"Mark"} 
{ "Id": 7, "Fruit": "Orange", "BoughtInStore"="Jungle", "BoughtDate"=20160120, "BestBeforeDate": 20160121, "BiteBy":"Simon"} 
{ "Id": 8, "Fruit": "Kiwi", "BoughtInStore"="Shop", "BoughtDate"=20160121, "BestBeforeDate": 20160121, "BiteBy":"Mark"} 
{ "Id": 8, "Fruit": "Kiwi", "BoughtInStore"="Jungle", "BoughtDate"=20160121, "BestBeforeDate": 20160121, "BiteBy":"Simon"} 

Wenn ich, wie viele Früchte in verschiedenen Speichern Menschen in bestimmtem Datumsbereich in SQL Ich beißt gekauft wissen möchte So etwas ist

SELECT 
    COUNT(DISTINCT kpi.Fruit) as Fruits, 
    kpi.BoughtInStore, 
    kpi.BiteBy 
FROM 
    (
     SELECT f1.Fruit, f1.BoughtInStore, f1.BiteBy 
     FROM FruitsTable f1 
     WHERE f1.BoughtDate = (
      SELECT MAX(f2.BoughtDate) 
      FROM FruitsTable f2 
      WHERE f1.Fruit = f2.Fruit 
      and f2.BoughtDate between 20160101 and 20160131 
      and (f2.BestBeforeDate between 20160101 and 20160131) 
     ) 
    ) kpi 
GROUP BY kpi.BoughtInStore, kpi.ByteBy 

die Ergebnisse

schreiben so etwas wie dieses

{ "Fruits": 1, "BoughtInStore": "Jungle", "BiteBy"="Mark"} 
{ "Fruits": 1, "BoughtInStore": "Shop", "BiteBy"="Mark"} 
{ "Fruits": 2, "BoughtInStore": "Jungle", "BiteBy"="Simon"} 

Haben Sie eine Idee, wie ich das gleiche Ergebnis in Elastic mit Aggregation erreichen kann?

In wenigen Worten die Probleme, die ich in elastischen bin vor sind:

  1. Wie ein subsed von Daten vor der Aggregation vorbereiten (wie in diesem Beispiel die letzte Zeile im Bereich für jede Frucht)
  2. Wie gruppieren Ergebnisse nach mehreren Feldern

Danke

Antwort

2

Wie ich verstehe keinen Weg gibt es Aggregation Ergebnis in Filter der gleichen Abfrage zu verweisen. So können Sie sich nur ein Teil des Puzzles mit einzelnen Abfrage lösen:

GET /purchases/fruits/_search 
{ 
    "query": { 
    "filtered":{ 
     "filter": { 
     "range": { 
      "BoughtDate": { 
      "gte": "2015-01-01", //assuming you have right mapping for dates 
      "lte": "2016-03-01" 
      } 
     } 
     } 
    } 
    }, 
    "sort": { "BoughtDate": { "order": "desc" }}, 
    "aggs": { 
    "byBoughtDate": { 
     "terms": { 
     "field": "BoughtDate", 
     "order" : { "_term" : "desc" } 
     }, 
     "aggs": { 
     "distinctCount": { 
      "cardinality": { 
      "field": "Fruit" 
      } 
     } 
     } 
    } 
    } 
} 

So werden Sie alle haben die Dokumente im Bereich von Daten, und Sie werden aggregiert Eimer zählt haben, durch Begriff sortiert, so max Datum wird an der Spitze. Der Client kann diesen ersten Bucket analysieren (sowohl Anzahl als auch Wert) und dann die Dokumente für diesen Datumswert übernehmen. Für eine bestimmte Anzahl von Früchten verwenden Sie einfach die Nested-Kardinalitäts-Aggregation.

Yep, gibt die Abfrage viel mehr Informationen, als Sie benötigt, aber das ist das Leben :)

1

Natürlich gibt es keinen direkten Weg von SQL zum Elasticsearch DSL, aber es gibt einige ziemlich häufig Korrelationen.

Für den Anfang wird jede GROUP BY/HAVING zu einer Aggregation kommen. Die normale Abfragesemantik kann im Allgemeinen (und mehr) von der Query DSL abgedeckt werden.

Wie eine vor der Aggregation subsed von Daten vorbereiten (wie in diesem Beispiel im Bereich der neuesten Reihe für jede Frucht)

So, du bist Art für zwei verschiedene Dinge zu fragen.

Wie ein subsed von Daten vor der Aggregation

Diese prepare ist die Abfrage Phase.

(wie in diesem Beispiel wird die letzte Zeile im Bereich für jede Frucht)

Sie fragen es technisch die Antwort auf dieses Beispiel erhalten zu aggregieren: keine normale Abfrage. In deinem Beispiel machst du MAX, um das zu erhalten, was mit einer GROUP BY funktioniert, um es zu bekommen.

So gruppieren Ergebnisse nach mehreren Feldern

Es hängt davon ab. Willst du sie abgestuft (in der Regel ja) oder willst du sie zusammen?

Wenn Sie möchten, dass sie mehrstufig sind, dann verwenden Sie nur Unteraggregationen, um zu bekommen, was Sie wollen. Wenn Sie sie kombinieren möchten, verwenden Sie im Allgemeinen nur eine filters Aggregation für die verschiedenen Gruppierungen.

Alles wieder zusammenfassen: Sie möchten den letzten Kauf pro Obst, bei einem bestimmten gefilterten Datumsbereich. Die Datumsbereiche sind nur ganz normale Abfragen/Filter:

{ 
    "query": { 
    "bool": { 
     "filter": [ 
     { 
      "range": { 
      "BoughtDate": { 
       "gte": "2016-01-01", 
       "lte": "2016-01-31" 
      } 
      } 
     }, 
     { 
      "range": { 
      "BestBeforeDate": { 
       "gte": "2016-01-01", 
       "lte": "2016-01-31" 
      } 
      } 
     } 
     ] 
    } 
    } 
} 

Damit wird kein Dokument in der Anforderung enthalten sein, die nicht innerhalb dieser Datumsbereiche für beide Felder ist (effektiv ein AND). Weil ich einen Filter verwendet habe, ist dieser nicht gescort und kann zwischengespeichert werden.

Jetzt müssen Sie beginnen zu aggregieren, um den Rest der Informationen zu erhalten. Beginnen wir mit der Annahme, dass die Dokumente mit dem obigen Filter gefiltert wurden, um das zu vereinfachen, was wir betrachten. Wir werden es am Ende kombinieren.

{ 
    "size": 0, 
    "aggs": { 
    "group_by_date": { 
     "date_histogram": { 
     "field": "BoughtDate", 
     "interval": "day", 
     "min_doc_count": 1 
     }, 
     "aggs": { 
     "group_by_store": { 
      "terms": { 
      "field": "BoughtInStore" 
      }, 
      "aggs": { 
      "group_by_person": { 
       "terms": { 
       "field": "BiteBy" 
       } 
      } 
      } 
     } 
     } 
    } 
    } 
} 

Sie wollen "size" : 0 auf der obersten Ebene, weil man eigentlich nicht über Hits kümmern. Sie möchten nur aggregierte Ergebnisse.

Ihre erste Aggregation wurde tatsächlich nach dem letzten Datum gruppiert. Ich änderte es ein wenig, um es ein wenig realistischer zu machen (jeder Tag), aber es ist effektiv das gleiche. Die Art, wie Sie MAX verwenden, könnten wir eine terms Aggregation mit "size": 1 verwenden, aber das ist wahrer zu, wie Sie es tun möchten, wenn ein Datum (und vermutlich Zeit!) Beteiligt ist. Ich habe auch darum gebeten, Tage in den übereinstimmenden Dokumenten zu ignorieren, die keine Daten haben (da es von Anfang bis Ende läuft, interessieren wir uns eigentlich nicht für diese Tage).

Wenn Sie wirklich nur den letzten Tag wollten, dann könnten Sie eine Pipeline-Aggregation verwenden, um alles außer dem maximalen Bucket zu löschen, aber eine realistische Verwendung dieser Art von Anforderung würde den gesamten Datumsbereich wünschen.

Also, wir fahren dann fort, indem wir nach Geschäft gruppieren, was Sie wollen. Dann unterteilen wir uns nach Personen (BiteBy). Dies gibt Ihnen die Anzahl implizit.

alles wieder zusammen:

{ 
    "size": 0, 
    "query": { 
    "bool": { 
     "filter": [ 
     { 
      "range": { 
      "BoughtDate": { 
       "gte": "2016-01-01", 
       "lte": "2016-01-31" 
      } 
      } 
     }, 
     { 
      "range": { 
      "BestBeforeDate": { 
       "gte": "2016-01-01", 
       "lte": "2016-01-31" 
      } 
      } 
     } 
     ] 
    } 
    }, 
    "aggs": { 
    "group_by_date": { 
     "date_histogram": { 
     "field": "BoughtDate", 
     "interval": "day", 
     "min_doc_count": 1 
     }, 
     "aggs": { 
     "group_by_store": { 
      "terms": { 
      "field": "BoughtInStore" 
      }, 
      "aggs": { 
      "group_by_person": { 
       "terms": { 
       "field": "BiteBy" 
       } 
      } 
      } 
     } 
     } 
    } 
    } 
} 

Hinweis: Hier ist, wie ich die Daten indiziert.

PUT /grocery/store/_bulk 
{"index":{"_id":"1"}} 
{"Fruit":"Banana","BoughtInStore":"Jungle","BoughtDate":"2016-01-01","BestBeforeDate":"2016-01-02","BiteBy":"John"} 
{"index":{"_id":"2"}} 
{"Fruit":"Banana","BoughtInStore":"Jungle","BoughtDate":"2016-01-02","BestBeforeDate":"2016-01-04","BiteBy":"Mat"} 
{"index":{"_id":"3"}} 
{"Fruit":"Banana","BoughtInStore":"Jungle","BoughtDate":"2016-01-03","BestBeforeDate":"2016-01-05","BiteBy":"Mark"} 
{"index":{"_id":"4"}} 
{"Fruit":"Banana","BoughtInStore":"Jungle","BoughtDate":"2016-01-04","BestBeforeDate":"2016-02-01","BiteBy":"Simon"} 
{"index":{"_id":"5"}} 
{"Fruit":"Orange","BoughtInStore":"Jungle","BoughtDate":"2016-01-12","BestBeforeDate":"2016-01-12","BiteBy":"John"} 
{"index":{"_id":"6"}} 
{"Fruit":"Orange","BoughtInStore":"Jungle","BoughtDate":"2016-01-14","BestBeforeDate":"2016-01-16","BiteBy":"Mark"} 
{"index":{"_id":"7"}} 
{"Fruit":"Orange","BoughtInStore":"Jungle","BoughtDate":"2016-01-20","BestBeforeDate":"2016-01-21","BiteBy":"Simon"} 
{"index":{"_id":"8"}} 
{"Fruit":"Kiwi","BoughtInStore":"Shop","BoughtDate":"2016-01-21","BestBeforeDate":"2016-01-21","BiteBy":"Mark"} 
{"index":{"_id":"9"}} 
{"Fruit":"Kiwi","BoughtInStore":"Jungle","BoughtDate":"2016-01-21","BestBeforeDate":"2016-01-21","BiteBy":"Simon"} 

Es ist kritische, die Ihre String-Werte, die Sie aggregieren auf möchten (Speicher und Person) sind not_analyzedstring s (keyword in ES 5.0)! Ansonsten wird es Felddaten verwenden, und das ist keine gute Sache.

Die Zuordnungen wie dies in ES 1.x/2.x ES aussehen:

PUT /grocery 
{ 
    "settings": { 
    "number_of_shards": 1 
    }, 
    "mappings": { 
    "store": { 
     "properties": { 
     "Fruit": { 
      "type": "string", 
      "index": "not_analyzed" 
     }, 
     "BoughtInStore": { 
      "type": "string", 
      "index": "not_analyzed" 
     }, 
     "BiteBy": { 
      "type": "string", 
      "index": "not_analyzed" 
     }, 
     "BestBeforeDate": { 
      "type": "date" 
     }, 
     "BoughtDate": { 
      "type": "date" 
     } 
     } 
    } 
    } 
} 

All dies zusammen, und Sie erhalten die Antwort wie:

{ 
    "took": 8, 
    "timed_out": false, 
    "_shards": { 
    "total": 1, 
    "successful": 1, 
    "failed": 0 
    }, 
    "hits": { 
    "total": 8, 
    "max_score": 0, 
    "hits": [] 
    }, 
    "aggregations": { 
    "group_by_date": { 
     "buckets": [ 
     { 
      "key_as_string": "2016-01-01T00:00:00.000Z", 
      "key": 1451606400000, 
      "doc_count": 1, 
      "group_by_store": { 
      "doc_count_error_upper_bound": 0, 
      "sum_other_doc_count": 0, 
      "buckets": [ 
       { 
       "key": "Jungle", 
       "doc_count": 1, 
       "group_by_person": { 
        "doc_count_error_upper_bound": 0, 
        "sum_other_doc_count": 0, 
        "buckets": [ 
        { 
         "key": "John", 
         "doc_count": 1 
        } 
        ] 
       } 
       } 
      ] 
      } 
     }, 
     { 
      "key_as_string": "2016-01-02T00:00:00.000Z", 
      "key": 1451692800000, 
      "doc_count": 1, 
      "group_by_store": { 
      "doc_count_error_upper_bound": 0, 
      "sum_other_doc_count": 0, 
      "buckets": [ 
       { 
       "key": "Jungle", 
       "doc_count": 1, 
       "group_by_person": { 
        "doc_count_error_upper_bound": 0, 
        "sum_other_doc_count": 0, 
        "buckets": [ 
        { 
         "key": "Mat", 
         "doc_count": 1 
        } 
        ] 
       } 
       } 
      ] 
      } 
     }, 
     { 
      "key_as_string": "2016-01-03T00:00:00.000Z", 
      "key": 1451779200000, 
      "doc_count": 1, 
      "group_by_store": { 
      "doc_count_error_upper_bound": 0, 
      "sum_other_doc_count": 0, 
      "buckets": [ 
       { 
       "key": "Jungle", 
       "doc_count": 1, 
       "group_by_person": { 
        "doc_count_error_upper_bound": 0, 
        "sum_other_doc_count": 0, 
        "buckets": [ 
        { 
         "key": "Mark", 
         "doc_count": 1 
        } 
        ] 
       } 
       } 
      ] 
      } 
     }, 
     { 
      "key_as_string": "2016-01-12T00:00:00.000Z", 
      "key": 1452556800000, 
      "doc_count": 1, 
      "group_by_store": { 
      "doc_count_error_upper_bound": 0, 
      "sum_other_doc_count": 0, 
      "buckets": [ 
       { 
       "key": "Jungle", 
       "doc_count": 1, 
       "group_by_person": { 
        "doc_count_error_upper_bound": 0, 
        "sum_other_doc_count": 0, 
        "buckets": [ 
        { 
         "key": "John", 
         "doc_count": 1 
        } 
        ] 
       } 
       } 
      ] 
      } 
     }, 
     { 
      "key_as_string": "2016-01-14T00:00:00.000Z", 
      "key": 1452729600000, 
      "doc_count": 1, 
      "group_by_store": { 
      "doc_count_error_upper_bound": 0, 
      "sum_other_doc_count": 0, 
      "buckets": [ 
       { 
       "key": "Jungle", 
       "doc_count": 1, 
       "group_by_person": { 
        "doc_count_error_upper_bound": 0, 
        "sum_other_doc_count": 0, 
        "buckets": [ 
        { 
         "key": "Mark", 
         "doc_count": 1 
        } 
        ] 
       } 
       } 
      ] 
      } 
     }, 
     { 
      "key_as_string": "2016-01-20T00:00:00.000Z", 
      "key": 1453248000000, 
      "doc_count": 1, 
      "group_by_store": { 
      "doc_count_error_upper_bound": 0, 
      "sum_other_doc_count": 0, 
      "buckets": [ 
       { 
       "key": "Jungle", 
       "doc_count": 1, 
       "group_by_person": { 
        "doc_count_error_upper_bound": 0, 
        "sum_other_doc_count": 0, 
        "buckets": [ 
        { 
         "key": "Simon", 
         "doc_count": 1 
        } 
        ] 
       } 
       } 
      ] 
      } 
     }, 
     { 
      "key_as_string": "2016-01-21T00:00:00.000Z", 
      "key": 1453334400000, 
      "doc_count": 2, 
      "group_by_store": { 
      "doc_count_error_upper_bound": 0, 
      "sum_other_doc_count": 0, 
      "buckets": [ 
       { 
       "key": "Jungle", 
       "doc_count": 1, 
       "group_by_person": { 
        "doc_count_error_upper_bound": 0, 
        "sum_other_doc_count": 0, 
        "buckets": [ 
        { 
         "key": "Simon", 
         "doc_count": 1 
        } 
        ] 
       } 
       }, 
       { 
       "key": "Shop", 
       "doc_count": 1, 
       "group_by_person": { 
        "doc_count_error_upper_bound": 0, 
        "sum_other_doc_count": 0, 
        "buckets": [ 
        { 
         "key": "Mark", 
         "doc_count": 1 
        } 
        ] 
       } 
       } 
      ] 
      } 
     } 
     ] 
    } 
    } 
} 
+0

Ab jetzt , meine kleine bekannte Problemumgehung mit der Bucket-Aggregation zur Begrenzung auf das maximale Datum wird nicht mit einem 'date_histogram' funktionieren. Ironischerweise würde es funktionieren, wenn ich die Werte einfach so belassen würde, wie Sie es ursprünglich gezeigt hatten. – pickypg