Esper學習之五:EPL語法(一)

上篇說到了Esper的Context,要是不瞭解的同學請參看《Esper學習之四:Context》,看過的同學如果還是不理解的話可以給我評論,我將會儘可能的解答。之前有些同學問我Context和Group by有什麼區別,其實如果只是很簡單的用Context,那麼確實沒太大區別,無非是在Context下select可以不包含group by修飾的屬性。但是Group by明顯沒有Context強大,很多複雜的分組Group by是沒法做到的。不過在能達到同樣效果的情況下,我還是建議使用Group by,畢竟Context的名字是不能重複的,而且在高併發的情況下Context會短時間鎖住。至於原因,這已經是Esper的高級篇了,這裏暫且不說。

        今天開始講解Esper的重中之重——EPL。EPL可以說是Esper的核心,要是不會將簡單的業務需求轉化爲EPL,更別說複雜的EPL了,最後就等着被客戶罵吧。接下來的很多篇都會圍繞EPL講解,大概會有6篇吧,畢竟英文文檔都有140頁了,草草兩篇根本就說不完。廢話不多說,先簡單介紹下什麼是EPL,即使第一篇有說過,但是這裏有必要細說一下。

        EPL,全稱Event Processing Language,是一種類似SQL的語言,包含了SELECT, FROM, WHERE, GROUP BY, HAVING 和 ORDER BY子句,同時用事件流代替了table作爲數據源,並且能像SQL那樣join,filtering和aggregation。所以如果各位有SQL基礎的話,簡單的EPL很容易掌握。除了select,EPL也有insert into,update,delete,不過含義和SQL並不是很接近。另外還有pattern和output子句,這兩個是SQL所沒有的。EPL還定義了一個叫view的東西,類似SQL的table,來決定哪些數據是可用的,Esper提供了十多個view,並且保證這些view可以被重複使用。而且用戶還可以擴展view成爲自定義view來滿足需求。在view的基礎上,EPL還提供了named window的定義,作用和view類似,但是更加靈活。。。還有很多東西,我再列舉估計大家都要暈了,就先說說語法吧。


1.EPL Syntax

[plain] view plaincopy
  1. [annotations]  
  2. [expression_declarations]  
  3. [context context_name]  
  4. [insert into insert_into_def]  
  5. select select_list  
  6. from stream_def [as name] [, stream_def [as name]] [,...]  
  7. [where search_conditions]  
  8. [group by grouping_expression_list]  
  9. [having grouping_search_conditions]  
  10. [output output_specification]  
  11. [order by order_by_expression_list]  
  12. [limit num_rows]  

        基本上大部分的EPL都是按照這個格式來定義。看過之前幾篇的同學應該對裏面的某些內容熟悉,比如context context_name,select select_list from stream_def等等。其他的可以先不關心,後面會有詳解。比如wher,group by都會有專門的篇幅進行描述。


2.Time Periods

這部分的內容說的是Esper中時間的表達形式。語法如下:

[plain] view plaincopy
  1. time-period : [year-part] [month-part] [week-part] [day-part] [hour-part]  
  2. [minute-part] [seconds-part] [milliseconds-part]  
  3. year-part : (number|variable_name) ("years" | "year")  
  4. month-part : (number|variable_name) ("months" | "month")  
  5. week-part : (number|variable_name) ("weeks" | "week")  
  6. day-part : (number|variable_name) ("days" | "day")  
  7. hour-part : (number|variable_name) ("hours" | "hour")  
  8. minute-part : (number|variable_name) ("minutes" | "minute" | "min")  
  9. seconds-part : (number|variable_name) ("seconds" | "second" | "sec")  
  10. milliseconds-part : (number|variable_name) ("milliseconds" | "millisecond" |  
  11. "msec")  

與時間相關的EPL基本都會用到上面列出的東西,舉幾個例子說明下:

[plain] view plaincopy
  1. // 計算過去的5分3秒中進入改語句的Fruit事件的平均price  
  2. select avg(price) from Fruit.win:time(5 minute 3 sec)  
  3.   
  4. // 每一天輸出一次用戶的賬戶總額  
  5. select sum(account) from User output every 1 day  

