元编程
使用元编程设计的程序具有读取、生成、分析或转换其他程序的功能,甚至可以在运行时自行修改代码。
DolphinDB支持生成动态表达式和延迟执行的元编程。通过元编程,用户可以生成SQL语句,并动态执行。
元代码是由"<"和">"包围的对象或表达式。
相关函数
-
函数 expr 从对象,运算符或其他元代码生成元代码。
语法: expr(X1, X2, ......) ,其中X可以是对象,运算符或其他元代码。
expr(6,<,8); // output < 6 < 8 > expr(sum, 1 2 3); // output < sum [1,2,3] > a=6; expr(a,+,1); // output < 6 + 1 > expr(<a>,+,1); // output < a + 1 > expr(<a>,+,<b>); // output < a + b > expr(a+7,*,8); // output < 13 * 8 > expr(<a+7>,*,8); // output < (a + 7) * 8 > expr(not, < a >); // output < ! a >
-
函数 eval 执行给定的元代码。
语法: eval(<X>) ,其中X是被执行的元代码。
eval(<1+2>); // output 3 eval(<1+2+3=10>); // output 0 eval(expr(6,<,8)); // output 1 eval(expr(sum, 1 2 3)); // output 6 a=6; b=9; eval(expr(<a>,+,<b>)); // output 15
-
函数 sqlCol 将列名转换为表达式。
语法:sqlCol(colNames),其中colNames可以是列名标量或向量。每一个列名都要用反引号、双引号或单引号。
sqlCol(`PRC); // output < PRC > sqlCol(["PRC", "RET", "DATE", "TICKER"]); // output (< PRC >,< RET >,< DATE >,< TICKER >)
-
函数 sqlColAlias 使用元代码和一个别名(可选参数)来定义一个列。计算列会经常使用此函数。
语法: sqlColAlias(<metacode>, [aliasName])
sqlColAlias(<x>, `y); // output < x as y > sqlColAlias(<avg(PRC)>, `avgPRC); // output < avg(PRC) as avgPRC > sqlColAlias(<avg(PRC)>); // output < avg(PRC) as avg_PRC >
-
函数 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); // output < select * from t1 > sql(sqlCol("*"), t1, whereConditions); // output < select * from t1 where symbol == "MSFT",volume > x > sql(select=sqlColAlias(<avg(price)>), from=t1, where=whereConditions, groupBy=sqlCol(`date)); // output < 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)]); // output < 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); // output < 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); // output < 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); // output < 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); // output < 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); // output < select * from t1 where symbol == "MSFT",volume > x order by date desc > sql(select=sqlCol("*"), from=t1, limit=1); // output < select top 1 * from t1 > sql(select=sqlCol("*"), from=t1, groupBy=sqlCol(`symbol), groupFlag=0, limit=1); // output < 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); // output < 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 -
函数 partial 可以创建部分应用。
语法:
partial(func, args...)
partial(add,1)(2); // output 3 def f(a,b):a pow b g=partial(f, 2) g(3); // output 8
-
函数 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", "#,###"]); // output < 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 |
-
函数 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; // output (< 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 >)
-
函数 unifiedExpr 可以生成一个多元运算表达式的元代码。
语法: unifiedExpr(objs, optrs)
以下例子在binaryExpr例子的基础上,生成一个sql的select语句代码, 用于对每列的值加权求和:
selects = sqlColAlias(unifiedExpr(binaryExpr(sqlCol(names), weights, *), take(+, n-1)), "weightedSum"); re = sql(selects, t); re; // output < 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 -
函数 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; // output < 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 |