Productos secos 丨 Tutorial de metaprogramación DolphinDB

La metaprogramación se refiere al uso de códigos de programa para generar códigos de programa que se pueden ejecutar de forma dinámica. El propósito de la metaprogramación es generalmente retrasar la ejecución del código o crear código dinámicamente.

1. DolphinDB implementa el método de metaprogramación

DolphinDB admite el uso de metaprogramación para crear expresiones dinámicamente, incluidas expresiones de llamada de función, expresiones de consulta SQL, etc. DolphinDB tiene dos formas de implementar la metaprogramación:

(1) Utilice un par de corchetes angulares <> para indicar el código dinámico que debe retrasarse. P.ej,

a = <1 + 2 * 3>
typestr(a);
CODE
//a是元代码,它的数据类型是CODE

eval(a);
7
//eval函数用于执行元代码

(2) Utilice funciones para crear varias expresiones. Funciones de programación de uso general incluyen meta exprparseExprpartialsqlColsqlColAliassqlevalmakeCall. Aquí el uso de estas funciones.

  • La función expr genera metacódigo basado en el objeto de entrada, operador u otro metacódigo. P.ej:
a = expr(1, +, 2, *, 3)
a.typestr();
CODE

a;
< 1 + 2 * 3 >
parseExpr("1+2")

< 1 + 2 >
  • La función parcial puede fijar parte de los parámetros de una función y generar una función con menos parámetros. P.ej:
partial(add,1)(2)
3

def f(a,b):a pow b
g=partial(f, 2)
g(3)

8
  • Las funciones sqlColsqlColAlias y sql se utilizan para generar dinámicamente expresiones SQL. sqlColLas funciones pueden convertir nombres de columnas en expresiones y, a sqlColAliasmenudo, se utilizan para generar metacódigos para columnas calculadas. Las sqlfunciones pueden generar sentencias SQL de forma dinámica.
sym = 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)
PRC=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
vol=2300 3500 3700 2100 1200 4600 8800 7800 6400 4200 2300 6800 4200 5600 8900 2300 6300 9600
t1 = table(sym, date, PRC, vol);

sql(sqlCol("*"),t1)
< select * from t1 >

sql(sqlCol("*"),t1,[<sym="MSFT">,<PRC>=5000>])
< select * from t1 where sym == "MSFT",PRC >= 5000 >

sql(sqlColAlias(<avg(vol)>,"avg_vol"),t1,<sym="MSFT">,sqlCol("date"))
< select avg(vol) as avg_vol from t1 where sym == "MSFT" group by date >

sql(sqlColAlias(<avg(vol)>,"avg_vol"),t1,<sym="MSFT">,sqlCol("date"),,,,<avg(vol)>3000>)
< select avg(vol) as avg_vol from t1 where sym == "MSFT" group by date having avg(vol) > 3000 >

sql(sqlColAlias(<avg(vol)>,"avg_vol"),t1,<sym="MSFT">,sqlCol("date"),0)
< select avg(vol) as avg_vol from t1 where sym == "MSFT" context by date >

sql(sqlColAlias(<avg(vol)>,"avg_vol"),t1,<sym="MSFT">,sqlCol("date"),0,sqlCol("avg_vol"),0)
< select avg(vol) as avg_vol from t1 where sym == "MSFT" context by date csort avg_vol desc >