用法比較簡單,大家可以多試試。要注意的是,Esper規定每月的天數都是30天,所以對準確性要求高的業務,以月爲單位進行計算會出現誤差的。


3.Comments

註釋基本上和Java差不多,只不過他沒有/** */和/* */之分,只有/* */和//,畢竟不需要生成文檔,所以就沒那個必要了。//只能單行註釋,而/* */可以多行註釋。示例如下:

[plain] view plaincopy
  1. a.單行註釋  
  2. // This comment extends to the end of the line.  
  3. // Two forward slashes with no whitespace between them begin such comments.  
  4. select * from MyEvent  
  5.   
  6. b.多行註釋  
  7. select * from OtherEvent   
  8. /* this is a very  
  9. *important Event */  
  10.   
  11. c.混合註釋  
  12. select field1 // first comment  
  13. /* second comment */ field2 from MyEvent  

4.Reserved Keywords

        EPL裏如果某個事件屬性,或者事件流的名稱和EPL的關鍵字一樣,那麼必須要以`括起來纔可用,`在鍵盤上esc的下面,1的左邊,叫什麼忘記了,估計說出來也有很多人不知道。比如:

[plain] view plaincopy
  1. // insert和Order是關鍵字,這個EPL無效  
  2. select insert from Order  
  3.   
  4. // `insert`和`Order`是屬性名稱和事件流名稱,這個EPL有效  
  5. select `insert` from `Order`  

5.Escaping Strings

在EPL中,字符串使用單引號或者雙引號括起來的,那如果字符串裏包含有單引號或者雙引號怎麼辦呢。請看例子:

[plain] view plaincopy
  1. select * from OrderEvent(name='John')  
  2. // 等同於  
  3. select * from OrderEvent(name="John")  

如果name=John's,則需要反斜槓進行轉義。

[plain] view plaincopy
  1. select * from OrderEvent(name="John\"s")  
  2. // 或者  
  3. select * from OrderEvent(name='john\'s')  

除了使用反斜槓,還可以使用unicode來表示單引號和雙引號。

[plain] view plaincopy
  1. select * from OrderEvent(name="John\u0022s")  
  2. // 或者  
  3. select * from OrderEvent(name='john\u0027s')  

注意在Java編寫EPL的時候,反斜槓和無含義的雙引號還得轉義,不然會和String的雙引號衝突。比如

[plain] view plaincopy
  1. epService.getEPAdministrator().createEPL("select * from OrderEvent(name='John\\'s')");  
  2. // ... and for double quotes...  
  3. epService.getEPAdministrator().createEPL("select * from OrderEvent(name=\"Quote \\\"Hello\\\"\")");  

6.Data Types

        EPL支持Java所有的數值數據類型,包括基本類型及其包裝類,同時還支持java.math.BigInteger和java.math.BigDecimal,並且能自動轉換數據類型不丟失精度(比如short轉int,int轉short則不行)。如果想在EPL內進行數據轉換,可以使用cast函數。完整例子如下:

