元编程

使用元编程设计的程序具有读取、生成、分析或转换其他程序的功能,甚至可以在运行时自行修改代码。

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 可以生成一个函数调用的元代码。高阶函数 unifiedCallmakeUnifiedCall 的区别是,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

下面的例子创建了一个自定义函数,给定表、列名和每个列的格式字符串,就能生成报表。sqlColsqlColAliassqlmakeCall 函数可以用于生成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