@@ -470,7 +470,9 @@ case class ProductSalesAnalysis(
470470 uniqueCustomers : Long // COUNT DISTINCT OVER
471471)
472472
473- val sqlQuery = """
473+ // Type-safe execution with compile-time validation
474+ val results : Source [ProductSalesAnalysis , NotUsed ] =
475+ client.scrollAs[ProductSalesAnalysis ]("""
474476 SELECT
475477 product_id AS productId,
476478 product_name AS productName,
@@ -505,23 +507,19 @@ val sqlQuery = """
505507 WHERE sale_date >= '2024-01-01'
506508 GROUP BY product_id, product_name, DATE_TRUNC('month', sale_date)
507509 ORDER BY product_id, saleMonth
508- """
509-
510- // Type-safe execution with compile-time validation
511- val results : Source [ProductSalesAnalysis , NotUsed ] =
512- client.scrollAs[ProductSalesAnalysis ](sqlQuery)
510+ """ )
513511
514512results.runWith(Sink .foreach { analysis =>
515513 println(s """
516514 Product: ${analysis.productName} ( ${analysis.productId})
517515 Month: ${analysis.saleMonth}
518- Monthly Sales: $$ {analysis.monthlySales}
519- First Sale: $$ {analysis.firstSaleAmount}
520- Last Sale: $$ {analysis.lastSaleAmount}
516+ Monthly Sales: ${analysis.monthlySales}
517+ First Sale: ${analysis.firstSaleAmount}
518+ Last Sale: ${analysis.lastSaleAmount}
521519 All Sales: ${analysis.allSaleAmounts.mkString(" [" , " , " , " ]" )}
522- Total Sales (All Time): $$ {analysis.totalSales}
523- Average Sale: $$ {analysis.avgSaleAmount}
524- Price Range: $$ {analysis.minSaleAmount} - $ ${analysis.maxSaleAmount}
520+ Total Sales (All Time): ${analysis.totalSales}
521+ Average Sale: ${analysis.avgSaleAmount}
522+ Price Range: ${analysis.minSaleAmount} - ${analysis.maxSaleAmount}
525523 Sale Count: ${analysis.saleCount}
526524 Unique Customers: ${analysis.uniqueCustomers}
527525 """ )
@@ -917,6 +915,131 @@ val sqlQuery = """
917915"""
918916```
919917
918+ #### ** Example 3: Translation to Elasticsearch DSL**
919+
920+ The SQL query above is decomposed into two Elasticsearch queries:
921+
922+ ** Query 1: Window Functions Aggregations**
923+
924+ ``` json
925+ {
926+ "query" : {
927+ "bool" : {
928+ "filter" : [
929+ {
930+ "range" : {
931+ "timestamp" : {
932+ "gte" : " now-1H"
933+ }
934+ }
935+ }
936+ ]
937+ }
938+ },
939+ "size" : 0 ,
940+ "_source" : false ,
941+ "aggs" : {
942+ "sensorId" : {
943+ "terms" : {
944+ "field" : " sensor_id" ,
945+ "min_doc_count" : 1
946+ },
947+ "aggs" : {
948+ "movingAvg" : {
949+ "avg" : {
950+ "field" : " temperature"
951+ }
952+ },
953+ "minTemp" : {
954+ "min" : {
955+ "field" : " temperature"
956+ }
957+ },
958+ "maxTemp" : {
959+ "max" : {
960+ "field" : " temperature"
961+ }
962+ },
963+ "firstReading" : {
964+ "top_hits" : {
965+ "size" : 1 ,
966+ "sort" : [
967+ {
968+ "timestamp" : {
969+ "order" : " asc"
970+ }
971+ }
972+ ],
973+ "_source" : {
974+ "includes" : [
975+ " temperature"
976+ ]
977+ }
978+ }
979+ },
980+ "currentReading" : {
981+ "top_hits" : {
982+ "size" : 1 ,
983+ "sort" : [
984+ {
985+ "timestamp" : {
986+ "order" : " desc"
987+ }
988+ }
989+ ],
990+ "_source" : {
991+ "includes" : [
992+ " temperature"
993+ ]
994+ }
995+ }
996+ }
997+ }
998+ }
999+ }
1000+ }
1001+ ```
1002+ ** Query 2: Main Query (Non-Aggregated Fields)**
1003+
1004+ Since this query includes non-aggregated fields, a separate search query would be executed:
1005+
1006+ ``` json
1007+ {
1008+ "query" : {
1009+ "bool" : {
1010+ "filter" : [
1011+ {
1012+ "range" : {
1013+ "timestamp" : {
1014+ "gte" : " now-1H"
1015+ }
1016+ }
1017+ }
1018+ ]
1019+ }
1020+ },
1021+ "sort" : [
1022+ {
1023+ "sensorId" : {
1024+ "order" : " asc"
1025+ }
1026+ },
1027+ {
1028+ "timestamp" : {
1029+ "order" : " asc"
1030+ }
1031+ }
1032+ ],
1033+ "_source" : {
1034+ "includes" : [
1035+ " sensor_id" ,
1036+ " timestamp" ,
1037+ " temperature"
1038+ ]
1039+ }
1040+ }
1041+ ```
1042+
9201043#### ** Performance Considerations**
9211044
9221045| Consideration | Recommendation | Impact |
0 commit comments