Guide pratique · BigQuery · Snowflake · PostgreSQL · Lecture : ~25 min Teste ton niveau sur modai-lab.com/test-sql/


L'histoire vraie derriĂšre ce guide

Un junior Ă  2 500€ par mois. MotivĂ©, sĂ©rieux, productif.

La facture BigQuery tombe Ă  la fin du mois. 7 500€ de calcul consommĂ©. Rien que sur ses requĂȘtes.

Il ne savait pas. Personne ne lui avait appris qu'une requĂȘte mal Ă©crite a un coĂ»t rĂ©el. Personne ne lui avait dit qu'un index peut diviser ce coĂ»t par 100. En formation, les datasets font 500 lignes. En entreprise, ils en font 200 millions.

Ce guide liste les 15 requĂȘtes les plus coĂ»teuses — avec leur version optimisĂ©e et les rĂ©flexes Ă  adopter immĂ©diatement.

Ce guide couvre :

  • Les 15 patterns qui font exploser la facture cloud

  • Les rĂ©flexes que personne ne t'apprend en formation

  • Un prompt Claude pour auditer le coĂ»t de tes requĂȘtes


đŸ§Ș Calibre ton niveau avant de commencer

Sais-tu déjà expliquer pourquoi une fonction dans un WHERE peut multiplier le coût par 100 ? Fais le test en 5 minutes.

👉 Faire le test sur modai-lab.com/test-sql/

_Résultat en direct · Niveau calibré · Parcours personnalisé_


BLOC 1 — Les requĂȘtes qui lisent trop de donnĂ©es


RequĂȘte 1 — SELECT * sur une grande table

Coût réel : sur BigQuery à 5$/TB, une table de 2 TB lue 30 fois par jour = 300$/jour = ~9 000$/mois.

-- ❌ Lit TOUTES les colonnes de la table
SELECT * FROM orders WHERE statut = 'livré';
-- Table : 200M lignes, 50 colonnes, 2 TB → 2 TB lus à chaque fois
 
-- ✅ Lister uniquement les colonnes nĂ©cessaires
SELECT order_id, user_id, montant, created_at, statut
FROM orders
WHERE statut = 'livré';
-- 4 colonnes sur 50 → ~160 GB lus → coĂ»t divisĂ© par ~12
15-requetes-sql-facture-cloud · 01

Calcul d'impact :

SELECT * → 2 TB × 5$/TB = 10$ par requĂȘte
SELECT 4 cols → 160 GB × 5$/TB = 0.80$ par requĂȘte
 
30 exĂ©cutions/jour × 30 jours = 9 000$ vs 720$
Économie : 8 280$ par mois sur une seule requĂȘte
15-requetes-sql-facture-cloud · 02 · Calcul d'impact :

RĂ©flexe : ne jamais utiliser SELECT * en dehors d'une exploration ponctuelle LIMIT 100. MĂȘme pour "regarder vite".


RequĂȘte 2 — Pas de filtre sur la colonne de partition

Sur les data warehouses modernes, les tables sont partitionnées (généralement par date). Sans filtre sur la partition, le moteur scanne tout.