[java] view plaincopy
  1. import com.espertech.esper.client.EPAdministrator;  
  2. import com.espertech.esper.client.EPRuntime;  
  3. import com.espertech.esper.client.EPServiceProvider;  
  4. import com.espertech.esper.client.EPServiceProviderManager;  
  5. import com.espertech.esper.client.EPStatement;  
  6. import com.espertech.esper.client.EventBean;  
  7. import com.espertech.esper.client.UpdateListener;  
  8.   
  9. /** 
  10.  * 可用cast函數將其他的數值數據類型轉爲BigDecimal。 
  11.  *  
  12.  * @author luonanqin 
  13.  * 
  14.  */  
  15. class Banana  
  16. {  
  17.     private int price;  
  18.   
  19.     public int getPrice()  
  20.     {  
  21.         return price;  
  22.     }  
  23.   
  24.     public void setPrice(int price)  
  25.     {  
  26.         this.price = price;  
  27.     }  
  28. }  
  29.   
  30. class CastDataTypeListener1 implements UpdateListener  
  31. {  
  32.     public void update(EventBean[] newEvents, EventBean[] oldEvents)  
  33.     {  
  34.         if (newEvents != null)  
  35.         {  
  36.             EventBean event = newEvents[0];  
  37.             // cast(avg(price), int)中間的空格在EPL中可以不寫,但是event.get的時候必須加上,建議用as一個別名來代表轉換後的值  
  38.             System.out.println("Average Price: " + event.get("cast(avg(price), int)") + ", DataType is "  
  39.                     + event.get("cast(avg(price), int)").getClass().getName());  
  40.         }  
  41.     }  
  42. }  
  43.   
  44. class CastDataTypeListener2 implements UpdateListener  
  45. {  
  46.     public void update(EventBean[] newEvents, EventBean[] oldEvents)  
  47.     {  
  48.         if (newEvents != null)  
  49.         {  
  50.             EventBean event = newEvents[0];  
  51.             System.out.println("Average Price: " + event.get("avg(price)") + ", DataType is " + event.get("avg(price)").getClass().getName());  
  52.         }  
  53.     }  
  54. }  
  55.   
  56. public class CastDataTypeTest  
  57. {  
  58.     public static void main(String[] args) throws InterruptedException  
  59.     {  
  60.         EPServiceProvider epService = EPServiceProviderManager.getDefaultProvider();  
  61.   
  62.         EPAdministrator admin = epService.getEPAdministrator();  
  63.   
  64.         String banana = Banana.class.getName();  
  65.         String epl1 = "select cast(avg(price),int) from " + banana + ".win:length_batch(2)";  
  66.         String epl2 = "select avg(price) from " + banana + ".win:length_batch(2)";  
  67.   
  68.         EPStatement state1 = admin.createEPL(epl1);  
  69.         state1.addListener(new CastDataTypeListener1());  
  70.         EPStatement state2 = admin.createEPL(epl2);  
  71.         state2.addListener(new CastDataTypeListener2());  
  72.   
  73.         EPRuntime runtime = epService.getEPRuntime();  
  74.   
  75.         Banana b1 = new Banana();  
  76.         b1.setPrice(1);  
  77.         runtime.sendEvent(b1);  
  78.   
  79.         Banana b2 = new Banana();  
  80.         b2.setPrice(2);  
  81.         runtime.sendEvent(b2);  
  82.     }  
  83. }  

執行結果:

[plain] view plaincopy
  1. Average Price: 1, DataType is java.lang.Integer  
  2. Average Price: 1.5, DataType is java.lang.Double  

要提醒的是,如果某個數除以0,那麼默認會返回正無窮大或者負無窮大,不過可以配置這個結果,比如用null來代替。


7.Annotation

EPL也可以寫註解,種類不多,大部分簡單而有效。不過有些註解內容較多,在這裏只是簡單介紹,以後會在具體的使用場景進行詳細講解。首先來了解下註解的語法。

[plain] view plaincopy
  1. // 不包含參數或者單個參數的註解  
  2. @annotation_name [(annotation_parameters)]  
  3.   
  4. // 包含多個屬性名-值對的註解  
  5. @annotation_name (attribute_name = attribute_value, [name=value, ...])  
  6.   
  7. // 多個註解聯合使用  
  8. @annotation_name [(annotation_parameters)] [@annotation_name [(annotation_parameters)]] [...]  

下面講解具體註解時,會結合語法進行說明。

a) @Name 指定EPL的名稱,參數只有一個。例如:@Name("MyEPL")

b) @Description 對EPL進行描述,參數只有一個。例如:@Description("This is MyEPL")

c) @Tag 對EPL進行額外的說明,參數有兩個,分別爲Tag的名稱和Tag的值,用逗號分隔。例如:@Tag(name="author",value="luonanqin")

d) @Priority 指定EPL的優先級,參數只有一個,並且整數(可負可正)。例如:@Priority(10)

