SQL Cheatsheet Exhaustif
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+) | ROWNUM

SELECT — 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 uniquement

ORDER 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 / Oracle

LIMIT / Pagination

SGBDSyntaxe
MySQL / PostgreSQLLIMIT 10 OFFSET 20
SQL ServerOFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY
Oracle 12c+OFFSET 20 ROWS FETCH FIRST 10 ROWS ONLY
Oracle <12cWHERE 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 POSIX

Opé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 c

CASE 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

FonctionDescriptionExemple
COUNT(*)Nb total de lignesSELECT COUNT(*) FROM t
COUNT(col)Nb de non-NULLCOUNT(email)
COUNT(DISTINCT col)Nb valeurs uniquesCOUNT(DISTINCT ville)
SUM(col)SommeSUM(montant)
AVG(col)MoyenneAVG(note)
MIN(col)Valeur minMIN(date_creation)
MAX(col)Valeur maxMAX(salaire)
GROUP_CONCATConcaténation (MySQL)GROUP_CONCAT(nom SEPARATOR ',')
STRING_AGGConcaténation (PG/SS)STRING_AGG(nom, ',')
ARRAY_AGGEn 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égoriePostgreSQLMySQLSQL Server
EntierINT, BIGINT, SMALLINTINT, BIGINT, TINYINTINT, BIGINT, TINYINT
DécimalDECIMAL(p,s), NUMERICDECIMAL(p,s), FLOATDECIMAL(p,s), FLOAT
Auto-incr.SERIAL, BIGSERIALAUTO_INCREMENTIDENTITY(1,1)
TexteVARCHAR(n), TEXTVARCHAR(n), TEXT, LONGTEXTNVARCHAR(n), NVARCHAR(MAX)
BooléenBOOLEANTINYINT(1) / BOOLBIT
Date/HeureDATE, TIMESTAMP, TIMESTAMPTZDATE, DATETIME, TIMESTAMPDATE, DATETIME2
JSONJSON, JSONBJSONNVARCHAR(MAX)
UUIDUUIDCHAR(36)UNIQUEIDENTIFIER
BinaireBYTEABLOB, LONGBLOBVARBINARY(MAX)
TableauINT[], 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 chevauchement

Index

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

NiveauDirty ReadNon-RepeatablePhantomUsage
READ UNCOMMITTEDPossiblePossiblePossibleRare, perf max
READ COMMITTEDNonPossiblePossibleDéfaut PG/Oracle/SS
REPEATABLE READNonNonPossibleDéfaut MySQL InnoDB
SERIALIZABLENonNonNonSé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

FonctionDescriptionExemple → Résultat
LENGTH / LENLongueurLENGTH('abc') → 3
UPPER / LOWERCasseUPPER('abc') → 'ABC'
TRIMEspacesTRIM(' ab ') → 'ab'
SUBSTRINGExtractionSUBSTRING('abcde',2,3) → 'bcd'
LEFT / RIGHTN premiers/derniersLEFT('abcde',3) → 'abc'
REPLACERemplacementREPLACE('abc','b','x') → 'axc'
CONCATConcaténationCONCAT('a','b') → 'ab'
CONCAT_WSConcat avec sépar.CONCAT_WS('-','a','b') → 'a-b'
LPAD / RPADCompléterLPAD('5',3,'0') → '005'
REVERSEInverserREVERSE('abc') → 'cba'
POSITIONPositionPOSITION('c' IN 'abcd') → 3
SPLIT_PARTDécouper (PG)SPLIT_PART('a-b-c','-',2) → 'b'
REGEXP_REPLACERegex replaceREGEXP_REPLACE(s,'[0-9]','','g')

Fonctions de date/heure

ActionPostgreSQLMySQLSQL Server
MaintenantNOW() / CURRENT_TIMESTAMPNOW()GETDATE() / SYSDATETIME()
Date du jourCURRENT_DATECURDATE()CAST(GETDATE() AS DATE)
ExtraireEXTRACT(YEAR FROM d)YEAR(d), MONTH(d)DATEPART(year, d)
TronquerDATE_TRUNC('month', d)DATE_FORMAT(d,'%Y-%m-01')DATETRUNC(month, d)
Ajouterd + INTERVAL '3 days'DATE_ADD(d, INTERVAL 3 DAY)DATEADD(day, 3, d)
Différenced2 - d1DATEDIFF(d2, d1)DATEDIFF(day, d1, d2)
FormaterTO_CHAR(d, 'DD/MM/YYYY')DATE_FORMAT(d, '%d/%m/%Y')FORMAT(d, 'dd/MM/yyyy')
ParserTO_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 Cost

Top 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 couvrants

Partitionnement (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

ConceptPostgreSQLMySQLSQL ServerOracle
Auto-incr.SERIAL / GENERATEDAUTO_INCREMENTIDENTITY(1,1)SEQUENCE + trigger
UpsertON CONFLICT DO UPDATEON DUPLICATE KEYMERGEMERGE
Concat||CONCAT()+||
BoolBOOLEANTINYINT(1)BIT (0/1)NUMBER(1)
LIMITLIMIT n OFFSET mLIMIT n OFFSET mOFFSET..FETCHFETCH FIRST / ROWNUM
If NULLCOALESCEIFNULL / COALESCEISNULL / COALESCENVL / COALESCE
String aggSTRING_AGGGROUP_CONCATSTRING_AGGLISTAGG
Regex~ / ~* / SIMILAR TOREGEXPLIKE (limité)REGEXP_LIKE
CTE récursiveWITH RECURSIVEWITH RECURSIVE (8.0+)WITH (auto)WITH (auto)
JSONJSONB (natif)JSON (natif)OPENJSONJSON_TABLE (21c)
Full-texttsvector / GINFULLTEXT INDEXCONTAINSOracle 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/JSON

Conventions 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