-- ❌ Scan de toutes les partitions (toute l'histoire de la table)
SELECT user_id, SUM(montant) AS ca
FROM orders
WHERE statut = 'livré' -- statut n'est pas la colonne de partition
GROUP BY user_id;
-- 5 ans de donnĂ©es lus alors qu'on veut peut-ĂȘtre juste cette annĂ©e
 
-- ✅ Toujours filtrer sur la colonne de partition en premier
SELECT user_id, SUM(montant) AS ca
FROM orders
WHERE created_at >= '2024-01-01' -- filtre partition → seulement 2024
AND statut = 'livré'
GROUP BY user_id;
-- CoĂ»t rĂ©duit de 5× si la table contient 5 ans d'historique
15-requetes-sql-facture-cloud · 03

Comment savoir quelle colonne est la partition :

-- BigQuery
SELECT * FROM `projet.dataset.INFORMATION_SCHEMA.PARTITIONS`
WHERE table_name = 'orders';
 
-- PostgreSQL
SELECT schemaname, tablename, partitionboundspec
FROM pg_partitions WHERE tablename = 'orders';
15-requetes-sql-facture-cloud · 04 · Comment savoir quelle colonne est la partition :

RequĂȘte 3 — COUNT(*) suivi d'un GROUP BY sur toutes les colonnes

-- ❌ GROUP BY sur toutes les colonnes = autant de groupes que de lignes
SELECT *, COUNT(*) AS n
FROM orders
GROUP BY order_id, user_id, montant, statut, created_at, ...;
-- Résultat : une ligne par commande avec n = 1 dans tous les cas
-- Aucune valeur, lecture complĂšte de la table
 
-- ✅ Identifier ce qu'on veut vraiment
-- Si on cherche les doublons sur un champ spécifique :
SELECT order_id, COUNT(*) AS n
FROM orders
GROUP BY order_id
HAVING COUNT(*) > ;
-- Lit seulement order_id → coĂ»t minimal
15-requetes-sql-facture-cloud · 05

RequĂȘte 4 — DISTINCT sur toutes les colonnes pour "nettoyer"

-- ❌ DISTINCT * force un tri complet de toute la table
SELECT DISTINCT *
FROM orders;
-- Le moteur lit tout, trie tout, compare tout
 
-- ✅ Identifier pourquoi il y a des doublons et corriger la cause
-- Si doublons sur une clé spécifique :
SELECT DISTINCT order_id FROM orders; -- lecture d'une seule colonne
 
-- Ou avec ROW_NUMBER pour dédupliquer proprement
SELECT * FROM (
SELECT *, ROW_NUMBER() OVER (PARTITION BY order_id ORDER BY created_at DESC) AS rn
FROM orders
) t WHERE rn = ;
15-requetes-sql-facture-cloud · 06

RequĂȘte 5 — Jointure sur des tables entiĂšres sans filtres prĂ©alables

-- ❌ Jointure sur 200M lignes × 5M lignes sans rĂ©duire d'abord
SELECT o.order_id, p.categorie, SUM(o.montant) AS ca
FROM orders o
JOIN products p ON o.product_id = p.id
JOIN order_lines ol ON o.id = ol.order_id
WHERE o.created_at >= '2024-01-01'
AND p.categorie = 'Électronique'
GROUP BY o.order_id, p.categorie;
-- Le filtre est appliqué APRÈS les jointures selon certains optimiseurs
 
-- ✅ RĂ©duire les tables AVANT de les joindre via CTEs
WITH commandes_recentes AS (
SELECT order_id, product_id, montant
FROM orders
WHERE created_at >= '2024-01-01' -- filtre partition : ~10M lignes au lieu de 200M
),
produits_electronique AS (
SELECT id
FROM products
WHERE categorie = 'Électronique' -- filtre : ~200K lignes au lieu de 5M
)
SELECT c.order_id, SUM(c.montant) AS ca
FROM commandes_recentes c
JOIN produits_electronique p ON c.product_id = p.id
GROUP BY c.order_id;
-- Jointure sur 10M × 200K au lieu de 200M × 5M → 200× moins de donnĂ©es
15-requetes-sql-facture-cloud · 07

đŸ§Ș Teste ces 5 premiers patterns sur Modai Lab

Ces 5 requĂȘtes reprĂ©sentent 80% des factures cloud Ă©vitables. Modai Lab simule des tables de grande taille avec mĂ©triques de performance visibles.

👉 Pratiquer sur modai-lab.com

_Exercices avec simulation de coût · Feedback instantané_


BLOC 2 — Les requĂȘtes qui font du travail inutile


RequĂȘte 6 — Sous-requĂȘte corrĂ©lĂ©e sur une grande table

-- ❌ ExĂ©cutĂ©e une fois par ligne → 200M exĂ©cutions si la table a 200M lignes
SELECT
user_id,
email,
(SELECT COUNT(*) FROM orders o WHERE o.user_id = u.id) AS nb_commandes,
(SELECT SUM(montant) FROM orders o WHERE o.user_id = u.id) AS ca_total
FROM users u;
-- Chaque sous-requĂȘte = un scan partiel de orders par utilisateur
-- 10M users × 2 sous-requĂȘtes = 20M scans supplĂ©mentaires
 
-- ✅ AgrĂ©gation en une seule passe
WITH stats AS (
SELECT user_id, COUNT(*) AS nb_commandes, SUM(montant) AS ca_total
FROM orders GROUP BY user_id
)
SELECT u.user_id, u.email,
COALESCE(s.nb_commandes, ) AS nb_commandes,
COALESCE(s.ca_total, ) AS ca_total
FROM users u
LEFT JOIN stats s ON u.id = s.user_id;
-- Une seule passe sur orders, une jointure → 20M× moins de travail
15-requetes-sql-facture-cloud · 08

RequĂȘte 7 — Fonction sur une colonne indexĂ©e dans le WHERE

-- ❌ La fonction empĂȘche l'utilisation de l'index → scan complet
SELECT * FROM orders WHERE YEAR(created_at) = ;
SELECT * FROM orders WHERE DATE_TRUNC('month', created_at) = '2024-01-01';
SELECT * FROM orders WHERE LOWER(email_client) = 'alice@example.com';
SELECT * FROM orders WHERE CAST(montant AS VARCHAR) LIKE '1%';
-- Dans chaque cas : l'index sur la colonne est ignoré
 
-- ✅ Réécrire sans fonction sur la colonne indexĂ©e
SELECT * FROM orders
WHERE created_at >= '2024-01-01' AND created_at < '2025-01-01';
 
SELECT * FROM orders
WHERE created_at >= '2024-01-01' AND created_at < '2024-02-01';
 
-- Pour LOWER : créer un index fonctionnel
CREATE INDEX idx_orders_email_lower ON orders(LOWER(email_client));
SELECT * FROM orders WHERE LOWER(email_client) = 'alice@example.com';
-- Maintenant l'index est utilisé
15-requetes-sql-facture-cloud · 09

RequĂȘte 8 — ORDER BY sans LIMIT sur une grande table

-- ❌ Trier 200M lignes pour ne garder que les 10 premiùres
SELECT * FROM orders ORDER BY montant DESC;
-- Sans LIMIT : trie TOUTES les lignes, retourne TOUTES les lignes
-- Avec LIMIT mais ORDER BY sur colonne non indexée : trie toutes les lignes avant
 
-- ✅ Toujours limiter quand on explore
SELECT order_id, montant, created_at FROM orders
ORDER BY montant DESC
LIMIT ;
 
-- ✅ Et crĂ©er l'index pour le tri frĂ©quent
CREATE INDEX idx_orders_montant_desc ON orders(montant DESC);
-- Le top 100 est lu directement dans l'index sans trier toute la table
15-requetes-sql-facture-cloud · 10

RequĂȘte 9 — UNION au lieu de UNION ALL

-- ❌ UNION dĂ©duplique → tri complet du rĂ©sultat combinĂ©
SELECT user_id FROM clients_france
UNION
SELECT user_id FROM clients_belgique;
-- Le moteur lit les deux tables, combine, trie tout, élimine les doublons
 
-- ✅ UNION ALL si les doublons ne posent pas de problùme
SELECT user_id FROM clients_france
UNION ALL
SELECT user_id FROM clients_belgique;
-- Pas de tri, pas de dĂ©duplication → 2× Ă  10× plus rapide selon le volume
 
-- Si la déduplication est vraiment nécessaire :
SELECT DISTINCT user_id FROM (
SELECT user_id FROM clients_france
UNION ALL
SELECT user_id FROM clients_belgique
) combined;
-- MĂȘme rĂ©sultat que UNION mais avec UNION ALL d'abord
15-requetes-sql-facture-cloud · 11

RequĂȘte 10 — COUNT(DISTINCT col) sur une trĂšs grande table

-- ❌ COUNT(DISTINCT) force un tri ou un hash complet des valeurs
SELECT COUNT(DISTINCT user_id) FROM events;
-- Sur 5 milliards de lignes : trÚs lent et coûteux
 
-- ✅ Pour une approximation (souvent suffisante en analytique) :
-- HyperLogLog : rĂ©sultat approximatif mais 100× plus rapide
 
-- BigQuery
SELECT APPROX_COUNT_DISTINCT(user_id) FROM events; -- erreur < 2%
 
-- PostgreSQL (avec extension)
SELECT hll_cardinality(hll_add_agg(hll_hash_bigint(user_id))) FROM events;
 
-- Snowflake
SELECT APPROX_COUNT_DISTINCT(user_id) FROM events;
 
-- Si l'exact est requis : calculer une fois et matérialiser
15-requetes-sql-facture-cloud · 12

BLOC 3 — Les requĂȘtes qui se rĂ©pĂštent inutilement


RequĂȘte 11 — La mĂȘme agrĂ©gation lourde rĂ©pĂ©tĂ©e plusieurs fois

-- ❌ La mĂȘme sous-requĂȘte lourde recalculĂ©e 3 fois
SELECT
mois,
ca_total,
ca_total - LAG(ca_total) OVER (ORDER BY mois) AS variation,
ca_total / (SELECT SUM(montant) FROM orders) * AS pct_total, -- recalcul 1
RANK() OVER (ORDER BY ca_total DESC) AS rang
FROM (
SELECT DATE_TRUNC('month', created_at) AS mois, SUM(montant) AS ca_total
FROM orders GROUP BY
) monthly
WHERE (SELECT SUM(montant) FROM orders) > ; -- recalcul 2
 
-- ✅ CTE calculĂ©e une fois, rĂ©utilisĂ©e partout
WITH ca_mensuel AS (
SELECT DATE_TRUNC('month', created_at) AS mois, SUM(montant) AS ca_total
FROM orders GROUP BY
),
total_global AS (
SELECT SUM(montant) AS total FROM orders
)
SELECT
m.mois,
m.ca_total,
m.ca_total - LAG(m.ca_total) OVER (ORDER BY m.mois) AS variation,
ROUND(. * m.ca_total / t.total, ) AS pct_total,
RANK() OVER (ORDER BY m.ca_total DESC) AS rang
FROM ca_mensuel m
CROSS JOIN total_global t
ORDER BY m.mois;
-- orders scanné une seule fois au lieu de 3+
15-requetes-sql-facture-cloud · 13

RequĂȘte 12 — RequĂȘtes rĂ©currentes sans matĂ©rialisation

-- ❌ Ce rapport est lancĂ© 50 fois par jour par toute l'Ă©quipe
-- À chaque fois : scan complet de orders (2 TB)
SELECT
DATE_TRUNC('month', o.created_at) AS mois,
p.categorie,
SUM(o.montant) AS ca,
COUNT(DISTINCT o.user_id) AS clients
FROM orders o
JOIN products p ON o.product_id = p.id
WHERE o.statut = 'livré'
GROUP BY , ;
-- 50 exĂ©cutions × 2 TB = 100 TB lus par jour = 500$/jour
 
-- ✅ MatĂ©rialiser une fois par jour (dbt, Airflow, ou job planifiĂ©)
-- Le job de nuit calcule et stocke le résultat :
CREATE TABLE rapport_ca_cache AS
SELECT
DATE_TRUNC('month', o.created_at) AS mois,
p.categorie,
SUM(o.montant) AS ca,
COUNT(DISTINCT o.user_id) AS clients
FROM orders o
JOIN products p ON o.product_id = p.id
WHERE o.statut = 'livré'
GROUP BY , ;
 
-- Les 50 requĂȘtes quotidiennes lisent la cache (quelques Ko) :
SELECT * FROM rapport_ca_cache ORDER BY mois DESC, ca DESC;
-- Coût : quasi nul au lieu de 500$/jour
15-requetes-sql-facture-cloud · 14

RequĂȘte 13 — Les mĂȘmes requĂȘtes relancĂ©es sans utiliser le cache

Sur BigQuery et Snowflake, les rĂ©sultats sont mis en cache. Une requĂȘte identique relancĂ©e est gratuite. Une lĂ©gĂšre variation invalide le cache.

-- ❌ Ces deux requĂȘtes semblent identiques mais ne partagent pas le cache
SELECT user_id, SUM(montant) FROM orders GROUP BY user_id;
SELECT user_id, SUM(montant) FROM orders GROUP BY ; -- syntaxe diffĂ©rente → nouveau scan
 
-- ❌ La date courante invalide le cache à chaque seconde
SELECT COUNT(*) FROM orders WHERE created_at >= CURRENT_DATE;
-- CURRENT_DATE change → jamais de cache
 
-- ✅ Standardiser la syntaxe des requĂȘtes rĂ©currentes
-- Toujours GROUP BY colonne_nom, jamais GROUP BY numéro
-- ParamĂ©trer les dates pour les requĂȘtes de veille
SELECT COUNT(*) FROM orders WHERE created_at >= '2024-11-03'; -- date fixe = cache
 
-- ✅ Ou interroger la table matĂ©rialisĂ©e mise Ă  jour chaque nuit
SELECT nb_commandes_du_jour FROM stats_cache WHERE jour = CURRENT_DATE - ;
15-requetes-sql-facture-cloud · 15

đŸ§Ș Identifie les requĂȘtes redondantes dans tes scripts

Sous-requĂȘtes rĂ©pĂ©tĂ©es, rapports non matĂ©rialisĂ©s, cache invalidĂ© : ce sont les patterns les plus difficiles Ă  voir — et les plus coĂ»teux.

👉 Tester sur modai-lab.com/test-sql/

_Score immédiat · Simulation de coût · Feedback senior_


BLOC 4 — Les requĂȘtes qui ignorent la structure des donnĂ©es


RequĂȘte 14 — JOIN sur un champ VARCHAR non normalisĂ©

-- ❌ Les espaces cachĂ©s et la casse diffĂ©rente empĂȘchent les correspondances
SELECT o.order_id, u.nom
FROM orders o
JOIN users u ON o.email_client = u.email;
-- 'Alice@EXAMPLE.COM ' ≠ 'alice@example.com'
-- Résultat : des milliers de commandes orphelines silencieusement
 
-- SymptĂŽme : COUNT avant JOIN >> COUNT aprĂšs JOIN
SELECT
(SELECT COUNT(*) FROM orders) AS total_orders,
COUNT(*) AS orders_apres_join
FROM orders o JOIN users u ON o.email_client = u.email;
 
-- ✅ Normaliser les deux cĂŽtĂ©s
SELECT o.order_id, u.nom
FROM orders o
JOIN users u ON LOWER(TRIM(o.email_client)) = LOWER(TRIM(u.email));
 
-- Encore mieux : normaliser Ă  l'import (migration)
UPDATE orders SET email_client = LOWER(TRIM(email_client));
UPDATE users SET email = LOWER(TRIM(email));
-- Index aprĂšs normalisation
CREATE INDEX idx_users_email ON users(email);
15-requetes-sql-facture-cloud · 16

RequĂȘte 15 — AgrĂ©gation intermĂ©diaire oubliĂ©e avant JOIN (fan-out)

-- ❌ Fan-out : chaque ligne de order_lines multiplie les lignes d'orders
-- Si une commande a 5 produits → elle apparaüt 5 fois → SUM × 5
SELECT
u.id,
u.nom,
SUM(o.montant) AS ca_total -- multiplié par le nombre de lignes par commande
FROM users u
JOIN orders o ON u.id = o.user_id
JOIN order_lines ol ON o.id = ol.order_id -- ← fan-out ici
GROUP BY u.id, u.nom;
 
-- Diagnostic du fan-out
SELECT
COUNT(*) AS lignes_totales,
COUNT(DISTINCT o.id) AS commandes_distinctes,
ROUND(COUNT(*) * . / NULLIF(COUNT(DISTINCT o.id), ), ) AS ratio_fan_out
FROM orders o
JOIN order_lines ol ON o.id = ol.order_id;
-- ratio > 1 = problĂšme → chaque commande est comptĂ©e plusieurs fois
 
-- ✅ AgrĂ©ger order_lines avant de joindre
WITH ca_par_commande AS (
SELECT order_id, SUM(prix_unitaire * quantite) AS montant_lignes
FROM order_lines GROUP BY order_id
)
SELECT u.id, u.nom, SUM(c.montant_lignes) AS ca_total
FROM users u
JOIN orders o ON u.id = o.user_id
JOIN ca_par_commande c ON o.id = c.order_id
GROUP BY u.id, u.nom;
-- Chaque commande apparaĂźt exactement une fois
15-requetes-sql-facture-cloud · 17

đŸ§Ș Pratique la dĂ©tection de fan-out et de VARCHAR non normalisĂ©

Ces deux patterns font partie des exercices intermĂ©diaires de Modai Lab. Tu pars d'une requĂȘte avec un bug silencieux — Ă  toi de le trouver et corriger.

👉 Pratiquer sur modai-lab.com

_Scénarios enterprise · Feedback senior · Progression mesurée_


PARTIE 5 — Les rĂ©flexes que personne ne t'apprend en formation


RĂ©flexe 1 — Estimer le coĂ»t AVANT d'exĂ©cuter

BigQuery : bouton "Query Preview" → affiche les bytes estimĂ©s
Ne pas lancer si > 500 MB pour une requĂȘte de dĂ©veloppement
 
PostgreSQL : EXPLAIN sans ANALYZE
→ Lit le plan sans exĂ©cuter la requĂȘte
 
Snowflake : EXPLAIN USING TABULAR
→ Affiche le plan et les statistiques estimĂ©es
15-requetes-sql-facture-cloud · 18

RĂ©flexe 2 — DĂ©velopper sur un sous-ensemble de donnĂ©es

-- Pendant la phase de développement
WITH sample AS (
SELECT * FROM orders
WHERE created_at >= '2024-11-01' -- 1 mois seulement
LIMIT
)
-- Tester toute la logique sur le sample
SELECT ... FROM sample ...;
 
-- Une fois la logique validée : retirer les restrictions et lancer en prod
15-requetes-sql-facture-cloud · 19

RĂ©flexe 3 — VĂ©rifier le fan-out aprĂšs chaque JOIN

-- Template à appliquer systématiquement aprÚs toute jointure
SELECT
COUNT(*) AS lignes_totales,
COUNT(DISTINCT table_principale.id) AS entites_distinctes,
ROUND(COUNT(*) * . / NULLIF(COUNT(DISTINCT table_principale.id), ), ) AS ratio
FROM table_principale
JOIN table_secondaire ON ...;
-- ratio = 1 → OK
-- ratio > 1 → fan-out → agrĂ©ger avant de joindre
15-requetes-sql-facture-cloud · 20

RĂ©flexe 4 — MatĂ©rialiser les rapports partagĂ©s

Rapport vu 50 fois par jour par l'équipe ?
→ Ne pas le laisser scanner la table brute à chaque fois
→ CrĂ©er une table matĂ©rialisĂ©e mise Ă  jour 1× par jour (ou par heure si besoin)
→ Le rapport lit la table matĂ©rialisĂ©e : quelques Ko au lieu de plusieurs TB
15-requetes-sql-facture-cloud · 21

RĂ©flexe 5 — Utiliser APPROX_COUNT_DISTINCT pour les mĂ©triques approximatives

-- En analytique, une approximation Ă  2% est souvent suffisante
-- Et 100× moins chùre qu'un COUNT DISTINCT exact sur 5 milliards de lignes
 
SELECT APPROX_COUNT_DISTINCT(user_id) AS utilisateurs_uniques FROM events;
-- BigQuery, Snowflake : disponible nativement
-- Si l'exact est requis : prĂ©ciser explicitement dans le commentaire de la requĂȘte
15-requetes-sql-facture-cloud · 22

RĂ©flexe 6 — Documenter les requĂȘtes coĂ»teuses avec leur coĂ»t estimĂ©

-- Bonne pratique : commenter le coût estimé et la fréquence
-- Coût estimé : ~50 GB lus, ~0.25$ par exécution
-- FrĂ©quence : 1× par nuit dans le job ETL 23h00
-- DerniÚre révision : 2024-11-03 - Optimisé le SELECT * initial
SELECT
user_id,
DATE_TRUNC('month', created_at) AS mois,
SUM(montant) AS ca
FROM orders
WHERE created_at >= '2024-01-01' -- filtre partition : réduit de 2 TB à 160 GB
GROUP BY , ;
15-requetes-sql-facture-cloud · 23

PARTIE 6 — Prompt Claude pour auditer le coĂ»t de tes requĂȘtes


Prompt d'audit coût complet

Tu es un expert SQL performance et coĂ»t cloud. Audite la requĂȘte suivante
pour identifier les 15 patterns de gaspillage les plus courants.
 
REQUÊTE À AUDITER :
[coller ta requĂȘte]
 
CONTEXTE :
- Cloud/SGBD : [BigQuery / Snowflake / Redshift / PostgreSQL]
- Table principale : [nom, volume approximatif, partitionnement]
- Fréquence d'exécution : [ex: 50 fois par jour par l'équipe]
- Colonnes réellement utilisées dans le résultat final : [lister]
 