e) @Drop 指定事件經過此EPL後不再參與其他的EPL計算,該註解無參數。

f) @Hint 爲EPL加上某些標記,讓引擎對此EPL產生其他的操作,會改變EPL實例的內存佔用,但通常不會改變輸出。其參數固定,由Esper提供,之後的篇幅會穿插講解這個註解的使用場景。

g) @Audit EPL添加此註解後,可以額外輸出EPL運行情況,有點類似日誌的感覺(當然沒有日誌的功能全啦),具體使用場景在此先不提。

h) @Hook 與SQL相關,這裏暫且不說

i) @EventRepresentation 這是用來指定EPL產生的計算結果事件包含的數據形式。參數只有一個,即array=true或array=false。false爲默認值,代表數據形式爲Map,若爲true,則數據形式爲數組。

針對以上幾個簡單的註解,例子如下:

[java] view plaincopy
  1. import com.espertech.esper.client.EPAdministrator;  
  2. import com.espertech.esper.client.EPRuntime;  
  3. import com.espertech.esper.client.EPServiceProvider;  
  4. import com.espertech.esper.client.EPServiceProviderManager;  
  5. import com.espertech.esper.client.EPStatement;  
  6. import com.espertech.esper.client.EventBean;  
  7. import com.espertech.esper.client.UpdateListener;  
  8.   
  9. class Apple  
  10. {  
  11.     private int price;  
  12.   
  13.     public int getPrice()  
  14.     {  
  15.         return price;  
  16.     }  
  17.   
  18.     public void setPrice(int price)  
  19.     {  
  20.         this.price = price;  
  21.     }  
  22. }  
  23.   
  24. class SomeAnnotationListener implements UpdateListener  
  25. {  
  26.     public void update(EventBean[] newEvents, EventBean[] oldEvents)  
  27.     {  
  28.         if (newEvents != null)  
  29.         {  
  30.             EventBean event = newEvents[0];  
  31.             // 當加上註解@EventRepresentation(array=true)時,結果事件類型爲數組而不是Map。  
  32.             // array=false時,也就是默認情況,結果事件類型爲數組是Map。  
  33.             System.out.println("Sum Price: " + event.get("sum(price)") + ", Event Type is " + event.getEventType().getUnderlyingType());  
  34.         }  
  35.     }  
  36. }  
  37.   
  38. public class SomeAnnotationTest  
  39. {  
  40.     public static void main(String[] args) throws InterruptedException  
  41.     {  
  42.         EPServiceProvider epService = EPServiceProviderManager.getDefaultProvider();  
  43.   
  44.         EPAdministrator admin = epService.getEPAdministrator();  
  45.   
  46.         String apple = Apple.class.getName();  
  47.         String epl1 = "@Priority(10)@EventRepresentation(array=true) select sum(price) from " + apple + ".win:length_batch(2)";  
  48.         String epl2 = "@Name(\"EPL2\")select sum(price) from " + apple + ".win:length_batch(2)";  
  49.         String epl3 = "@Drop select sum(price) from " + apple + ".win:length_batch(2)";  
  50.   
  51.         UpdateListener listenenr = new SomeAnnotationListener();  
  52.         EPStatement state1 = admin.createEPL(epl1);  
  53.         state1.addListener(listenenr);  
  54.         EPStatement state2 = admin.createEPL(epl2);  
  55.         state2.addListener(listenenr);  
  56.         System.out.println("epl2's name is " + state2.getName());  
  57.         EPStatement state3 = admin.createEPL(epl3);  
  58.         state3.addListener(listenenr);  
  59.   
  60.         EPRuntime runtime = epService.getEPRuntime();  
  61.   
  62.         Apple a1 = new Apple();  
  63.         a1.setPrice(1);  
  64.         runtime.sendEvent(a1);  
  65.   
  66.         Apple a2 = new Apple();  
  67.         a2.setPrice(2);  
  68.         runtime.sendEvent(a2);  
  69.     }  
  70. }  

執行結果:

[plain] view plaincopy
  1. epl2's name is EPL2  
  2. Sum Price: 3, Event Type is class [Ljava.lang.Object;  
  3. Sum Price: 3, Event Type is interface java.util.Map  
  4. Sum Price: 3, Event Type is interface java.util.Map  
可以發現,@Name和@EventRepresentation都起效果了,但是@Priority和@Drop沒用,那是因爲這兩個是要配置才能生效的。以後講Esper的Configuration的時候會說到。

8.Expression

        Expression類似自定義函數,通常用Lambda表達式來建立的(也有別的方法建立),而Lambda表達式就一個“ => ”符號,表示“gose to”。符號的左邊表示輸入參數,符號右邊表示計算過程,計算結果就是這個表達式的返回值,即Expression的返回值。語法如下:

[plain] view plaincopy
  1. expression expression_name { expression_body }  
expression是關鍵字,expression_name爲expression的名稱(唯一),expression_body是expression的具體內容。

[plain] view plaincopy
  1. expression_body: (input_param [,input_param [,...]]) => expression  
input_param必須爲事件流的別名,注意不是事件流名稱。參數名寫什麼都可以(當然最好不用關鍵字),多個參數用逗號分隔,並用圓括號括起來。同時針對lamdba,舉個例子一起說明下:

[plain] view plaincopy
  1. expression middle { x => (x.max+x.min)/2 } select middle(apple) from Apple as apple  
1. x表示輸入參數,而x.max和x.min都是x代表的事件流的屬性,如果事件流沒這個屬性,expression的定義就是錯誤的。

2. express的定義必須在使用它的句子之前完成。使用時直接寫expression的名字和用圓括號包含要計算的參數即可。再次提醒,expression的參數只能是事件流別名,即apple,別名的定義就如上面那樣,事件流之後跟着as,然後再跟別名。

上面這個句子的執行結果,就是middle的計算結果,各位自己試試吧。

對於多個參數的expression定義,例子如下:

[plain] view plaincopy
  1. expression sumage { (x,y) => x.age+y.age } select sumage(me,you) from Me as me, You as you  
要是兩個age的數據類型不一樣是什麼結果呢?還是請各位自己試試。

        expression_body除了可以用lambda表達式之外,還可以用聚合函數,變量,常量,子查詢語句,甚至另一個表達式。子查詢語句在沒有in或者exist關鍵字的情況下,需要圓括號括起來。示例如下:

[plain] view plaincopy
  1. expression newsSubq(md) {  
  2. (select sentiment from NewsEvent.std:unique(symbol) where symbol = md.symbol)  
  3. }  
  4. select newsSubq(mdstream) from MarketDataEvent mdstream  
針對變量和常量的示例如下:

[plain] view plaincopy
  1. expression twoPI { Math.PI * 2} select twoPI() from SampleEvent  

        對於expression裏用另一個expression,EPL不允許在一個句子裏建立兩個expression,所以就出現了Global-Expression。普通的expression只作用於定義它的epl,如上面所有的包含select子句的epl就是如此。Global-Expression的語法如下:

[plain] view plaincopy
  1. create expression expression_name { expression_body }  
和普通的expression相比,就是多了個create,不過他不能和別的子句放在一起,即他是單獨執行的。例如:

[java] view plaincopy
  1. epService.getEPAdministrator().createEPL("create expression avgPrice { x => (x.fist+x.last)/2 }");  

最後再舉個例子說一下某個expression裏用另一個expression。

[java] view plaincopy
  1. // 先定義全局的avgPrice  
  2. create expression avgPrice { x => (x.fist+x.last)/2 }  
  3.   
  4. // bananaPrice Banana事件中包含了first和last屬性,否則將報錯  
  5. expression bananaPrice{ x => avgPrice(x) } select bananaPrice(b) from Banana as b  

expression_body裏的空格只是爲了看着清晰,有沒有空格不會有影響。


對於EPL的語法,今天的內容先說到這,各位消化起來應該還是很快的,如果有不明白的可以找我或者看看文檔。下篇將講解select子句和from子句,敬請期待。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章