1
Fondamentaux — SELECT
Syntaxe de base
SQL
SELECT colonne1, colonne2 -- colonnes à afficher
FROM table_name -- table source
WHERE condition -- filtre lignes
ORDER BY colonne1 ASC|DESC -- tri
LIMIT n OFFSET m; -- pagination (MySQL/PG)
-- SQL Server : TOP n | FETCH NEXT n ROWS ONLY
-- Oracle : FETCH FIRST n ROWS ONLY (12c+) | ROWNUMSELECT — Variations
SQL
SELECT * -- toutes les colonnes
SELECT DISTINCT col -- valeurs uniques
SELECT col AS alias -- renommer
SELECT col1, col2, col1+col2 AS total -- expression calculée
SELECT 'texte' AS label, 42 AS nb -- constantes
SELECT DISTINCT ON (col) ... -- PostgreSQL uniquementORDER BY
SQL
ORDER BY col1 ASC, col2 DESC -- tri multi-colonnes
ORDER BY 1, 3 -- par position
ORDER BY CASE WHEN col IS NULL THEN 1 ELSE 0 END, col -- NULLs last
ORDER BY FIELD(status,'actif','inactif') -- MySQL : ordre custom
ORDER BY col NULLS FIRST | NULLS LAST -- PG / OracleLIMIT / Pagination
| SGBD | Syntaxe |
|---|---|
| MySQL / PostgreSQL | LIMIT 10 OFFSET 20 |
| SQL Server | OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY |
| Oracle 12c+ | OFFSET 20 ROWS FETCH FIRST 10 ROWS ONLY |
| Oracle <12c | WHERE ROWNUM <= 30 (puis sous-requête) |
2
Filtrage avancé
Opérateurs de comparaison
SQL
= <> != < > <= >=
IS NULL IS NOT NULL
BETWEEN a AND b -- inclusif : a <= x <= b
IN (v1, v2, v3) NOT IN (...)
LIKE 'pattern' NOT LIKE 'pattern'
% = 0+ caractères _ = 1 caractère exactement
ILIKE 'pattern' -- PG : insensible à la casse
SIMILAR TO -- PG : regex-like
REGEXP / RLIKE -- MySQL : expressions régulières
col ~ 'regex' -- PG : regex POSIXOpérateurs logiques
SQL
WHERE cond1 AND cond2
WHERE cond1 OR cond2
WHERE NOT cond
-- Priorité : NOT > AND > OR → utiliser des parenthèses !
WHERE (a OR b) AND cCASE WHEN
SQL
-- Forme recherchée (la plus courante)
SELECT col,
CASE
WHEN prix < 10 THEN 'pas cher'
WHEN prix < 50 THEN 'moyen'
ELSE 'cher'
END AS categorie
FROM produits;
-- Forme simple
SELECT CASE statut
WHEN 'A' THEN 'Actif'
WHEN 'I' THEN 'Inactif'
ELSE 'Inconnu'
END AS libelle
FROM comptes;COALESCE / NULLIF / NVL
SQL
COALESCE(a, b, c) -- premier non-NULL
NULLIF(a, b) -- NULL si a = b, sinon a
NVL(a, b) -- Oracle : si a NULL → b
IFNULL(a, b) -- MySQL : si a NULL → b
ISNULL(a, b) -- SQL Server : si a NULL → b
3
Fonctions d'agrégation
Fonctions principales
| Fonction | Description | Exemple |
|---|---|---|
| COUNT(*) | Nb total de lignes | SELECT COUNT(*) FROM t |
| COUNT(col) | Nb de non-NULL | COUNT(email) |
| COUNT(DISTINCT col) | Nb valeurs uniques | COUNT(DISTINCT ville) |
| SUM(col) | Somme | SUM(montant) |
| AVG(col) | Moyenne | AVG(note) |
| MIN(col) | Valeur min | MIN(date_creation) |
| MAX(col) | Valeur max | MAX(salaire) |
| GROUP_CONCAT | Concaténation (MySQL) | GROUP_CONCAT(nom SEPARATOR ',') |
| STRING_AGG | Concaténation (PG/SS) | STRING_AGG(nom, ',') |
| ARRAY_AGG | En tableau (PG) | ARRAY_AGG(DISTINCT tag) |
GROUP BY / HAVING
SQL
SELECT departement, COUNT(*) AS nb, AVG(salaire) AS moy
FROM employes
WHERE statut = 'actif' -- filtre AVANT agrégation
GROUP BY departement -- regroupement
HAVING COUNT(*) >= 5 -- filtre APRÈS agrégation
ORDER BY moy DESC;
-- GROUP BY avec expressions
GROUP BY YEAR(date_embauche), departement
GROUP BY 1, 2 -- par position
-- GROUPING SETS / ROLLUP / CUBE (PG, SQL Server, Oracle)
GROUP BY ROLLUP(region, ville) -- sous-totaux hiérarchiques
GROUP BY CUBE(region, ville) -- toutes combinaisons
GROUP BY GROUPING SETS ((region), (ville), ())
4
Jointures
Types de jointures
SQL
-- INNER JOIN : intersection
SELECT a.*, b.nom
FROM commandes a
INNER JOIN clients b ON a.client_id = b.id;
-- LEFT JOIN : tout à gauche + correspondances à droite
SELECT a.*, b.nom
FROM commandes a
LEFT JOIN clients b ON a.client_id = b.id;
-- RIGHT JOIN : tout à droite + correspondances à gauche
RIGHT JOIN ...
-- FULL OUTER JOIN : tout des deux côtés
FULL OUTER JOIN ...
-- CROSS JOIN : produit cartésien
SELECT a.taille, b.couleur
FROM tailles a CROSS JOIN couleurs b;
-- SELF JOIN : jointure d'une table avec elle-même
SELECT e.nom AS employe, m.nom AS manager
FROM employes e
LEFT JOIN employes m ON e.manager_id = m.id;
-- NATURAL JOIN : auto sur colonnes homonymes — ⚠️ fragile
SELECT * FROM a NATURAL JOIN b;Jointures multi-tables
SQL
SELECT c.nom, co.date, p.libelle, lc.quantite
FROM clients c
JOIN commandes co ON c.id = co.client_id
JOIN lignes_cmd lc ON co.id = lc.commande_id
JOIN produits p ON lc.produit_id = p.id
WHERE co.date >= '2024-01-01';Anti-join & patterns courants
SQL
-- Trouver les NON-correspondances (anti-join)
SELECT a.* FROM table_a a
LEFT JOIN table_b b ON a.id = b.a_id
WHERE b.a_id IS NULL;
-- Équivalent avec NOT EXISTS (souvent + performant)
SELECT * FROM table_a a
WHERE NOT EXISTS (SELECT 1 FROM table_b b WHERE b.a_id = a.id);
-- Jointure avec plage de dates
JOIN prix p ON p.produit_id = pr.id
AND p.date_debut <= CURRENT_DATE
AND (p.date_fin IS NULL OR p.date_fin >= CURRENT_DATE);
5
Sous-requêtes
SQL
-- Sous-requête scalaire (retourne 1 valeur)
SELECT nom, salaire,
(SELECT AVG(salaire) FROM employes) AS moy_globale
FROM employes;
-- Sous-requête avec IN
SELECT * FROM produits
WHERE categorie_id IN (SELECT id FROM categories WHERE active = 1);
-- EXISTS (test d'existence — très performant)
SELECT * FROM clients c
WHERE EXISTS (
SELECT 1 FROM commandes co WHERE co.client_id = c.id
AND co.date >= '2024-01-01'
);
-- Sous-requête corrélée
SELECT e.nom, e.salaire
FROM employes e
WHERE e.salaire > (
SELECT AVG(salaire) FROM employes
WHERE departement = e.departement
);
-- ANY / ALL
WHERE salaire > ALL (SELECT salaire FROM employes WHERE dept = 'RH')
WHERE salaire > ANY (SELECT salaire FROM employes WHERE dept = 'RH')
-- Table dérivée (sous-requête dans FROM)
SELECT dept, moy FROM (
SELECT departement AS dept, AVG(salaire) AS moy
FROM employes GROUP BY departement
) sub
WHERE moy > 50000;
-- LATERAL JOIN (PG, MySQL 8.0.14+)
SELECT d.nom, top3.*
FROM departements d,
LATERAL (
SELECT nom, salaire FROM employes e
WHERE e.dept_id = d.id ORDER BY salaire DESC LIMIT 3
) top3;
6
Opérations ensemblistes
SQL
-- UNION : combine + déduplique
SELECT nom FROM clients
UNION
SELECT nom FROM fournisseurs;
-- UNION ALL : combine SANS dédupliquer (+ rapide)
SELECT nom FROM clients
UNION ALL
SELECT nom FROM fournisseurs;
-- INTERSECT : lignes communes aux deux
SELECT email FROM newsletter
INTERSECT
SELECT email FROM clients;
-- EXCEPT / MINUS : dans le 1er mais pas le 2e
SELECT email FROM clients
EXCEPT -- MINUS en Oracle
SELECT email FROM desabonnes;
-- ⚠️ Même nombre de colonnes et types compatibles !
-- ORDER BY s'applique au résultat final
7
Fonctions de fenêtrage (Window Functions)
Syntaxe générale
SQL
fonction(...) OVER (
[PARTITION BY col1, col2] -- groupes (optionnel)
[ORDER BY col3 ASC|DESC] -- tri dans le groupe
[frame_clause] -- fenêtre glissante
)Fonctions de rang
SQL
ROW_NUMBER() OVER (ORDER BY salaire DESC) -- 1,2,3,4,5
RANK() OVER (ORDER BY salaire DESC) -- 1,2,2,4,5 (saute)
DENSE_RANK() OVER (ORDER BY salaire DESC) -- 1,2,2,3,4 (ne saute pas)
NTILE(4) OVER (ORDER BY salaire DESC) -- quartiles
PERCENT_RANK() OVER (ORDER BY salaire) -- rang en %
CUME_DIST() OVER (ORDER BY salaire) -- distribution cumulative
-- Top N par groupe
SELECT * FROM (
SELECT *, ROW_NUMBER() OVER (
PARTITION BY departement ORDER BY salaire DESC
) AS rn
FROM employes
) sub WHERE rn <= 3;Fonctions de décalage
SQL
LAG(col, n, defaut) OVER (ORDER BY ...) -- n lignes AVANT
LEAD(col, n, defaut) OVER (ORDER BY ...) -- n lignes APRÈS
FIRST_VALUE(col) OVER (ORDER BY ...) -- 1ère valeur
LAST_VALUE(col) OVER (ORDER BY ...
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
NTH_VALUE(col, n) OVER (ORDER BY ...) -- n-ième valeur
-- Variation jour par jour
SELECT date, ventes,
ventes - LAG(ventes, 1) OVER (ORDER BY date) AS variation,
ROUND(100.0 * (ventes - LAG(ventes,1) OVER (ORDER BY date))
/ NULLIF(LAG(ventes,1) OVER (ORDER BY date), 0), 2) AS pct
FROM ventes_jour;Agrégats en fenêtre + Cadres (Frames)
SQL
-- Agrégats sans réduire les lignes
SELECT nom, salaire,
SUM(salaire) OVER () AS total_global,
SUM(salaire) OVER (PARTITION BY dept) AS total_dept,
salaire * 100.0 / SUM(salaire) OVER (PARTITION BY dept) AS pct
FROM employes;
-- Somme cumulative
SUM(montant) OVER (ORDER BY date ROWS UNBOUNDED PRECEDING)
-- Moyenne mobile sur 7 jours
AVG(ventes) OVER (ORDER BY date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW)
-- Cadres disponibles
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW -- défaut
ROWS BETWEEN 3 PRECEDING AND 3 FOLLOWING -- glissant
RANGE BETWEEN INTERVAL '7 days' PRECEDING AND CURRENT ROW -- PG
-- WINDOW nommée
SELECT nom,
SUM(ventes) OVER w AS cum,
AVG(ventes) OVER w AS moy_mobile
FROM resultats
WINDOW w AS (PARTITION BY region ORDER BY mois
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW);
8
CTE & Requêtes récursives (WITH)
SQL
-- CTE simple
WITH ventes_mensuelles AS (
SELECT DATE_TRUNC('month', date) AS mois, SUM(montant) AS total
FROM ventes GROUP BY 1
)
SELECT mois, total,
LAG(total) OVER (ORDER BY mois) AS mois_prec
FROM ventes_mensuelles;
-- CTE multiples
WITH
actifs AS (SELECT * FROM clients WHERE actif = true),
cmd_recentes AS (
SELECT client_id, MAX(date) AS derniere
FROM commandes GROUP BY client_id
)
SELECT a.nom, cr.derniere
FROM actifs a
JOIN cmd_recentes cr ON a.id = cr.client_id;
-- CTE récursive (hiérarchie, arbres, graphes)
WITH RECURSIVE arbre AS (
-- Cas de base
SELECT id, nom, parent_id, 1 AS niveau,
CAST(nom AS VARCHAR(1000)) AS chemin
FROM categories WHERE parent_id IS NULL
UNION ALL
-- Récursion
SELECT c.id, c.nom, c.parent_id, a.niveau + 1,
CAST(a.chemin || ' > ' || c.nom AS VARCHAR(1000))
FROM categories c
JOIN arbre a ON c.parent_id = a.id
)
SELECT * FROM arbre ORDER BY chemin;
9
DDL — CREATE, ALTER, DROP
CREATE TABLE
SQL
CREATE TABLE employes (
id SERIAL PRIMARY KEY, -- PG auto-increment
-- id INT AUTO_INCREMENT PRIMARY KEY, -- MySQL
-- id INT IDENTITY(1,1) PRIMARY KEY, -- SQL Server
nom VARCHAR(100) NOT NULL,
email VARCHAR(255) UNIQUE,
salaire DECIMAL(10,2) DEFAULT 0.00,
dept_id INT REFERENCES departements(id),
date_embauche DATE DEFAULT CURRENT_DATE,
actif BOOLEAN DEFAULT TRUE,
metadata JSONB,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT chk_salaire CHECK (salaire >= 0),
CONSTRAINT fk_dept FOREIGN KEY (dept_id)
REFERENCES departements(id) ON DELETE SET NULL ON UPDATE CASCADE
);
-- Créer à partir d'une requête (CTAS)
CREATE TABLE archive_2024 AS
SELECT * FROM commandes WHERE YEAR(date) = 2024;
-- Table temporaire
CREATE TEMPORARY TABLE tmp_calcul (id INT, val DECIMAL(10,2));Types de données courants
| Catégorie | PostgreSQL | MySQL | SQL Server |
|---|---|---|---|
| Entier | INT, BIGINT, SMALLINT | INT, BIGINT, TINYINT | INT, BIGINT, TINYINT |
| Décimal | DECIMAL(p,s), NUMERIC | DECIMAL(p,s), FLOAT | DECIMAL(p,s), FLOAT |
| Auto-incr. | SERIAL, BIGSERIAL | AUTO_INCREMENT | IDENTITY(1,1) |
| Texte | VARCHAR(n), TEXT | VARCHAR(n), TEXT, LONGTEXT | NVARCHAR(n), NVARCHAR(MAX) |
| Booléen | BOOLEAN | TINYINT(1) / BOOL | BIT |
| Date/Heure | DATE, TIMESTAMP, TIMESTAMPTZ | DATE, DATETIME, TIMESTAMP | DATE, DATETIME2 |
| JSON | JSON, JSONB | JSON | NVARCHAR(MAX) |
| UUID | UUID | CHAR(36) | UNIQUEIDENTIFIER |
| Binaire | BYTEA | BLOB, LONGBLOB | VARBINARY(MAX) |
| Tableau | INT[], TEXT[] | — | — |
ALTER TABLE
SQL
ALTER TABLE employes ADD COLUMN telephone VARCHAR(20);
ALTER TABLE employes DROP COLUMN telephone;
ALTER TABLE employes ALTER COLUMN nom SET NOT NULL; -- PG
ALTER TABLE employes MODIFY COLUMN nom VARCHAR(200) NOT NULL; -- MySQL
ALTER TABLE employes RENAME COLUMN nom TO full_name;
ALTER TABLE employes RENAME TO personnel;
ALTER TABLE employes ADD CONSTRAINT uq_email UNIQUE(email);
ALTER TABLE employes DROP CONSTRAINT uq_email;DROP / TRUNCATE
SQL
DROP TABLE IF EXISTS table_name; -- supprime table + données
DROP TABLE table_name CASCADE; -- PG : supprime dépendances
TRUNCATE TABLE table_name; -- vide la table (rapide)
TRUNCATE TABLE t RESTART IDENTITY; -- PG : reset séquence
10
DML — INSERT, UPDATE, DELETE, MERGE
INSERT
SQL
INSERT INTO clients (nom, email) VALUES ('Alice', 'a@x.com');
-- Insertion multiple
INSERT INTO clients (nom, email) VALUES
('Alice', 'a@x.com'),
('Bob', 'b@x.com'),
('Clara', 'c@x.com');
-- INSERT depuis une requête
INSERT INTO archive SELECT * FROM commandes WHERE date < '2023-01-01';
-- INSERT ... RETURNING (PG)
INSERT INTO clients (nom) VALUES ('Alice') RETURNING id, created_at;
-- UPSERT PostgreSQL
INSERT INTO compteurs (cle, valeur) VALUES ('visites', 1)
ON CONFLICT (cle) DO UPDATE SET valeur = compteurs.valeur + 1;
-- UPSERT MySQL
INSERT INTO compteurs (cle, valeur) VALUES ('visites', 1)
ON DUPLICATE KEY UPDATE valeur = valeur + 1;
-- MERGE (SQL Server / Oracle)
MERGE INTO cible t
USING source s ON t.id = s.id
WHEN MATCHED THEN UPDATE SET t.val = s.val
WHEN NOT MATCHED THEN INSERT (id, val) VALUES (s.id, s.val);UPDATE
SQL
UPDATE employes SET salaire = salaire * 1.05 WHERE dept = 'IT';
-- UPDATE avec sous-requête
UPDATE produits p
SET prix = (SELECT AVG(prix) FROM produits WHERE cat_id = p.cat_id)
WHERE prix IS NULL;
-- UPDATE avec JOIN (MySQL)
UPDATE commandes c
JOIN clients cl ON c.client_id = cl.id
SET c.region = cl.region;
-- UPDATE avec FROM (PG)
UPDATE commandes c
SET region = cl.region
FROM clients cl
WHERE c.client_id = cl.id;
-- UPDATE … RETURNING (PG)
UPDATE employes SET actif = false WHERE derniere_connexion < '2023-01-01'
RETURNING id, nom, email;DELETE
SQL
DELETE FROM commandes WHERE date < '2020-01-01';
-- DELETE avec JOIN (MySQL)
DELETE c FROM clients c
LEFT JOIN commandes co ON c.id = co.client_id
WHERE co.id IS NULL;
-- DELETE avec USING (PG)
DELETE FROM clients c
USING (SELECT client_id FROM blacklist) bl
WHERE c.id = bl.client_id;
-- DELETE … RETURNING (PG)
DELETE FROM sessions WHERE expire_at < NOW() RETURNING id, user_id;
11
Contraintes & Index
Contraintes
SQL
PRIMARY KEY (col) -- unicité + NOT NULL
UNIQUE (col) -- unicité (NULL possible)
NOT NULL -- obligatoire
CHECK (condition) -- ex: CHECK (age >= 0)
DEFAULT valeur -- valeur par défaut
FOREIGN KEY (col) REFERENCES parent(id)
ON DELETE CASCADE|SET NULL|RESTRICT|NO ACTION|SET DEFAULT
ON UPDATE CASCADE|RESTRICT
-- Contrainte composite
PRIMARY KEY (col1, col2)
UNIQUE (col1, col2)
-- Contrainte d'exclusion (PG)
EXCLUDE USING gist (period WITH &&) -- pas de chevauchementIndex
SQL
-- Index B-tree (défaut)
CREATE INDEX idx_nom ON employes (nom);
CREATE UNIQUE INDEX idx_email ON employes (email);
-- Index composite
CREATE INDEX idx_dept_sal ON employes (departement, salaire DESC);
-- Index partiel / filtré
CREATE INDEX idx_actifs ON employes (nom) WHERE actif = true; -- PG
-- Index sur expression
CREATE INDEX idx_lower ON employes (LOWER(email)); -- PG
-- Types d'index spéciaux (PG)
CREATE INDEX idx_gin ON docs USING GIN (contenu); -- full-text, JSONB
CREATE INDEX idx_gist ON geo USING GiST (position); -- géométrie
CREATE INDEX idx_brin ON logs USING BRIN (date); -- très grandes tables
CREATE INDEX idx_hash ON sess USING HASH (token); -- égalité pure
-- Index couvrant (PG 11+ / SQL Server)
CREATE INDEX idx_cover ON employes (dept) INCLUDE (nom, salaire);
-- Supprimer / Reconstruire
DROP INDEX idx_nom;
REINDEX INDEX idx_nom; -- PG
ALTER INDEX idx_nom REBUILD; -- SQL Server
12
Vues
SQL
-- Vue simple
CREATE VIEW v_employes_actifs AS
SELECT id, nom, email, departement
FROM employes WHERE actif = true;
-- Modifier une vue
CREATE OR REPLACE VIEW v_employes_actifs AS ...;
-- Supprimer
DROP VIEW IF EXISTS v_employes_actifs;
-- Vue matérialisée (PG) — stocke les données physiquement
CREATE MATERIALIZED VIEW mv_stats AS
SELECT ... FROM ... GROUP BY ...;
REFRESH MATERIALIZED VIEW mv_stats;
REFRESH MATERIALIZED VIEW CONCURRENTLY mv_stats; -- sans lock
-- Vue updatable avec contrôle
CREATE VIEW v_rh AS SELECT * FROM employes WHERE dept = 'RH'
WITH CHECK OPTION; -- empêche INSERT/UPDATE hors du filtre
13
Transactions
SQL
BEGIN;
UPDATE comptes SET solde = solde - 100 WHERE id = 1;
UPDATE comptes SET solde = solde + 100 WHERE id = 2;
COMMIT; -- valide tout
-- ou ROLLBACK; -- annule tout
-- SAVEPOINT
BEGIN;
INSERT INTO log VALUES (...);
SAVEPOINT sp1;
UPDATE comptes SET ...;
ROLLBACK TO sp1; -- annule jusqu'au savepoint
COMMIT;Niveaux d'isolation
| Niveau | Dirty Read | Non-Repeatable | Phantom | Usage |
|---|---|---|---|---|
| READ UNCOMMITTED | Possible | Possible | Possible | Rare, perf max |
| READ COMMITTED | Non | Possible | Possible | Défaut PG/Oracle/SS |
| REPEATABLE READ | Non | Non | Possible | Défaut MySQL InnoDB |
| SERIALIZABLE | Non | Non | Non | Sécurité maximale |
Verrouillage explicite
SQL
SELECT * FROM comptes WHERE id = 1 FOR UPDATE; -- verrou exclusif
SELECT * FROM comptes WHERE id = 1 FOR SHARE; -- verrou partagé
SELECT * FROM comptes WHERE id = 1 FOR UPDATE NOWAIT; -- erreur si verrouillé
SELECT * FROM comptes WHERE id = 1 FOR UPDATE SKIP LOCKED; -- saute
14
Fonctions SQL courantes
Fonctions chaînes de caractères
| Fonction | Description | Exemple → Résultat |
|---|---|---|
| LENGTH / LEN | Longueur | LENGTH('abc') → 3 |
| UPPER / LOWER | Casse | UPPER('abc') → 'ABC' |
| TRIM | Espaces | TRIM(' ab ') → 'ab' |
| SUBSTRING | Extraction | SUBSTRING('abcde',2,3) → 'bcd' |
| LEFT / RIGHT | N premiers/derniers | LEFT('abcde',3) → 'abc' |
| REPLACE | Remplacement | REPLACE('abc','b','x') → 'axc' |
| CONCAT | Concaténation | CONCAT('a','b') → 'ab' |
| CONCAT_WS | Concat avec sépar. | CONCAT_WS('-','a','b') → 'a-b' |
| LPAD / RPAD | Compléter | LPAD('5',3,'0') → '005' |
| REVERSE | Inverser | REVERSE('abc') → 'cba' |
| POSITION | Position | POSITION('c' IN 'abcd') → 3 |
| SPLIT_PART | Découper (PG) | SPLIT_PART('a-b-c','-',2) → 'b' |
| REGEXP_REPLACE | Regex replace | REGEXP_REPLACE(s,'[0-9]','','g') |
Fonctions de date/heure
| Action | PostgreSQL | MySQL | SQL Server |
|---|---|---|---|
| Maintenant | NOW() / CURRENT_TIMESTAMP | NOW() | GETDATE() / SYSDATETIME() |
| Date du jour | CURRENT_DATE | CURDATE() | CAST(GETDATE() AS DATE) |
| Extraire | EXTRACT(YEAR FROM d) | YEAR(d), MONTH(d) | DATEPART(year, d) |
| Tronquer | DATE_TRUNC('month', d) | DATE_FORMAT(d,'%Y-%m-01') | DATETRUNC(month, d) |
| Ajouter | d + INTERVAL '3 days' | DATE_ADD(d, INTERVAL 3 DAY) | DATEADD(day, 3, d) |
| Différence | d2 - d1 | DATEDIFF(d2, d1) | DATEDIFF(day, d1, d2) |
| Formater | TO_CHAR(d, 'DD/MM/YYYY') | DATE_FORMAT(d, '%d/%m/%Y') | FORMAT(d, 'dd/MM/yyyy') |
| Parser | TO_DATE(s, 'DD/MM/YYYY') | STR_TO_DATE(s, '%d/%m/%Y') | CONVERT(DATE, s, 103) |
Fonctions numériques
SQL
ROUND(x, n) -- arrondi à n décimales
CEIL(x) / CEILING(x) -- arrondi supérieur
FLOOR(x) -- arrondi inférieur
TRUNC(x, n) -- troncature (PG/Oracle)
ABS(x) -- valeur absolue
MOD(a, b) / a % b -- modulo
POWER(x, n) -- puissance
SQRT(x) -- racine carrée
GREATEST(a,b,c) -- max parmi des valeurs
LEAST(a,b,c) -- min parmi des valeurs
RANDOM() / RAND() -- aléatoire [0,1)CAST / Conversion
SQL
CAST(col AS INTEGER) -- standard SQL
CAST(col AS VARCHAR(50))
CAST(col AS DECIMAL(10,2))
col::INTEGER -- PG : notation raccourcie
col::TEXT
CONVERT(INT, col) -- SQL Server
TRY_CAST(col AS INT) -- SQL Server : NULL si échec
15
JSON & Données semi-structurées
PostgreSQL (JSONB recommandé)
SQL
-- Accéder aux clés
data->>'nom' -- texte
data->'adresse'->>'ville' -- accès imbriqué
data#>>'{adresse,ville}' -- chemin (notation array)
-- Opérateurs
data @> '{"actif": true}' -- contient
data ? 'cle' -- la clé existe ?
data ?| ARRAY['a','b'] -- au moins une clé
data ?& ARRAY['a','b'] -- toutes les clés
-- Fonctions
jsonb_array_elements(data->'items') -- déplie tableau JSON
jsonb_each(data) -- (key, value) pairs
jsonb_set(data, '{nom}', '"Alice"') -- modifier
data || '{"age": 30}'::jsonb -- merge
data - 'cle_a_supprimer' -- supprimer une clé
-- Index GIN sur JSONB
CREATE INDEX idx_gin ON t USING GIN (data);
CREATE INDEX idx_path ON t USING GIN (data jsonb_path_ops);MySQL (JSON natif)
SQL
JSON_EXTRACT(data, '$.nom') -- ou data->>'$.nom'
JSON_SET(data, '$.age', 30)
JSON_REMOVE(data, '$.old_key')
JSON_CONTAINS(data, '"valeur"', '$.tags')
JSON_ARRAYAGG(col) -- agrège en tableau JSON
JSON_OBJECTAGG(cle, val) -- agrège en objet JSON
JSON_TABLE(data, '$.items[*]' COLUMNS (
nom VARCHAR(100) PATH '$.nom',
prix DECIMAL(10,2) PATH '$.prix'
)) AS jt
16
Optimisation & EXPLAIN
SQL
EXPLAIN SELECT * FROM ...;
EXPLAIN ANALYZE SELECT * FROM ...; -- PG : exécute réellement
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT) SELECT ...; -- PG détaillé
-- Ce qu'il faut regarder :
-- • Seq Scan vs Index Scan (préférer Index Scan)
-- • Type de join : Nested Loop / Hash Join / Merge Join
-- • Rows estimées vs réelles (ANALYZE)
-- • Sort en mémoire ou sur disque
-- • Actual Time / Total CostTop 10 conseils d'optimisation
Bonnes pratiques
-- 1. Indexer les colonnes des WHERE, JOIN, ORDER BY
-- 2. Éviter SELECT * → lister les colonnes
-- 3. Pas de fonctions sur colonnes indexées dans WHERE
-- ❌ WHERE YEAR(date) = 2024
-- ✅ WHERE date >= '2024-01-01' AND date < '2025-01-01'
-- 4. EXISTS au lieu de IN pour sous-requêtes corrélées
-- 5. LIMIT tôt si possible
-- 6. Mettre à jour les statistiques
ANALYZE table_name; -- PG
ANALYZE TABLE table_name; -- MySQL
UPDATE STATISTICS table_name; -- SQL Server
-- 7. Éviter DISTINCT si GROUP BY suffit
-- 8. Préférer JOINs aux boucles applicatives (N+1)
-- 9. Partitionner les très grandes tables
-- 10. Utiliser les index couvrantsPartitionnement (PG)
SQL
CREATE TABLE logs (id SERIAL, date DATE, msg TEXT)
PARTITION BY RANGE (date);
CREATE TABLE logs_2024 PARTITION OF logs
FOR VALUES FROM ('2024-01-01') TO ('2025-01-01');
-- Partition par liste
PARTITION BY LIST (region);
CREATE TABLE logs_eu PARTITION OF logs FOR VALUES IN ('FR','DE','ES');
-- Partition par hash
PARTITION BY HASH (id);
CREATE TABLE logs_p0 PARTITION OF logs FOR VALUES WITH (MODULUS 4, REMAINDER 0);
17
DCL — Sécurité, GRANT, REVOKE
SQL
-- Créer un utilisateur
CREATE USER analyst WITH PASSWORD 'secure_pwd'; -- PG
CREATE USER 'analyst'@'%' IDENTIFIED BY 'secure_pwd'; -- MySQL
-- Créer un rôle
CREATE ROLE readonly;
-- Accorder des privilèges
GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly;
GRANT SELECT, INSERT ON table_name TO analyst;
GRANT ALL PRIVILEGES ON DATABASE mydb TO admin;
GRANT readonly TO analyst; -- assigner rôle
-- Révoquer
REVOKE INSERT ON table_name FROM analyst;
REVOKE ALL ON table_name FROM PUBLIC;
-- Row Level Security (PG)
ALTER TABLE documents ENABLE ROW LEVEL SECURITY;
CREATE POLICY doc_owner ON documents
USING (owner_id = current_user_id());
-- Supprimer
DROP USER analyst;
DROP ROLE readonly;
18
Procédures, Fonctions & Triggers
Fonction (PG)
SQL
CREATE OR REPLACE FUNCTION calculer_ttc(
prix_ht DECIMAL, taux DECIMAL DEFAULT 0.20
) RETURNS DECIMAL AS $$
BEGIN
RETURN prix_ht * (1 + taux);
END;
$$ LANGUAGE plpgsql IMMUTABLE;
-- Utilisation
SELECT nom, calculer_ttc(prix) AS prix_ttc FROM produits;Procédure (PG 11+)
SQL
CREATE OR REPLACE PROCEDURE archiver_commandes(annee INT)
LANGUAGE plpgsql AS $$
BEGIN
INSERT INTO archive SELECT * FROM commandes
WHERE EXTRACT(YEAR FROM date) = annee;
DELETE FROM commandes WHERE EXTRACT(YEAR FROM date) = annee;
COMMIT;
END;
$$;
CALL archiver_commandes(2023);Trigger
SQL
-- PG : trigger function + trigger
CREATE OR REPLACE FUNCTION set_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_updated
BEFORE UPDATE ON employes
FOR EACH ROW
EXECUTE FUNCTION set_updated_at();
-- MySQL
CREATE TRIGGER trg_before_insert
BEFORE INSERT ON employes
FOR EACH ROW
SET NEW.created_at = NOW();
19
Aide-mémoire différences SGBD
| Concept | PostgreSQL | MySQL | SQL Server | Oracle |
|---|---|---|---|---|
| Auto-incr. | SERIAL / GENERATED | AUTO_INCREMENT | IDENTITY(1,1) | SEQUENCE + trigger |
| Upsert | ON CONFLICT DO UPDATE | ON DUPLICATE KEY | MERGE | MERGE |
| Concat | || | CONCAT() | + | || |
| Bool | BOOLEAN | TINYINT(1) | BIT (0/1) | NUMBER(1) |
| LIMIT | LIMIT n OFFSET m | LIMIT n OFFSET m | OFFSET..FETCH | FETCH FIRST / ROWNUM |
| If NULL | COALESCE | IFNULL / COALESCE | ISNULL / COALESCE | NVL / COALESCE |
| String agg | STRING_AGG | GROUP_CONCAT | STRING_AGG | LISTAGG |
| Regex | ~ / ~* / SIMILAR TO | REGEXP | LIKE (limité) | REGEXP_LIKE |
| CTE récursive | WITH RECURSIVE | WITH RECURSIVE (8.0+) | WITH (auto) | WITH (auto) |
| JSON | JSONB (natif) | JSON (natif) | OPENJSON | JSON_TABLE (21c) |
| Full-text | tsvector / GIN | FULLTEXT INDEX | CONTAINS | Oracle Text |
20
Anti-patterns & Bonnes pratiques
Anti-patterns à éviter
Exemples
-- ❌ SELECT *
SELECT * FROM employes; -- fragile, coûteux
-- ✅ Lister les colonnes
SELECT id, nom, email FROM employes;
-- ❌ Fonction sur colonne indexée
WHERE UPPER(email) = 'A@X.COM';
-- ✅ Stocker en minuscule ou index fonctionnel
WHERE email = LOWER('A@X.COM');
-- ❌ NOT IN avec NULL possibles → résultat vide !
WHERE id NOT IN (SELECT id FROM t ...)
-- ✅ NOT EXISTS
WHERE NOT EXISTS (SELECT 1 FROM t WHERE t.id = a.id);
-- ❌ Requêtes N+1 (boucle applicative)
-- ✅ Un seul JOIN
SELECT c.*, o.* FROM clients c JOIN orders o ON c.id = o.client_id;
-- ❌ LIKE '%alice%' → full scan, pas d'index
-- ✅ Full-text search
-- ❌ CSV dans une colonne : tags = 'python,sql,data'
-- ✅ Table de liaison (many-to-many) ou ARRAY/JSONConventions de nommage
Conventions
-- Tables : pluriel, snake_case
employes, lignes_commande, categories_produits
-- Colonnes : snake_case, préfixer les FK
id, nom, email, created_at, updated_at
client_id (FK), categorie_id (FK)
-- Index : idx_table_colonne
idx_employes_email, idx_commandes_date_statut
-- Contraintes : pk_ / uq_ / fk_ / chk_
pk_employes, uq_employes_email, fk_employes_dept
-- Vues : v_ ou vw_
v_employes_actifs, vw_stats_ventes
-- Procédures/Fonctions : verbe + objet
calculer_ttc(), archiver_commandes(), get_client_by_id()Checklist performance
Checklist
□ Index sur toutes les FK
□ Index sur les colonnes WHERE / JOIN / ORDER BY fréquentes
□ EXPLAIN ANALYZE sur les requêtes lentes
□ Statistiques à jour (ANALYZE)
□ Pas de SELECT *
□ Pas de fonctions sur colonnes indexées dans WHERE
□ Pagination keyset plutôt que OFFSET pour gros volumes
□ Connection pooling côté applicatif
□ Requêtes préparées (prepared statements) contre injection SQL
□ Monitorer les requêtes lentes (pg_stat_statements / slow_query_log)SQL Cheatsheet Complet — 2026