Ogni comando e clausola SQL principale con sintassi, esempi e note — dal SELECT base alle funzioni finestra e CTE.
SELECT col1, col2 FROM table;
SELECT name, email FROM users;
ℹ Use * to select all columns (avoid in production)
SELECT col AS alias FROM table;
SELECT first_name AS name, created_at AS joined FROM users;
SELECT DISTINCT col FROM table;
SELECT DISTINCT country FROM customers;
SELECT ... FROM table WHERE condition;
SELECT * FROM orders WHERE status = 'pending' AND total > 100;
SELECT ... ORDER BY col ASC|DESC;
SELECT name, price FROM products ORDER BY price DESC;
SELECT ... LIMIT n OFFSET m;
SELECT * FROM posts LIMIT 20 OFFSET 40;
ℹ SQL Server: use TOP n or FETCH NEXT
WHERE col LIKE 'pattern'
SELECT * FROM users WHERE email LIKE '%@gmail.com';
WHERE col IN (v1, v2, ...)
SELECT * FROM orders WHERE status IN ('pending', 'processing');WHERE col BETWEEN v1 AND v2
SELECT * FROM orders WHERE total BETWEEN 100 AND 500;
WHERE col IS NULL | IS NOT NULL
SELECT * FROM users WHERE deleted_at IS NULL;
CASE WHEN cond THEN val ... ELSE val END
SELECT name, CASE WHEN age >= 18 THEN 'adult' ELSE 'minor' END AS type FROM users;
SELECT COUNT(*) | COUNT(col) FROM table;
SELECT COUNT(*) AS total, COUNT(email) AS with_email FROM users;
SELECT SUM(col), AVG(col), MIN(col), MAX(col) FROM table;
SELECT SUM(total) AS revenue, AVG(total) AS avg_order FROM orders;
SELECT col, AGG(x) FROM table GROUP BY col;
SELECT country, COUNT(*) AS users FROM users GROUP BY country;
... GROUP BY col HAVING AGG(x) > n
SELECT country, COUNT(*) as cnt FROM users GROUP BY country HAVING COUNT(*) > 100;
... GROUP BY col ORDER BY AGG(x) DESC
SELECT status, COUNT(*) cnt FROM orders GROUP BY status ORDER BY cnt DESC;
SELECT ... FROM a INNER JOIN b ON a.id = b.a_id;
SELECT u.name, o.total FROM users u INNER JOIN orders o ON u.id = o.user_id;
SELECT ... FROM a LEFT JOIN b ON a.id = b.a_id;
SELECT u.name, COUNT(o.id) FROM users u LEFT JOIN orders o ON u.id = o.user_id GROUP BY u.id;
SELECT ... FROM a RIGHT JOIN b ON a.id = b.a_id;
SELECT o.id, u.name FROM orders o RIGHT JOIN users u ON o.user_id = u.id;
SELECT ... FROM a FULL OUTER JOIN b ON condition;
SELECT a.id, b.id FROM table_a a FULL OUTER JOIN table_b b ON a.key = b.key;
ℹ Not supported in MySQL 8 — use UNION of LEFT + RIGHT JOIN
SELECT ... FROM t AS a JOIN t AS b ON a.col = b.col;
SELECT e.name, m.name AS manager FROM employees e LEFT JOIN employees m ON e.manager_id = m.id;
SELECT ... FROM a CROSS JOIN b;
SELECT colors.name, sizes.label FROM colors CROSS JOIN sizes;
ℹ Returns Cartesian product — rows(a) × rows(b)
WHERE id IN (SELECT id FROM ...)
SELECT * FROM products WHERE category_id IN (SELECT id FROM categories WHERE active = 1);
SELECT (SELECT COUNT(*) FROM ...) AS n
SELECT name, (SELECT COUNT(*) FROM orders WHERE user_id = u.id) AS order_count FROM users u;
WHERE EXISTS (SELECT 1 FROM ...)
SELECT * FROM users u WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id);
WITH cte AS (SELECT ...) SELECT * FROM cte;
WITH recent AS (SELECT * FROM orders WHERE created_at > NOW() - INTERVAL '7 days') SELECT user_id, COUNT(*) FROM recent GROUP BY user_id;
WITH RECURSIVE cte AS (base UNION ALL recursive) ...
WITH RECURSIVE nums(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM nums WHERE n < 10) SELECT n FROM nums;
ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...)
SELECT name, ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) AS rank FROM employees;
RANK() OVER (ORDER BY col DESC)
SELECT name, salary, RANK() OVER (ORDER BY salary DESC) AS pay_rank FROM employees;
ℹ RANK skips numbers on ties; DENSE_RANK does not
LAG(col, n) OVER (ORDER BY ...)
SELECT date, revenue, LAG(revenue,1) OVER (ORDER BY date) AS prev_revenue FROM daily_sales;
SUM(col) OVER (ORDER BY col ROWS UNBOUNDED PRECEDING)
SELECT date, amount, SUM(amount) OVER (ORDER BY date) AS running_total FROM transactions;
NTILE(n) OVER (ORDER BY col)
SELECT name, salary, NTILE(4) OVER (ORDER BY salary) AS quartile FROM employees;
INSERT INTO table (col1, col2) VALUES (v1, v2);
INSERT INTO users (name, email) VALUES ('Alice', '[email protected]');INSERT INTO table (cols) VALUES (r1), (r2), ...;
INSERT INTO tags (name) VALUES ('sql'), ('database'), ('tutorial');INSERT INTO t1 (cols) SELECT cols FROM t2;
INSERT INTO archive SELECT * FROM orders WHERE created_at < '2023-01-01';
UPDATE table SET col = val WHERE condition;
UPDATE users SET status = 'inactive', updated_at = NOW() WHERE last_login < NOW() - INTERVAL '90 days';
DELETE FROM table WHERE condition;
DELETE FROM sessions WHERE expires_at < NOW();
ℹ Always include WHERE — omitting it deletes all rows
INSERT … ON CONFLICT (col) DO UPDATE SET …
INSERT INTO settings (key, value) VALUES ('theme', 'dark') ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value;ℹ PostgreSQL syntax. MySQL uses ON DUPLICATE KEY UPDATE.
TRUNCATE TABLE table;
TRUNCATE TABLE temp_imports;
ℹ Faster than DELETE; resets auto-increment; cannot be rolled back in some DBs
CREATE TABLE name (col type constraints, ...);
CREATE TABLE users (id SERIAL PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL, created_at TIMESTAMPTZ DEFAULT NOW());
ALTER TABLE t ADD COLUMN col type;
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
ALTER TABLE t RENAME COLUMN old TO new;
ALTER TABLE users RENAME COLUMN username TO display_name;
CREATE INDEX name ON table (col);
CREATE INDEX idx_users_email ON users (email); CREATE UNIQUE INDEX idx_users_email_unique ON users (email);
DROP TABLE [IF EXISTS] table;
DROP TABLE IF EXISTS temp_staging;
REFERENCES other_table(col) ON DELETE action
CREATE TABLE orders (id SERIAL PRIMARY KEY, user_id INT REFERENCES users(id) ON DELETE CASCADE);
Questo è l'ordine di elaborazione logico. WHERE viene eseguito prima di SELECT, quindi non è possibile fare riferimento a un alias SELECT in una Clausola WHERE — usa un CTE o una subquery. HAVING può filtrare sugli aggregati GROUP BY.
| Espressione | Risultato | Note |
|---|---|---|
| NULL = NULL | FALSE / UNKNOWN | Use IS NULL instead |
| NULL != NULL | UNKNOWN | Any comparison with NULL is UNKNOWN |
| NULL + 5 | NULL | Arithmetic on NULL propagates NULL |
| COALESCE(NULL, 'x') | 'x' | Returns first non-NULL value |
| NULLIF(a, a) | NULL | Returns NULL if both args are equal |
| COUNT(*) | Counts all rows | COUNT(col) skips NULLs |
Le clausole SQL vengono eseguite in questo ordine logico: FROM → JOIN → WHERE → GROUP BY → HAVING → SELECT → DISTINCT → ORDER BY → LIMIT/OFFSET. Questo è importante perché WHERE non può fare riferimento agli alias SELECT, mentre HAVING può fare riferimento alle colonne GROUP BY.
INNER JOIN restituisce solo le righe in cui la condizione di join corrisponde in entrambe le tabelle. LEFT JOIN (LEFT OUTER JOIN) restituisce tutte le righe dalla tabella sinistra, con NULL per le colonne della tabella destra senza corrispondenza. Usa LEFT JOIN quando vuoi tutti i record dalla tabella sinistra indipendentemente dall'esistenza di una corrispondenza a destra.
Le funzioni finestra calcolano valori su un insieme di righe correlate alla riga corrente senza ridurle a una singola riga (a differenza degli aggregati GROUP BY). Funzioni finestra comuni: ROW_NUMBER(), RANK(), DENSE_RANK(), LAG(), LEAD(), SUM() OVER(), AVG() OVER(). Usano la clausola OVER() per definire la finestra.
Un CTE è un insieme di risultati temporaneo con nome definito con la parola chiave WITH prima di SELECT, INSERT, UPDATE o DELETE. I CTE migliorano la leggibilità rispetto alle subquery annidate e possono essere ricorsivi (WITH RECURSIVE) per dati gerarchici come organigrammi o alberi di categorie.