Vérifie dans l'ordre :
 
1. SELECT * → lister les colonnes rĂ©ellement nĂ©cessaires
2. Filtre de partition manquant → identifier la colonne de partition
3. Jointures sur tables entiĂšres → proposer des CTEs filtrĂ©es
4. Sous-requĂȘtes corrĂ©lĂ©es → remplacer par JOIN ou window function
5. Fonctions sur colonnes indexĂ©es dans WHERE → réécrire sans fonction
6. ORDER BY sans LIMIT → limiter le rĂ©sultat
7. UNION vs UNION ALL → UNION ALL si doublons acceptables
8. COUNT(DISTINCT) sur grande table → APPROX_COUNT_DISTINCT si applicable
9. AgrĂ©gation rĂ©pĂ©tĂ©e → CTE calculĂ©e une fois
10. Fan-out de JOIN → agrĂ©ger avant de joindre
11. VARCHAR non normalisĂ© dans la clĂ© de JOIN → normaliser
12. Rapport rĂ©current non matĂ©rialisĂ© → proposer une table matĂ©rialisĂ©e
13. RequĂȘte sans filtre de date → ajouter un filtre temporel par dĂ©faut
14. DISTINCT * → identifier et corriger la cause des doublons
15. Cache invalidĂ© par date courante → paramĂ©trer
 
