Skip to content
Reference

SQL Commands: DDL, DML, DCL and TCL Explained

7 min readUpdated September 24, 2026Every example verified
On this page 10 sections ▾
  1. The four families at a glance
  2. DDL: defining the structure
  3. DML: changing the rows
  4. DCL: who is allowed to do what
  5. TCL: committing and undoing work
  6. The consequence that actually bites: implicit commit
  7. Sorting any statement into its family
  8. Practice this topic
  9. Frequently asked questions
  10. Related reading

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

SQL command families
FamilyStands forActs onMain statements
DDLData Definition Languagethe structureCREATE, ALTER, DROP, TRUNCATE, RENAME
DMLData Manipulation Languagethe rowsINSERT, UPDATE, DELETE, MERGE
DCLData Control LanguagepermissionsGRANT, REVOKE
TCLTransaction Control Languageunits of workCOMMIT, 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_iddept_name
10Engineering
20Sales
30Marketing
40Support
50Finance

● 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_iddept_namelocationbudget
10EngineeringBengaluru2400000
20SalesLondon980000
30MarketingNew York640000
40SupportBerlin410000
50FinanceSingapore520000
60LegalPune300000

● 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';
nameunit_price
Nimbus Docking Hub208.45
Quill Keyboard130.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';
nameunit_price
Nimbus Docking Hub189.5
Quill Keyboard119

● 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_idnameunit_pricein_stock
201Aurora Laptop 14"129937

● 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

  1. Does it change what the data means or where it lives? Structure → DDL.
  2. Does it change which rows exist or what they contain? Rows → DML.
  3. Does it change who can do something? Permissions → DCL.
  4. Does it mark the start or end of a unit of work? Transactions → TCL.
  5. 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:

See all 42 exercises →

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.

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.