KDB knowledge

Stored procedures

Save many SQL steps under one name and run them with CALL, as one transaction.

A stored procedure (or just "procedure") is a list of SQL statements with a name. You run it with CALL name(...). KDB keeps it in the database, like a table. Your app, the web console and kdb sql can all call it.

A real job: after the market closes, a trading app builds the daily candle of each share from the one-minute candles.

Example: End of day: build the daily candles from the minute candles (sample data: sample)

CREATE PROCEDURE end_of_day(day DATE) AS BEGIN
  DECLARE minutes LONG DEFAULT 0;
  SET minutes = (SELECT count(*) FROM ochl_minutes
                 WHERE time >= day AND time < dateadd('d', 1, day));
  IF minutes = 0 THEN
    RAISE concat('no minute candles for ', day);
  END IF;
  FOR c IN (SELECT symbol, first(open) AS open, max(high) AS high, min(low) AS low,
                   last(close) AS close, sum(volume) AS volume
            FROM ochl_minutes
            WHERE time >= day AND time < dateadd('d', 1, day)
            GROUP BY symbol) DO
    INSERT OR REPLACE INTO ochl_day VALUES (day, c.symbol, c.open, c.high, c.low, c.close, c.volume);
  END FOR;
  PRINT concat(minutes, ' minute candles read');
  SELECT symbol, open, high, low, close, volume FROM ochl_day WHERE time = day ORDER BY symbol;
END;

CALL end_of_day('2026-09-10');

Result:

CREATE PROCEDURE end_of_day(day DATE) AS BEGIN

procedure 'end_of_day' created

CALL end_of_day('2026-09-10')

procedure 'end_of_day' done: 2 row(s) changed

SELECT symbol, open, high, low, close, volume FROM ochl_day WHERE time = day ORDER BY symbol

symbol (SYMBOL)open (DOUBLE)high (DOUBLE)low (DOUBLE)close (DOUBLE)volume (DOUBLE)
CITY504.6507500.7504.9183040
COAST380.1382373.2376.1186200
VOLT185.6192.1184.2191.241470

(3 rows)

print

output (STRING)
476 minute candles read

(1 row)

CREATE PROCEDURE

Syntax:

CREATE [OR REPLACE] PROCEDURE name(parameter TYPE, ...) [READ ONLY] AS BEGIN
  statement;
  statement;
  ...
END

Saves a procedure. OR REPLACE changes one that already exists.

PartMeaning
namethe procedure name. Upper and lower case are the same.
parameter TYPEan input value and its type, like day DATE or amt DECIMAL(2). Write () for none.
READ ONLYoptional: the procedure changes no data. KDB refuses a body that does, and every CALL of it runs like a SELECT. See a CALL that only reads.
BEGIN ... ENDthe body. Every statement in it ends with ;.

What a body can hold: SELECT, INSERT, UPDATE, DELETE, CALL, SHOW ..., EXPLAIN, and the procedure statements DECLARE, SET, IF, WHILE, FOR, OPEN, FETCH, CLOSE, RETURN, RAISE and PRINT (the procedure language).

Setup used by the examples below:

-- A small bank: accounts, and a ledger with one row for every transfer.
CREATE TABLE accounts (
  id INT PRIMARY KEY,
  owner STRING NOT NULL,
  balance DECIMAL(2) NOT NULL DEFAULT 0
);
CREATE TABLE ledger (
  id LONG PRIMARY KEY AUTO,
  from_account INT NOT NULL REFERENCES accounts(id),
  to_account INT NOT NULL REFERENCES accounts(id),
  amount DECIMAL(2) NOT NULL,
  created TIMESTAMP DEFAULT now()
);
INSERT INTO accounts VALUES (1, 'Ram', 1000), (2, 'Sita', 0), (3, 'Hari', 500);

Example: Accounting: move money between two accounts (double entry)