sql(sqlCol("*"),t1,,,,,,,sqlCol(`vol),0,5)
< select top 5 * from t1 order by vol desc >
  • La función eval puede ejecutar metacódigo. P.ej:
a = <1 + 2 * 3>
eval(a);
7

sql(sqlColAlias(<avg(vol)>,"avg_vol"),t1,,sqlCol(["sym","date"])).eval();
sym  date       avg_vol
---- ---------- -------
F    2017.01.03 4900   
F    2017.01.04 6775   
GE   2017.01.03 2900   
GE   2017.01.04 2900   
MSFT 2017.01.03 8300   
MSFT 2017.01.04 4925   
//这里使用的t1是第(2)部分的t1
  • La función makeCall puede generar metacódigo de acuerdo con la función especificada y los parámetros de entrada. Por ejemplo, al consultar la tabla t1, genere la columna de fecha como una cadena y muéstrela en un formato similar al 01/03/2017.
sql([sqlColAlias(makeCall(temporalFormat,sqlCol(`date),"dd/MM/yyyy"),"date"),sqlCol(`sym),sqlCol(`PRC),sqlCol(`vol)],t1)
< select temporalFormat(date, "dd/MM/yyyy") as date,sym,PRC,vol from t1 >

2. Aplicación de metaprogramación DolphinDB

2.1 Actualizar tabla de memoria de particiones

Las operaciones como la actualización y eliminación de la tabla de memoria de partición se pueden completar no solo a través de declaraciones SQL, sino también a través de la metaprogramación. Cree una tabla de memoria de partición:

n=1000000
sym=rand(`IBM`MSFT`GOOG`FB`IBM`MSFT,n)
date=rand(2018.01.02 2018.01.02 2018.01.02 2018.01.03 2018.01.03 2018.01.03,n)
price=rand(1000.0,n)
qty=rand(10000,n)
t=table(sym,date,price,qty)

db=database("",VALUE,`IBM`MSFT`GOOG`FB`IBM`MSFT)
trades=db.createPartitionedTable(t,`trades,`sym).append!(t)

2.1.1 Actualizar datos

Por ejemplo, actualice el número de transacciones con el código de acciones IBM:

trades[`qty,<sym=`IBM>]=<qty+100>
//等价于update trades set qty=qty+100 where sym=`IBM

2.1.2 Agregar una nueva fila

Por ejemplo, agregue un nuevo volumen de columna para almacenar el volumen de transacciones:

trades[`volume]=<price*qty>
//等价于update trades set volume=price*qty

2.1.3 Eliminar datos

Por ejemplo, elimine los datos cuya cantidad sea 0:

trades.erase!(<qty=0>)
//等价于delete from trades where qty=0

2.1.4 Genere dinámicamente condiciones de filtro y actualice datos

Este ejemplo utiliza la siguiente tabla de datos.

ind1=rand(100,10)
ind2=rand(100,10)
ind3=rand(100,10)
ind4=rand(100,10)
ind5=rand(100,10)
ind6=rand(100,10)
ind7=rand(100,10)
ind8=rand(100,10)
ind9=rand(100,10)
ind10=rand(100,10)
indNum=1..10
t=table(ind1,ind2,ind3,ind4,ind5,ind6,ind7,ind8,ind9,ind10,indNum)

Necesitamos actualizar la tabla de datos, la declaración SQL es la siguiente:

update t set ind1=1 where indNum=1
update t set ind2=1 where indNum=2
update t set ind3=1 where indNum=3
update t set ind4=1 where indNum=4
update t set ind5=1 where indNum=5
update t set ind6=1 where indNum=6
update t set ind7=1 where indNum=7
update t set ind8=1 where indNum=8
update t set ind9=1 where indNum=9
update t set ind10=1 where indNum=10

Si la tabla de datos tiene una gran cantidad de columnas, debe escribir muchas sentencias SQL manualmente. Al observar la declaración anterior, podemos encontrar que existe una cierta relación entre el nombre de la columna y la condición del filtro. Las operaciones anteriores se pueden realizar de manera muy conveniente utilizando metaprogramación.

for(i in 1..10){
	t["ind"+i,<indNum=i>]=1
}

2.2 Usar metaprogramación en funciones integradas

Algunas funciones integradas de DolphinDB utilizarán metaprogramación.

2.2.1 Conexión de ventana

En la combinación de ventana (combinación de ventana), debe especificar una o más funciones agregadas para el conjunto de datos de ventana de la tabla derecha y los parámetros necesarios cuando se ejecutan estas funciones. Dado que la descripción y ejecución del problema se encuentran en dos etapas diferentes, utilizamos metaprogramación para lograr una ejecución retrasada.

t = table(take(`ibm, 3) as sym, 10:01:01 10:01:04 10:01:07 as time, 100 101 105 as price)
q = table(take(`ibm, 8) as sym, 10:01:01+ 0..7 as time, 101 103 103 104 104 107 108 107 as ask, 98 99 102 103 103 104 106 106 as bid)
wj(t, q, -2 : 1, < [max(ask), min(bid), avg((bid+ask)*0.5) as avg_mid]>, `time)

sym time     price max_ask min_bid avg_mid
--- -------- ----- ------- ------- -------
ibm 10:01:01 100   103     98      100.25
ibm 10:01:04 101   104     99      102.625
ibm 10:01:07 105   108     103     105.625

2.2.2 Motor de cálculo de secuencias

DolphinDB tiene tres tipos de motores de flujo informático: motor de agregación de series de tiempo (createTimeSeriesAggregator), motor de sección transversal (createCrossSectionalAggregator) y motor de detección de anomalías (createAnomalyDetectionEngine). Cuando utilice estos motores de flujo de datos, debe especificar funciones o expresiones agregadas para el conjunto de datos en la ventana de datos y los parámetros que requieren en tiempo de ejecución. En este caso, usamos la metaprogramación para representar funciones o expresiones agregadas y sus parámetros requeridos. Tome la aplicación del motor de agregación de series de tiempo como ejemplo:

share streamTable(1000:0, `time`sym`qty, [DATETIME, SYMBOL, INT]) as trades
output1 = table(10000:0, `time`sym`sumQty, [DATETIME, SYMBOL, INT])
agg1 = createTimeSeriesAggregator("agg1",60, 60, <[sum(qty)]>, trades, output1, `time, false,`sym, 50,,false)
subscribeTable(, "trades", "agg1",  0, append!{agg1}, true)

insert into trades values(2018.10.08T01:01:01,`A,10)
insert into trades values(2018.10.08T01:01:02,`B,26)
insert into trades values(2018.10.08T01:01:10,`B,14)
insert into trades values(2018.10.08T01:01:12,`A,28)
insert into trades values(2018.10.08T01:02:10,`A,15)
insert into trades values(2018.10.08T01:02:12,`B,9)
insert into trades values(2018.10.08T01:02:30,`A,10)
insert into trades values(2018.10.08T01:04:02,`A,29)
insert into trades values(2018.10.08T01:04:04,`B,32)
insert into trades values(2018.10.08T01:04:05,`B,23)

select * from output1

time                sym sumQty
------------------- --- ------
2018.10.08T01:02:00 A   38    
2018.10.08T01:03:00 A   25    
2018.10.08T01:02:00 B   40    
2018.10.08T01:03:00 B   9  

2.3 informes personalizados

La metaprogramación se puede utilizar para personalizar informes. El siguiente ejemplo define una función personalizada para generar informes, el usuario solo necesita ingresar la tabla de datos, el nombre del campo y la cadena de formato correspondiente del campo.

def generateReport(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()
}

Cree una base de datos histórica simulada:

if(existsDatabase("dfs://historical_db")){
	dropDatabase("dfs://historical_db")
}
n=5000000
dates=2012.09.01..2012.09.30
syms=symbol(`IBM`MSFT`GOOG`FB`AAPL)
t=table(rand(dates,n) as date, rand(syms,n) as sym, rand(200.0,n) as price, rand(1000..2000,n) as qty)

db1=database("",VALUE,dates)
db2=database("",VALUE,syms)
db=database("dfs://historical_db",COMPO,[db1,db2])
stock=db.createPartitionedTable(t,`stock,`date`sym).append!(t)

Seleccione los datos con el código de acciones como IBM el 1 de septiembre de 2012 para generar el informe:

generateReport(stock,`date`sym`price`qty,["MM/dd/yyyy","","###.00","#,###"],<date=2012.09.01 and sym=`IBM >)
date       sym price  qty  
---------- --- ------ -----
09/01/2012 IBM 90.97  1,679
09/01/2012 IBM 22.36  1,098
09/01/2012 IBM 133.42 1,404
09/01/2012 IBM 182.08 1,002
09/01/2012 IBM 144.67 1,468
09/01/2012 IBM 6.59   1,256
09/01/2012 IBM 73.09  1,149
09/01/2012 IBM 83.35  1,415
09/01/2012 IBM 93.13  1,006
09/01/2012 IBM 88.05  1,406
...

La declaración anterior es equivalente a la siguiente declaración SQL:

select format(date,"MM/dd/yyyy") as date, sym, format(price,"###.00") as price, format(qty,"#,###") as qty  from stock where date=2012.09.01 and sym=`IBM

2.4 Generar indicadores de cálculo de forma dinámica en Internet de las cosas

En el flujo de datos en tiempo real de Internet de las cosas, la fuente de datos contiene tres campos: etiqueta, marca de tiempo y valor. Ahora es necesario realizar el cálculo del indicador en tiempo real sobre los datos brutos de entrada. Dado que la cantidad y los tipos de etiquetas de los datos originales recibidos cada vez pueden ser diferentes, y los indicadores calculados también pueden ser diferentes cada vez, no podemos fijar los indicadores calculados. Por lo tanto, en este caso, podemos utilizar el método de metaprogramación. . Necesitamos definir una tabla de configuración, poner los indicadores calculados en esta tabla y agregar, eliminar o modificar los indicadores calculados según la situación real. En cada cálculo en tiempo real, lea dinámicamente los indicadores que se calcularán a partir de la tabla de configuración y envíe los resultados calculados a otra tabla.

El siguiente es el código de muestra. pubTable es una tabla de publicación para transmisión de datos. La tabla de configuración es una tabla de configuración para almacenar indicadores calculados Dado que los indicadores calculados pueden ser diferentes cada vez, aquí se usa la tabla de control de versiones concurrentes (mvccTable). La subTable realiza el cálculo en tiempo real de la transmisión de datos suscribiéndose a pubTable.

t1=streamTable(1:0,`tag`value`time,[STRING,DOUBLE,DATETIME])
share t1 as pubTable

config = mvccTable(`index1`index2`index3`index4 as targetTag, ["tag1 + tag2", "sqrt(tag3)", "floor(tag4)", "abs(tag5)"] as formular)

subTable = streamTable(100:0, `targetTag`value, [STRING, FLOAT])

def calculateTag(mutable subTable,config,msg){
	pmsg = select value from msg pivot by time, tag
	for(row in config){
		try{
			insert into subTable values(row.targetTag, sql(sqlColAlias(parseExpr(row.formular), "value"), pmsg).eval().value)
		}
			catch(ex){print ex}
	}
}

subscribeTable(,`pubTable,`calculateTag,-1,calculateTag{subTable,config},true)

//模拟写入数据
tmp = table(`tag1`tag2`tag3`tag4 as tag, 1.2 1.3 1.4 1.5 as value, take(2019.01.01T12:00:00, 4) as time)
pubTable.append!(tmp)

select * from subTable

targetTag value   
--------- --------
index1    2.5     
index2    1.183216
index3    1       

2.5 Ejecutar un conjunto de consultas y combinar resultados de consultas

En el análisis de datos, a veces necesitamos realizar un conjunto de consultas relacionadas en el mismo conjunto de datos y combinar los resultados de la consulta para mostrarlos. Si escribe manualmente todas las sentencias SQL cada vez, la carga de trabajo es pesada y la escalabilidad es pobre. Este problema se puede resolver generando SQL de forma dinámica mediante metaprogramación.

La estructura del conjunto de datos utilizada en este ejemplo es la siguiente (tome la primera línea como ejemplo):

mt         vn       bc  cc   stt  vt  gn  bk    sc  vas  pm  dls         dt          ts      val    vol
--------   -------  --  ---  ---  --  --  ----  --  ---  --  ----------  ----------  ------  -----  -----
52354955  50982208  25  814  11   2   1   4194  0   0    0   2020.02.05  2020.02.05  153234  5.374  18600

Necesitamos ejecutar un conjunto de consultas relacionadas con los datos diarios. como:

select * from t where vn=50982208,bc=25,cc=814,stt=11,vt=2, dsl=2020.02.05, mt<52355979 order by mt desc limit 1
select * from t where vn=50982208,bc=25,cc=814,stt=12,vt=2, dsl=2020.02.05, mt<52355979 order by mt desc limit 1
select * from t where vn=51180116,bc=25,cc=814,stt=12,vt=2, dsl=2020.02.05, mt<52354979 order by mt desc limit 1
select * from t where vn=41774759,bc=1180,cc=333,stt=3,vt=116, dsl=2020.02.05, mt<52355979 order by mt desc limit 1

Se puede observar que en este grupo de consultas, las condiciones de filtrado incluyen las mismas columnas y columnas de clasificación, y se toma la primera fila de registros después de la clasificación, y algunas condiciones de filtrado tienen el mismo valor. Con este fin, escribimos una función personalizada bundleQuery:

def bundleQuery(tbl, dt, dtColName, mt, mtColName, filterColValues, filterColNames){
	cnt = filterColValues[0].size()
	filterColCnt =filterColValues.size()
	orderByCol = sqlCol(mtColName)
	selCol = sqlCol("*")
	filters = array(ANY, filterColCnt + 2)
	filters[filterColCnt] = expr(sqlCol(dtColName), ==, dt)
	filters[filterColCnt+1] = expr(sqlCol(mtColName), <, mt)
	
	queries = array(ANY, cnt)
	for(i in 0:cnt)	{
		for(j in 0:filterColCnt){
			filters[j] = expr(sqlCol(filterColNames[j]), ==, filterColValues[j][i])
		}
		queries.append!(sql(select=selCol, from=tbl, where=filters, orderBy=orderByCol, ascOrder=false, limit=1))
	}
	return loop(eval, queries).unionAll(false)
}

El significado de cada parámetro en bundleQuery es el siguiente:

  • tbl es la tabla de datos
  • dt es el valor de la fecha en la condición del filtro
  • dtColName es el nombre de la columna de fecha en el filtro
  • mt es el valor de mt en la condición de filtro
  • mtColName es el nombre de la columna mt en la condición de filtro y el nombre de la columna de clasificación
  • filterColValues ​​son los valores en otras condiciones de filtro, expresados ​​en tuplas, donde cada vector representa una condición de filtro y los elementos en cada vector representan el valor de la condición de filtro
  • filterColNames son los nombres de columna en otras condiciones de filtro, representados por un vector

El conjunto anterior de sentencias SQL equivale a ejecutar el siguiente código:

dt = 2020.02.05 
dtColName = "dls" 
mt = 52355979 
mtColName = "mt"
colNames = `vn`bc`cc`stt`vt
colValues = [50982208 50982208 51180116 41774759, 25 25 25 1180, 814 814 814 333, 11 12 12 3, 2 2 2 116]

bundleQuery(t, dt, dtColName, mt, mtColName, colValues, colNames)

Podemos ejecutar el siguiente script para definir la función bundleQuery como una vista de función, de modo que la función se pueda usar directamente en cualquier nodo del clúster o después de reiniciar el sistema.

//please login as admin first
addFunctionView(bundleQuery)

3. Resumen

La metaprogramación de DolphinDB es potente y fácil de usar, lo que puede mejorar enormemente la eficiencia del desarrollo del programa.

Supongo que te gusta

Origin blog.csdn.net/qq_41996852/article/details/112258440
Recomendado
Clasificación