Foreign keys¶
The role of foreign keys within Refinery¶
In Refinery, foreign keys link tables together (primary → foreign), just as they do in SQL. This allows columns that weren't originally in a table to be queried and used as needed. By creating standard kdb+ foreign keys, this same functionality can be implemented within Refinery.
Foreign key links set up through standard kdb+ foreign key creation¶
This method of setting up a foreign key is aligned to the standard foreign key creation within kdb+. A basic example will be shown with using this through Refinery but for more details on setting up foreign keys in general see the following whitepaper: https://code.kx.com/q/wp/foreign-keys/
Configuring a table with a foreign key link¶
In this example, a date partitioned table named DemoTable was created through a Refinery pipeline. The YAML file for this table is below.
table:
name: DemoTable
id-col: sym
time-col: time
intra-persist-type: splay
end-persist-type: date-partition
taxonomy:
-
region: global
data-source: foreignKeyDoc
data-class: foreignKeyDoc
sub-class: foreignKeyDoc
columns:
-
name: sym
data-type: symbol
attribute: parted
-
name: time
data-type: timestamp
data-type: sorted
-
name: category
data-type: symbol
-
name: price
data-type: float
-
name: volume
data-type: long
As we can see, there is nothing specific in the YAML file when it comes to the foreign key creation. We will create a foreign key between the column sym in DemoTable and the table Contract, which has the following schema below.
|sym |chain |expiry |
|----|------|----------|
|a |aa |2023.10.10|
|b |bb |2023.11.11|
|c |cc |2023.12.12|
The foreign key links will have to be set up on the RDB and HDB processes.
Note
Refinery does not support foreign key links within an IDB setup.
In order to create and maintain the foreign key linking on the RDB, we will need to edit the upd analytic .tpClient.upsertFunc. This analytic as standard is a basic upsert. We will update this to perform the upsert and also create the foreign key link between DemoTable and Contract. An example of what we would change this analytic to on the RDB is:
.tpClient.upsertFunc:{[t;d]
//Upsert data (d) to table (t)
t upsert d;
//If table is DemoTable, update the sym column to be foreign keyed to the Contract table
if[t=`DemoTable;
update `Contract$sym from `DemoTable;
//Update the .schema.meta table so correct meta data is saved in the cache table
.schema.meta[`DemoTable]:meta DemoTable
];
}
Note
This method assumes that the Contract table has already been created on the RDB.
Following this, every time that DemoTable data is sent to the RDB the foriegn key link with Contract will be set up.
On the HDB, the Contract table and the foreign key between the two tables will be created on disk. This will need to be refreshed at a per partition level at every occurance of a writedown to disk. In this example, we create a listener analytic that will run after EOD is complete. This will create the foreign key between the tables once an EOD is ran.
.event.createForeignKeyOnDisk:{[]
{[dt]
//Check if foreign key link already exists for date, if it does, do not recreate
if[not `Contract=key symForDate:get `$":",(string dt),"/DemoTable/sym";
//If the forieng key is not present for a given date, create it and re-save down the sym column and Contract table
Contract::0!select from Contract;
symForDate: `p#`Contract!Contract.sym?symForDate;
(`$":",(string dt),"/DemoTable/sym") set symForDate;
`:./Contract set `sym xkey Contract
]
} each date;
//Reload the HDB
system "l .";
//Update the .schema.meta table so correct meta data is saved in the cache table
.schema.meta[`DemoTable]:meta DemoTable
};
.event.addListener[`hdb.reload.complete;`.event.createForeignKeyOnDisk]
Note
This code assumes that the Contract table has been saved to the HDB as a flat keyed table on the column sym.
Once this is complete, we will have a foreign key link in the RDB and HDB between the sym column in DemoTable and the Contract table. We can then run API calls on these foreign key columns.
API Calls using foreign key links¶
When a table has been correctly created with a foreign key column in the database, we can utilize this in Refinery API calls. Below are some examples with the tables that we have created. We can see the use of dot notation when we are calling a foreign key column.
getTicks - columns using foreign key¶
getTicks `assetClass`dataType`startDate`endDate`symList`columns!(`foreignKeyDoc; `DemoTable; .z.d-1; .z.d; `;`category`price`sym`sym.chain)
|sym |time |category |price |sym.chain|
|----|------------------------------|---------|------|---------|
|a |2023.10.06D09:55:56.962070717 |top |100 |aa |
|b |2023.10.06D09:55:56.962070718 |mid |200 |bb |
|c |2023.10.06D09:55:56.962070719 |bot |300 |cc |
getTicks - applyFilter using foreign key¶
getTicks `assetClass`dataType`startDate`endDate`symList`applyFilter!(`foreignKeyDoc; `DemoTable; .z.d-1; .z.d; `;(=;`sym.chain;`aa))
|sym |time |category |price |volume|
|----|------------------------------|---------|------|------|
|a |2023.10.06D09:55:56.962070717 |top |100 |1 |
getTicks - sortCols using foreign key¶
getTicks `assetClass`dataType`startDate`endDate`symList`columns`sortCols!(`foreignKeyDoc; `DemoTable; .z.d-1; .z.d; `;`sym`time`category`price`volume`sym.chain;`desc`sym.chain)
|sym |time |category |price |volume |sym.chain|
|----|------------------------------|---------|------|-------|---------|
|c |2023.10.06D09:55:56.962070719 |bot |300 |3 |cc |
|b |2023.10.06D09:55:56.962070718 |mid |200 |2 |bb |
|a |2023.10.06D09:55:56.962070717 |top |100 |1 |aa |