元编程
使用元编程设计的程序具有读取、生成、分析或转换其他程序的功能,甚至可以在运行时自行修改代码。
DolphinDB支持生成动态表达式和延迟执行的元编程。通过元编程,用户可以生成SQL语句,并动态执行。
元代码是由”<”和”>”包围的对象或表达式。
相关函数
1. 函数 expr 从对象,运算符或其他元代码生成元代码。
语法: expr(X1, X2, ……) ,其中X可以是对象,运算符或其他元代码。
$ expr(6,<,8);
< 6 < 8 >
$ expr(sum, 1 2 3);
< sum [1,2,3] >
$ a=6;
$ expr(a,+,1);
< 6 + 1 >
$ expr(<a>,+,1);
< a + 1 >
$ expr(<a>,+,<b>);
< a + b >
$ expr(a+7,*,8);
< 13 * 8 >
$ expr(<a+7>,*,8);
< (a + 7) * 8 >
$ expr(not, < a >);
< ! a >
2. 函数 eval 执行给定的元代码。
语法: eval(<X>) ,其中X是被执行的元代码。
$ eval(<1+2>);
3
$ eval(<1+2+3=10>);
0
$ eval(expr(6,<,8));
1
$ eval(expr(sum, 1 2 3));
6
$ a=6; b=9;
$ eval(expr(<a>,+,<b>));
15
3. 函数 sqlCol 将列名转换为表达式。
语法:sqlCol(colNames),其中colNames可以是列名标量或向量。每一个列名都要用反引号、双引号或单引号。
$ sqlCol(`PRC);
< PRC >
$ sqlCol(["PRC", "RET", "DATE", "TICKER"]);
(< PRC >,< RET >,< DATE >,< TICKER >)
4. 函数 sqlColAlias 使用元代码和一个别名(可选参数)来定义一个列。计算列会经常使用此函数。
语法: sqlColAlias(<metacode>, [aliasName])
$ sqlColAlias(<x>, `y);
< x as y >
$ sqlColAlias(<avg(PRC)>, `avgPRC);
< avg(PRC) as avgPRC >
$ sqlColAlias(<avg(PRC)>);
< avg(PRC) as avg_PRC >
5. 函数 sql 可以动态生成SQL语句。
语法: sql(select, from, [where], [groupBy], [groupFlag], [csort], [ascSort], [having], [orderBy], [ascOrder], [limit], [hint])
$ symbol = take(`GE,6) join take(`MSFT,6) join take(`F,6)
$ date=take(take(2017.01.03,2) join take(2017.01.04,4), 18)
$ price=31.82 31.69 31.92 31.8 31.75 31.76 63.12 62.58 63.12 62.77 61.86 62.3 12.46 12.59 13.24 13.41 13.36 13.17
$ volume=2300 3500 3700 2100 1200 4600 1800 3800 6400 4200 2300 6800 4200 5600 8900 2300 6300 9600
$ t1 = table(symbol, date, price, volume);
$ t1;
symbol |
date |
price |
volume |
---|---|---|---|
GE |
2017.01.03 |
31.82 |
2300 |
GE |
2017.01.03 |
31.69 |
3500 |
GE |
2017.01.04 |
31.92 |
3700 |
GE |
2017.01.04 |
31.8 |
2100 |
GE |
2017.01.04 |
31.75 |
1200 |
GE |
2017.01.04 |
31.76 |
4600 |
MSFT |
2017.01.03 |
63.12 |
1800 |
MSFT |
2017.01.03 |
62.58 |
3800 |
MSFT |
2017.01.04 |
63.12 |
6400 |
MSFT |
2017.01.04 |
62.77 |
4200 |
MSFT |
2017.01.04 |
61.86 |
2300 |
MSFT |
2017.01.04 |
62.3 |
6800 |
F |
2017.01.03 |
12.46 |
4200 |
F |
2017.01.03 |
12.59 |
5600 |
F |
2017.01.04 |
13.24 |
8900 |
F |
2017.01.04 |
13.41 |
2300 |
F |
2017.01.04 |
13.36 |
6300 |
F |
2017.01.04 |
13.17 |
9600 |
$ x=5000
$ whereConditions = [<symbol=`MSFT>,<volume>x>]
$ havingCondition = <sum(volume)>200>;
$ sql(sqlCol("*"), t1);
< select * from t1 >
$ sql(sqlCol("*"), t1, whereConditions);
< select * from t1 where symbol == "MSFT",volume > x >
$ sql(select=sqlColAlias(<avg(price)>), from=t1, where=whereConditions, groupBy=sqlCol(`date));
< select avg(price) as avg_price from t1 where symbol == "MSFT",volume > x group by date >
$ sql(select=sqlColAlias(<avg(price)>), from=t1, groupBy=[sqlCol(`date),sqlCol(`symbol)]);
< select avg(price) as avg_price from t1 group by date,symbol >
$ sql(select=(sqlCol(`symbol),sqlCol(`date),sqlColAlias(<cumsum(volume)>, `cumVol)), from=t1, groupBy=sqlCol(`date`symbol), groupFlag=0);
< select symbol,date,cumsum(volume) as cumVol from t1 context by date,symbol >
$ sql(select=(sqlCol(`symbol),sqlCol(`date),sqlColAlias(<cumsum(volume)>, `cumVol)), from=t1, where=whereConditions, groupBy=sqlCol(`date), groupFlag=0);
< select symbol,date,cumsum(volume) as cumVol from t1 where symbol == "MSFT",volume > x context by date >
$ sql(select=(sqlCol(`symbol),sqlCol(`date),sqlColAlias(<cumsum(volume)>, `cumVol)), from=t1, where=whereConditions, groupBy=sqlCol(`date), groupFlag=0, csort=sqlCol(`volume), ascSort=0);
< select symbol,date,cumsum(volume) as cumVol from t1 where symbol == "MSFT",volume > x context by date csort volume desc >
$ sql(select=(sqlCol(`symbol),sqlCol(`date),sqlColAlias(<cumsum(volume)>, `cumVol)), from=t1, where=whereConditions, groupBy=sqlCol(`date), groupFlag=0, having=havingCondition);
< select symbol,date,cumsum(volume) as cumVol from t1 where symbol == "MSFT",volume > x context by date having sum(volume) > 200 >
$ sql(select=sqlCol("*"), from=t1, where=whereConditions, orderBy=sqlCol(`date), ascOrder=0);
< select * from t1 where symbol == "MSFT",volume > x order by date desc >
$ sql(select=sqlCol("*"), from=t1, limit=1);
< select top 1 * from t1 >
$ sql(select=sqlCol("*"), from=t1, groupBy=sqlCol(`symbol), groupFlag=0, limit=1);
< select top 1 * from t1 context by symbol >
$ sql(select=(sqlCol(`symbol),sqlCol(`date),sqlColAlias(<cumsum(volume)>, `cumVol)), from=t1, groupBy=sqlCol(`date`symbol), groupFlag=0, hint=HINT_KEEPORDER);
< select [128] symbol,date,cumsum(volume) as cumVol from t1 context by date,symbol >
可以定义一个函数使用 sql 函数来动态生成SQL语句。
$ def f1(t, sym, x){
$ whereConditions=[<symbol=sym>,<volume>x>]
$ return sql(sqlCol("*"),t,whereConditions).eval()
$ };
$ f1(t1, `MSFT, 5000);
symbol |
date |
price |
volume |
---|---|---|---|
MSFT |
2017.01.04 |
63.12 |
6400 |
MSFT |
2017.01.04 |
62.3 |
6800 |
$ f1(t1, `F, 9000);
symbol |
date |
price |
volume |
---|---|---|---|
F |
2017.01.04 |
13.17 |
9600 |
$ def f2(t, sym, colNames, filterColumn, filterValue){
$ whereConditions=[<symbol=sym>,expr(sqlCol(filterColumn),>,filterValue)]
$ return sql(sqlCol(colNames),t,whereConditions).eval()
$ };
$ f2(t1,`GE, `symbol`date`volume, `volume, 3000);
symbol |
date |
volume |
---|---|---|
GE |
2017.01.03 |
3500 |
GE |
2017.01.04 |
3700 |
GE |
2017.01.04 |
4600 |
$ f2(t1,`F, `symbol`date`volume,`price,13.2);
symbol |
date |
volume |
---|---|---|
F |
2017.01.04 |
8900 |
F |
2017.01.04 |
2300 |
F |
2017.01.04 |
6300 |
6. 函数 partial 可以创建部分应用。
语法:partial(func, args…)
$ partial(add,1)(2);
3
$ def f(a,b):a pow b
$ g=partial(f, 2)
$ g(3);
8
7. 函数 makeCall 使用指定参数调用函数并生成脚本。与高阶函数 call 的区别是 makeCall 不执行脚本。
在下列例子中,我们创建了 generateReport 函数。它能够以指定格式显示表的某些列。
$ def generateReport(tbl, colNames, colFormat): sql(sqlColAlias(each(makeCall{format},sqlCol(colNames),colFormat),colNames), tbl).eval()
$ t = table(1..100 as id, (1..100 + 2018.01.01) as date, rand(100.0, 100) as price, rand(10000, 100) as qty, rand(`A`B`C`D`E`F`G, 100) as city);
$ t;
id |
date |
price |
qty |
city |
---|---|---|---|---|
1 |
2018.01.02 |
99.662328 |
6002 |
D |
2 |
2018.01.03 |
24.965461 |
5836 |
B |
3 |
2018.01.04 |
77.006418 |
5 |
G |
4 |
2018.01.05 |
93.930603 |
5141 |
G |
5 |
2018.01.06 |
70.994914 |
5778 |
C |
6 |
2018.01.07 |
11.221769 |
9403 |
G |
7 |
2018.01.08 |
59.387621 |
4632 |
G |
8 |
2018.01.09 |
86.830752 |
4574 |
C |
9 |
2018.01.10 |
53.928317 |
8397 |
G |
10 |
2018.01.11 |
21.123212 |
6615 |
B |
… |
$ generateReport(t, ["id", "date","price","qty"], ["0", "MM/dd/yyyy", "0.00", "#,###"]);
id |
date |
price |
qty |
---|---|---|---|
1 |
01/02/2018 |
16.32 |
9,972 |
2 |
01/03/2018 |
63.10 |
7,785 |
3 |
01/04/2018 |
30.43 |
3,629 |
4 |
01/05/2018 |
33.57 |
2,178 |
5 |
01/06/2018 |
61.12 |
9,406 |
6 |
01/07/2018 |
43.29 |
6,955 |
7 |
01/08/2018 |
54.97 |
9,914 |
8 |
01/09/2018 |
86.20 |
6,696 |
9 |
01/10/2018 |
90.82 |
6,141 |
10 |
01/11/2018 |
4.29 |
3,774 |
… |
与之等效的SQL脚本是
$ def generateReportSQL(tbl, colNames, colFormat): sql(sqlColAlias(each(makeCall{format},sqlCol(colNames),colFormat),colNames), tbl)
$ generateReportSQL(t, ["id", "date","price","qty"], ["0", "MM/dd/yyyy", "0.00", "#,###"]);
< select format(id, "0") as id,format(date, "MM/dd/yyyy") as date,format(price, "0.00") as price,format(qty, "#,###") as qty from t >
以下3个示例使用了3种不同的方法来创建一个生成SQL语句的函数:
例1:
$ def f1(t, sym, x): select * from t where name=sym, vol>x;
$ f1(t1, `MSFT, 7000);
name |
date |
PRC |
vol |
---|---|---|---|
MSFT |
2017.01.03 |
63.12 |
8800 |
MSFT |
2017.01.03 |
62.58 |
7800 |
我们也可以使用函数 eval 和一个元代码块创建这个函数。
例2:
$ def f2(t, sym, x): eval(<select * from t where name=`MSFT, vol>x >);
$ f2(t1,`MSFT, 7000);
name |
date |
PRC |
vol |
---|---|---|---|
MSFT |
2017.01.03 |
63.12 |
8800 |
MSFT |
2017.01.03 |
62.58 |
7800 |
我们还可以定义一个函数使用 sql 函数来生成SQL语句。当调用该函数时,代码中的sql函数动态地生成SQL语句并执行。对于生成更加复杂的查询,这种方法更加方便灵活。
例3:
$ def f3(t, sym, x){
$ whereConditions=[<name=sym>,<vol>x>]
$ return sql(sqlCol("*"),t,whereConditions).eval()
$ };
$ f3(t1, `MSFT, 7000);
name |
date |
PRC |
vol |
---|---|---|---|
MSFT |
2017.01.03 |
63.12 |
8800 |
MSFT |
2017.01.03 |
62.58 |
7800 |
$ f3(t1, `F, 9000);
name |
date |
PRC |
vol |
---|---|---|---|
F |
2017.01.04 |
13.17 |
在这3种方法中,函数 sql 最灵活。它可以动态地生成SQL语句。
$ def f4(t, sym, colNames, filterColumn, filterValue){
$ whereConditions=[<name=sym>,expr(sqlCol(filterColumn),>,filterValue)]
$ return sql(sqlCol(colNames),t,whereConditions).eval()
$ };
$ f4(t1,`MSFT, `name`date`vol, `vol, 7000);
name |
date |
vol |
---|---|---|
MSFT |
2017.01.03 |
8800 |
MSFT |
2017.01.03 |
7800 |
$ f4(t1,`F, `name`date`vol,`PRC,13.2);
name |
date |
vol |
---|---|---|
F |
2017.01.04 |
8900 |
F |
2017.01.04 |
2300 |
F |
2017.01.04 |
6300 |
8. 函数 binaryExpr 可以生成一个二元运算的元代码。
语法:binaryExpr(X, Y, optr)
$ t = table(reshape(rand(1.0, 200), 20:10));
$ n=10;
$ weights = array(ANY, n).fill!(0..(n-1), 1..n)\n;
$ names = t.colNames();
$ tp = binaryExpr(sqlCol(names), weights, *);
$ tp;
(< col0 * 0.1 >,< col1 * 0.2 >,< col2 * 0.3 >,< col3 * 0.4 >,< col4 * 0.5 >,< col5 * 0.6 >,< col6 * 0.7 >,< col7 * 0.8 >,< col8 * 0.9 >,< col9 * 1 >)
9. 函数 unifiedExpr 可以生成一个多元运算表达式的元代码。
语法: unifiedExpr(objs, optrs)
以下例子在binaryExpr例子的基础上,生成一个sql的select语句代码, 用于对每列的值加权求和:
$ selects = sqlColAlias(unifiedExpr(binaryExpr(sqlCol(names), weights, *), take(+, n-1)), "weightedSum");
$ re = sql(selects, t);
$ re;
< select (col0 * 0.1) + (col1 * 0.2) + (col2 * 0.3) + (col3 * 0.4) + (col4 * 0.5) + (col5 * 0.6) + (col6 * 0.7) + (col7 * 0.8) + (col8 * 0.9) + (col9 * 1) as weightedSum from tc0ccbd6a00000000 >
$ re.eval()
weightedSum |
---|
2.1239 |
3.5725 |
3.5009 |
3.3487 |
2.8268 |
1.9753 |
2.4287 |
2.9222 |
3.0469 |
2.9751 |
2.8848 |
2.993 |
2.7185 |
2.2578 |
2.6231 |
2.3871 |
3.0311 |
2.0183 |
2.897 |
2.5982 |
10. 函数 makeUnifiedCall 可以生成一个函数调用的元代码。与高阶函数 unifiedCall 与 makeUnifiedCall 的区别是,makeUnifiedCall 不执行脚本。
语法: makeUnifiedCall(func, args)
以下例子生成一个和9中例子等价的元代码:
$ selects = sqlColAlias(makeCall(flatten, makeCall(dot, makeUnifiedCall(matrix, sqlCol(names)), 1..n\n)), "weightedSum");
$ re = sql(selects, t);
$ re;
< select flatten(dot(matrix(col0, col1, col2, col3, col4, col5, col6, col7, col8, col9), [0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1])) as weightedSum from tc0ccbd6a00000000 >
$ re.eval();
weightedSum |
---|
2.1239 |
3.5725 |
3.5009 |
3.3487 |
2.8268 |
1.9753 |
2.4287 |
2.9222 |
3.0469 |
2.9751 |
2.8848 |
2.993 |
2.7185 |
2.2578 |
2.6231 |
2.3871 |
3.0311 |
2.0183 |
2.897 |
2.5982 |
使用元编程生成报表
我们可以使用函数 format 函数生成报表。format函数有两个参数:列名和对应的格式字符串。格式字符串与Java中的数字和日期格式字符串相似。我们使用6种符号(0/#/,/./%/E)来表示数字的格式,使用9种符号(y/M/d/H/h/m/s/S/n)表示时序数据的格式。例如:
输入数据 |
格式字符串 |
输出数据 |
---|---|---|
2012.12.05T15:30:59.125 |
yyyy-MM-dd HH:mm |
2012-12-05 15:30 |
2012.12.05 |
MMMddyyyy |
DEC052012 |
0.1356 |
0.0% |
13.6% |
1234567.42 |
#,###.0 |
1,234,567.4 |
1234567.42 |
0.00####E0 |
1.234567E6 |
下面的例子创建了一个自定义函数,给定表、列名和每个列的格式字符串,就能生成报表。sqlCol、sqlColAlias、sql 和 makeCall 函数可以用于生成SQL语句,调用 eval 函数来生成报表,makeCall 函数调用脚本并生成一系列元代码,makeCall 函数的第一个参数是函数,其他参数是函数所需的参数。
$ def generateReport(tbl, colNames, colFormat){
$ colCount = colNames.size()
$ colDefs = array(ANY, colCount)
$ for(i in 0:colCount){
$ if(colFormat[i] == "")
$ colDefs[i] = sqlCol(colNames[i])
$ else
$ colDefs[i] = sqlCol(colNames[i], format{,colFormat[i]})
$ }
$ return sql(colDefs, tbl).eval()
$ }
生成100行4列(列名为id, date, price和qty)的样本表,随后调用generateReport函数生成报表。
$ t = table(1..100 as id, (1..100 + 2018.01.01) as date, rand(100.0, 100) as price, rand(10000, 100) as qty);
$ t;
id |
date |
price |
qty |
---|---|---|---|
1 |
2018.01.02 |
61.483376 |
8733 |
2 |
2018.01.03 |
21.254616 |
8365 |
3 |
2018.01.04 |
37.408301 |
444 |
4 |
2018.01.05 |
70.608384 |
9944 |
5 |
2018.01.06 |
80.361811 |
7185 |
… |
… |
… |
… |
$ generateReport(t, ["id", "date","price","qty"], ["0", "MM/dd/yyyy", "0.00", "#,###"]);
id |
date |
price |
qty |
---|---|---|---|
1 |
01/02/2018 |
61.48 |
8,733 |
2 |
01/03/2018 |
21.25 |
8,365 |
3 |
01/04/2018 |
37.41 |
444 |
4 |
01/05/2018 |
70.61 |
9,944 |
5 |
01/06/2018 |
80.36 |
7,185 |
… |
… |
… |
… |
也可使用元编程生成过滤条件。
$ def generateReportWithFilter(tbl, colNames, colFormat, filter){
$ colCount = colNames.size()
$ colDefs = array(ANY, colCount)
$ for(i in 0:colCount){
$ if(colFormat[i] == "")
$ colDefs[i] = sqlCol(colNames[i])
$ else
$ colDefs[i] = sqlCol(colNames[i], format{,colFormat[i]})
$ }
$ return sql(colDefs, tbl, filter).eval()
$ }
$ generateReportWithFilter(t, ["id","date","price","qty"], ["","MM/dd/yyyy", "00.00", "#,###"], < id>10 and price<20 >);
id |
date |
price |
qty |
---|---|---|---|
11 |
01/12/2018 |
11.21 |
7,033 |
18 |
01/19/2018 |
09.97 |
6,136 |
29 |
01/30/2018 |
06.33 |
3,834 |
31 |
02/01/2018 |
05.52 |
6,851 |
38 |
02/08/2018 |
14.55 |
7,482 |