Pour chaque problÚme trouvé :
- Expliquer l'impact en volume de données
- Donner la version corrigée avec commentaires
- Estimer le gain (en % de données lues ou en $)
 
Format : une section par problĂšme trouvĂ©, ❌ / ✅. Version finale optimisĂ©e Ă  la fin.
15-requetes-sql-facture-cloud · 24

Prompt rapide

Audite cette requĂȘte SQL pour les 5 patterns les plus coĂ»teux :
SELECT *, partition non filtrée, jointures sans filtre préalable,
sous-requĂȘtes corrĂ©lĂ©es, fonctions sur colonnes indexĂ©es.
 
Cloud : [BigQuery / Snowflake / PostgreSQL]
Volume : [taille approximative des tables]
 
Donne la version optimisée avec l'estimation du gain en %.
 
[coller ta requĂȘte]
15-requetes-sql-facture-cloud · 25

RĂ©capitulatif — Les 15 requĂȘtes et leur coĂ»t

Bloc 1 — Trop de donnĂ©es lues :

#

Erreur

Impact

Correction

1

SELECT *

Lit 10-50× plus de donnĂ©es

Lister les colonnes

2

Pas de filtre de partition

Scan complet de l'historique

WHERE colonne_partition >= date

3

GROUP BY sur toutes les colonnes

