Produits secs 丨 Tutoriel de métaprogrammation DolphinDB

La métaprogrammation fait référence à l'utilisation de codes de programme pour générer des codes de programme qui peuvent être exécutés dynamiquement. Le but de la métaprogrammation est généralement de retarder l'exécution du code ou de créer du code dynamiquement.

1. DolphinDB implémente la méthode de méta-programmation

DolphinDB prend en charge l'utilisation de la métaprogrammation pour créer dynamiquement des expressions, y compris des expressions d'appel de fonction, des expressions de requête SQL, etc. DolphinDB a deux façons d'implémenter la métaprogrammation:

(1) Utilisez une paire de crochets angulaires <> pour indiquer le code dynamique qui doit être retardé. Par exemple,

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

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

(2) Utilisez des fonctions pour créer diverses expressions. Fonctions de programmation couramment utilisés comprennent méta exprparseExprpartialsqlColsqlColAliassqlevalmakeCall. Ici , l'utilisation de ces fonctions.

  • La fonction expr génère un méta-code basé sur l'objet d'entrée, l'opérateur ou un autre méta-code. Par exemple:
a = expr(1, +, 2, *, 3)
a.typestr();
CODE

a;
< 1 + 2 * 3 >
  • La fonction parseExpr peut convertir une chaîne en un méta-code. Par exemple:
parseExpr("1+2")

< 1 + 2 >
  • La fonction partielle peut fixer une partie des paramètres d'une fonction et générer une fonction avec moins de paramètres. Par exemple:
partial(add,1)(2)
3

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

8
  • Les fonctions sqlColsqlColAlias et sql sont utilisées pour générer dynamiquement des expressions SQL. sqlColLes fonctions peuvent convertir les noms de colonnes en expressions et sont sqlColAliassouvent utilisées pour générer des métacodes pour les colonnes calculées. Les sqlfonctions peuvent générer dynamiquement des instructions SQL.
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 fonction eval peut exécuter du méta-code. Par exemple:
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 fonction makeCall peut générer du méta-code en fonction de la fonction et des paramètres d'entrée spécifiés. Par exemple, lors de l'interrogation de la table t1, affichez la colonne de date sous forme de chaîne et affichez-la sous une forme similaire à 03/01/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. Application de méta-programmation DolphinDB

2.1 Mettre à jour la table de mémoire de partition

Des opérations telles que la mise à jour et la suppression de la table de mémoire de partition peuvent être effectuées non seulement via des instructions SQL, mais également via une métaprogrammation. Créez une table de mémoire de partition:

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 Mettre à jour les données

Par exemple, mettez à jour le nombre de transactions avec le code stock IBM:

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

2.1.2 Ajouter une nouvelle ligne

Par exemple, ajoutez un nouveau volume de colonne pour stocker le volume de transaction:

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

2.1.3 Supprimer des données

Par exemple, supprimez les données dont la quantité est 0:

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

2.1.4 Générer dynamiquement des conditions de filtre et mettre à jour les données

Cet exemple utilise la table de données suivante.

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)

Nous devons mettre à jour la table de données, l'instruction SQL est la suivante:

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 table de données comporte un grand nombre de colonnes, vous devez écrire manuellement de nombreuses instructions SQL. En observant l'instruction ci-dessus, nous pouvons constater qu'il existe une certaine relation entre le nom de la colonne et la condition de filtre. Les opérations ci-dessus peuvent être effectuées très facilement en utilisant la métaprogrammation.

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

2.2 Utiliser la métaprogrammation dans les fonctions intégrées

Certaines fonctions intégrées de DolphinDB utiliseront la métaprogrammation.

2.2.1 Connexion de fenêtre

Dans la jointure de fenêtre (jointure de fenêtre), vous devez spécifier une ou plusieurs fonctions d'agrégation pour l'ensemble de données de fenêtre de la table de droite et les paramètres requis lorsque ces fonctions sont exécutées. La description et l'exécution du problème étant en deux étapes différentes, nous utilisons la méta-programmation pour obtenir une exécution retardée.

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 Moteur de calcul de flux

DolphinDB dispose de trois types de moteurs de calcul de flux: le moteur d'agrégation de séries chronologiques (createTimeSeriesAggregator), le moteur de coupe transversale (createCrossSectionalAggregator) et le moteur de détection d'anomalies (createAnomalyDetectionEngine). Lorsque vous utilisez ces moteurs de calcul de flux, vous devez spécifier des fonctions d'agrégation ou des expressions pour l'ensemble de données dans la fenêtre de données et les paramètres requis par eux au moment de l'exécution. Dans ce cas, nous utilisons la métaprogrammation pour représenter des fonctions ou expressions d'agrégation et leurs paramètres requis. Prenons l'exemple de l'application du moteur d'agrégation de séries chronologiques:

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 Rapports personnalisés

