MySQL Cheat Sheet

Everything below runs on MySQL 8.0, with the 5.7 differences called out where they matter (window functions, CTEs and ON DUPLICATE KEY alias syntax all changed). The reference is grouped the way you work: tables and columns, rows, querying, indexes and keys, and server administration. Every line links to a guide covering what it locks and where it bites.

Tables and columns

Create a table
CREATE TABLE orders (
  id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  customer_id BIGINT UNSIGNED NOT NULL,
  total DECIMAL(10,2) NOT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB;
RENAME TABLE orders TO customer_orders;
CREATE TABLE orders_backup LIKE orders;
INSERT INTO orders_backup SELECT * FROM orders;
ALTER TABLE orders ADD COLUMN status VARCHAR(20) NOT NULL DEFAULT 'new';
ALTER TABLE orders RENAME COLUMN total TO amount;  -- 8.0+
ALTER TABLE orders CHANGE total amount DECIMAL(10,2) NOT NULL;  -- 5.7
ALTER TABLE orders DROP COLUMN status;
ALTER TABLE orders MODIFY COLUMN total DECIMAL(12,2) NOT NULL;
TRUNCATE TABLE orders;
Drop a table
DROP TABLE IF EXISTS orders_backup;
DESCRIBE orders;
SHOW FULL COLUMNS FROM orders;
SHOW CREATE TABLE orders\G

Rows and data

INSERT INTO orders (customer_id, total)
VALUES (1, 49.90), (2, 12.00);
Insert the result of a query
INSERT INTO orders_archive (id, total)
SELECT id, total FROM orders WHERE created_at < NOW() - INTERVAL 1 YEAR;
Read the generated id
SELECT LAST_INSERT_ID();
UPDATE orders SET status = 'shipped' WHERE id = 42;
Update from another table
UPDATE orders o
JOIN order_totals t ON t.order_id = o.id
SET o.total = t.amount;
DELETE FROM orders WHERE created_at < NOW() - INTERVAL 2 YEAR;
INSERT INTO orders (id, total) VALUES (1, 49.90) AS new
ON DUPLICATE KEY UPDATE total = new.total;  -- 8.0.19+
Insert and skip duplicates
INSERT IGNORE INTO orders (id, total) VALUES (1, 49.90);
SELECT email, COUNT(*)
FROM customers
GROUP BY email
HAVING COUNT(*) > 1;
DELETE a FROM customers a
JOIN customers b ON a.email = b.email AND a.id > b.id;

Querying, dates and strings

SELECT * FROM orders ORDER BY created_at DESC LIMIT 50 OFFSET 100;
CREATE OR REPLACE VIEW recent_orders AS
SELECT * FROM orders WHERE created_at > NOW() - INTERVAL 30 DAY;
SELECT CASE WHEN total > 100 THEN 'large' ELSE 'small' END AS bucket
FROM orders;
SELECT CURDATE(), NOW(), UTC_TIMESTAMP();
SELECT DATE_FORMAT(created_at, '%Y-%m-%d %H:%i') FROM orders;
Difference between two dates
SELECT DATEDIFF(delivered_at, created_at) AS days,
       TIMESTAMPDIFF(HOUR, created_at, delivered_at) AS hours
FROM orders;
Add or subtract time
SELECT DATE_ADD(created_at, INTERVAL 7 DAY), DATE_SUB(NOW(), INTERVAL 1 MONTH);
Group by month
SELECT DATE_FORMAT(created_at, '%Y-%m') AS month, COUNT(*)
FROM orders GROUP BY month ORDER BY month;
SELECT CONCAT(first_name, ' ', last_name) AS name,
       CONCAT_WS(', ', city, country)     AS location
FROM customers;
SELECT customer_id, GROUP_CONCAT(sku ORDER BY sku SEPARATOR ', ')
FROM order_items GROUP BY customer_id;
First non-null value
SELECT COALESCE(nickname, first_name, 'there') FROM customers;
Running total (8.0+)
SELECT created_at, SUM(total) OVER (ORDER BY created_at) AS running_total
FROM orders;

Indexes and keys

CREATE INDEX orders_created_at_idx ON orders (created_at);
Create a unique index
CREATE UNIQUE INDEX customers_email_idx ON customers (email);
SHOW INDEX FROM orders;
Drop an index
DROP INDEX orders_created_at_idx ON orders;
ALTER TABLE orders ADD PRIMARY KEY (id);
ALTER TABLE orders
  ADD CONSTRAINT orders_customer_fk
  FOREIGN KEY (customer_id) REFERENCES customers (id) ON DELETE CASCADE;
Drop a foreign key
ALTER TABLE orders DROP FOREIGN KEY orders_customer_fk;
See how a query uses indexes
EXPLAIN SELECT * FROM orders WHERE status = 'new';

Databases and administration

SHOW DATABASES;
CREATE DATABASE analytics
  CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
USE analytics;
SHOW TABLES;
Table sizes on disk
SELECT table_name,
       ROUND((data_length + index_length) / 1024 / 1024, 1) AS mb
FROM information_schema.tables
WHERE table_schema = DATABASE()
ORDER BY mb DESC;
See what is running now
SHOW FULL PROCESSLIST;
KILL QUERY 12345;      -- stop the statement
KILL CONNECTION 12345; -- drop the session
Grant read access
GRANT SELECT ON analytics.* TO 'analyst'@'%';
FLUSH PRIVILEGES;
Version and current connection
SELECT VERSION(), DATABASE(), CURRENT_USER();

All 30 MySQL how-to guides

Each guide is a short answer with examples you can copy and run, plus the gotchas and errors that come with it.

Run MySQL queries without a desktop client

SaturnSQL is a browser-based SQL editor for teams: shared query library, schema-aware autocomplete, and scheduled exports to Google Sheets and Slack.

Try it free

Do more with MySQL

The same tasks in other databases