SPL 的宏

除了常见的静态代码外,有时候也需要用动态代码解决问题,比如根据参数生成代码(或一部分)并动态执行。对于缺乏动态代码机制的程序语言,通常要将代码的可变部分写成字符串形式,比如Python中的引用数据集字段名时都要写成字符串,这样才能实现动态代码的效果,但会导致更常见的静态代码的阅读和书写都不方便。而SQL则可以直接在代码中书写字段名(以及过滤条件、分组表达式等),不必写进字符串中,这样更方便阅读和书写静态代码,但却很难处理动态代码。

SPL在静态代码中延用SQL的风格,可以直接书写代码的部分,比如字段名就直接写在语句中,不必写成字符串。SPL另外提供了宏来实现动态代码的效果。

例 1:根据参数 pSortList 对订单表进行动态排序,pSortList 含有数量不定的排序字段并以逗号分隔

用 SPL 的宏可以实现这种动态代码:T("Orders.txt").sort(${pSortList})

格式:${返回字段串的计算表达式,可以用变量或常量}

SPL 引擎在解析语句时,先检查是否含有宏,如果有宏,则将宏内的表达式计算出来拼到原语句中,形成一个新的语句再来执行。

更多例子

例 2:根据参数 pFields 里的多个维度字段(及计算列),对事实表 OrdersFact.csv 进行动态分组,并对 Amount 字段进行求和。pFields 形如"year(OrderDate),Product,Client"

SPL 代码:T("d:/OrdersFact.csv").groups(${pFields};sum(Amount))

例 3:根据参数 pConditon 里的条件,对订单表 Orders.xls 进行动态查询,选出符合条件的订单记录。pCondition 形如:year(OrderDate)==2009 && Amount>1000 && Amount<3000

SPL 代码:T("d:/Orders.xls").select(${pCondition})

例 4:先根据起止日期参数过滤事实表 OrdersFact.csv,再根据维度参数 pGroup 进行动态分组,按月份横向转置并统计每个月的金额。如果 pGroup="Product",计算结果应当像下面:

1png

如果 pGroup="Area",计算结果应当像下面:

2png

SPL 代码:


A

1

=T("d:/OrdersFact.csv")

2

=A1.select(OrderDate>=pBegin && OrderDate<=pEnd)

3

=A2.pivot(${pGroup};year(OrderDate)*100+month(OrderDate),Amount)

宏的其他用法

有时候需要重复生成形式类似的代码,虽然难度不高,但枯燥费力,这种情况也适合用宏来解决。

例 5:某文件记录了许多设备各时间点的电压,从第 2 列开始理论上都是浮点值,但实际存在带引号的脏数据,需要把这些脏数据转换成正常的浮点。部分数据:

point

N_2102

N_2104

T1_903

N_2203

00:00:01

"221.4"

220.3

224.5

221.0

00:00:02

218.0

217.5

229.8

233.1

00:00:03

197.4

219.8

220.0

224.4

00:00:04

207.1

201.8

230.3

207.3

00:00:05

201.5

"227.0"

224.5

203.4

去掉某列的引号不难,比如第 2 列:A.run(flaot(#2):#2)。但如果字段较多,就需要写大量重复代码:A.run(float(#2):#2, float(#3):#3...),这就比较费力了。宏可以方便地生成重复代码,实现起来轻松许多。


A

B

1

=file("d:/2020-02-01.txt").import@tq()

读文件,@q去掉引号

2

=to(2,A1.fname().len())

第 2 至第 N 个字段的序号列表

3

=A2.("float(#"/ ~/ "):#"/ ~).concat@c()

拼成串 float(#2):#2, float(#3):#3...

4

=A1.run(${A3})

批量转换字段

5

=file("d:/2020-02-01.txt").export@t(A1)

写文件

注意事项

每个语句中出现的宏,只会在执行到这个语句时被解析替换一次,如果宏表达式在语句执行过程中还在变化,则不会再重新解析替换了,这和语句中的变量不同。

例 6:用另一种方法实现例 5,即先用大循环遍历字段名,再用小循环对每个字段分别清洗一次。


A

B

1

=file("d:/2020-02-01.txt").import@tq()


2

=A1.fname().to(2,)

字段名列表

3

=A2.run(A1.run( float(${A2.~}):${A2.~}))


4

=file("d:/2020-02-01.txt").export@t(A1)


上面 A3 里的代码是错的,因为 A2.~ 只会被解析一次,只有第 1 个字段会被转换。

把 A3 换成下面这样就对了:


A

B

3

for A2

=A1.run(float(${A3}):${A3})

虽然还是在循环里,但 B3 会被多次执行到,每次都会重新解析一遍里面的宏。

如果要写成一句,并且循环中的宏表达式每次都被解析,可以用 eval 函数,把 A3 写成:A2.run(eval("A1.run(float(" / A2.~ / "):" / A2.~ / ")"))

eval 函数的性能不如宏,要谨慎使用。