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类似,但是更加灵活。。。还有很多东西,我再列举估计大家都要晕了,就先说说语法吧。
[annotations] [expression_declarations] [context context_name] [insert into insert_into_def] select select_list from stream_def [as name] [, stream_def [as name]] [,...] [where search_conditions] [group by grouping_expression_list] [having grouping_search_conditions] [output output_specification] [order by order_by_expression_list] [limit num_rows]
基本上大部分的EPL都是按照这个格式来定义。看过之前几篇的同学应该对里面的某些内容熟悉,比如context context_name,select select_list from stream_def等等。其他的可以先不关心,后面会有详解。比如wher,group by都会有专门的篇幅进行描述。
2.Time Periods
这部分的内容说的是Esper中时间的表达形式。语法如下:
time-period : [year-part] [month-part] [week-part] [day-part] [hour-part] [minute-part] [seconds-part] [milliseconds-part] year-part : (number|variable_name) ("years" | "year") month-part : (number|variable_name) ("months" | "month") week-part : (number|variable_name) ("weeks" | "week") day-part : (number|variable_name) ("days" | "day") hour-part : (number|variable_name) ("hours" | "hour") minute-part : (number|variable_name) ("minutes" | "minute" | "min") seconds-part : (number|variable_name) ("seconds" | "second" | "sec") milliseconds-part : (number|variable_name) ("milliseconds" | "millisecond" | "msec")
与时间相关的EPL基本都会用到上面列出的东西,举几个例子说明下:
// 计算过去的5分3秒中进入改语句的Fruit事件的平均price select avg(price) from Fruit.win:time(5 minute 3 sec) // 每一天输出一次用户的账户总额 select sum(account) from User output every 1 day
用法比较简单,大家可以多试试。要注意的是,Esper规定每月的天数都是30天,所以对准确性要求高的业务,以月为单位进行计算会出现误差的。
3.Comments
注释基本上和Java差不多,只不过他没有/** */和/* */之分,只有/* */和//,毕竟不需要生成文档,所以就没那个必要了。//只能单行注释,而/* */可以多行注释。示例如下:
a.单行注释 // This comment extends to the end of the line. // Two forward slashes with no whitespace between them begin such comments. select * from MyEvent b.多行注释 select * from OtherEvent /* this is a very *important Event */ c.混合注释 select field1 // first comment /* second comment */ field2 from MyEvent
4.Reserved Keywords
EPL里如果某个事件属性,或者事件流的名称和EPL的关键字一样,那么必须要以`括起来才可用,`在键盘上esc的下面,1的左边,叫什么忘记了,估计说出来也有很多人不知道。比如:
// insert和Order是关键字,这个EPL无效 select insert from Order // `insert`和`Order`是属性名称和事件流名称,这个EPL有效 select `insert` from `Order`
5.Escaping Strings 在EPL中,字符串使用单引号或者双引号括起来的,那如果字符串里包含有单引号或者双引号怎么办呢。请看例子:
select * from OrderEvent(name='John') // 等同于 select * from OrderEvent(name="John")
如果name=John's,则需要反斜杠进行转义。
select * from OrderEvent(name="John\"s") // 或者 select * from OrderEvent(name='john\'s')
除了使用反斜杠,还可以使用unicode来表示单引号和双引号。
select * from OrderEvent(name="John\u0022s") // 或者 select * from OrderEvent(name='john\u0027s')
注意在Java编写EPL的时候,反斜杠和无含义的双引号还得转义,不然会和String的双引号冲突。比如:
epService.getEPAdministrator().createEPL("select * from OrderEvent(name='John\\'s')"); // ... and for double quotes... 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函数。完整例子如下:
import com.espertech.esper.client.EPAdministrator; import com.espertech.esper.client.EPRuntime; import com.espertech.esper.client.EPServiceProvider; import com.espertech.esper.client.EPServiceProviderManager; import com.espertech.esper.client.EPStatement; import com.espertech.esper.client.EventBean; import com.espertech.esper.client.UpdateListener; /** * 可用cast函数将其他的数值数据类型转为BigDecimal。 * * */ class Banana { private int price; public int getPrice() { return price; } public void setPrice(int price) { this.price = price; } } class CastDataTypeListener1 implements UpdateListener { public void update(EventBean[] newEvents, EventBean[] oldEvents) { if (newEvents != null) { EventBean event = newEvents[0]; // cast(avg(price), int)中间的空格在EPL中可以不写,但是event.get的时候必须加上,建议用as一个别名来代表转换后的值 System.out.println("Average Price: " + event.get("cast(avg(price), int)") + ", DataType is " + event.get("cast(avg(price), int)").getClass().getName()); } } } class CastDataTypeListener2 implements UpdateListener { public void update(EventBean[] newEvents, EventBean[] oldEvents) { if (newEvents != null) { EventBean event = newEvents[0]; System.out.println("Average Price: " + event.get("avg(price)") + ", DataType is " + event.get("avg(price)").getClass().getName()); } } } public class CastDataTypeTest { public static void main(String[] args) throws InterruptedException { EPServiceProvider epService = EPServiceProviderManager.getDefaultProvider(); EPAdministrator admin = epService.getEPAdministrator(); String banana = Banana.class.getName(); String epl1 = "select cast(avg(price),int) from " + banana + ".win:length_batch(2)"; String epl2 = "select avg(price) from " + banana + ".win:length_batch(2)"; EPStatement state1 = admin.createEPL(epl1); state1.addListener(new CastDataTypeListener1()); EPStatement state2 = admin.createEPL(epl2); state2.addListener(new CastDataTypeListener2()); EPRuntime runtime = epService.getEPRuntime(); Banana b1 = new Banana(); b1.setPrice(1); runtime.sendEvent(b1); Banana b2 = new Banana(); b2.setPrice(2); runtime.sendEvent(b2); } }
执行结果:
Average Price: 1, DataType is java.lang.Integer Average Price: 1.5, DataType is java.lang.Double
要提醒的是,如果某个数除以0,那么默认会返回正无穷大或者负无穷大,不过可以配置这个结果,比如用null来代替。
7.Annotation
EPL也可以写注解,种类不多,大部分简单而有效。不过有些注解内容较多,在这里只是简单介绍,以后会在具体的使用场景进行详细讲解。首先来了解下注解的语法。
// 不包含参数或者单个参数的注解 @annotation_name [(annotation_parameters)] // 包含多个属性名-值对的注解 @annotation_name (attribute_name = attribute_value, [name=value, ...]) // 多个注解联合使用 @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,则数据形式为数组。
针对以上几个简单的注解,例子如下:
import com.espertech.esper.client.EPAdministrator; import com.espertech.esper.client.EPRuntime; import com.espertech.esper.client.EPServiceProvider; import com.espertech.esper.client.EPServiceProviderManager; import com.espertech.esper.client.EPStatement; import com.espertech.esper.client.EventBean; import com.espertech.esper.client.UpdateListener; class Apple { private int price; public int getPrice() { return price; } public void setPrice(int price) { this.price = price; } } class SomeAnnotationListener implements UpdateListener { public void update(EventBean[] newEvents, EventBean[] oldEvents) { if (newEvents != null) { EventBean event = newEvents[0]; // 当加上注解@EventRepresentation(array=true)时,结果事件类型为数组而不是Map。 // array=false时,也就是默认情况,结果事件类型为数组是Map。 System.out.println("Sum Price: " + event.get("sum(price)") + ", Event Type is " + event.getEventType().getUnderlyingType()); } } } public class SomeAnnotationTest { public static void main(String[] args) throws InterruptedException { EPServiceProvider epService = EPServiceProviderManager.getDefaultProvider(); EPAdministrator admin = epService.getEPAdministrator(); String apple = Apple.class.getName(); String epl1 = "@Priority(10)@EventRepresentation(array=true) select sum(price) from " + apple + ".win:length_batch(2)"; String epl2 = "@Name(\"EPL2\")select sum(price) from " + apple + ".win:length_batch(2)"; String epl3 = "@Drop select sum(price) from " + apple + ".win:length_batch(2)"; UpdateListener listenenr = new SomeAnnotationListener(); EPStatement state1 = admin.createEPL(epl1); state1.addListener(listenenr); EPStatement state2 = admin.createEPL(epl2); state2.addListener(listenenr); System.out.println("epl2's name is " + state2.getName()); EPStatement state3 = admin.createEPL(epl3); state3.addListener(listenenr); EPRuntime runtime = epService.getEPRuntime(); Apple a1 = new Apple(); a1.setPrice(1); runtime.sendEvent(a1); Apple a2 = new Apple(); a2.setPrice(2); runtime.sendEvent(a2); } }
执行结果:
epl2's name is EPL2 Sum Price: 3, Event Type is class [Ljava.lang.Object; Sum Price: 3, Event Type is interface java.util.Map Sum Price: 3, Event Type is interface java.util.Map
可以发现,@Name和@EventRepresentation都起效果了,但是@Priority和@Drop没用,那是因为这两个是要配置才能生效的。以后讲Esper的Configuration的时候会说到。
8.Expression
Expression类似自定义函数,通常用Lambda表达式来建立的(也有别的方法建立),而Lambda表达式就一个“ => ”符号,表示“gose to”。符号的左边表示输入参数,符号右边表示计算过程,计算结果就是这个表达式的返回值,即Expression的返回值。语法如下:
expression expression_name { expression_body }
expression是关键字,expression_name为expression的名称(唯一),expression_body是expression的具体内容。
expression_body: (input_param [,input_param [,...]]) => expression
input_param必须为事件流的别名,注意不是事件流名称。参数名写什么都可以(当然最好不用关键字),多个参数用逗号分隔,并用圆括号括起来。同时针对lamdba,举个例子一起说明下:
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定义,例子如下:
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关键字的情况下,需要圆括号括起来。示例如下:
expression newsSubq(md) { (select sentiment from NewsEvent.std:unique(symbol) where symbol = md.symbol) } select newsSubq(mdstream) from MarketDataEvent mdstream
针对变量和常量的示例如下:
expression twoPI { Math.PI * 2} select twoPI() from SampleEvent
对于expression里用另一个expression,EPL不允许在一个句子里建立两个expression,所以就出现了Global- Expression。普通的expression只作用于定义它的epl,如上面所有的包含select子句的epl就是如此。Global- Expression的语法如下:
create expression expression_name { expression_body }
和普通的expression相比,就是多了个create,不过他不能和别的子句放在一起,即他是单独执行的。例如:
epService.getEPAdministrator().createEPL("create expression avgPrice { x => (x.fist+x.last)/2 }");
最后再举个例子说一下某个expression里用另一个expression。
// 先定义全局的avgPrice create expression avgPrice { x => (x.fist+x.last)/2 } // bananaPrice Banana事件中包含了first和last属性,否则将报错 expression bananaPrice{ x => avgPrice(x) } select bananaPrice(b) from Banana as b
expression_body里的空格只是为了看着清晰,有没有空格不会有影响。
对于EPL的语法,今天的内容先说到这,各位消化起来应该还是很快的,如果有不明白的可以找我或者看看文档。下篇将讲解select子句和from子句,敬请期待。
相关推荐
本篇文章将基于《esper语法pdf版(下)》的部分内容展开详细介绍,重点聚焦于API参考章节,特别是第13章的内容。Esper是一种事件处理语言和引擎,用于处理实时数据流中的复杂事件。本文将对Esper的主要接口进行深入...
4. **声明事件模式**: 使用Esper的SQL-like语法`EPL(Esper Programming Language)`来定义事件模式。例如,我们可以定义一个模式,当连续两个交易的股票价格上升时触发通知: ```sql create continuous query ...
EPL语法与SQL相似,但增加了对时间窗口、事件序列和状态变化的支持,这使得它在实时分析和决策制定中非常强大。 例如,一个简单的EPL语句可能是: ```sql select * from Event where price > 100 ``` 这条语句会...
1. `esper-5.2.0.jar`:这是Esper的核心库,包含了Esper的所有核心类和接口,如EPServiceProvider、EPL(Esper的SQL方言)语法等。开发者通过这个JAR文件可以直接在应用程序中使用Esper进行事件处理。 2. `antlr-...
理解 Esper 的核心概念,熟练掌握 EPL 语法,以及合理利用窗口和监听器机制,可以帮助我们构建出高效、智能的实时数据处理系统。在实际应用中,还可以结合其他框架如 Spring,以更方便地集成 Esper 到现有的 Java ...
Esper 的核心功能在于其事件处理语言(Event Processing Language, EPL),这是一种类似 SQL 的查询语言,允许用户定义事件模式并根据这些模式进行实时分析。以下是一些主要的知识点: 1. **EPL 语言**:EPL 提供了...
Esper的运行依赖于一些第三方库,如ANTLR用于解析模式和EPL语法,CGLIB用于代码生成,Apache Commons Logging用于日志处理,而在构建和测试阶段还需要JUnit单元测试框架和MySQL连接库。此外,Esper允许在应用启动前...
开发和部署EsperDist时,开发者需要掌握Java编程、Esper API、EPL语法以及分布式系统原理,如Apache ZooKeeper(用于协调集群状态)、Hadoop或Kafka(可能作为数据输入/输出接口)。同时,了解如何优化Esper的性能,...
- **事件流**:一组连续的事件序列。 - **复杂事件处理**:通过分析多个简单事件来识别复杂的业务情景。 - **应用场景**: - **金融交易监控**:实时监控市场数据,识别异常交易行为。 - **网络监控**:监测...
EPL 是 Esper 语言,Esper 是一个流行的企业级复杂事件处理引擎,它允许开发者定义对实时数据流中的模式进行监听和响应。EPL 的语法类似于 SQL,但针对时间序列和事件数据进行了优化,可以用于实时监控、报警、交易...