CREATE PROCEDURE transfer(from_id INT, to_id INT, amt DECIMAL(2)) AS BEGIN
  DECLARE have DECIMAL(2);
  IF amt <= 0 THEN
    RAISE 'the amount must be more than 0';
  END IF;
  SET have = (SELECT balance FROM accounts WHERE id = from_id);
  IF have IS NULL THEN
    RAISE concat('account ', from_id, ' does not exist');
  ELSIF have < amt THEN
    RAISE concat('not enough money: account ', from_id, ' has ', have, ', the transfer needs ', amt);
  END IF;
  UPDATE accounts SET balance = balance - amt WHERE id = from_id;
  UPDATE accounts SET balance = balance + amt WHERE id = to_id;
  INSERT INTO ledger (from_account, to_account, amount) VALUES (from_id, to_id, amt);
  PRINT concat('moved ', amt, ' from account ', from_id, ' to account ', to_id);
END;

CALL transfer(1, 2, 250.50);
SELECT id, owner, balance FROM accounts ORDER BY id;

Result:

CREATE PROCEDURE transfer(from_id INT, to_id INT, amt DECIMAL(2)) AS BEGIN

procedure 'transfer' created

CALL transfer(1, 2, 250.50)

procedure 'transfer' done: 3 row(s) changed

print

output (STRING)
moved 250.50 from account 1 to account 2

(1 row)

SELECT id, owner, balance FROM accounts ORDER BY id

id (INT)owner (STRING)balance (DECIMAL(2))
1Ram749.50
2Sita250.50
3Hari500.00

(3 rows)

Example: Forum: a new post and its topic counter in one step

CREATE TABLE topics (id INT PRIMARY KEY, title STRING NOT NULL, posts LONG NOT NULL DEFAULT 0);
CREATE TABLE posts (id LONG PRIMARY KEY AUTO, topic_id INT NOT NULL REFERENCES topics(id), author STRING, body STRING, created TIMESTAMP DEFAULT now());
INSERT INTO topics (id, title) VALUES (1, 'CITY quarterly results');

CREATE PROCEDURE add_post(topic INT, who STRING, text STRING) AS BEGIN
  IF length(trim(text)) = 0 THEN
    RAISE 'a post cannot be empty';
  END IF;
  INSERT INTO posts (topic_id, author, body) VALUES (topic, who, text);
  UPDATE topics SET posts = posts + 1 WHERE id = topic;
END;

CALL add_post(1, 'ram', 'Profit is up 12%.');
CALL add_post(1, 'sita', 'Good for the dividend.');
SELECT id, title, posts FROM topics;

Result:

CREATE TABLE topics (

table 'topics' created

CREATE TABLE posts (

table 'posts' created

INSERT INTO topics (id, title) VALUES (1, 'CITY quarterly results')

1 row(s) inserted

CREATE PROCEDURE add_post(topic INT, who STRING, text STRING) AS BEGIN

procedure 'add_post' created

CALL add_post(1, 'ram', 'Profit is up 12%.')

procedure 'add_post' done: 2 row(s) changed

CALL add_post(1, 'sita', 'Good for the dividend.')

procedure 'add_post' done: 2 row(s) changed

SELECT id, title, posts FROM topics

id (INT)title (STRING)posts (LONG)
1CITY quarterly results2

(1 row)

Example: Tables are checked when the procedure runs, not when it is created (shows an error)

CREATE PROCEDURE count_archive() AS BEGIN
  SELECT count(*) AS rows_in_archive FROM archive;
END;
CREATE TABLE audit (id INT PRIMARY KEY);
CALL count_archive();

Result:

CREATE PROCEDURE count_archive() AS BEGIN

procedure 'count_archive' created

CREATE TABLE audit (

table 'audit' created

Error: procedure 'count_archive', line 2 col 43: table or view 'archive' does not exist

Example: BEGIN cannot be used inside a body (shows an error)

CREATE PROCEDURE p() AS BEGIN
  BEGIN;
  UPDATE accounts SET balance = 0;
  COMMIT;
END;

Result:

Error: line 2, column 3: BEGIN cannot be used inside a procedure or trigger body: the whole CALL is one transaction (use RAISE to undo all of it)

Example: CREATE cannot be used inside a body (shows an error)

CREATE PROCEDURE make_table() AS BEGIN
  CREATE TABLE archive (id INT);
END;

Result:

Error: line 2, column 3: CREATE cannot be used inside a procedure or trigger body; expected SELECT, INSERT, UPDATE, DELETE, CALL, DECLARE, SET, IF, WHILE, FOR, OPEN, FETCH, CLOSE, RETURN, RAISE, PRINT or NOTIFY

Example: The name is already used (shows an error)

CREATE PROCEDURE hello() AS BEGIN PRINT 'hello'; END;
CREATE PROCEDURE hello() AS BEGIN PRINT 'namaste'; END;

Result:

procedure 'hello' created

Error: procedure 'hello' already exists; use CREATE OR REPLACE PROCEDURE to change it

CALL

Syntax:

CALL name(argument, ...)
CALL name()
CALL name(argument, ...) WITH DEBUG

Runs a procedure. Each argument can be a value or an expression, like '2026-09-10' or dateadd('d', -1, now()).

Example: Transfer, then look at the books

CREATE PROCEDURE transfer(from_id INT, to_id INT, amt DECIMAL(2)) AS BEGIN
  IF (SELECT balance FROM accounts WHERE id = from_id) < amt THEN
    RAISE concat('not enough money in account ', from_id);
  END IF;
  UPDATE accounts SET balance = balance - amt WHERE id = from_id;
  UPDATE accounts SET balance = balance + amt WHERE id = to_id;
  INSERT INTO ledger (from_account, to_account, amount) VALUES (from_id, to_id, amt);
  SELECT id, owner, balance FROM accounts WHERE id IN (from_id, to_id) ORDER BY id;
  PRINT concat('ledger rows now: ', (SELECT count(*) FROM ledger));
END;
CALL transfer(3, 2, 120);

Result:

CREATE PROCEDURE transfer(from_id INT, to_id INT, amt DECIMAL(2)) AS BEGIN

procedure 'transfer' created

CALL transfer(3, 2, 120)

procedure 'transfer' done: 3 row(s) changed

SELECT id, owner, balance FROM accounts WHERE id IN (from_id, to_id) ORDER BY id

id (INT)owner (STRING)balance (DECIMAL(2))
2Sita120.00
3Hari380.00

(2 rows)

print

output (STRING)
ledger rows now: 1

(1 row)

Example: A text argument becomes a DATE (sample data: sample)

CREATE PROCEDURE trades_of(day DATE) AS BEGIN
  SELECT symbol, count(*) AS trades, sum(quantity) AS shares
  FROM trade_log WHERE date = day GROUP BY symbol ORDER BY symbol;
END;
CALL trades_of('2026-09-09');

Result:

CREATE PROCEDURE trades_of(day DATE) AS BEGIN

procedure 'trades_of' created

CALL trades_of('2026-09-09')

procedure 'trades_of' done: 0 row(s) changed

SELECT symbol, count(*) AS trades, sum(quantity) AS shares FROM trade_log WHERE date = day GROUP BY

symbol (SYMBOL)trades (LONG)shares (LONG)
CITY41510
COAST2220
TELCO22000

(3 rows)

Example: Wrong number of arguments (shows an error)

CREATE PROCEDURE pay(to_id INT, amt DECIMAL(2)) AS BEGIN
  UPDATE accounts SET balance = balance + amt WHERE id = to_id;
END;
CALL pay(2);

Result:

procedure 'pay' created

Error: procedure 'pay' takes 2 argument(s) (to_id INT, amt DECIMAL(2)), got 1

Example: An argument that does not fit the type (shows an error)

CREATE PROCEDURE close_account(account INT) AS BEGIN
  DELETE FROM accounts WHERE id = account;
END;
CALL close_account('Ram');

Result:

procedure 'close_account' created

Error: argument 1 of procedure 'close_account': variable 'account' (INT): 'Ram' is not a whole number

Example: No such procedure (shows an error)

CALL no_such_procedure();

Result:

Error: procedure 'no_such_procedure' does not exist (SHOW PROCEDURES lists them)

Example: A procedure that calls itself too often (shows an error)

CREATE PROCEDURE countdown(n INT) AS BEGIN
  PRINT n;
  IF n > 0 THEN
    CALL countdown(n - 1);
  END IF;
END;
CALL countdown(3);
CALL countdown(20);

Result:

CREATE PROCEDURE countdown(n INT) AS BEGIN

procedure 'countdown' created

CALL countdown(3)

procedure 'countdown' done: 0 row(s) changed

print

output (STRING)
3
2
1
0

(4 rows)

print

output (STRING)
20
19
18
17
16
15
14
13
12
11
10
9
8
7
6
5

(16 rows)

Error: procedure 'countdown', line 4: procedure 'countdown', line 4: procedure 'countdown', line 4: procedure 'countdown', line 4: procedure 'countdown', line 4: procedure 'countdown', line 4: procedure 'countdown', line 4: procedure 'countdown', line 4: procedure 'countdown', line 4: procedure 'countdown', line 4: procedure 'countdown', line 4: procedure 'countdown', line 4: procedure 'countdown', line 4: procedure 'countdown', line 4: procedure 'countdown', line 4: procedure 'countdown', line 4: procedures call each other too deeply (more than 16 levels, at procedure 'countdown')

A CALL is one transaction

Syntax:

CALL name(...)                        -- one transaction by itself
BEGIN; CALL a(...); CALL b(...); COMMIT;   -- part of a bigger transaction

All changes of a CALL are saved together when it ends, with one write to the log (the file where KDB writes every change first). If any statement fails, or RAISE runs, nothing of the CALL is saved, also not the steps before the error.

In this example the first UPDATE takes 100 from Ram. Then the ledger row fails, because account 99 does not exist. The whole CALL is undone, so Ram still has 1000.

Command line: A failed CALL saves nothing (shows an error)

Setup:

CREATE TABLE accounts (id INT PRIMARY KEY, owner STRING NOT NULL, balance DECIMAL(2) NOT NULL DEFAULT 0);
CREATE TABLE ledger (id LONG PRIMARY KEY AUTO, from_account INT NOT NULL REFERENCES accounts(id), to_account INT NOT NULL REFERENCES accounts(id), amount DECIMAL(2) NOT NULL);
INSERT INTO accounts VALUES (1, 'Ram', 1000), (2, 'Sita', 0);
CREATE PROCEDURE transfer(from_id INT, to_id INT, amt DECIMAL(2)) AS BEGIN
  UPDATE accounts SET balance = balance - amt WHERE id = from_id;
  UPDATE accounts SET balance = balance + amt WHERE id = to_id;
  INSERT INTO ledger (from_account, to_account, amount) VALUES (from_id, to_id, amt);
END;
kdb sql market "CALL transfer(1, 99, 100)"
kdb sql market "SELECT id, owner, balance FROM accounts ORDER BY id"

Output:

> kdb sql market "CALL transfer(1, 99, 100)"

error: procedure 'transfer', line 4: column 'to_account' = '99' has no matching row in accounts(id)

exit code 1

> kdb sql market "SELECT id, owner, balance FROM accounts ORDER BY id"

id | owner | balance
---+-------+--------
 1 | Ram   | 1000.00
 2 | Sita  |    0.00
(2 rows, 0.3 ms)

Example: Two CALLs in one transaction, then ROLLBACK

CREATE PROCEDURE deposit(account INT, amt DECIMAL(2)) AS BEGIN
  UPDATE accounts SET balance = balance + amt WHERE id = account;
END;
BEGIN;
CALL deposit(2, 100);
CALL deposit(3, 100);
SELECT id, balance FROM accounts ORDER BY id;
ROLLBACK;
SELECT id, balance FROM accounts ORDER BY id;

Result:

CREATE PROCEDURE deposit(account INT, amt DECIMAL(2)) AS BEGIN

procedure 'deposit' created

BEGIN

transaction started

CALL deposit(2, 100)

procedure 'deposit' done: 1 row(s) changed

CALL deposit(3, 100)

procedure 'deposit' done: 1 row(s) changed

SELECT id, balance FROM accounts ORDER BY id

id (INT)balance (DECIMAL(2))
11000.00
2100.00
3600.00

(3 rows)

ROLLBACK

transaction rolled back: 2 change(s) undone

SELECT id, balance FROM accounts ORDER BY id

id (INT)balance (DECIMAL(2))
11000.00
20.00
3500.00

(3 rows)

A CALL that only reads

Syntax:

CREATE PROCEDURE name(...) READ ONLY AS BEGIN ... END   -- promised: it never changes data
CALL name(...)                                          -- runs like a SELECT

A procedure that only reads (a report, a lookup, a chart query) runs like a SELECT:

KDB works this out by itself before each CALL, from everything the body runs: the procedures it calls, the views and saved functions it uses. A CALL stays a normal transaction (it locks its tables) when its body, or anything it calls, has one of these:

In the bodyWhy
INSERT, UPDATE, DELETEit changes data
SELECT ... FOR UPDATEit locks rows for a later change
nextval, setval, set_config, pg_advisory_..., pg_sleepthey change state, or wait on purpose
EXECUTE of text made while it runs ('...' || name)KDB cannot tell what it runs
SHOW ..., information_schema or pg_catalogthey read the whole database
EXPLAIN CALLit works through another procedure's steps

EXPLAIN of anything else only shows a plan, so it does not count as a change.

Write READ ONLY to make it a promise:

Example: A report procedure that only reads

CREATE PROCEDURE rich_accounts(at_least DECIMAL(2)) READ ONLY AS BEGIN
  SELECT id, owner, balance FROM accounts WHERE balance >= at_least ORDER BY id;
  PRINT concat((SELECT count(*) FROM ledger), ' ledger rows');
END;
CALL rich_accounts(100);

Result:

CREATE PROCEDURE rich_accounts(at_least DECIMAL(2)) READ ONLY AS BEGIN

procedure 'rich_accounts' created

CALL rich_accounts(100)

procedure 'rich_accounts' done: 0 row(s) changed

SELECT id, owner, balance FROM accounts WHERE balance >= at_least ORDER BY id

id (INT)owner (STRING)balance (DECIMAL(2))
1Ram1000.00
3Hari500.00

(2 rows)

print

output (STRING)
0 ledger rows

(1 row)

Example: READ ONLY refuses a body that changes data (shows an error)

CREATE PROCEDURE empty_accounts() READ ONLY AS BEGIN
  UPDATE accounts SET balance = 0;
END;

Result:

Error: procedure 'empty_accounts' cannot be READ ONLY: it changes table 'accounts'. Take READ ONLY away to let it change data

Matching another system's rounding

A DOUBLE keeps about 16 digits: 9609.0 / 17 is 565.2352941176... in KDB. Many programs round a rate before they store it, for example to 6 places: 565.235294.

When a procedure takes over a job from such a program, round the same way, with round(x, 6):

WhereWhy
every rate or ratio the procedure storesthe stored numbers then equal the old program's, digit for digit
both sides of a comparison in a filtera difference in the 7th place can flip a > b, so a row appears or disappears

Example: Store the buy rate rounded to 6 places

Setup:

CREATE TABLE broker_day (broker INT, amount DOUBLE, quantity LONG);
INSERT INTO broker_day VALUES (1, 9609, 17), (2, 1245.5, 3);
CREATE TABLE broker_rate (broker INT PRIMARY KEY, buy_rate DOUBLE, buy_rate_full DOUBLE);
CREATE PROCEDURE build_rates() AS BEGIN
  DELETE FROM broker_rate;
  INSERT INTO broker_rate SELECT broker, round(amount / quantity, 6), amount / quantity FROM broker_day;
END;
CALL build_rates();
SELECT broker, buy_rate, buy_rate_full FROM broker_rate ORDER BY broker;

Result:

CALL build_rates()

procedure 'build_rates' done: 2 row(s) changed

SELECT broker, buy_rate, buy_rate_full FROM broker_rate ORDER BY broker

broker (INT)buy_rate (DOUBLE)buy_rate_full (DOUBLE)
1565.235294565.235294117647
2415.166667415.166666666667

(2 rows)

Example: Round both sides before a filter compares them

SELECT 9609.0 / 17 > 565.235294 AS full_is_bigger,
       round(9609.0 / 17, 6) > 565.235294 AS rounded_is_bigger;

Result:

full_is_bigger (BOOL)rounded_is_bigger (BOOL)
truefalse

(1 row)

CALL ... WITH DEBUG

Syntax:

CALL name(argument, ...) WITH DEBUG

Runs the procedure and adds a result called debug, with one row for every step it took. Use it to find out why a procedure does something you did not expect.

ColumnMeaning
step1, 2, 3, ... in the order the steps started
routinethe procedure the step is in, or trigger name for a trigger. Empty for the CALL itself.
statementthe statement, or the control line: IF ... THEN, ELSIF ... THEN, ELSE, WHILE ... DO, FOR r IN (...) DO, FOR r: row 2 of 5
rowsrows changed (INSERT, UPDATE, DELETE, CALL), rows returned (SELECT), rows read (OPEN, FOR)
time_usthe time of the step in microseconds (millionths of a second). The first row is the whole CALL.
changesvariables that got a new value (have=1000.00), the result of a condition (true or false), the PRINT text, the values of a FOR row, or failed: <error>. A statement that read a table also says partitions read: N; so do IF, WHILE and FOR lines whose query read one, and the CALL row counts the whole call.

Example: Every step of a transfer

CREATE PROCEDURE transfer(from_id INT, to_id INT, amt DECIMAL(2)) AS BEGIN
  DECLARE have DECIMAL(2) DEFAULT (SELECT balance FROM accounts WHERE id = from_id);
  IF have < amt THEN
    RAISE concat('not enough money in account ', from_id);
  ELSIF amt >= 500 THEN
    PRINT 'big transfer: check it by hand';
  END IF;
  UPDATE accounts SET balance = balance - amt WHERE id = from_id;
  UPDATE accounts SET balance = balance + amt WHERE id = to_id;
  INSERT INTO ledger (from_account, to_account, amount) VALUES (from_id, to_id, amt);
END;
CALL transfer(1, 2, 600) WITH DEBUG;

Result:

CREATE PROCEDURE transfer(from_id INT, to_id INT, amt DECIMAL(2)) AS BEGIN

procedure 'transfer' created

CALL transfer(1, 2, 600) WITH DEBUG

procedure 'transfer' done: 3 row(s) changed

debug

step (LONG)routine (STRING)statement (STRING)rows (LONG)time_us (LONG)changes (STRING)
1CALL transfer(1, 2, 600) WITH DEBUG33276
2transferDECLARE have DECIMAL(2) DEFAULT (SELECT balance FROM accounts WHERE id = from_id)0123have=1000.00
3transferIF have < amt THEN05false
4transferELSIF amt >= 500 THEN02true
5transferPRINT 'big transfer: check it by hand'01big transfer: check it by hand
6transferUPDATE accounts SET balance = balance - amt WHERE id = from_id161
7transferUPDATE accounts SET balance = balance + amt WHERE id = to_id117
8transferINSERT INTO ledger (from_account, to_account, amount) VALUES (from_id, to_id, amt)194

(8 rows)

Example: The debug rows of a CALL that fails (shows an error)

CREATE PROCEDURE transfer(from_id INT, to_id INT, amt DECIMAL(2)) AS BEGIN
  DECLARE have DECIMAL(2) DEFAULT (SELECT balance FROM accounts WHERE id = from_id);
  IF have < amt THEN
    RAISE concat('not enough money in account ', from_id, ': it has ', have);
  END IF;
  UPDATE accounts SET balance = balance - amt WHERE id = from_id;
  UPDATE accounts SET balance = balance + amt WHERE id = to_id;
END;
CALL transfer(2, 1, 50) WITH DEBUG;

Result:

CREATE PROCEDURE transfer(from_id INT, to_id INT, amt DECIMAL(2)) AS BEGIN

procedure 'transfer' created

debug

step (LONG)routine (STRING)statement (STRING)rows (LONG)time_us (LONG)changes (STRING)
1CALL transfer(2, 1, 50) WITH DEBUG0314failed
2transferDECLARE have DECIMAL(2) DEFAULT (SELECT balance FROM accounts WHERE id = from_id)0157have=0.00
3transferIF have < amt THEN010true
4transferRAISE concat('not enough money in account ', from_id, ': it has ', have)097failed: not enough money in account 2: it has 0.00

(4 rows)

Error: procedure 'transfer', line 4: not enough money in account 2: it has 0.00

DROP PROCEDURE

Syntax:

DROP PROCEDURE [IF EXISTS] name

Removes a procedure.

Example

CREATE PROCEDURE hello() AS BEGIN PRINT 'hello'; END;
DROP PROCEDURE hello;
DROP PROCEDURE IF EXISTS hello;

Result:

CREATE PROCEDURE hello() AS BEGIN

procedure 'hello' created

DROP PROCEDURE hello

procedure 'hello' dropped

DROP PROCEDURE IF EXISTS hello

procedure 'hello' does not exist

Example: A trigger that calls a dropped procedure fails (shows an error)

CREATE TABLE posts (id INT PRIMARY KEY, body STRING);
CREATE PROCEDURE recount() AS BEGIN PRINT 'counting'; END;
CREATE TRIGGER count_posts AFTER INSERT ON posts EXECUTE CALL recount();
DROP PROCEDURE recount;
INSERT INTO posts VALUES (1, 'hello');

Result:

CREATE TABLE posts (

table 'posts' created

CREATE PROCEDURE recount() AS BEGIN

procedure 'recount' created

CREATE TRIGGER count_posts AFTER INSERT ON posts EXECUTE CALL recount()

trigger 'count_posts' created on table 'posts'

DROP PROCEDURE recount

procedure 'recount' dropped

Error: trigger 'count_posts', line 1: procedure 'recount' does not exist (SHOW PROCEDURES lists them)

SHOW PROCEDURES

Syntax:

SHOW PROCEDURES

Lists the procedures of the database, in name order.

ColumnMeaning
namethe procedure name
argumentsthe parameters and their types
definitionthe whole CREATE PROCEDURE statement, written again by KDB (one statement per line)

Example

CREATE PROCEDURE deposit(account INT, amt DECIMAL(2)) AS BEGIN
  UPDATE accounts SET balance = balance + amt WHERE id = account;
END;
CREATE PROCEDURE hello() AS BEGIN PRINT 'hello'; END;
SHOW PROCEDURES;

Result:

CREATE PROCEDURE deposit(account INT, amt DECIMAL(2)) AS BEGIN

procedure 'deposit' created

CREATE PROCEDURE hello() AS BEGIN

procedure 'hello' created

SHOW PROCEDURES

name (STRING)arguments (STRING)definition (STRING)
depositaccount INT, amt DECIMAL(2)CREATE PROCEDURE deposit(account INT, amt DECIMAL(2)) AS BEGIN UPDATE accounts SET balance = balance + amt WHERE id = account; END
helloCREATE PROCEDURE hello() AS BEGIN PRINT 'hello'; END

(2 rows)