Essential SQL syntax with MySQL / PostgreSQL differences noted
SELECT col1, col2 FROM table_name; SELECT DISTINCT col FROM table_name; SELECT * FROM users WHERE age > 18; SELECT * FROM users ORDER BY name ASC; SELECT * FROM users LIMIT 10 OFFSET 20; SELECT col AS alias_name FROM t;
WHERE col = 'value' WHERE col IN ('a', 'b', 'c') WHERE col BETWEEN 10 AND 50 WHERE col LIKE '%pattern%' WHERE col IS NULL / IS NOT NULL WHERE a = 1 AND (b = 2 OR c = 3) WHERE col REGEXP '^[A-Z]' -- MySQL WHERE col ~ '^[A-Z]' -- PostgreSQL
INSERT INTO users (name, email) VALUES ('Alice', 'a@b.com'); -- Multiple rows INSERT INTO users (name, email) VALUES ('Alice', 'a@b.com'), ('Bob', 'b@b.com'); -- Insert from select INSERT INTO archive SELECT * FROM users WHERE created < '2024-01-01'; -- Upsert (PostgreSQL) INSERT INTO t (id, val) VALUES (1, 'x') ON CONFLICT (id) DO UPDATE SET val = 'x'; -- Upsert (MySQL) INSERT INTO t (id, val) VALUES (1, 'x') ON DUPLICATE KEY UPDATE val = 'x';
UPDATE users SET email = 'new@b.com' WHERE id = 1; UPDATE users SET name = 'Alice', age = age + 1 WHERE active = TRUE;
DELETE FROM users WHERE id = 1; DELETE FROM users WHERE active = FALSE; TRUNCATE TABLE users; -- fast, no WHERE
-- INNER JOIN (matching rows only) SELECT * FROM orders o INNER JOIN users u ON o.user_id = u.id; -- LEFT JOIN (all left + matching right) SELECT * FROM users u LEFT JOIN orders o ON u.id = o.user_id; -- RIGHT JOIN (all right + matching left) SELECT * FROM orders o RIGHT JOIN users u ON o.user_id = u.id; -- FULL OUTER JOIN (all from both) SELECT * FROM a FULL OUTER JOIN b ON a.id = b.a_id; -- CROSS JOIN (cartesian product) SELECT * FROM colors CROSS JOIN sizes; -- Self join SELECT e.name, m.name AS manager FROM employees e JOIN employees m ON e.mgr_id = m.id;
SELECT COUNT(*) FROM users; SELECT COUNT(DISTINCT country) FROM users; SELECT SUM(amount), AVG(amount), MIN(amount), MAX(amount) FROM orders; SELECT country, COUNT(*) AS cnt FROM users GROUP BY country HAVING COUNT(*) > 5 ORDER BY cnt DESC;
-- Scalar subquery SELECT * FROM users WHERE age > (SELECT AVG(age) FROM users); -- IN subquery SELECT * FROM users WHERE id IN ( SELECT user_id FROM orders WHERE amount > 100 ); -- EXISTS SELECT * FROM users u WHERE EXISTS ( SELECT 1 FROM orders o WHERE o.user_id = u.id ); -- Derived table SELECT * FROM ( SELECT dept, AVG(sal) AS avg_sal FROM employees GROUP BY dept ) AS t WHERE avg_sal > 50000;
SELECT name, dept, salary, ROW_NUMBER() OVER ( PARTITION BY dept ORDER BY salary DESC ) AS rn FROM employees; -- Ranking RANK() OVER (...) -- gaps on ties DENSE_RANK() OVER (...) -- no gaps NTILE(4) OVER (...) -- quartiles -- Aggregates as windows SUM(amount) OVER (ORDER BY date) -- running total AVG(amount) OVER ( ORDER BY date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW ) -- 3-row moving average -- Offset functions LAG(col, 1) OVER (ORDER BY date) LEAD(col, 1) OVER (ORDER BY date) FIRST_VALUE(col) OVER (...)
WITH active_users AS ( SELECT * FROM users WHERE active = TRUE ) SELECT * FROM active_users WHERE age > 25; -- Recursive CTE WITH RECURSIVE tree AS ( SELECT id, name, parent_id, 0 AS depth FROM categories WHERE parent_id IS NULL UNION ALL SELECT c.id, c.name, c.parent_id, t.depth+1 FROM categories c JOIN tree t ON c.parent_id = t.id ) SELECT * FROM tree;
CREATE TABLE users ( id SERIAL PRIMARY KEY, -- PG id INT AUTO_INCREMENT, -- MySQL name VARCHAR(100) NOT NULL, email VARCHAR(255) UNIQUE, created TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); ALTER TABLE users ADD COLUMN age INT; ALTER TABLE users DROP COLUMN age; ALTER TABLE users RENAME COLUMN a TO b; DROP TABLE IF EXISTS users;
CREATE INDEX idx_email ON users(email); CREATE UNIQUE INDEX idx_u ON users(email); DROP INDEX idx_email; -- PG DROP INDEX idx_email ON users; -- MySQL
-- Auto-increment MySQL: INT AUTO_INCREMENT PG: SERIAL or GENERATED ALWAYS AS IDENTITY -- String concat MySQL: CONCAT(a, b) PG: a || b -- Current time MySQL: NOW() PG: NOW() or CURRENT_TIMESTAMP -- LIMIT MySQL: LIMIT 10, 20 -- offset, limit PG: LIMIT 20 OFFSET 10 -- Boolean MySQL: TINYINT(1) PG: BOOLEAN -- JSON Both: col->>'key' -- extract as text