Scan inutile

Identifier la vraie agrégation

4

DISTINCT *

Tri de la table entiĂšre

Corriger la cause des doublons

5

JOIN sans filtres préalables

Jointure sur volumes maximaux

CTEs filtrées avant JOIN

Bloc 2 — Travail inutile :

#

Erreur

Impact

Correction

6

Sous-requĂȘte corrĂ©lĂ©e

N exécutions pour N lignes

CTE + JOIN

7

Fonction sur colonne indexée

Index ignorĂ© → scan complet

Réécrire sans fonction

8

ORDER BY sans LIMIT

Tri de 200M lignes

LIMIT + index de tri

9

UNION au lieu de UNION ALL

Tri et déduplication inutiles

UNION ALL

10

COUNT(DISTINCT) sur 5B lignes

Hash de tout le volume

APPROX_COUNT_DISTINCT

Bloc 3 — RĂ©pĂ©titions inutiles :

#

Erreur

Impact

Correction

11

AgrĂ©gation rĂ©pĂ©tĂ©e en sous-requĂȘte

N scans au lieu de 1

CTE calculée une fois

12

Rapport récurrent non matérialisé

CoĂ»t × frĂ©quence chaque jour

Table matérialisée

13

Cache invalidé par date courante

Jamais de cache

Date fixe, table matérialisée

