Vous devez trouver le top 3 des déploiements les plus longs par service, ajouter une colonne avec le cumul progressif du nombre d'alertes et comparer chaque durée de déploiement à la précédente. Avec un SELECT classique et des sous-requêtes imbriquées, la requête devient vite illisible. C'est exactement le problème que résolvent les CTE (Common Table Expressions) et les window functions.
Ce guide couvre les constructions SQL avancées qui transforment des requêtes complexes en code structuré et lisible : CASE, CTE (WITH), fonctions de fenêtrage (OVER, PARTITION BY), vues, opérations ensemblistes et les bases du DDL. Il se termine par un tour d'horizon de SQL:2023.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Écrire des expressions conditionnelles avec
CASE WHEN - Structurer des requêtes complexes avec les CTE (
WITH) - Explorer des hiérarchies avec les CTE récursifs (
WITH RECURSIVE) - Calculer sans regrouper grâce aux window functions (
ROW_NUMBER,RANK,LAG,SUM OVER) - Créer des vues pour réutiliser et sécuriser les requêtes
- Combiner des résultats avec
UNION,INTERSECT,EXCEPT - Connaître les bases du DDL et les nouveautés SQL:2023
Dans quel contexte ?
Section intitulée « Dans quel contexte ? »Le SQL avancé intervient dans des situations d'administration réelles, dès que la question posée ne se résout pas par un simple filtre. Le signal le plus fiable : vous avez besoin d'un classement, d'un cumul ou d'une comparaison entre lignes, et GROUP BY détruit justement les lignes dont vous avez besoin. Voici les cas récurrents que couvre ce guide :
- Classement des déploiements les plus longs par service (top N par groupe)
- Suivi de l'évolution du nombre d'alertes jour par jour (cumul progressif)
- Comparaison de la durée de déploiement entre deux versions (
LAG) - Création de vues de supervision lisibles pour un tableau de bord Grafana
- Exploration de catégories hiérarchiques avec un CTE récursif
Ce guide ne couvre pas…
Section intitulée « Ce guide ne couvre pas… »Les notions ci-dessous sont supposées acquises, elles sont traitées dans les guides précédents de la série. Si l'une d'elles vous manque, faites ce détour d'abord : les requêtes de cette page les combinent sans les réexpliquer.
- Les bases du
SELECT,WHERE,ORDER BY→ voir le guide Syntaxe de base INSERT,UPDATE,DELETEet transactions → voir le guide Manipuler les données- Les fonctions d'agrégation simples (
COUNT,GROUP BY) → voir le guide Fonctions et agrégations
Prérequis
Section intitulée « Prérequis »- Maîtriser
SELECT,WHERE,JOIN,GROUP BY(guides précédents) - La base fil rouge
infra.dbchargée dans~/Projets/cours-sql
CASE : la logique conditionnelle en SQL
Section intitulée « CASE : la logique conditionnelle en SQL »CASE permet d'évaluer des conditions directement dans une requête, comme un if/else intégré au SQL. Il ne filtre rien : il produit une valeur à partir de la première condition vraie, et s'arrête là. Sans clause ELSE, les lignes qui ne satisfont aucune condition reçoivent NULL, ce qui est la cause la plus fréquente de colonnes vides inattendues.
CASE WHEN … THEN … ELSE … END
Section intitulée « CASE WHEN … THEN … ELSE … END »Les conditions sont évaluées dans l'ordre d'écriture : ici, un serveur à 64 Go tombe dans la première branche et n'est jamais testé contre les suivantes.
SELECT hostname, ram_gb, CASE WHEN ram_gb >= 32 THEN 'Haute capacité' WHEN ram_gb >= 16 THEN 'Standard' WHEN ram_gb >= 8 THEN 'Entrée de gamme' ELSE 'Minimal' END AS categorie_ramFROM serversWHERE active = 1;CASE dans différents contextes
Section intitulée « CASE dans différents contextes »CASE fonctionne partout où une expression est attendue :
-- Dans un ORDER BY : trier les alertes critiques en premierSELECT severity, messageFROM alertsORDER BY CASE severity WHEN 'critical' THEN 0 WHEN 'warning' THEN 1 WHEN 'info' THEN 2 END;
-- Dans un agrégat : comptage conditionnel (pivot)SELECT sv.hostname, COUNT(CASE WHEN a.severity = 'critical' THEN 1 END) AS critiques, COUNT(CASE WHEN a.severity = 'warning' THEN 1 END) AS warnings, COUNT(CASE WHEN a.severity = 'info' THEN 1 END) AS infosFROM servers svLEFT JOIN alerts a ON a.server_id = sv.idGROUP BY sv.hostname;
-- Classifier les serveurs par tailleSELECT hostname, CASE WHEN cpu_cores >= 8 AND ram_gb >= 32 THEN 'large' WHEN cpu_cores >= 4 AND ram_gb >= 8 THEN 'medium' ELSE 'small' END AS tailleFROM serversWHERE active = 1;Common Table Expressions (CTE)
Section intitulée « Common Table Expressions (CTE) »Un CTE donne un nom à une étape intermédiaire de calcul, et ce nom devient utilisable dans la requête qui suit. Le gain est d'abord de lisibilité : au lieu d'imbriquer trois niveaux de sous-requêtes, vous décrivez les étapes de haut en bas. La contrepartie à connaître : un CTE n'existe que le temps de la requête, il n'est pas stocké et n'est pas visible d'une requête à l'autre.
Syntaxe WITH … AS
Section intitulée « Syntaxe WITH … AS »Un CTE est une requête nommée, définie avec WITH, utilisable dans la requête principale comme si c'était une table temporaire :
WITH deploiements_longs AS ( SELECT service_id, deployed_by, version, duration_seconds FROM deployments WHERE status = 'success' AND duration_seconds > 60)SELECT s.name AS service, u.full_name, dl.version, dl.duration_secondsFROM deploiements_longs dlINNER JOIN services s ON s.id = dl.service_idINNER JOIN users u ON u.id = dl.deployed_byORDER BY dl.duration_seconds DESC;Le CTE rend la requête lisible : la logique de filtrage est séparée de la jointure finale.
Plusieurs CTE dans une même requête
Section intitulée « Plusieurs CTE dans une même requête »Chaînez les CTE séparés par des virgules :
WITH alertes_recentes AS ( SELECT * FROM alerts WHERE created_at > datetime('now', '-7 days')),alertes_par_serveur AS ( SELECT server_id, COUNT(*) AS nb, COUNT(CASE WHEN severity = 'critical' THEN 1 END) AS critiques FROM alertes_recentes GROUP BY server_id)SELECT sv.hostname, a.nb, a.critiquesFROM servers svINNER JOIN alertes_par_serveur a ON a.server_id = sv.idORDER BY a.critiques DESC, a.nb DESC;CTE récursif (WITH RECURSIVE)
Section intitulée « CTE récursif (WITH RECURSIVE) »Un CTE récursif s'appelle lui-même pour parcourir des structures hiérarchiques. La table categories de votre base fil rouge a une hiérarchie parent/enfant idéale pour cela :
WITH RECURSIVE arborescence AS ( -- Cas de base : les racines (parent_id IS NULL) SELECT id, name, parent_id, 0 AS depth, name AS chemin FROM categories WHERE parent_id IS NULL
UNION ALL
-- Récursion : les enfants SELECT c.id, c.name, c.parent_id, a.depth + 1, a.chemin || ' > ' || c.name FROM categories c INNER JOIN arborescence a ON a.id = c.parent_id)SELECT chemin, depthFROM arborescenceORDER BY chemin;Résultat :
| chemin | depth |
|---|---|
| Infrastructure | 0 |
| Infrastructure > Compute | 1 |
| Infrastructure > Compute > Conteneurs | 2 |
| Infrastructure > Compute > Machines virtuelles | 2 |
| Infrastructure > Réseau | 1 |
| Infrastructure > Réseau > Firewalls | 2 |
| Infrastructure > Réseau > Load Balancers | 2 |
| Infrastructure > Stockage | 1 |
| Infrastructure > Stockage > Bloc | 2 |
| Infrastructure > Stockage > Objet | 2 |
| Infrastructure > Stockage > Sauvegarde | 2 |
CTE vs sous-requête
Section intitulée « CTE vs sous-requête »Le choix se joue rarement sur la performance : la plupart des optimiseurs traitent un CTE non récursif exactement comme la sous-requête équivalente. Il se joue sur la réutilisation (un CTE se référence plusieurs fois, une sous-requête doit être recopiée) et sur la récursivité, que seule la forme WITH RECURSIVE permet.
| Critère | CTE | Sous-requête |
|---|---|---|
| Lisibilité | Meilleure (nom explicite, séparation logique) | Correcte pour des requêtes courtes |
| Réutilisation | Utilisable plusieurs fois dans la même requête | Doit être dupliquée |
| Récursivité | Supportée (WITH RECURSIVE) | Non supportée |
| Performance | Identique dans la plupart des cas (l'optimiseur inline le CTE) | Identique |
Window functions (fonctions de fenêtrage)
Section intitulée « Window functions (fonctions de fenêtrage) »Les window functions répondent aux questions que GROUP BY rend impossibles : « quel est le rang de cette ligne dans son groupe », « combien avait-on cumulé à cette date », « de combien cette valeur a-t-elle bougé depuis la précédente ». Elles s'exécutent après WHERE et GROUP BY dans l'ordre d'évaluation SQL, ce qui explique l'erreur classique window functions are not allowed in WHERE : pour filtrer sur leur résultat, il faut passer par un CTE. SQLite les prend en charge depuis la version 3.25.
Le concept : calculer sans regrouper
Section intitulée « Le concept : calculer sans regrouper »Les window functions calculent une valeur pour chaque ligne en utilisant un ensemble de lignes liées (la « fenêtre »), sans réduire le nombre de lignes comme GROUP BY :
-- GROUP BY : une ligne par service (les lignes sont fusionnées)SELECT service_id, SUM(duration_seconds) FROM deployments GROUP BY service_id;
-- Window function : toutes les lignes sont conservées + le total par serviceSELECT service_id, version, duration_seconds, SUM(duration_seconds) OVER (PARTITION BY service_id) AS total_serviceFROM deploymentsWHERE status = 'success';La clause OVER (PARTITION BY … ORDER BY …)
Section intitulée « La clause OVER (PARTITION BY … ORDER BY …) »OVER est obligatoire : c'est elle qui transforme une fonction en fonction de fenêtrage, et une agrégation sans OVER reste une agrégation classique. Ses deux paramètres sont indépendants : PARTITION BY découpe les lignes en sous-ensembles isolés, ORDER BY définit l'ordre à l'intérieur de chaque sous-ensemble. Sans PARTITION BY, la fenêtre couvre l'ensemble du résultat, comme dans l'exemple ci-dessous.
SELECT hostname, created_at, ROW_NUMBER() OVER (ORDER BY created_at) AS rangFROM servers;| Composant | Rôle |
|---|---|
OVER () | Fenêtre = toutes les lignes |
PARTITION BY col | Divise en sous-fenêtres (comme GROUP BY mais sans fusion) |
ORDER BY col | Ordonne les lignes dans chaque partition |
Fonctions de classement
Section intitulée « Fonctions de classement »Les trois fonctions ci-dessous partagent la même fenêtre et ne diffèrent que sur le traitement des ex-aequo, d'où l'intérêt de les afficher côte à côte sur les mêmes données.
SELECT d.service_id, d.version, d.duration_seconds, ROW_NUMBER() OVER (PARTITION BY d.service_id ORDER BY d.duration_seconds DESC) AS rang, RANK() OVER (PARTITION BY d.service_id ORDER BY d.duration_seconds DESC) AS rang_ex_aequo, DENSE_RANK() OVER (PARTITION BY d.service_id ORDER BY d.duration_seconds DESC) AS rang_denseFROM deployments dWHERE d.status = 'success';| Fonction | Comportement en cas d'ex-aequo |
|---|---|
ROW_NUMBER | Attribue un numéro unique (pas d'ex-aequo) |
RANK | Ex-aequo possibles, saute les rangs (1, 2, 2, 4) |
DENSE_RANK | Ex-aequo possibles, ne saute pas (1, 2, 2, 3) |
NTILE(n) | Divise en n groupes de taille égale |
Cas concret : top 3 des déploiements les plus longs par service :
WITH classement AS ( SELECT d.service_id, s.name AS service, d.version, d.duration_seconds, ROW_NUMBER() OVER (PARTITION BY d.service_id ORDER BY d.duration_seconds DESC) AS rang FROM deployments d INNER JOIN services s ON s.id = d.service_id WHERE d.status = 'success')SELECT service, version, duration_seconds, rangFROM classementWHERE rang <= 3;Fonctions de décalage
Section intitulée « Fonctions de décalage »LAG et LEAD lisent la valeur d'une autre ligne de la fenêtre sans jointure. Sur la première ligne, LAG n'a pas de précédent et renvoie NULL : la colonne variation est donc vide sur la première ligne, ce n'est pas une anomalie.
SELECT deployed_at, version, duration_seconds, LAG(duration_seconds, 1) OVER (ORDER BY deployed_at) AS duree_precedente, LEAD(duration_seconds, 1) OVER (ORDER BY deployed_at) AS duree_suivante, duration_seconds - LAG(duration_seconds, 1) OVER (ORDER BY deployed_at) AS variationFROM deploymentsWHERE service_id = 3 AND status = 'success';| Fonction | Renvoie |
|---|---|
LAG(col, n) | La valeur de col n lignes avant |
LEAD(col, n) | La valeur de col n lignes après |
FIRST_VALUE(col) | La première valeur de la fenêtre |
LAST_VALUE(col) | La dernière valeur de la fenêtre |
Agrégations fenêtrées : cumul progressif
Section intitulée « Agrégations fenêtrées : cumul progressif »Ajouter ORDER BY dans la clause OVER d'une agrégation change son sens : au lieu du total de la fenêtre, elle renvoie le total jusqu'à la ligne courante.
SELECT date(created_at) AS jour, severity, COUNT(*) OVER (ORDER BY date(created_at)) AS cumul_alertesFROM alertsORDER BY created_at;Le cumul progressif ajoute chaque compteur aux lignes précédentes, sans GROUP BY.
ROWS vs RANGE : le cadre de la fenêtre
Section intitulée « ROWS vs RANGE : le cadre de la fenêtre »Par défaut, ORDER BY dans la clause OVER utilise RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW. Vous pouvez contrôler le cadre précisément :
-- Moyenne glissante sur les 3 derniers déploiements d'un serviceSELECT deployed_at, version, duration_seconds, AVG(duration_seconds) OVER ( PARTITION BY service_id ORDER BY deployed_at ROWS BETWEEN 2 PRECEDING AND CURRENT ROW ) AS moyenne_glissanteFROM deploymentsWHERE status = 'success';Les vues
Section intitulée « Les vues »Une vue enregistre une requête sous un nom, dans le schéma de la base : contrairement au CTE, elle survit à la session et devient interrogeable par n'importe quel client. Elle ne duplique aucune donnée, la requête sous-jacente est réexécutée à chaque appel, ce qui garantit la fraîcheur mais reporte le coût sur chaque lecture. C'est aussi un outil de cloisonnement : on peut donner accès à une vue sans donner accès aux tables qu'elle interroge.
CREATE VIEW : une requête enregistrée
Section intitulée « CREATE VIEW : une requête enregistrée »Une vue est une requête SQL sauvegardée sous un nom. Elle ne stocke pas les données, elle est exécutée à chaque appel :
CREATE VIEW v_servers_production ASSELECT hostname, ip_address, os, os_version, cpu_cores, ram_gb, disk_gbFROM serversWHERE active = 1 AND environment = 'production';
-- Utilisation : comme une tableSELECT * FROM v_servers_production WHERE os = 'Debian';Un deuxième exemple, plus complexe :
CREATE VIEW v_alertes_actives ASSELECT a.id, sv.hostname, a.severity, a.message, a.created_atFROM alerts aINNER JOIN servers sv ON sv.id = a.server_idWHERE a.acknowledged = 0ORDER BY CASE a.severity WHEN 'critical' THEN 0 WHEN 'warning' THEN 1 ELSE 2 END, a.created_at DESC;Les vues servent à :
- Simplifier les requêtes complexes récurrentes
- Sécuriser l'accès (exposer certaines colonnes sans accès à la table entière)
- Abstraire le schéma physique
UNION, INTERSECT, EXCEPT
Section intitulée « UNION, INTERSECT, EXCEPT »Ces trois opérateurs travaillent sur des ensembles de lignes entiers, pas sur des colonnes. Ils imposent une contrainte forte : les requêtes combinées doivent renvoyer le même nombre de colonnes, dans le même ordre, avec des types compatibles. Les noms de colonnes du résultat sont ceux de la première requête, les autres sont ignorés.
Combiner les résultats de plusieurs requêtes
Section intitulée « Combiner les résultats de plusieurs requêtes »UNION empile les résultats verticalement. Ici, la colonne littérale 'serveur' / 'utilisateur' sert à savoir de quelle requête vient chaque ligne, information autrement perdue.
-- Liste complète des hostnames et des usernames (dans une seule colonne)SELECT hostname AS identifiant, 'serveur' AS type FROM serversUNIONSELECT username, 'utilisateur' FROM users;UNION vs UNION ALL
Section intitulée « UNION vs UNION ALL »La différence n'est pas cosmétique : UNION déduplique, ce qui l'oblige à trier ou hacher l'intégralité du résultat avant de le renvoyer.
| Opérateur | Comportement |
|---|---|
UNION | Supprime les doublons (plus lent, tri implicite) |
UNION ALL | Garde tout (plus rapide, pas de tri) |
Utilisez UNION ALL par défaut si vous n'avez pas besoin de déduplication.
INTERSECT et EXCEPT
Section intitulée « INTERSECT et EXCEPT »INTERSECT ne garde que les lignes présentes dans les deux résultats, EXCEPT retire du premier résultat celles qui figurent dans le second. L'ordre compte pour EXCEPT : inverser les deux requêtes donne un résultat différent.
-- Serveurs qui ont des alertes ET des services (INTERSECT)SELECT server_id FROM alertsINTERSECTSELECT server_id FROM services;
-- Serveurs qui ont des alertes mais AUCUN service (EXCEPT)SELECT server_id FROM alertsEXCEPTSELECT server_id FROM services;Opérations DDL essentielles
Section intitulée « Opérations DDL essentielles »Le DDL (Data Definition Language) gère la structure des tables. Voici les opérations les plus courantes.
CREATE TABLE : aperçu des différences
Section intitulée « CREATE TABLE : aperçu des différences »Vous avez déjà la structure complète dans votre init-db.sql. Voici les principales différences entre SQLite et les SGBD de production :
| Concept | SQLite | PostgreSQL | MySQL |
|---|---|---|---|
| Auto-increment | INTEGER PRIMARY KEY AUTOINCREMENT | SERIAL ou GENERATED ALWAYS AS IDENTITY | AUTO_INCREMENT |
| Booléen | INTEGER (0/1) | BOOLEAN | BOOLEAN (alias de TINYINT) |
| Date/heure | TEXT au format ISO | TIMESTAMP / TIMESTAMPTZ | DATETIME / TIMESTAMP |
| Texte | TEXT (pas de VARCHAR limité) | TEXT ou VARCHAR(n) | VARCHAR(n) ou TEXT |
| CHECK | Supporté | Supporté | Supporté depuis MySQL 8.0.16 |
ALTER TABLE
Section intitulée « ALTER TABLE »ALTER TABLE modifie la structure d'une table existante sans la recréer. Les deux opérations ci-dessous sont les seules réellement portables entre SQLite, PostgreSQL et MySQL.
-- Ajouter une colonne (fonctionne partout)ALTER TABLE servers ADD COLUMN last_reboot TEXT;
-- Renommer une colonne (SQLite 3.25+, PostgreSQL, MySQL 8.0)ALTER TABLE servers RENAME COLUMN last_reboot TO last_restart;CREATE INDEX et EXPLAIN QUERY PLAN
Section intitulée « CREATE INDEX et EXPLAIN QUERY PLAN »Un index accélère les recherches sur une colonne fréquemment filtrée :
CREATE INDEX idx_alerts_server ON alerts(server_id);CREATE INDEX idx_alerts_severity ON alerts(severity);CREATE INDEX idx_deployments_date ON deployments(deployed_at);Vérifiez si l'index est utilisé avec EXPLAIN QUERY PLAN (SQLite) :
EXPLAIN QUERY PLANSELECT * FROM alerts WHERE server_id = 3;Recherchez USING INDEX dans le résultat, si vous voyez SCAN, l'index n'est pas utilisé.
Aperçu de SQL:2023 (ISO/IEC 9075:2023)
Section intitulée « Aperçu de SQL:2023 (ISO/IEC 9075:2023) »Le standard SQL:2023 (publié en juin 2023) apporte plusieurs nouveautés. La plupart ne sont pas encore supportées par tous les SGBD, mais certaines sont déjà disponibles.
JSON natif dans le standard
Section intitulée « JSON natif dans le standard »SQL:2023 ajoute un type JSON natif et des fonctions standardisées (JSON_QUERY, JSON_VALUE, JSON_TABLE). SQLite, PostgreSQL et MySQL supportent déjà JSON, mais avec des syntaxes propriétaires. SQLite utilise json(), json_extract(), etc.
Property Graph Queries (SQL/PGQ)
Section intitulée « Property Graph Queries (SQL/PGQ) »Nouveauté majeure : interroger des graphes de propriétés directement en SQL, sans base de données graphe séparée. Pas encore supporté par les SGBD courants.
Nouvelles fonctions utiles
Section intitulée « Nouvelles fonctions utiles »Deux ajouts de confort méritent d'être connus, avec la réserve habituelle : SQLite les orthographie différemment de PostgreSQL et MySQL.
-- GREATEST / LEAST : valeur max/min parmi une listeSELECT MAX(cpu_cores, ram_gb) FROM servers; -- SQLite : MAX avec 2+ args-- PostgreSQL / MySQL : GREATEST(cpu_cores, ram_gb)
-- Underscores dans les littéraux numériques (lisibilité)SELECT 1_000_000; -- 1000000Exercice pratique
Section intitulée « Exercice pratique »Ouvrez sqlite3 infra.db : les cinq exercices reprennent chacune des constructions du guide sur la base fil rouge. La correction est donnée sous chaque énoncé : essayez d'écrire la requête avant de la lire, c'est la seule manière de repérer ce qui n'est pas encore acquis. Le dernier exercice crée une vue persistante dans la base, supprimez-la avec DROP VIEW v_alertes_actives; si vous voulez repartir d'un état propre.
-
Créez un CTE qui calcule le nombre de déploiements par utilisateur, puis affichez uniquement ceux qui en ont fait plus de 5.
WITH totaux AS (SELECT deployed_by, COUNT(*) AS nb, COUNT(CASE WHEN status = 'success' THEN 1 END) AS reussisFROM deploymentsGROUP BY deployed_by)SELECT u.full_name, t.nb, t.reussisFROM users uINNER JOIN totaux t ON t.deployed_by = u.idWHERE t.nb > 5ORDER BY t.nb DESC; -
Affichez la hiérarchie des catégories avec un CTE récursif.
WITH RECURSIVE arbo AS (SELECT id, name, parent_id, 0 AS depth, name AS cheminFROM categories WHERE parent_id IS NULLUNION ALLSELECT c.id, c.name, c.parent_id, a.depth + 1, a.chemin || ' > ' || c.nameFROM categories cINNER JOIN arbo a ON a.id = c.parent_id)SELECT chemin, depth FROM arbo ORDER BY chemin; -
Classez les déploiements de chaque service par durée décroissante et affichez les 3 plus longs.
WITH classement AS (SELECT d.service_id, s.name AS service, d.version, d.duration_seconds,ROW_NUMBER() OVER (PARTITION BY d.service_id ORDER BY d.duration_seconds DESC) AS rangFROM deployments dINNER JOIN services s ON s.id = d.service_idWHERE d.status = 'success')SELECT service, version, duration_seconds, rangFROM classement WHERE rang <= 3; -
Calculez la variation de durée entre chaque déploiement consécutif du service
api-backend(service_id = 3).SELECT deployed_at, version, duration_seconds,LAG(duration_seconds) OVER (ORDER BY deployed_at) AS precedent,duration_seconds - LAG(duration_seconds) OVER (ORDER BY deployed_at) AS variationFROM deploymentsWHERE service_id = 3 AND status = 'success'; -
Créez une vue qui expose les alertes actives avec le hostname du serveur.
CREATE VIEW v_alertes_actives ASSELECT a.id, sv.hostname, a.severity, a.message, a.created_atFROM alerts aINNER JOIN servers sv ON sv.id = a.server_idWHERE a.acknowledged = 0;SELECT * FROM v_alertes_actives;
Dépannage
Section intitulée « Dépannage »La plupart des blocages sur ces constructions viennent de l'ordre d'évaluation de SQL, pas d'une erreur de syntaxe. Une window function ne peut pas être filtrée dans le WHERE de la même requête, UNION déduplique avant de renvoyer les lignes, et un index n'est utilisé que si la colonne filtrée est bien celle qu'il couvre. Ce tableau associe le message exact à la cause réelle.
| Symptôme | Cause probable | Solution |
|---|---|---|
window functions are not allowed in WHERE | Les window functions ne s'exécutent qu'après WHERE | Encapsulez dans un CTE ou une sous-requête, puis filtrez dans la requête externe |
| CTE récursif qui ne s'arrête pas | Pas de condition d'arrêt dans la partie récursive | Ajoutez WHERE depth < N ou LIMIT |
| Vue très lente | La vue n'est pas matérialisée, la requête s'exécute à chaque appel | Créez un index sur les colonnes filtrées, ou créez une table de cache |
UNION supprime des lignes attendues | UNION déduplique par défaut | Utilisez UNION ALL si vous voulez garder toutes les lignes |
EXPLAIN QUERY PLAN montre SCAN | L'index n'est pas utilisé | Vérifiez qu'un index existe sur la colonne filtrée avec .indices table_name |
À retenir
Section intitulée « À retenir »CASEajoute de la logique conditionnelle dans n'importe quelle requête- Les CTE (
WITH) structurent les requêtes complexes, préférez-les aux sous-requêtes imbriquées - Les CTE récursifs parcourent les hiérarchies (catégories, arbres, graphes)
- Les window functions calculent sur un ensemble de lignes sans réduire le résultat
ROW_NUMBER,RANK,LAG,LEADsont les fonctions de fenêtrage les plus utiles- Les vues encapsulent une requête fréquente, SQLite ne supporte que les vues simples
UNION ALLest plus rapide queUNIONquand la déduplication n'est pas nécessaireEXPLAIN QUERY PLAN(SQLite) est votre outil pour vérifier qu'un index est utilisé