# 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](procedures.md), [The procedure language](procedure-language.md), [Views](views.md), [Math functions](functions-math.md), [13. Functions, procedures and triggers](../tutorial/13-functions-procedures-triggers.md) 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`. - KarelaDB 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, KarelaDB puts the body in the place of the call. - Only **one expression**. For many steps, variables or loops, use a [stored procedure](procedures.md). **Example: The change of each share today, in percent** (sample data: sample) ```sql 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` ```text 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: ```sql 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` (or `replaced`). - **Notes:** - Each argument is changed to the type of its parameter, like `CAST`. So `change_pct('5', '4')` works: the text becomes a `DOUBLE`. - `RETURNS DECIMAL(2)` fails when the value has more than 2 decimals, for example `112.9887`. Round first: `AS (round(amount * 0.13, 2))`. - Without `OR REPLACE`, a name that is already used gives an error. - KarelaDB 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](saved-functions.md#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. **Example: VAT of 13% on an amount of money, exact to the cent** ```sql 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))` ```text 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) ```sql 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` ```text function 'change_pct' created ``` `CREATE FUNCTION move(price DOUBLE, prev DOUBLE) RETURNS STRING AS (CASE WHEN change_pct(price, prev)` ```text 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** ```sql 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)` ```text 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))` ```text 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) ```sql CREATE FUNCTION fee(amount DOUBLE) AS (amount * 0.004); CREATE FUNCTION fee(amount DOUBLE) AS (amount * 0.0036); ``` Result: ```text 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) ```sql CREATE FUNCTION with_vat(amount DOUBLE) RETURNS DECIMAL(2) AS (amount * 1.13); SELECT with_vat(99.99); ``` Result: ```text 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: ```sql 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](views.md), in [procedures](procedures.md) and in [triggers](triggers.md). - **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 (...)` in `CREATE TABLE`), and not with `DISTINCT` or `OVER`. - 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)`. Use `AS` to give it a better name. **Example: Big trades only: a function in WHERE** (sample data: sample) ```sql 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)` ```text 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** ```sql 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)` ```text function 'fee' created ``` `CREATE VIEW fee_on_lakh AS SELECT fee(100000) AS fee` ```text 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)` ```text function 'fee' replaced ``` `SELECT * FROM fee_on_lakh` | fee (DOUBLE) | |---| | 360 | (1 row) **Example: Wrong number of arguments** (shows an error) ```sql CREATE FUNCTION change_pct(price DOUBLE, prev DOUBLE) RETURNS DOUBLE AS ((price - prev) / prev * 100); SELECT change_pct(110); ``` Result: ```text 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) ```sql 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: ```text 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: ```sql 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](procedures.md) or a [view](views.md) | | 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 FUNCTION` does not check who uses a function. A function, view or procedure that uses a dropped function fails when it runs, with `unknown function`. **Example: A built-in name** (shows an error) ```sql 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) ```sql 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) ```sql 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) ```sql 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) ```sql 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)` ```text function 'a' created ``` `CREATE FUNCTION b(x DOUBLE) AS (a(x) * 2)` ```text 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: ```sql DROP FUNCTION [IF EXISTS] name ``` Removes a saved function. - **Returns:** `function 'name' dropped`. With `IF EXISTS`, a name that does not exist gives the message `function 'name' does not exist` and no error. - **Notes:** KarelaDB does not check who uses the function. Other functions, views, procedures and triggers that use it fail later, when they run. **Example** ```sql 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)` ```text function 'fee' created ``` `DROP FUNCTION fee` ```text function 'fee' dropped ``` `DROP FUNCTION IF EXISTS fee` ```text function 'fee' does not exist ``` **Example: A function that used the dropped one fails when it runs** (shows an error) ```sql 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)` ```text function 'a' created ``` `CREATE FUNCTION b(x DOUBLE) AS (a(x) * 2)` ```text function 'b' created ``` `DROP FUNCTION a` ```text 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) ```sql DROP FUNCTION no_such_function; ``` Result: Error: `function 'no_such_function' does not exist` ## SHOW FUNCTIONS Syntax: ```sql 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 `kareladb sql` cuts long text after 48 characters; use `--format csv` or `--format json` to see the whole definition. **Example** ```sql 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` ```text function 'change_pct' created ``` `CREATE FUNCTION vat(amount DECIMAL(2)) RETURNS DECIMAL(2) AS (round(amount * 0.13, 2))` ```text function 'vat' created ``` `CREATE FUNCTION greeting(name STRING) AS (concat('Namaste, ', name))` ```text 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)