Skip to content

MySQL Tutorial SQL Practice and Interview Preparation

MySQL Cheat Sheet and What to Learn Next

Lesson 308 of 308 4 min read By Deepak Paswan Updated Oct 5, 2026

A one-page reference for the statements you use every day, with links to the lessons that explain them. For anything not shown here, use HELP in the client, or the lesson index of this course.

Connect and look around

terminal
BASH
mysql -u user -p -h host -P 3306 dbname
query.sql
SQL
SHOW DATABASES;                              -- list databases
USE shop;                                    -- choose a database
SHOW TABLES;                                 -- list tables
DESCRIBE orders;                             -- columns of a table (also: SHOW COLUMNS FROM orders)
SHOW CREATE TABLE orders\G                   -- the exact CREATE statement
SELECT DATABASE(), CURRENT_USER(), VERSION();

See Getting Started and Show and Describe.

Databases and tables

query.sql
SQL
CREATE DATABASE shop CHARACTER SET utf8mb4;
DROP DATABASE IF EXISTS shop;

CREATE TABLE customers (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(60) NOT NULL,
    email VARCHAR(100) NOT NULL,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY uq_customers_email (email)
);

ALTER TABLE customers ADD COLUMN phone VARCHAR(20) NULL AFTER email;
ALTER TABLE customers MODIFY phone VARCHAR(30) NOT NULL DEFAULT '';
ALTER TABLE customers DROP COLUMN phone;
ALTER TABLE customers RENAME COLUMN name TO full_name;
RENAME TABLE customers TO clients;
TRUNCATE TABLE clients;
DROP TABLE IF EXISTS clients;

Data types in one table

NeedType
Whole numbersTINYINT, SMALLINT, INT, BIGINT (add UNSIGNED for non-negative)
Money, exact decimalsDECIMAL(10,2)
Approximate numbersFLOAT, DOUBLE (never money)
Short textCHAR(n) (fixed), VARCHAR(n)
Long textTEXT, MEDIUMTEXT, LONGTEXT
Yes or noTINYINT(1) / BOOLEAN
Date, date and timeDATE, DATETIME, TIMESTAMP (to 2038)
A fixed set of valuesENUM('a','b')
Flexible structured dataJSON
Binary data, UUIDsBLOB, BINARY(16)

More: Choosing Data Types.

Insert, update, delete

query.sql
SQL
INSERT INTO customers (name, email) VALUES ('Maya', 'maya@example.com'), ('Ben', 'ben@example.com');
INSERT INTO archive SELECT * FROM customers WHERE created_at < '2025-01-01';
INSERT INTO customers (name, email) VALUES ('Maya', 'maya@example.com') ON DUPLICATE KEY UPDATE name = VALUES(name);
UPDATE customers SET name = 'Maya Shah' WHERE id = 1;
DELETE FROM customers WHERE id = 1;
SELECT ROW_COUNT(), LAST_INSERT_ID();

Always check a WHERE with a SELECT first (Update Safety).

SELECT: the clauses in order

query.sql
SQL
SELECT   DISTINCT c.city, COUNT(*) AS customers, AVG(o.total) AS avg_total
FROM     customers c
JOIN     orders o ON o.customer_id = c.id
WHERE    o.status = 'paid' AND o.ordered_on >= '2026-01-01'
GROUP BY c.city
HAVING   COUNT(*) > 5
ORDER BY customers DESC, c.city
LIMIT    10 OFFSET 20;

Written order: SELECT, FROM, JOIN, WHERE, GROUP BY, HAVING, ORDER BY, LIMIT. Run order: FROM, WHERE, GROUP BY, HAVING, SELECT, ORDER BY, LIMIT (Execution Order).

Filtering

PatternExample
Comparison= <> < <= > >=
Several valuescity IN ('Pune', 'Delhi')
Rangeprice BETWEEN 10 AND 20 (both ends included)
Text patternname LIKE 'A%', '%a', '_a%'
Regular expressionname REGEXP '^[A-M]'
Missing valuesphone IS NULL, phone IS NOT NULL, a <=> b (NULL-safe)
CombineAND, OR, NOT, with parentheses
Date rangesd >= '2026-03-01' AND d < '2026-04-01'

Joins

query.sql
SQL
FROM a JOIN b ON b.a_id = a.id                  -- INNER: matching rows only
FROM a LEFT JOIN b ON b.a_id = a.id             -- all of a, b where it matches
FROM a LEFT JOIN b ON b.a_id = a.id WHERE b.id IS NULL     -- rows of a with no match
FROM a JOIN a AS parent ON parent.id = a.parent_id          -- self join
FROM a CROSS JOIN b                             -- every combination

MySQL has no FULL OUTER JOIN: use LEFT JOIN ... UNION ... RIGHT JOIN (Joins).

Subqueries, CTEs and set operations

query.sql
SQL
SELECT * FROM products WHERE price > (SELECT AVG(price) FROM products);
SELECT * FROM customers c WHERE EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.id);

WITH monthly AS (SELECT DATE_FORMAT(d, '%Y-%m') AS m, SUM(x) AS s FROM t GROUP BY m)
SELECT * FROM monthly;

WITH RECURSIVE n AS (SELECT 1 AS i UNION ALL SELECT i + 1 FROM n WHERE i < 10) SELECT i FROM n;

