# SQL command groups: DDL, DML, DCL, TCL, DQL > The five groups of SQL statements, every KDB statement in its group, and the role each group needs. - Category: Getting started - Keywords: DDL, DML, DCL, TCL, DQL, data definition language, data manipulation language, data control language, transaction control language, data query language, command groups, statement types, kinds of statements, sublanguage, which role do I need, TRUNCATE, SAVEPOINT - See also: [SQL basics](sql-basics.md), [CREATE TABLE](create-table.md), [ALTER, RENAME and DROP TABLE](alter-table.md), [Views](views.md), [Indexes](indexes.md), [INSERT](insert.md), [UPDATE and DELETE](update-delete.md), [Users and roles](users-and-roles.md), [Transactions](transactions.md), [SELECT](select.md), [SHOW TABLES, COLUMNS, CREATE TABLE](show-tables.md) SQL statements are sorted into five **groups** by what they do. The group tells you three things at once: what the statement changes, which [role](users-and-roles.md#roles) a user needs for it, and whether it can go inside [BEGIN ... COMMIT](transactions.md). | Group | Name | What it does | Main statements | Role needed | |---|---|---|---|---| | **DDL** | Data Definition Language | makes, changes and removes **objects**: tables, views, indexes, functions, procedures, triggers | `CREATE`, `ALTER`, `DROP`, `RENAME` | `admin` | | **DML** | Data Manipulation Language | changes the **rows** inside tables | `INSERT`, `UPDATE`, `DELETE`, `CALL` | `write` | | **DCL** | Data Control Language | says **who** may do what | `CREATE USER`, `GRANT`, `REVOKE` | `admin` as the user's own `ROLE` | | **TCL** | Transaction Control Language | groups changes so they are saved **together or not at all** | `BEGIN`, `COMMIT`, `ROLLBACK` | `read` | | **DQL** | Data Query Language | **reads** rows and never changes anything | `SELECT`, `EXPLAIN`, `SHOW` | `read` | - Roles are checked only by `kdb serve` (HTTP, web console and the PostgreSQL port). `kdb sql` and `kdb shell` open the files directly and may run everything. - Some books put `SELECT` into DML. KDB's help keeps it apart as DQL, because a read needs only the `read` role. - The web console lists what DDL made: open the **Objects** list on the left (see [web console](web-console.md#tables)). ## DDL: Data Definition Language DDL statements build the **shape** of a database. They change objects, not rows (except `DROP`, which removes the rows with the table). | Object | Make | Change | Remove | Help | |---|---|---|---|---| | table | `CREATE TABLE` | `ALTER TABLE`, `RENAME TABLE` | `DROP TABLE`, then `UNDROP TABLE` to bring it back | [create-table](create-table.md), [alter-table](alter-table.md), [trash](trash.md) | | view | `CREATE VIEW`, `CREATE CACHED VIEW` | drop and create again | `DROP VIEW` | [views](views.md) | | materialized view | `CREATE MATERIALIZED VIEW` | `ALTER TABLE ... SET TTL`; otherwise drop and create again | `DROP MATERIALIZED VIEW` | [views](views.md#materialized-view) | | index | `CREATE INDEX`, `CREATE UNIQUE INDEX`, `CREATE FULLTEXT INDEX` | - | `DROP INDEX` | [indexes](indexes.md) | | saved function | `CREATE FUNCTION` | `CREATE OR REPLACE FUNCTION` | `DROP FUNCTION` | [saved-functions](saved-functions.md) | | stored procedure | `CREATE PROCEDURE` | `CREATE OR REPLACE PROCEDURE` | `DROP PROCEDURE` | [procedures](procedures.md) | | trigger | `CREATE TRIGGER` | `ALTER TRIGGER ... ENABLE / DISABLE` | `DROP TRIGGER` | [triggers](triggers.md) | | sequence | `CREATE SEQUENCE` | `ALTER SEQUENCE` | `DROP SEQUENCE` | [functions-postgres](functions-postgres.md) | | ENUM type | `CREATE TYPE ... AS ENUM` | `ALTER TYPE ... ADD VALUE` | `DROP TYPE` | [column-types](column-types.md#enum) | | trading calendar | `CREATE CALENDAR` | `ALTER CALENDAR ... ADD HOLIDAY` / `ADD ERA` / ... | `DROP CALENDAR` | [calendars](calendars.md) | | database | `CREATE DATABASE` | `ALTER DATABASE` | `DROP DATABASE` | [databases](databases.md) | - **Role:** `admin`. Databases need `admin` as the user's own `ROLE`. - **Notes:** - Each DDL statement is saved on its own, at once. Most of them can not go inside `BEGIN` ... `COMMIT` (see [what is not allowed inside a transaction](transactions.md#not-inside)). - Look at what DDL made with `SHOW TABLES`, `SHOW COLUMNS FROM t`, `SHOW CREATE TABLE t`, `SHOW VIEWS`, `SHOW INDEXES`, `SHOW FUNCTIONS`, `SHOW PROCEDURES` and `SHOW TRIGGERS` (see [show-tables](show-tables.md)). - `TRUNCATE TABLE t` empties a table and keeps it (see [UPDATE and DELETE](update-delete.md#truncate)). PostgreSQL counts it as DDL. **Example: DDL: a table, a column added later, a view and an index** (sample data: sample) ```sql CREATE TABLE watchlist (symbol STRING PRIMARY KEY, note STRING); ALTER TABLE watchlist ADD COLUMN target DOUBLE; CREATE VIEW watchlist_names AS SELECT w.symbol, s.name, w.target FROM watchlist w JOIN securities s ON s.symbol = w.symbol; CREATE INDEX watchlist_note ON watchlist (note); SHOW COLUMNS FROM watchlist; SHOW VIEWS; ``` Result: `CREATE TABLE watchlist (` ```text table 'watchlist' created ``` `ALTER TABLE watchlist ADD COLUMN target DOUBLE` ```text table 'watchlist' altered ``` `CREATE VIEW watchlist_names AS SELECT w.symbol, s.name, w.target FROM watchlist AS w JOIN securities` ```text view 'watchlist_names' created ``` `CREATE INDEX watchlist_note ON watchlist (note)` ```text index 'watchlist_note' created on table 'watchlist' (0 rows) ``` `SHOW COLUMNS FROM watchlist` | name (STRING) | type (STRING) | primary_key (BOOL) | not_null (BOOL) | computed (BOOL) | references (STRING) | |---|---|---|---|---|---| | symbol | STRING | true | false | false | NULL | | note | STRING | false | false | false | NULL | | target | DOUBLE | false | false | false | NULL | (3 rows) `SHOW VIEWS` | name (STRING) | query (STRING) | kind (STRING) | refresh (STRING) | |---|---|---|---| | watchlist_names | SELECT w.symbol, s.name, w.target FROM watchlist AS w JOIN securities AS s ON s.symbol = w.symbol | complex | NULL | (1 row) ## DML: Data Manipulation Language DML statements change the **rows** of tables. The table itself stays as it is. | Statement | What it does | Help | |---|---|---| | `INSERT INTO ... VALUES` | adds rows | [insert](insert.md) | | `INSERT OR REPLACE`, `INSERT OR IGNORE`, `ON CONFLICT` | adds rows, or replaces / keeps the row with the same key | [insert](insert.md#insert-or-replace) | | `INSERT INTO ... SELECT` | adds the rows a query gives | [insert](insert.md#insert-select) | | `UPDATE ... SET ... WHERE` | changes rows | [update-delete](update-delete.md#update) | | `DELETE FROM ... WHERE` | removes rows | [update-delete](update-delete.md#delete) | | `... RETURNING` | shows the rows that were added, changed or removed | [update-delete](update-delete.md#returning) | | `CALL name(...)` | runs a stored procedure, which changes rows | [procedures](procedures.md) | - **Role:** `write`. - **Notes:** - Each statement is all or nothing: an `INSERT` of 5000 rows with one bad row saves no row. - To make **many** DML statements succeed or fail together, put them inside `BEGIN` ... `COMMIT` (TCL). - Triggers run by themselves after (or before) DML on their table. - A view has no rows of its own: DML on a view is refused. Change the table the view reads. **Example: DML: add, change and remove rows** (sample data: sample) Setup: ```sql CREATE TABLE watchlist (symbol STRING PRIMARY KEY, note STRING, target DOUBLE); ``` ```sql INSERT INTO watchlist VALUES ('CITY', 'bank', 520), ('VOLT', 'hydro', 250); UPDATE watchlist SET target = 540 WHERE symbol = 'CITY'; DELETE FROM watchlist WHERE symbol = 'VOLT' RETURNING symbol, note; SELECT * FROM watchlist; ``` Result: `INSERT INTO watchlist VALUES ('CITY', 'bank', 520), ('VOLT', 'hydro', 250)` ```text 2 row(s) inserted ``` `UPDATE watchlist SET target = 540 WHERE symbol = 'CITY'` ```text 1 row(s) updated ``` `DELETE FROM watchlist WHERE symbol = 'VOLT' RETURNING symbol, note` | symbol (STRING) | note (STRING) | |---|---| | VOLT | hydro | (1 row) ```text 1 row(s) deleted ``` `SELECT * FROM watchlist` | symbol (STRING) | note (STRING) | target (DOUBLE) | |---|---|---| | CITY | bank | 540 | (1 row) ## DCL: Data Control Language DCL statements decide **who** may connect and what each user may do. | Statement | What it does | Help | |---|---|---| | `CREATE USER name PASSWORD '...' ROLE ...` | adds a user with a role for every database | [users-and-roles](users-and-roles.md#create-user) | | `ALTER USER` | changes a password or a role | [users-and-roles](users-and-roles.md#alter-user) | | `DROP USER` | removes a user | [users-and-roles](users-and-roles.md#drop-user) | | `GRANT role ON DATABASE db TO name` | gives a role on one database | [users-and-roles](users-and-roles.md#grant) | | `GRANT ... ON TABLE` | gives rights on one table | [table-rights](table-rights.md) | | `REVOKE ... FROM name` | takes a grant away | [users-and-roles](users-and-roles.md#revoke) | | `SHOW USERS` | lists users, roles and grants | [users-and-roles](users-and-roles.md#show-users) | The roles, from least to most: `none` < `read` < `write` < `admin`. Each role may do everything of the roles before it. - **Role:** `admin` as the user's **own** `ROLE` (a `GRANT` of admin on one database is not enough), because users belong to the whole server. Changing your own password needs no role. - **Notes:** - The **first** user must have `ROLE admin`. From then on, `kdb serve` asks everyone to log in. - Users are kept in the `_system` database, not in the database you are using. - Give each program only what it needs: an app's backend `write` on its own database, a report script `read`. **Example: DCL: an admin, a read-only report user, and an app that may write one database** (sample data: sample) Setup: ```sql CREATE USER admin WITH PASSWORD 'Long-Admin-Passw0rd' ROLE admin; ``` ```sql CREATE USER report WITH PASSWORD 'Reports-only-secret-7' ROLE read; CREATE USER shop_app WITH PASSWORD 'Another-long-secret-42' ROLE none; GRANT write ON DATABASE demo TO shop_app; SHOW USERS; ``` Result: `CREATE USER report WITH PASSWORD '***' ROLE read` ```text user 'report' created with role read ``` `CREATE USER shop_app WITH PASSWORD '***' ROLE none` ```text user 'shop_app' created with role none ``` `GRANT write ON DATABASE demo TO shop_app` ```text user 'shop_app' has role write on database 'demo' ``` `SHOW USERS` | name (STRING) | role (STRING) | grants (STRING) | created (TIMESTAMP) | changed (TIMESTAMP) | table_grants (STRING) | two_factor (BOOL) | statement_timeout (STRING) | priority (STRING) | |---|---|---|---|---|---|---|---|---| | admin | admin | NULL | 2026-10-22T05:44:28.977Z | 2026-10-22T05:44:28.977Z | NULL | false | NULL | NULL | | report | read | NULL | 2026-10-22T05:44:29.197Z | 2026-10-22T05:44:29.197Z | NULL | false | NULL | NULL | | shop_app | none | demo=write | 2026-10-22T05:44:29.265Z | 2026-10-22T05:44:29.268Z | NULL | false | NULL | NULL | (3 rows) ## TCL: Transaction Control Language TCL statements group DML into one **transaction**: KDB saves all of its changes together, or none of them. | Statement | What it does | Help | |---|---|---| | `BEGIN` (also `BEGIN TRANSACTION`, `BEGIN WORK`) | starts a transaction | [transactions](transactions.md#begin) | | `COMMIT` | saves every change since `BEGIN`, in one write | [transactions](transactions.md#commit) | | `ROLLBACK` | throws away every change since `BEGIN` | [transactions](transactions.md#rollback) | | `SELECT ... FOR UPDATE` | reads rows that no other session may change before you `COMMIT` | [transactions](transactions.md#for-update) | - **Role:** `read` to start and end one. The statements inside still need their own role (`write` for DML). - **Notes:** - Without `BEGIN`, every statement is its own small transaction and is saved at once. - When a statement inside the transaction fails, KDB undoes the whole transaction. - `SAVEPOINT`, `ROLLBACK TO SAVEPOINT` and `RELEASE SAVEPOINT` undo part of a transaction ([savepoints](transactions.md#savepoint)). - One writer at a time: while a transaction changes rows, other writers wait (see [one writer at a time](transactions.md#one-writer)). **Example: TCL: a transfer that is saved, and a mistake that is undone** (sample data: sample) Setup: ```sql CREATE TABLE accounts (id INT PRIMARY KEY, owner STRING NOT NULL, balance DECIMAL(2) NOT NULL); INSERT INTO accounts VALUES (1, 'Ram', 1000), (2, 'Sita', 0); ``` ```sql BEGIN; UPDATE accounts SET balance = balance - 300 WHERE id = 1; UPDATE accounts SET balance = balance + 300 WHERE id = 2; COMMIT; BEGIN; DELETE FROM accounts; ROLLBACK; SELECT * FROM accounts ORDER BY id; ``` Result: `BEGIN` ```text transaction started ``` `UPDATE accounts SET balance = balance - 300 WHERE id = 1` ```text 1 row(s) updated ``` `UPDATE accounts SET balance = balance + 300 WHERE id = 2` ```text 1 row(s) updated ``` `COMMIT` ```text transaction committed: 2 change(s) saved ``` `BEGIN` ```text transaction started ``` `DELETE FROM accounts` ```text 2 row(s) deleted ``` `ROLLBACK` ```text transaction rolled back: 1 change(s) undone ``` `SELECT * FROM accounts ORDER BY id` | id (INT) | owner (STRING) | balance (DECIMAL(2)) | |---|---|---| | 1 | Ram | 700.00 | | 2 | Sita | 300.00 | (2 rows) ## DQL: Data Query Language DQL statements **read**. They never change a row or an object, so they are safe to run at any time. | Statement | What it does | Help | |---|---|---| | `SELECT` | reads rows: `WHERE`, `JOIN`, `GROUP BY`, `ORDER BY`, `LIMIT` | [select](select.md) | | `SELECT ... SAMPLE BY`, `LATEST ON` | time-series candles and the newest row per key | [sample-by](sample-by.md), [latest-on](latest-on.md) | | `WITH ... SELECT` | a query with named parts | [union-subqueries](union-subqueries.md) | | `EXPLAIN SELECT ...` | shows how KDB will run a query, without running it | [explain](explain.md) | | `SHOW TABLES`, `SHOW VIEWS`, `SHOW INDEXES`, ... | lists the objects of a database | [show-tables](show-tables.md) | | `DECLARE`, `FETCH`, `CLOSE` | reads a big result a part at a time | [cursors](cursors.md) | - **Role:** `read`. - **Notes:** - Selecting from a view is DQL too: the view's query runs, and nothing is changed. - `SELECT ... FOR UPDATE` is the one read that waits for writers; it belongs with TCL. **Example: DQL: a report, a query plan and a list of objects** (sample data: sample) ```sql SELECT symbol, count(*) AS trades, sum(quantity) AS shares FROM trade_log GROUP BY symbol ORDER BY trades DESC, symbol LIMIT 3; EXPLAIN SELECT * FROM ochl_day WHERE symbol = 'CITY'; SHOW TABLES; ``` Result: `SELECT symbol, count(*) AS trades, sum(quantity) AS shares FROM trade_log GROUP BY symbol ORDER BY t` | symbol (SYMBOL) | trades (LONG) | shares (LONG) | |---|---|---| | CITY | 11 | 2420 | | COAST | 11 | 3070 | | TELCO | 6 | 2770 | (3 rows) `EXPLAIN SELECT * FROM ochl_day WHERE symbol = 'CITY'` | plan (STRING) | |---| | scan table ochl_day (2 of 2 partitions, about 57 rows) | | read only 1 key range(s) of each partition | | filter while scanning: 1 condition(s) | | engine: column | (4 rows) `SHOW TABLES` | name (STRING) | kind (STRING) | partition (STRING) | columns (INT) | rows (LONG) | |---|---|---|---|---| | brokers | relational | NONE | 2 | 5 | | live_data | timeseries | DAY | 12 | 12 | | ochl_day | timeseries | YEAR | 7 | 57 | | ochl_minutes | timeseries | MONTH | 7 | 956 | | securities | relational | NONE | 3 | 5 | | trade_log | timeseries | DAY | 8 | 32 | (6 rows) ## Which group is my statement? | Starts with | Group | |---|---| | `CREATE`, `ALTER`, `DROP`, `RENAME`, `UNDROP` (tables, views, indexes, functions, procedures, triggers, sequences, databases) | DDL | | `INSERT`, `UPDATE`, `DELETE`, `CALL` | DML | | `CREATE USER`, `ALTER USER`, `DROP USER`, `GRANT`, `REVOKE` | DCL | | `BEGIN`, `COMMIT`, `ROLLBACK` | TCL | | `SELECT`, `WITH`, `EXPLAIN`, `SHOW`, `DECLARE`, `FETCH`, `CLOSE` | DQL | Other statements belong to looking after the database, not to one of the five groups: `BACKUP DATABASE`, `CHECK TABLE`, `CHECKPOINT` (see [backup and safety](backup-and-safety.md)) and `USE` (see [databases](databases.md#use)).