Bloc 4 — Structure ignorĂ©e :

#

Erreur

Impact

Correction

14

JOIN sur VARCHAR non normalisé

Lignes perdues silencieusement

LOWER(TRIM(...))

15

Fan-out de JOIN

Métriques multipliées

Agréger avant de joindre


✅ Checklist avant d'exĂ©cuter une requĂȘte en prod

  • ☐ Pas de SELECT * — colonnes listĂ©es explicitement

  • ☐ Filtre sur la colonne de partition prĂ©sent

  • ☐ CoĂ»t estimĂ© avant exĂ©cution (dry run, EXPLAIN)

  • ☐ Pas de sous-requĂȘte corrĂ©lĂ©e → CTE ou window function

  • ☐ Pas de fonction sur une colonne indexĂ©e dans le WHERE

  • ☐ ORDER BY avec LIMIT si on n'a pas besoin de tout le rĂ©sultat

  • ☐ UNION ALL sauf si la dĂ©duplication est explicitement nĂ©cessaire

  • ☐ Fan-out vĂ©rifiĂ© aprĂšs chaque JOIN (COUNT(*) / COUNT(DISTINCT id) = 1)

  • ☐ ClĂ©s de JOIN normalisĂ©es (pas d'espaces, pas de casse mixte)

  • ☐ Si requĂȘte relancĂ©e par toute l'Ă©quipe → matĂ©rialiser le rĂ©sultat


🚀 Pratiquer sur Modai Lab

Ces 15 patterns ne se retiennent pas en lisant une liste. Ils s'ancrent en les dĂ©tectant dans de vraies requĂȘtes, sur de vraies donnĂ©es, avec du feedback immĂ©diat.

👉 modai-lab.com/test-sql/ — Exercices sur grandes tables simulĂ©es · MĂ©triques de coĂ»t · Feedback senior


_Guide offert suite au post LinkedIn · EntraĂźnez-vous sur modai-lab.com/test-sql/ · Partagez librement ♻_