SELECT a FROM t1 UNION SELECT a FROM t2;          -- distinct   (UNION ALL keeps duplicates)
SELECT a FROM t1 INTERSECT SELECT a FROM t2;      -- MySQL 8.0.31 and later
SELECT a FROM t1 EXCEPT SELECT a FROM t2;

Common functions

GroupFunctions
TextCONCAT, CONCAT_WS, LENGTH, CHAR_LENGTH, UPPER, LOWER, TRIM, SUBSTRING, REPLACE, LEFT, RIGHT, LPAD, INSTR
NumbersROUND, CEIL, FLOOR, ABS, MOD, POW, RAND
DatesNOW(), CURDATE(), DATE_ADD, DATE_SUB, DATEDIFF, TIMESTAMPDIFF, DATE_FORMAT, STR_TO_DATE, EXTRACT
ConditionsIF, IFNULL, COALESCE, NULLIF, CASE WHEN ... THEN ... END
AggregatesCOUNT, SUM, AVG, MIN, MAX, GROUP_CONCAT
JSONJSON_OBJECT, JSON_EXTRACT / ->, ->>, JSON_SET, JSON_TABLE
ConversionCAST(x AS UNSIGNED), CONVERT(x, CHAR)

Window functions

query.sql
SQL
ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC)
RANK() OVER w          DENSE_RANK() OVER w         NTILE(4) OVER w
LAG(x) OVER w          LEAD(x) OVER w              FIRST_VALUE(x) OVER w
SUM(x) OVER (ORDER BY d)                                        -- running total
AVG(x) OVER (ORDER BY d ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)   -- moving average
... WINDOW w AS (PARTITION BY dept ORDER BY salary DESC)

Constraints, indexes and keys

query.sql
SQL
PRIMARY KEY (id)
UNIQUE KEY uq_email (email)
FOREIGN KEY (customer_id) REFERENCES customers (id) ON DELETE CASCADE
CHECK (price >= 0)
NOT NULL    DEFAULT 0

CREATE INDEX idx_orders_customer_date ON orders (customer_id, ordered_on);
CREATE FULLTEXT INDEX ft_body ON posts (title, body);
DROP INDEX idx_orders_customer_date ON orders;
EXPLAIN SELECT ...;           EXPLAIN ANALYZE SELECT ...;
ANALYZE TABLE orders;         OPTIMIZE TABLE orders;

Transactions and locking

query.sql
SQL
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;            -- or ROLLBACK;
SAVEPOINT sp1;     ROLLBACK TO sp1;

SELECT * FROM t WHERE id = 1 FOR UPDATE;                 -- lock the row
SELECT * FROM jobs WHERE status = 'new' LIMIT 1 FOR UPDATE SKIP LOCKED;
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;          -- before START TRANSACTION
SHOW ENGINE INNODB STATUS\G                               -- last deadlock, lock waits

Users and privileges

query.sql
SQL
CREATE USER 'shop_app'@'10.0.0.%' IDENTIFIED BY 'long-random-password';
GRANT SELECT, INSERT, UPDATE, DELETE ON shop.* TO 'shop_app'@'10.0.0.%';
SHOW GRANTS FOR 'shop_app'@'10.0.0.%';
REVOKE DELETE ON shop.* FROM 'shop_app'@'10.0.0.%';
ALTER USER 'shop_app'@'10.0.0.%' IDENTIFIED BY 'new-password' RETAIN CURRENT PASSWORD;
DROP USER 'shop_app'@'10.0.0.%';
CREATE ROLE 'reporting';  GRANT SELECT ON shop.* TO 'reporting';  GRANT 'reporting' TO 'asha'@'%';

Backup, restore, maintenance

terminal
BASH
mysqldump -u backup -p --single-transaction --routines --triggers --events --databases shop > shop.sql
mysql -u root -p < shop.sql
mysqlbinlog --start-position=158 --stop-position=1145 binlog.000002 | mysql -u root -p
mysqlcheck -u root -p --check shop

Useful server information

query.sql
SQL
SHOW VARIABLES LIKE 'max_connections';        SHOW GLOBAL STATUS LIKE 'Threads%';
SHOW FULL PROCESSLIST;                        KILL QUERY 123;
SELECT @@sql_mode, @@transaction_isolation, @@time_zone;
SHOW WARNINGS;                                SHOW ERRORS;

What to learn next

You have covered the whole path from the first SELECT to schema design, performance, security and operations. Where to go from here:

  1. Use it in a project. Pick one of the ten projects, change the requirements, and build it with your own data and a real application.
  2. Connect it to code. Use MySQL from PHP with PDO and prepared statements, and read the PHP course for the application side.
  3. Learn to read plans. Take your slowest query, run EXPLAIN ANALYZE, and improve it (Query Tuning Workflow).
  4. Practise regularly. Redo the practice exercises and interview problems until they are easy.
  5. Learn operations. Set up a replica, take a backup, break something, and restore it (Backup Strategy).
  6. Look at the neighbours. PostgreSQL, SQLite and the NoSQL systems each solve some problems better. Knowing MySQL well makes learning them quick.
  7. Read the manual. The official MySQL Reference Manual for your version is the final authority on syntax, defaults and version differences.

A note on versions

This course was tested on MySQL 8.4 (LTS) as the main version, and also on 9.7 and 8.0. Differences between them are called out in the lessons (Upgrades). Your server’s SELECT VERSION() and the release notes for it are always the source of truth.

lightbulb
Keep a personal snippet file
Every time you solve a tricky query, save it with a one-line note. In a year you will have a cheat sheet that fits your work better than any general one.