SQL Commands: DDL, DML, DCL and TCL Explained
On this page 10 sections ▾
SQL statements are grouped into four families, and the grouping is not academic trivia: it tells you whether a statement can be undone, whether it fires triggers, and whether running it quietly commits everything you had done before it.
This page defines each family, lists what belongs in it, and then covers the one consequence that catches people out in production. The playground runs SQLite in your browser, transactions included, so most examples here are runnable. Where it cannot run one — SQLite has no TRUNCATE, and a database with no user accounts has nobody to GRANT anything to — the block is labelled rather than given a Run button that would only produce an error.
The four families at a glance
| Family | Stands for | Acts on | Main statements |
|---|---|---|---|
| DDL | Data Definition Language | the structure | CREATE, ALTER, DROP, TRUNCATE, RENAME |
| DML | Data Manipulation Language | the rows | INSERT, UPDATE, DELETE, MERGE |
| DCL | Data Control Language | permissions | GRANT, REVOKE |
| TCL | Transaction Control Language | units of work | COMMIT, ROLLBACK, SAVEPOINT, BEGIN |
Two notes before the detail. SELECT does not fit any of them cleanly — it reads without changing anything, and is sometimes given its own family, DQL (Data Query Language). And TRUNCATE is DDL despite feeling like a delete, which is exactly the kind of surprise the families are useful for predicting.
DDL: defining the structure
DDL creates, changes and removes the objects data lives in. CREATE makes one:
CREATE TABLE Suppliers (
supplier_id INTEGER PRIMARY KEY,
name VARCHAR(100) NOT NULL,
country VARCHAR(60)
);
SELECT * FROM Suppliers;
● 0 rows · the query ran, and nothing in the sample database matched it
An empty table, with its shape defined. ALTER changes that shape afterwards:
ALTER TABLE Departments ADD COLUMN head VARCHAR(100);
SELECT dept_id, dept_name FROM Departments;
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Sales |
| 30 | Marketing |
| 40 | Support |
| 50 | Finance |
● 5 rows · produced by running this query on the sample database
DROP removes the object entirely, and TRUNCATE empties a table while keeping it — both covered in detail on the DELETE vs TRUNCATE vs DROP page, because choosing between them is a common interview question in its own right.
-- MySQL, PostgreSQL, SQL Server and Oracle (SQLite has no TRUNCATE)
TRUNCATE TABLE Orders;
SQLite, the engine behind the playground, has no TRUNCATE. Its equivalent is a DELETE with no WHERE clause. When no triggers or foreign keys are involved, as here, SQLite spots it and clears the table in one step rather than row by row:
DELETE FROM Orders;
SELECT COUNT(*) AS orders_left FROM Orders;
| orders_left |
|---|
| 0 |
● 1 row · produced by running this query on the sample database
DML: changing the rows
DML is the family you use every day. INSERT adds rows:
INSERT INTO Departments (dept_id, dept_name, location, budget)
VALUES (60, 'Legal', 'Pune', 300000);
SELECT * FROM Departments;
| dept_id | dept_name | location | budget |
|---|---|---|---|
| 10 | Engineering | Bengaluru | 2400000 |
| 20 | Sales | London | 980000 |
| 30 | Marketing | New York | 640000 |
| 40 | Support | Berlin | 410000 |
| 50 | Finance | Singapore | 520000 |
| 60 | Legal | Pune | 300000 |
● 6 rows · produced by running this query on the sample database
UPDATE changes existing ones, and DELETE removes them — both taking a WHERE clause that decides which rows are affected:
UPDATE Products
SET unit_price = unit_price * 1.1
WHERE category = 'Accessories';
SELECT name, unit_price FROM Products WHERE category = 'Accessories';
| name | unit_price |
|---|---|
| Nimbus Docking Hub | 208.45 |
| Quill Keyboard | 130.9 |
● 2 rows · produced by running this query on the sample database
Because DML changes data rather than structure, it lives inside the transaction machinery: each change is logged, row triggers fire, and everything can be rolled back until it is committed. The changing data guide covers the habits that keep DML safe.
DCL: who is allowed to do what
Two statements, and they are the whole family. GRANT gives a permission and REVOKE takes it back:
-- PostgreSQL, MySQL, SQL Server and Oracle (SQLite has no users to grant to)
GRANT SELECT ON Employees TO analyst;
REVOKE SELECT ON Employees FROM analyst;
Permissions in SQL are per-object and per-action, which is what makes least privilege practical: a reporting account can be granted SELECT on the tables it needs and nothing else, so a mistake in a report cannot delete anything. Roles group permissions so they can be granted once and assigned to many users.
This is the family that does not exist in the playground, and for a structural reason rather than a missing feature: the database here runs inside your own browser tab with no users, no accounts and nobody else to grant anything to.
TCL: committing and undoing work
A transaction is a group of statements that either all take effect or none do. TCL is how you mark the boundaries:
BEGIN TRANSACTION;
UPDATE Products SET unit_price = unit_price * 1.1
WHERE category = 'Accessories';
-- look at the result, then either keep it with COMMIT,
-- or undo everything since BEGIN:
ROLLBACK;
SELECT name, unit_price FROM Products WHERE category = 'Accessories';
| name | unit_price |
|---|---|
| Nimbus Docking Hub | 189.5 |
| Quill Keyboard | 119 |
● 2 rows · produced by running this query on the sample database
The prices are back to 189.5 and 119, the values they had before the transaction began: the ROLLBACK threw the 10% rise away. Change ROLLBACK to COMMIT and run the block again, and the new prices stay.
SAVEPOINT adds partial undo inside a transaction: set one part-way through, and ROLLBACK TO SAVEPOINT name rewinds to that point without abandoning the whole transaction:
BEGIN TRANSACTION;
UPDATE Products SET in_stock = in_stock - 5 WHERE product_id = 201;
SAVEPOINT before_price_change;
UPDATE Products SET unit_price = 0 WHERE product_id = 201;
ROLLBACK TO SAVEPOINT before_price_change;
COMMIT;
SELECT product_id, name, unit_price, in_stock FROM Products WHERE product_id = 201;
| product_id | name | unit_price | in_stock |
|---|---|---|---|
| 201 | Aurora Laptop 14" | 1299 | 37 |
● 1 row · produced by running this query on the sample database
The mistaken price of 0 was rewound, but the stock change made before the savepoint survived and was committed: 42 laptops in stock became 37.
This is the safety net for a risky UPDATE
Open a transaction, run the UPDATE, SELECT the rows to check what happened, and only then COMMIT — or ROLLBACK if the count is wrong. It turns an irreversible mistake into a reversible one, which is why it is worth the two extra statements on anything that matters.
The consequence that actually bites: implicit commit
In several databases, including Oracle and MySQL, running a DDL statement commits the current transaction automatically, whether you wanted it to or not. So this sequence does not do what it looks like:
-- MySQL (Oracle behaves the same way)
START TRANSACTION;
DELETE FROM Orders WHERE order_total < 2000;
CREATE TABLE audit_note (note VARCHAR(200)); -- DDL: implicit COMMIT here
ROLLBACK; -- too late, the DELETE is permanent
The CREATE TABLE committed the delete before the ROLLBACK was reached. PostgreSQL, SQL Server and SQLite keep most DDL inside the transaction instead; Oracle and MySQL commit it implicitly. That is why migrations on PostgreSQL can be written as all-or-nothing scripts and migrations on MySQL generally cannot.
The playground runs SQLite, so there the same sequence has the opposite ending:
BEGIN TRANSACTION;
DELETE FROM Orders WHERE order_total < 2000;
CREATE TABLE audit_note (note VARCHAR(200));
ROLLBACK;
SELECT COUNT(*) AS orders_now FROM Orders;
| orders_now |
|---|
| 15 |
● 1 row · produced by running this query on the sample database
All 15 orders are still there, and the audit_note table is not: the ROLLBACK undid the delete and the CREATE TABLE together. Add SELECT * FROM audit_note; to the end of the block and SQLite reports that no such table exists.
That single behaviour is why knowing which family a statement belongs to is worth the five minutes it takes to learn. Never mix DDL into a transaction you intend to be able to undo.
Sorting any statement into its family
- Does it change what the data means or where it lives? Structure → DDL.
- Does it change which rows exist or what they contain? Rows → DML.
- Does it change who can do something? Permissions → DCL.
- Does it mark the start or end of a unit of work? Transactions → TCL.
- Does it just read? SELECT — DQL, if your course insists on a name.
Applied to the two statements people most often place wrongly: TRUNCATE is DDL, because it deallocates storage rather than deleting rows one by one, and DELETE is DML, because it removes rows through the normal logged path.
Practice this topic
Reading explains the idea; producing it yourself is what makes it stick. These exercises use exactly what this page covers, and your answer is checked by running it against the same sample database:
- Look at the whole product catalogueEasy
- How many customers do we have?Easy
- Revenue actually shippedMedium
Frequently asked questions
What are DDL, DML, DCL and TCL in SQL?
They are the four families of SQL statements. DDL (Data Definition Language) defines structure with CREATE, ALTER, DROP and TRUNCATE. DML (Data Manipulation Language) changes rows with INSERT, UPDATE and DELETE. DCL (Data Control Language) manages permissions with GRANT and REVOKE. TCL (Transaction Control Language) marks units of work with COMMIT, ROLLBACK and SAVEPOINT.
Is TRUNCATE a DDL or DML command?
DDL. Although it removes rows, it does so by deallocating the storage they occupied rather than deleting them one at a time, which is also why it fires no row triggers, takes no WHERE clause, and in several databases cannot be rolled back.
Which SQL family does SELECT belong to?
Strictly none of the four, because it reads without changing anything. Many courses give it its own family, DQL - Data Query Language. In practice nobody is confused by calling SELECT a query.
What is an implicit commit?
Some databases, notably Oracle and MySQL, commit the current transaction automatically when a DDL statement runs. Anything you had done in that transaction becomes permanent at that moment, so a later ROLLBACK cannot undo it. PostgreSQL, SQL Server and SQLite keep most DDL inside the transaction, which is why their migrations can be all-or-nothing.
Can DDL commands be rolled back?
It depends entirely on the database. PostgreSQL, SQL Server and SQLite allow most DDL inside a transaction to be rolled back; Oracle and MySQL commit it immediately. Never rely on undoing a CREATE, ALTER or DROP without checking your specific database first.
What is the difference between DCL and TCL?
DCL controls who is permitted to do something - GRANT and REVOKE on tables, views and other objects. TCL controls when work becomes permanent - COMMIT, ROLLBACK and SAVEPOINT around a group of statements. One is about access, the other about durability.
Related reading
About the author
SQL Practice
SQL Practice publishes free SQL tutorials, an in-browser playground and 42 practice exercises, built so you can run what you read. Every runnable example in our tutorials is executed against the site’s sample database before it is published. Read how we keep tutorials accurate, or report a mistake.