Saved functions
Save a formula with a name, and use it in any query like a built-in function.
- Category: Saved functions
- Keywords: function, user-defined function, UDF, formula, RETURNS, OR REPLACE, FUNCTIONS, CREATE OR REPLACE FUNCTION, calls itself, built-in function, nested too deeply
- See also: Stored procedures, The procedure language, Views, Math functions, 13. Functions, procedures and triggers
A saved function is a formula with a name. Its body is one expression (one formula, like (close - open) / open * 100).
You write the formula once. Then every query can use it, like round or concat.
- KDB keeps the function in the database. It is still there after a restart and in a backup.
- A call costs nothing extra: when the query starts, KDB puts the body in the place of the call.
- Only one expression. For many steps, variables or loops, use a stored procedure.
Example: The change of each share today, in percent (sample data: sample)
CREATE FUNCTION change_pct(price DOUBLE, prev DOUBLE) RETURNS DOUBLE
AS ((price - prev) / prev * 100);
SELECT symbol, open, close, round(change_pct(close, open), 2) AS change
FROM ochl_day
WHERE time IN '2026-09-10'
ORDER BY change DESC;
Result:
CREATE FUNCTION change_pct(price DOUBLE, prev DOUBLE) RETURNS DOUBLE AS ((price - prev) / prev * 100
function 'change_pct' created
SELECT symbol, open, close, round(change_pct(close, open), 2) AS change FROM ochl_day WHERE time IN
| symbol (SYMBOL) | open (DOUBLE) | close (DOUBLE) | change (DOUBLE) |
|---|---|---|---|
| VOLT | 185.6 | 191.2 | 3.02 |
| CITY | 504.6 | 504.9 | 0.06 |
| COAST | 380.1 | 376.1 | -1.05 |
(3 rows)
CREATE FUNCTION
Syntax:
CREATE [OR REPLACE] FUNCTION name(parameter TYPE, ...) [RETURNS TYPE] AS (expression)
CREATE [OR REPLACE] FUNCTION name() [RETURNS TYPE] AS (expression)
Saves a new function. OR REPLACE changes a function that already exists.
| Part | Meaning |
|---|---|
name | the name of the function. Upper and lower case are the same (Tax and tax are one function). |
parameter TYPE | an input value and its type (INT, DOUBLE, DECIMAL(2), STRING, TIMESTAMP, ...). Write () for a function without inputs. |
RETURNS TYPE | optional. The result is changed to this type (like CAST). Without it, the result keeps the type of the expression. |
AS (expression) | the body: one expression in brackets. It can use the parameters, operators, CASE, built-in functions and other saved functions. |
- Returns: the message
function 'name' created(orreplaced). - Notes:
- Each argument is changed to the type of its parameter, like
CAST. Sochange_pct('5', '4')works: the text becomes aDOUBLE. RETURNS DECIMAL(2)fails when the value has more than 2 decimals, for example112.9887. Round first:AS (round(amount * 0.13, 2)).- Without
OR REPLACE, a name that is already used gives an error. - KDB checks the body when you create the function: unknown functions, wrong argument counts and a function that calls itself are refused at once. The rules list everything a body cannot hold.
- There are no default values for parameters, and no two functions with the same name and other parameters.
- Each argument is changed to the type of its parameter, like
Example: VAT of 13% on an amount of money, exact to the cent
CREATE FUNCTION vat(amount DECIMAL(2)) RETURNS DECIMAL(2) AS (round(amount * 0.13, 2));
SELECT vat(1000) AS on_1000, vat(99.99) AS on_99_99, 99.99 + vat(99.99) AS total;
Result:
CREATE FUNCTION vat(amount DECIMAL(2)) RETURNS DECIMAL(2) AS (round(amount * 0.13, 2))
function 'vat' created
SELECT vat(1000) AS on_1000, vat(99.99) AS on_99_99, 99.99 + vat(99.99) AS total
| on_1000 (DECIMAL(2)) | on_99_99 (DECIMAL(2)) | total (DECIMAL(2)) |
|---|---|---|
| 130.00 | 13.00 | 112.99 |
(1 row)
Example: One function uses another: up, down or flat days (sample data: sample)
CREATE FUNCTION change_pct(price DOUBLE, prev DOUBLE) RETURNS DOUBLE
AS ((price - prev) / prev * 100);
CREATE FUNCTION move(price DOUBLE, prev DOUBLE) RETURNS STRING
AS (CASE WHEN change_pct(price, prev) > 0.5 THEN 'up'
WHEN change_pct(price, prev) < -0.5 THEN 'down'
ELSE 'flat' END);
SELECT move(close, open) AS move, count(*) AS days
FROM ochl_day
WHERE symbol = 'CITY'
GROUP BY move(close, open)
ORDER BY move;
Result:
CREATE FUNCTION change_pct(price DOUBLE, prev DOUBLE) RETURNS DOUBLE AS ((price - prev) / prev * 100
function 'change_pct' created
CREATE FUNCTION move(price DOUBLE, prev DOUBLE) RETURNS STRING AS (CASE WHEN change_pct(price, prev)
function 'move' created
SELECT move(close, open) AS move, count(*) AS days FROM ochl_day WHERE symbol = 'CITY' GROUP BY move
| move (STRING) | days (LONG) |
|---|---|
| down | 4 |
| flat | 13 |
| up | 2 |
(3 rows)
Example: Change a function with OR REPLACE
CREATE FUNCTION fee(amount DOUBLE) RETURNS DOUBLE AS (amount * 0.004);
SELECT fee(100000) AS old_fee;
CREATE OR REPLACE FUNCTION fee(amount DOUBLE) RETURNS DOUBLE AS (greatest(amount * 0.0036, 10));
SELECT fee(100000) AS new_fee, fee(1000) AS small_trade;
Result:
CREATE FUNCTION fee(amount DOUBLE) RETURNS DOUBLE AS (amount * 0.004)
function 'fee' created
SELECT fee(100000) AS old_fee
| old_fee (DOUBLE) |
|---|
| 400 |
(1 row)
CREATE OR REPLACE FUNCTION fee(amount DOUBLE) RETURNS DOUBLE AS (greatest(amount * 0.0036, 10))
function 'fee' replaced
SELECT fee(100000) AS new_fee, fee(1000) AS small_trade
| new_fee (DOUBLE) | small_trade (DOUBLE) |
|---|---|
| 360 | 10 |
(1 row)
Example: The name is already used (shows an error)
CREATE FUNCTION fee(amount DOUBLE) AS (amount * 0.004);
CREATE FUNCTION fee(amount DOUBLE) AS (amount * 0.0036);
Result:
function 'fee' created
Error: function 'fee' already exists; use CREATE OR REPLACE FUNCTION to change it
Example: RETURNS DECIMAL(2) with too many decimals (shows an error)
CREATE FUNCTION with_vat(amount DOUBLE) RETURNS DECIMAL(2) AS (amount * 1.13);
SELECT with_vat(99.99);
Result:
function 'with_vat' created
Error: line 2, column 8: cannot convert '112.98869999999998' to DECIMAL(2): 112.98869999999998 has more than 2 decimal places
Using a saved function
Syntax:
SELECT name(argument, ...) ... FROM ... WHERE name(...) ... GROUP BY name(...) ORDER BY name(...)
Call a saved function like a built-in one: its name and the arguments in brackets.
It works in SELECT, WHERE, GROUP BY, ORDER BY, in views, in procedures and in triggers.
- Returns: the value of the body, for each row.
- Notes:
- The number of arguments must match the parameters.
- Aggregates go around the call:
sum(change_pct(close, open))works. They cannot go inside the body. - A saved function cannot be used in a computed column (
AS (...)inCREATE TABLE), and not withDISTINCTorOVER. - Views and procedures always use the newest body: after
CREATE OR REPLACE FUNCTION, a view that uses it gives the new result. - The column name of an unnamed call is the call itself, like
change_pct(1, 2). UseASto give it a better name.
Example: Big trades only: a function in WHERE (sample data: sample)
CREATE FUNCTION trade_value(quantity LONG, rate DOUBLE) RETURNS DOUBLE AS (quantity * rate);
SELECT transaction_id, symbol, quantity, rate, trade_value(quantity, rate) AS value
FROM trade_log
WHERE trade_value(quantity, rate) >= 250000
ORDER BY value DESC;
Result:
CREATE FUNCTION trade_value(quantity LONG, rate DOUBLE) RETURNS DOUBLE AS (quantity * rate)
function 'trade_value' created
SELECT transaction_id, symbol, quantity, rate, trade_value(quantity, rate) AS value FROM trade_log W
| transaction_id (LONG) | symbol (SYMBOL) | quantity (LONG) | rate (DOUBLE) | value (DOUBLE) |
|---|---|---|---|---|
| 100001 | TELCO | 1000 | 881 | 881000 |
| 100007 | TELCO | 1000 | 879 | 879000 |
| 100017 | TELCO | 500 | 881 | 440500 |
| 100031 | COAST | 1000 | 402 | 402000 |
| 100015 | COAST | 1000 | 398 | 398000 |
| 100005 | CITY | 500 | 517 | 258500 |
| 100018 | CITY | 500 | 516 | 258000 |
| 100004 | CITY | 500 | 514.5 | 257250 |
| 100003 | CITY | 500 | 513 | 256500 |
(9 rows)
Example: A view uses the newest body of the function
CREATE FUNCTION fee(amount DOUBLE) RETURNS DOUBLE AS (amount * 0.004);
CREATE VIEW fee_on_lakh AS SELECT fee(100000) AS fee;
SELECT * FROM fee_on_lakh;
CREATE OR REPLACE FUNCTION fee(amount DOUBLE) RETURNS DOUBLE AS (amount * 0.0036);
SELECT * FROM fee_on_lakh;
Result:
CREATE FUNCTION fee(amount DOUBLE) RETURNS DOUBLE AS (amount * 0.004)
function 'fee' created
CREATE VIEW fee_on_lakh AS SELECT fee(100000) AS fee
view 'fee_on_lakh' created
SELECT * FROM fee_on_lakh
| fee (DOUBLE) |
|---|
| 400 |
(1 row)
CREATE OR REPLACE FUNCTION fee(amount DOUBLE) RETURNS DOUBLE AS (amount * 0.0036)
function 'fee' replaced
SELECT * FROM fee_on_lakh
| fee (DOUBLE) |
|---|
| 360 |
(1 row)
Example: Wrong number of arguments (shows an error)
CREATE FUNCTION change_pct(price DOUBLE, prev DOUBLE) RETURNS DOUBLE AS ((price - prev) / prev * 100);
SELECT change_pct(110);
Result:
function 'change_pct' created
Error: line 2, column 8: function 'change_pct' takes 2 argument(s) (price DOUBLE, prev DOUBLE), got 1
Example: Not in a computed column (shows an error)
CREATE FUNCTION trade_value(quantity LONG, rate DOUBLE) RETURNS DOUBLE AS (quantity * rate);
CREATE TABLE trades (quantity LONG, rate DOUBLE, value DOUBLE AS (trade_value(quantity, rate)));
Result:
function 'trade_value' created
Error: computed column 'value' cannot use the saved function 'trade_value'; computed columns can use built-in functions only
Rules for the body
Syntax:
AS (expression using only the parameters)
The body is checked when you create the function. These things are refused:
| Not allowed | Why | What to do |
|---|---|---|
| a column of a table | the function does not know any table | give the column as an argument: f(close) |
a subquery (SELECT ...), EXISTS, IN (SELECT ...) | a function works on one row | use a procedure or a view |
aggregates (sum, avg, ...) and window functions (sma, lag, ...) | they need many rows | call them around the function: sum(f(x)) |
* | ||
the name of a built-in function (round, pct_change, ...) | the name is already taken | choose another name |
| calling itself (also through another function) | it would never end | |
| more than 16 levels of functions calling functions |
- Notes:
DROP FUNCTIONdoes not check who uses a function. A function, view or procedure that uses a dropped function fails when it runs, withunknown function.
Example: A built-in name (shows an error)
CREATE FUNCTION pct_change(price DOUBLE, prev DOUBLE) AS ((price - prev) / prev * 100);
Result:
Error: 'pct_change' is a built-in function; choose another name for the function
Example: A column that is not a parameter (shows an error)
CREATE FUNCTION day_range(high DOUBLE) AS (high - low);
Result:
Error: the body of function 'day_range' uses low, which is not one of its parameters (high DOUBLE); a function can only use its parameters
Example: An aggregate inside the body (shows an error)
CREATE FUNCTION total(x DOUBLE) AS (sum(x));
Result:
Error: the body of function 'total' cannot use 'sum': aggregate and window functions work on many rows (call them around the function instead)
Example: A subquery inside the body (shows an error)
CREATE FUNCTION last_close(s STRING) AS ((SELECT last(close) FROM ochl_day WHERE symbol = s));
Result:
Error: the body of function 'last_close' cannot use a subquery
Example: Two functions that call each other (shows an error)
CREATE FUNCTION a(x DOUBLE) AS (x + 1);
CREATE FUNCTION b(x DOUBLE) AS (a(x) * 2);
CREATE OR REPLACE FUNCTION a(x DOUBLE) AS (b(x));
Result:
CREATE FUNCTION a(x DOUBLE) AS (x + 1)
function 'a' created
CREATE FUNCTION b(x DOUBLE) AS (a(x) * 2)
function 'b' created
Error: in function 'a': in function 'b': function 'a' calls itself (a -> b -> a); a saved function cannot call itself
DROP FUNCTION
Syntax:
DROP FUNCTION [IF EXISTS] name
Removes a saved function.
- Returns:
function 'name' dropped. WithIF EXISTS, a name that does not exist gives the messagefunction 'name' does not existand no error. - Notes: KDB does not check who uses the function. Other functions, views, procedures and triggers that use it fail later, when they run.
Example
CREATE FUNCTION fee(amount DOUBLE) RETURNS DOUBLE AS (amount * 0.004);
DROP FUNCTION fee;
DROP FUNCTION IF EXISTS fee;
Result:
CREATE FUNCTION fee(amount DOUBLE) RETURNS DOUBLE AS (amount * 0.004)
function 'fee' created
DROP FUNCTION fee
function 'fee' dropped
DROP FUNCTION IF EXISTS fee
function 'fee' does not exist
Example: A function that used the dropped one fails when it runs (shows an error)
CREATE FUNCTION a(x DOUBLE) AS (x + 1);
CREATE FUNCTION b(x DOUBLE) AS (a(x) * 2);
DROP FUNCTION a;
SELECT b(1);
Result:
CREATE FUNCTION a(x DOUBLE) AS (x + 1)
function 'a' created
CREATE FUNCTION b(x DOUBLE) AS (a(x) * 2)
function 'b' created
DROP FUNCTION a
function 'a' dropped
Error: line 4, column 8: in function 'b': unknown function 'a'
Example: Without IF EXISTS, a missing name is an error (shows an error)
DROP FUNCTION no_such_function;
Result:
Error: function 'no_such_function' does not exist
SHOW FUNCTIONS
Syntax:
SHOW FUNCTIONS
Lists the saved functions of the database, in name order.
| Column | Meaning |
|---|---|
name | the function name |
arguments | the parameters and their types |
returns | the RETURNS type, or NULL when there is none |
definition | the whole CREATE FUNCTION statement |
- Notes: built-in functions are not listed. The text table of
kdb sqlcuts long text after 48 characters; use--format csvor--format jsonto see the whole definition.
Example
CREATE FUNCTION change_pct(price DOUBLE, prev DOUBLE) RETURNS DOUBLE AS ((price - prev) / prev * 100);
CREATE FUNCTION vat(amount DECIMAL(2)) RETURNS DECIMAL(2) AS (round(amount * 0.13, 2));
CREATE FUNCTION greeting(name STRING) AS (concat('Namaste, ', name));
SHOW FUNCTIONS;
Result:
CREATE FUNCTION change_pct(price DOUBLE, prev DOUBLE) RETURNS DOUBLE AS ((price - prev) / prev * 100
function 'change_pct' created
CREATE FUNCTION vat(amount DECIMAL(2)) RETURNS DECIMAL(2) AS (round(amount * 0.13, 2))
function 'vat' created
CREATE FUNCTION greeting(name STRING) AS (concat('Namaste, ', name))
function 'greeting' created
SHOW FUNCTIONS
| name (STRING) | arguments (STRING) | returns (STRING) | definition (STRING) |
|---|---|---|---|
| change_pct | price DOUBLE, prev DOUBLE | DOUBLE | CREATE FUNCTION change_pct(price DOUBLE, prev DOUBLE) RETURNS DOUBLE AS ((price - prev) / prev * 100) |
| greeting | name STRING | NULL | CREATE FUNCTION greeting(name STRING) AS (concat('Namaste, ', name)) |
| vat | amount DECIMAL(2) | DECIMAL(2) | CREATE FUNCTION vat(amount DECIMAL(2)) RETURNS DECIMAL(2) AS (round(amount * 0.13, 2)) |
(3 rows)