La métaprogrammation peut être utilisée pour personnaliser les rapports. L'exemple suivant définit une fonction personnalisée pour générer des rapports. L'utilisateur n'a qu'à entrer la table de données, le nom du champ et la chaîne de format correspondante du champ.

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

Créez une base de données historique simulée:

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)

Sélectionnez les données avec le code de stock IBM le 1er septembre 2012 pour générer le rapport:

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
...

L'instruction ci-dessus est équivalente à l'instruction SQL suivante:

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 Générer dynamiquement des indicateurs de calcul dans l'Internet des objets

Dans le calcul de flux en temps réel de l'Internet des objets, la source de données contient trois champs: tag, horodatage et valeur. Il est maintenant nécessaire d'effectuer un calcul d'indicateur en temps réel sur les données brutes d'entrée. Étant donné que le nombre et les types de balises des données d'origine reçues à chaque fois peuvent être différents et que les indicateurs calculés peuvent également être différents à chaque fois, nous ne pouvons pas corriger les indicateurs calculés. Par conséquent, dans ce cas, nous pouvons utiliser la méthode de méta-programmation . Il faut définir un tableau de configuration, mettre les indicateurs calculés dans ce tableau et ajouter, supprimer ou modifier les indicateurs calculés en fonction de la situation réelle. Dans chaque calcul en temps réel, lisez dynamiquement les indicateurs à calculer à partir de la table de configuration et sortez les résultats calculés dans une autre table.

Voici l'exemple de code. pubTable est une table de publication pour le streaming de données. La table de configuration est une table de configuration pour stocker les indicateurs calculés. Étant donné que les indicateurs calculés peuvent être différents à chaque fois, la table de contrôle des versions simultanées (mvccTable) est utilisée ici. Le subTable effectue le calcul en temps réel des données de streaming en s'abonnant à 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 Exécuter un ensemble de requêtes et combiner les résultats des requêtes

Dans l'analyse des données, nous devons parfois effectuer un ensemble de requêtes associées sur le même ensemble de données et combiner les résultats de la requête pour les afficher. Si vous écrivez manuellement toutes les instructions SQL à chaque fois, la charge de travail est lourde et l'évolutivité est médiocre. Ce problème peut être résolu en générant dynamiquement du SQL via la métaprogrammation.

La structure de l'ensemble de données utilisée dans cet exemple est la suivante (prenez la première ligne comme exemple):

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

Nous devons exécuter un ensemble de requêtes associées sur les données quotidiennes. tel que:

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

On peut observer que dans ce groupe de requêtes, les conditions de filtre incluent les mêmes colonnes et colonnes de tri, et la première ligne d'enregistrements après le tri est prise, et certaines conditions de filtre ont la même valeur. À cette fin, nous avons écrit un bundleQuery de fonctions personnalisées:

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)
}

La signification de chaque paramètre dans bundleQuery est la suivante:

  • tbl est le tableau de données
  • dt est la valeur de la date dans la condition de filtre
  • dtColName est le nom de la colonne de date dans le filtre
  • mt est la valeur de mt dans la condition de filtre
  • mtColName est le nom de la colonne mt dans la condition de filtre et le nom de la colonne de tri
  • filterColValues ​​sont les valeurs dans d'autres conditions de filtre, exprimées en tuples, où chaque vecteur représente une condition de filtre et les éléments dans chaque vecteur représentent la valeur de la condition de filtre
  • filterColNames sont les noms de colonnes dans d'autres conditions de filtre, représentés par un vecteur

L'ensemble d'instructions SQL ci-dessus équivaut à exécuter le code suivant:

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)

Nous pouvons exécuter le script suivant pour définir la fonction bundleQuery en tant que vue de fonction, afin que la fonction puisse être utilisée directement sur n'importe quel nœud du cluster ou après le redémarrage du système.

//please login as admin first
addFunctionView(bundleQuery)

3. Résumé

La méta-programmation de DolphinDB est puissante et facile à utiliser, ce qui peut grandement améliorer l'efficacité du développement de programmes.

Je suppose que tu aimes

Origine blog.csdn.net/qq_41996852/article/details/112258440
conseillé
Classement