Skip to content

Repository files navigation

SQL jusqu'à l'IA

  • Du modèle relationnel aux agents IA connectés à vos bases
  • Environnement : Docker · MySQL/MariaDB · PostgreSQL · Tabularis
  • Méthode : Théorie → On fait ensemble → Vous faites

Objectifs pédagogiques (RNCP40573BC05)

  • Concevoir et créer un schéma relationnel adapté à un besoin data
  • Écrire des requêtes SQL : extraction, transformation, agrégation
  • Manipuler plusieurs SGBD (MySQL/MariaDB, PostgreSQL) et comprendre leurs différences
  • Garantir l'intégrité des données (contraintes, transactions)
  • Optimiser des requêtes (index, EXPLAIN)
  • Ouvrir la base aux outils IA de manière contrôlée (MCP)

Déroulé du module

flowchart TB
    A[Historique & Setup] --> B[Modèle relationnel]
    B --> C[DDL : créer]
    C --> D[DML : manipuler]
    D --> E[SELECT & filtres]
    E --> F[Agrégations]
    F --> G[Jointures]
    G --> H[Sous-requêtes & CTE]
    H --> I[Vues, Index, EXPLAIN]
    I --> J[Transactions]
    J --> K[MySQL vs PostgreSQL]
    K --> L[Tabularis + Claude via MCP]
    L --> M[Mini-projet]
Loading
  • Chaque notion = 3 temps : Théorie / On fait ensemble / Vous faites
  • Fil rouge : une base boutique (clients, produits, commandes)

Un peu d'histoire : d'où vient SQL ?

  • 1965 : architecture Ansi/Sparc par Bachmann (prix Turing 1973)
  • 1968 : Pick par Dick Pick - un OS avec SGDBR MV + langage de requêtage GIRLS
  • 1970 : E.F. Codd (IBM) publie le modèle relationnel (prix Turing 1981)
  • 1974 :
    • SEQUEL chez IBM (projet System R)
    • Ingres (qui donna Sybase puis... MS-SQL Server)
  • 1979 : Oracle, premier SGBDR commercial
  • 1985 : PostIngres = Postgres
  • 1986 : SQL devient norme ANSI (SQL-86)
  • 1995 :
    • MySQL démocratise SQL dans le monde open-souce
    • PostgreSQL (héritier d'Ingres / Postgres)
  • 2000 : Création du projet SQLite un serveur SQL... sans serveur : juste un fichier
  • 2008 : nov. rachat de AB Software (MySQL) par Sun Microsystems
  • 2009 : avr. Oracle rachète Sun Microsystems
  • 2009 : Vague NoSQL... qui n'a pas tué SQL
  • 2010 : MariaDB, fork communautaire de MySQL (par les anciens de MySQL)
  • 2020 : SQL partout, du cloud à DuckDB et même dans de l'embarqué grâce à SQLite
  • 2025 :
    • Les agents IA écrivent du SQL via MCP
    • SQLite devient production proof

Pourquoi SQL est incontournable en Data / IA

  • Langage déclaratif : on décrit quoi, pas comment
  • Norme depuis 40 ans → compétence transférable (MySQL, PostgreSQL, BigQuery, Spark SQL...)
  • 80 % du travail data = extraire, nettoyer, agréger → du SQL
  • Les pipelines ML commencent presque toujours par un SELECT
  • Les LLM génèrent du SQL... qu'il faut savoir relire et valider
  • Verdict historique : NoSQL a complété SQL, il ne l'a pas remplacé

Environnement local

À la racine du dépôt vous trouverez le fichier docker-compose.yml et les autres fichiers de configuration pour vous permettre de lancer à la fois MariaDB et PostgreSQL, mais aussi un phpMyAdmin et un pgAdmin pour vous permettre de manipuler vos bases de données, En plus d'un client web pour SQLite et un fichier DB SQLite accessible depuis votre machine dans le dossier ./sqlite-data

Il vous est demander d'installer Tabularis dès maintenant.

Tabularis Tabularis est un client "lourd" (application installée sur votre OS) qui permet de se connecter à un serveur SQL ou NoSQL mais aussi de faire office d'interface entre vos bases de données et une IA via un serveur MCP local intégré.

flowchart LR
    subgraph  
        T[Tabularis<br/>client lourd]
        SQ[( ./sqlite-data/DB_Cours.db)]
        subgraph D[Docker]

            subgraph C[Clients web]

                A[phpMyAdmin :8081]
                B[pgAdmin :8082]
                Q[sqlite-web :8083]
            end

            M[(MariaDB :3306)]
            P[(PostgreSQL :5433)]
            S[SQLite]
        end
    end
    T --> M
    T --> P
    T --> SQ
    A --> M
    B --> P
    S --> SQ
    Q --> SQ
    style D fill:#005
    style C fill:transparent
    style C stroke:#00a


Loading
  • MySQL(MariaDB) se trouve sur le port 3306
  • PostgreSQL se trouve sur le port 5433
  • Une DB SQLite se trouve dans le fichier ./sqlite-data/DB_Cours.db
  • phpMyAdmin se trouve sur le port 8081
  • pgAdmin se trouve sur le port 8082
  • SQLite Web se trouve sur le port 8083

Setup de votre environnement

  • Vous disposez déjà du fichier docker-compose.yml à la racine du dépôt
  • Ouvrez un terminal à la racine du dépôt cloné
  • En suite vous pouvez faire :
# Toute la stack : MariaDB + phpMyAdmin + PostgreSQL + pgAdmin + SQLite + sqlite-web
docker compose up -d

docker compose ps                # état des conteneurs
docker compose logs -f mariadb   # logs d'un service (mariadb, postgres, sqlite-web...)
  • ⚠️ docker compose down pour éteindre la stack
  • Détail des services, ports, identifiants et connexions (y compris nettoyage des volumes) : voir readme-infra-docker.md

Les clients : du web au client lourd

  • phpMyAdmin / pgAdmin / SQLite web (web) : pratiques...
    • mais un outil différent par moteur, éditeur SQL limité, ergonomie datée
    • cependant, installé à coté en environnement protégé (staging etc...) permet de se connecter à la DB sans gros besoins de sécurité
  • Tabularis (client lourd, open source, Windows/macOS/Linux) :
    • un seul outil pour PostgreSQL, MySQL/MariaDB, SQLite (+ plugins : DuckDB, ClickHouse...)
    • éditeur SQL Monaco, notebooks SQL, EXPLAIN visuel, diagrammes ER
    • connexions enregistrées, mots de passe dans le trousseau système
    • serveur MCP intégré → connectable à Claude (démo en fin de module)
    • mais besoin d'un VPN / infra protégée pour accès distants
  • Installation : winget install Debba.Tabularis · brew install --cask tabularis · snap... ou simplement via le site web

Fichiers de ce dépôt

  • Readme.md — ce document : le déroulé pédagogique complet du module (théorie, exemples, exercices)
  • readme-infra-docker.md — détail de l'infrastructure Docker : services, ports, identifiants, connexions, nettoyage des volumes
  • docker-compose.yml — définition de la stack (MariaDB, PostgreSQL, SQLite, phpMyAdmin, pgAdmin, sqlite-web)
  • readme-activites-mysql.md, readme-activites-postgresql.md, readme-activites-sqlite.md — les parties « 👥 On fait ensemble » et « 🎯 Vous faites » de ce Readme, déclinées et testées pour chaque SGBD (MySQL/MariaDB, PostgreSQL, SQLite), avec une solution complète pour chaque exercice
  • sql_joins.jpg — illustration des types de jointures (Notion 6)
  • sqlite-data/ — fichier de base SQLite (DB_Cours.db) partagé avec le conteneur sqlite/sqlite-web
  • pgadmin/ — configuration pré-enregistrée du serveur PostgreSQL dans pgAdmin

⚠️ Le corps de ce Readme (théorie, exemples, exercices) est rédigé en priorité pour MySQL/MariaDB : c'est le fil conducteur du cours. Les déclinaisons PostgreSQL et SQLite ne sont détaillées qu'en Notion 10 (comparatif) et dans les fichiers readme-activites-* dédiés — utilisez ceux-ci si vous travaillez principalement sur un autre moteur.


Notion 1 — Modèle relationnel · 📖 Théorie

  • Une base contient des tables ; une table = colonnes typées + lignes
  • Clé primaire (PK) : identifie chaque ligne de façon unique
  • Clé étrangère (FK) : référence la PK d'une autre table → crée la relation
  • Schéma = structure · Données = contenu
erDiagram
    CLIENTS ||--o{ COMMANDES : passe
    COMMANDES ||--|{ LIGNES_COMMANDE : contient
    PRODUITS ||--o{ LIGNES_COMMANDE : concerne
    CLIENTS {
        int id PK
        varchar nom
        varchar email
        date inscription
    }
    PRODUITS {
        int id PK
        varchar nom
        decimal prix
        int stock
    }
    COMMANDES {
        int id PK
        int client_id FK
        datetime date_commande
    }
    LIGNES_COMMANDE {
        int commande_id FK
        int produit_id FK
        int quantite
    }
Loading

Notion 1 — 👥 On fait ensemble

  1. docker compose up -d (depuis la racine du dépôt cloné, lance toute la stack)
  2. Tabularis → New Connection → MySQL
    • host 127.0.0.1, port 3306, user user, mdp password, database DB_Cours
    • (accès admin : user root, mdp rootPassword)
  3. Explorer information_schema dans l'arborescence
  4. Premiers SQL dans l'éditeur :
SHOW DATABASES;
SELECT VERSION();
SELECT NOW();
  • Faire la même exploration dans phpMyAdmin → comparer le confort
  1. Tabularis → New Connection → PostgreSQL
    • host 127.0.0.1, port 5433, user user, mdp password, database DB_Cours
  2. Explorer postgres dans l'arborescence
  3. Mêmes requêtes, dialecte PostgreSQL :
SELECT datname FROM pg_database;
SELECT version();
SELECT now();
  • Faire la même exploration dans pgAdmin → comparer le confort
  1. Tabularis → New Connection → SQLite
    • fichier {là où vous avez cloné le cours}/sqlite-data/DB_Cours.db
  2. Explorer l'arborescence : un seul fichier, pas de notion d'utilisateur ni de information_schema
  3. Premiers SQL dans l'éditeur :
SELECT sqlite_version();
SELECT datetime('now');
  • Faire la même exploration dans sqlite-web (8083) → comparer le confort
  • Bilan :
    • un seul outil (Tabularis) pour trois moteurs — trois dialectes, trois façons de dire « version » et « maintenant »
    • la base du dialecte SQL est "la même" mais avec des particularités apportées par les différents SGDB
    • mieux vaut aussi se faire la main et pratiquer avec phpMyAdmin et pgAdmin car souvent plus utilisés/utilisables/dispo en environnement de production/staging

Notion 2 — DDL : créer bases et tables · 📖 Théorie

  • DDL = Data Definition Language : CREATE, ALTER, DROP
Besoin MySQL/MariaDB PostgreSQL SQLite
Entier auto INT AUTO_INCREMENT GENERATED ALWAYS AS IDENTITY INTEGER PRIMARY KEY
Texte court VARCHAR(100) VARCHAR(100) / TEXT TEXT (longueur non contrainte)
Décimal exact DECIMAL(10,2) NUMERIC(10,2) NUMERIC (affinité seule, précision non garantie)
Date + heure DATETIME TIMESTAMP TEXT (ISO8601)
Booléen TINYINT(1) BOOLEAN INTEGER (0/1)
  • Contraintes : PRIMARY KEY · FOREIGN KEY · NOT NULL · UNIQUE · DEFAULT · CHECK
  • ⚠️ Jamais de FLOAT pour l'argent → DECIMAL / NUMERIC
  • ⚠️ SQLite = typage dynamique (affinité de colonne) : les types déclarés ne sont pas contraignants comme sous MySQL/PostgreSQL

Notion 2 — 👥 On fait ensemble

Exécuter le script SQL suivant dans phpMyAdmin :

⚠️ pour créer une database : il faut être root 😉

CREATE DATABASE boutique CHARACTER SET utf8mb4;
USE boutique;

CREATE TABLE clients (
    id INT AUTO_INCREMENT PRIMARY KEY,
    nom VARCHAR(100) NOT NULL,
    email VARCHAR(150) NOT NULL UNIQUE,
    ville VARCHAR(80),
    inscription DATE DEFAULT (CURRENT_DATE)
);

DESCRIBE clients;
ALTER TABLE clients ADD COLUMN telephone VARCHAR(20);
  • Et si on refait le script ? → Table already exists

Notion 2 — 🎯 Vous faites

  • Dans boutique, créez :
  1. produits : id (PK auto), nom obligatoire, prix DECIMAL(10,2) avec CHECK (prix >= 0), stock INT DEFAULT 0
  2. commandes : id (PK auto), client_id FK → clients, date_commande DATETIME DEFAULT CURRENT_TIMESTAMP
  3. lignes_commande : commande_id (FK), produit_id (FK), quantite INT NOT NULL, PK composée (commande_id, produit_id)
  • Vérifiez : SHOW CREATE TABLE commandes;
  • Testez la FK : insérer une commande avec client_id = 999 → que se passe-t-il ?
  • Bonus : générez le diagramme ER dans Tabularis

Notion 3 — DML : INSERT / UPDATE / DELETE · 📖 Théorie

INSERT INTO t (col1, col2) VALUES (v1, v2), (v3, v4);
UPDATE t SET col = valeur WHERE condition;
DELETE FROM t WHERE condition;
  • ⚠️ Règle d'or : UPDATE / DELETE sans WHERE = toute la table
  • Réflexe : valider le WHERE avec un SELECT avant de modifier à la main
  • Dans du code : bien check si on a une clause WHERE
flowchart LR
    A[SELECT ... WHERE ...] --> B{Bonnes lignes ?}
    B -->|oui| C[UPDATE / DELETE<br/>avec le même WHERE]
    B -->|non| A
Loading

Notion 3 — 👥 On fait ensemble

INSERT INTO clients (nom, email, ville) VALUES
('Alice Martin', 'alice@mail.fr', 'Montpellier'),
('Karim Benali', 'karim@mail.fr', 'Lyon'),
('Chloé Dubois', 'chloe@mail.fr', 'Montpellier'),
('Jean Petit',  'jean@mail.fr',  NULL);

SELECT * FROM clients;

-- 1. viser
SELECT * FROM clients WHERE email = 'jean@mail.fr';
-- 2. tirer
UPDATE clients SET ville = 'Nantes' WHERE email = 'jean@mail.fr';
  • Échec volontaire : réinsérer alice@mail.fr → erreur UNIQUE

Notion 3 — 🎯 Vous faites

  1. Insérez 6 produits (clavier, souris, écran, casque, webcam, hub USB), prix et stocks variés
  2. Insérez 4 commandes (3 clients différents) + leurs lignes_commande (2-3 produits chacune)
  3. Promo : baissez le prix du clavier de 10 % (UPDATE avec calcul)
  4. Supprimez le produit jamais commandé (à vous de le trouver)
  5. Piège : supprimez un client qui a des commandes → expliquez l'erreur FK
  • Livrable : un fichier seed.sql rejouable (avec DROP ... IF EXISTS)

Notion 4 — SELECT : filtrer et trier · 📖 Théorie

SELECT colonnes | *
FROM table
WHERE condition          -- =, <>, <, >, AND, OR, NOT
                         -- LIKE 'A%', IN (...), BETWEEN, IS NULL
ORDER BY col ASC|DESC
LIMIT n OFFSET m;
  • DISTINCT : dédoublonner · alias avec AS
  • Ordre logique d'exécution ≠ ordre d'écriture :
flowchart LR
    F[FROM] --> W[WHERE] --> S[SELECT] --> O[ORDER BY] --> L[LIMIT]
Loading
  • Conséquence : un alias du SELECT est utilisable dans ORDER BY, pas dans WHERE

Notion 4 — 👥 On fait ensemble

-- Clients de Montpellier
SELECT nom, email FROM clients WHERE ville = 'Montpellier';

-- Produits entre 20 et 100 €, du plus cher au moins cher
SELECT nom, prix FROM produits
WHERE prix BETWEEN 20 AND 100
ORDER BY prix DESC;

-- Emails en .fr, top 3
SELECT nom FROM clients WHERE email LIKE '%.fr' LIMIT 3;

-- Le piège NULL
SELECT * FROM clients WHERE ville = NULL;    -- 0 ligne !
SELECT * FROM clients WHERE ville IS NULL;   --

Notion 4 — 🎯 Vous faites

  1. Produits en rupture ou stock faible (stock < 5), triés par stock croissant
  2. Produits dont le nom contient « usb » (insensible à la casse ?)
  3. Les 2 produits les plus chers — puis les 3e et 4e (LIMIT ... OFFSET)
  4. Liste des villes distinctes des clients, sans les NULL
  5. Clients inscrits cette année (WHERE inscription >= '2026-01-01')
  • Pour chaque requête : prédire le nombre de lignes avant d'exécuter

Notion 5 — Agrégations : GROUP BY · 📖 Théorie

  • Fonctions d'agrégation : COUNT, SUM, AVG, MIN, MAX
  • GROUP BY : une ligne de résultat par groupe
  • WHERE filtre les lignes avant groupage ; HAVING filtre les groupes après
flowchart LR
    F[FROM] --> W[WHERE<br/>filtre lignes] --> G[GROUP BY] --> H[HAVING<br/>filtre groupes] --> S[SELECT] --> O[ORDER BY]
Loading
  • Règle : toute colonne du SELECT hors agrégat doit être dans le GROUP BY
  • COUNT(*) compte les lignes · COUNT(col) ignore les NULL

Notion 5 — 👥 On fait ensemble

-- Combien de clients par ville ?
SELECT ville, COUNT(*) AS nb
FROM clients
GROUP BY ville
ORDER BY nb DESC;

-- Prix moyen, min, max du catalogue
SELECT ROUND(AVG(prix),2) AS prix_moyen,
       MIN(prix) AS mini, MAX(prix) AS maxi
FROM produits;

-- Villes avec au moins 2 clients
SELECT ville, COUNT(*) AS nb
FROM clients
GROUP BY ville
HAVING nb >= 2;   -- PostgreSQL : HAVING COUNT(*) >= 2

Notion 5 — 🎯 Vous faites

  1. Nombre de commandes par client (client_id)
  2. Quantité totale vendue par produit (table lignes_commande)
  3. Valeur du stock : SUM(prix * stock) sur produits
  4. Produits commandés plus de 2 fois au total (→ HAVING)
  5. Question piège : pourquoi WHERE COUNT(*) > 2 est-il refusé ?

Notion 6 — Jointures · 📖 Théorie

  • Relier les tables via les clés : c'est le superpouvoir du relationnel
SELECT c.nom, o.date_commande
FROM clients c
INNER JOIN commandes o ON o.client_id = c.id;
  • INNER JOIN : seulement les correspondances
  • LEFT JOIN : tout à gauche + correspondances (sinon NULL)
  • RIGHT JOIN : miroir du LEFT · FULL OUTER JOIN : tout (PG ✅, MySQL ❌)
flowchart TB
    subgraph INNER
        A1[Clients AVEC commandes]
    end
    subgraph LEFT
        B1[Tous les clients]
        B2[+ commandes si elles existent, sinon NULL]
    end
Loading

joins


Notion 6 — 👥 On fait ensemble

-- Qui a commandé quoi, quand ?
SELECT c.nom, p.nom AS produit, l.quantite, o.date_commande
FROM commandes o
INNER JOIN clients c          ON o.client_id  = c.id
INNER JOIN lignes_commande l  ON l.commande_id = o.id
INNER JOIN produits p         ON p.id = l.produit_id
ORDER BY o.date_commande;

-- Clients SANS commande : le pattern anti-jointure
SELECT c.nom
FROM clients c
LEFT JOIN commandes o ON o.client_id = c.id
WHERE o.id IS NULL;
  • On dessine le chemin des jointures sur le diagramme ER de Tabularis

Notion 6 — 🎯 Vous faites

  1. Chiffre d'affaires par client : SUM(quantite * prix) (3 tables + GROUP BY)
  2. Produits jamais commandés (LEFT JOIN ... IS NULL)
  3. Pour chaque commande : nombre de lignes et montant total
  4. Top 3 des produits par quantité vendue
  5. Défi : clients ayant commandé et l'écran et la souris
  • Vérifiez chaque total à la main sur vos données de seed — un JOIN mal fait multiplie les lignes !

Notion 7 — Sous-requêtes & CTE · 📖 Théorie

  • Sous-requête : un SELECT dans un autre
SELECT nom, prix FROM produits
WHERE prix > (SELECT AVG(prix) FROM produits);
  • CTE (WITH) : nommer une étape intermédiaire → lisibilité

    CTE = Common Table Expression = Expression de Table Commune = une sélection temporaire qui permet de faire une autre sélection C'est un SELECT dans un SELECT

WITH ca_client AS (
    SELECT o.client_id, SUM(l.quantite * p.prix) AS ca
    FROM commandes o
    JOIN lignes_commande l ON l.commande_id = o.id
    JOIN produits p        ON p.id = l.produit_id
    GROUP BY o.client_id
)
SELECT c.nom, ca_client.ca
FROM ca_client JOIN clients c ON c.id = ca_client.client_id;
  • Règle d'équipe : dès 2 niveaux d'imbrication → CTE = dès qu'on va devoir faire 2 niveaux d'imbrication, on fait une CTE

Notion 7 — 👥 On fait ensemble

-- Sous-requête scalaire : produits au-dessus du prix moyen
SELECT nom, prix FROM produits
WHERE prix > (SELECT AVG(prix) FROM produits);

-- IN : clients ayant commandé
SELECT nom FROM clients
WHERE id IN (SELECT DISTINCT client_id FROM commandes);
  • On réécrit ensemble la 2e en JOIN, puis en CTE
  • Discussion : quand la lisibilité prime-t-elle sur la concision ?

Notion 7 — 🎯 Vous faites

  1. Sous-requête : commandes dont le montant dépasse le montant moyen des commandes
  2. Réécrivez l'exercice « CA par client » de la notion 6 en CTE
  3. Enchaînez deux CTE : ca_client puis top_clients (CA > moyenne des CA)
  4. Comparez lisibilité : version imbriquée vs version CTE — laquelle défendriez-vous en revue de code ?

Notion 8 — Vues, index, EXPLAIN · 📖 Théorie

  • Vue = requête nommée, interrogeable comme une table
CREATE VIEW v_ca_client AS
SELECT o.client_id, SUM(l.quantite * p.prix) AS ca
FROM commandes o
JOIN lignes_commande l ON l.commande_id = o.id
JOIN produits p ON p.id = l.produit_id
GROUP BY o.client_id;
  • Index = structure (B-tree) qui accélère les recherches
flowchart TB
    R[Racine B-tree] --> N1[A - K]
    R --> N2[L - Z]
    N1 --> F1[ligne 'Benali']
    N2 --> F2[ligne 'Martin']
Loading
  • Coût : écritures plus lentes, espace disque → indexer les colonnes de WHERE / JOIN
  • EXPLAIN : le plan d'exécution — full scan ou index scan ?

Notion 8 — 👥 On fait ensemble

-- Générer du volume pour "sentir" l'index (MariaDB, le moteur du docker-compose)
SET SESSION max_recursive_iterations = 600000;   -- sinon la CTE récursive est limitée à 1000 lignes

CREATE TABLE mesures AS
WITH RECURSIVE seq AS (
    SELECT 1 AS id
    UNION ALL
    SELECT id + 1 FROM seq WHERE id < 500000
)
SELECT id, FLOOR(RAND()*1000) AS capteur, RAND()*40 AS temperature
FROM seq;

-- ⚠️ MariaDB n'a PAS "EXPLAIN ANALYZE" (syntaxe MySQL 8+) : elle utilise ANALYZE [FORMAT=JSON]
ANALYZE SELECT * FROM mesures WHERE capteur = 42;
-- → type = ALL (scan complet des ~500 000 lignes)

CREATE INDEX idx_mesures_capteur ON mesures(capteur);

ANALYZE SELECT * FROM mesures WHERE capteur = 42;
-- → type = ref (recherche via l'index) 🎉

Sur un vrai MySQL (≥ 8.0.18), la variable s'appelle cte_max_recursion_depth et la commande est bien EXPLAIN ANALYZE. Testé ci-dessus directement sur le conteneur mariadb du docker-compose.yml : les deux divergent.

-- Générer du volume pour "sentir" l'index (PostgreSQL)
CREATE TABLE mesures AS
SELECT g AS id, (random()*1000)::int AS capteur, random()*40 AS temperature
FROM generate_series(1, 500000) g;

EXPLAIN ANALYZE SELECT * FROM mesures WHERE capteur = 42;
-- → Seq Scan

CREATE INDEX idx_mesures_capteur ON mesures(capteur);

EXPLAIN ANALYZE SELECT * FROM mesures WHERE capteur = 42;
-- → Index Scan 🎉
  • On ouvre l'EXPLAIN visuel de Tabularis : lire l'arbre au lieu du texte brut

Notion 8 — 🎯 Vous faites

  1. Créez la vue v_stock_faible (produits avec stock < 5) et interrogez-la
  2. Sur mesures : mesurez le temps d'une requête WHERE temperature > 39.5 avant/après un index sur temperature
  3. EXPLAIN une requête avec JOIN sur boutique : identifiez le type de scan sur chaque table
  4. Question : pourquoi ne pas indexer toutes les colonnes ?
  • Livrable : capture de l'EXPLAIN visuel avant/après + 3 lignes d'analyse

Notion 9 — Transactions & ACID · 📖 Théorie

  • Atomicité · Cohérence · Isolation · Durabilité
  • Tout ou rien : indispensable dès qu'une opération touche plusieurs tables
sequenceDiagram
    participant App
    participant DB
    App->>DB: BEGIN
    App->>DB: UPDATE produits SET stock = stock - 2 ...
    App->>DB: INSERT INTO lignes_commande ...
    alt tout est OK
        App->>DB: COMMIT ✅
    else erreur
        App->>DB: ROLLBACK ↩️ (rien n'est appliqué)
    end
Loading
  • MySQL : moteur InnoDB requis · autocommit actif par défaut dans les deux SGBD

Notion 9 — 👥 On fait ensemble

BEGIN;   -- START TRANSACTION sous MySQL
UPDATE produits SET stock = stock - 2 WHERE id = 1;
SELECT stock FROM produits WHERE id = 1;   -- modifié... pour NOUS
ROLLBACK;
SELECT stock FROM produits WHERE id = 1;   -- revenu à l'état initial
  • Démo d'isolation : deux onglets SQL Tabularis = deux sessions
    • Session A : BEGIN + UPDATE sans commit
    • Session B : que voit-elle ? (spoiler : l'ancienne valeur)
    • Session A : COMMIT → session B voit la nouvelle valeur

Notion 9 — 🎯 Vous faites

  1. Écrivez la transaction « passer une commande » : créer la commande, insérer 2 lignes, décrémenter les stocks — le tout dans un BEGIN ... COMMIT
  2. Rejouez-la avec un produit inexistant au milieu → vérifiez que ROLLBACK annule tout
  3. Ajoutez un CHECK (stock >= 0) sur produits puis tentez de vendre plus que le stock : que se passe-t-il ?
  4. Expliquez en 2 phrases pourquoi un virement bancaire est l'exemple canonique de transaction

Notion 10 — MySQL/MariaDB vs PostgreSQL · 📖 Théorie

Aspect MySQL / MariaDB PostgreSQL
Auto-incrément AUTO_INCREMENT IDENTITY / SERIAL
Guillemets identifiants `backticks` "doubles quotes"
Casse des noms selon l'OS minuscules par défaut
FULL OUTER JOIN ❌ (contournement UNION)
Types avancés JSON JSONB, arrays, types custom
Concaténation CONCAT() `
Réputation web, simplicité rigueur, data engineering
  • MariaDB = fork de MySQL (2010) : très compatible, gouvernance communautaire
  • En Data/IA, PostgreSQL est devenu le choix par défaut — mais on croise les deux partout

Notion 10 — 👥 On fait ensemble

  • Les deux stacks tournent en parallèle (ports différents) — deux connexions Tabularis côte à côte
  • On rejoue seed.sql sur PostgreSQL et on corrige en direct :
-- MySQL                         -- PostgreSQL
id INT AUTO_INCREMENT            id INT GENERATED ALWAYS AS IDENTITY
DATETIME                         TIMESTAMP
`nom`                            "nom"
DESCRIBE clients;                \d clients  (ou l'arborescence Tabularis)
  • Même requête d'agrégation sur les deux → mêmes résultats, dialectes différents

Notion 10 — 🎯 Vous faites

  1. Portez tout votre seed.sql vers PostgreSQL (seed_pg.sql)
  2. Vérifiez que vos 5 requêtes de la notion 6 donnent des résultats identiques sur les deux moteurs
  3. Trouvez une requête qui passe sur PostgreSQL mais pas sur MySQL (indice : FULL OUTER JOIN) et écrivez son contournement MySQL
  4. Tableau récap : 5 différences rencontrées par vous pendant le portage

Notion Bonus — ON DELETE / ON UPDATE : les cascades & transactions avancées · 📖 Théorie

  • Vous avez rencontré le problème en Notion 3 : « supprimez un client qui a des commandes → erreur FK »
  • Et en Notion 9 : les transactions garantissent le tout ou rien
  • Ce chapitre bonus réunit les deux : que fait la base quand une ligne référencée disparaît ? Et comment garder la main quand ça se propage ?

Les actions référentielles

Une FK peut déclarer ce qui se passe quand la ligne parente est supprimée (ON DELETE) ou que sa PK change (ON UPDATE) :

Action Comportement Cas d'usage typique
RESTRICT / NO ACTION ❌ Refuse l'opération (c'est le défaut) Protéger : un client avec commandes ne peut pas disparaître
CASCADE 🌊 Propage : supprime/modifie aussi les enfants lignes_commande n'ont aucun sens sans leur commande
SET NULL La FK des enfants passe à NULL Commande orpheline mais conservée (client anonymisé)
SET DEFAULT La FK prend sa valeur DEFAULT Réaffecter à un « client générique » (PG ✅, MySQL/InnoDB ❌)
flowchart TB
    A[DELETE FROM clients WHERE id = 3] --> B{ON DELETE ?}
    B -->|RESTRICT| C[❌ Erreur FK<br/>rien n'est supprimé]
    B -->|CASCADE| D[🌊 Le client ET ses commandes<br/>ET leurs lignes disparaissent]
    B -->|SET NULL| E[Client supprimé<br/>commandes.client_id = NULL]
Loading
  • ⚠️ La cascade peut traverser plusieurs niveaux : clientscommandeslignes_commande. Un seul DELETE peut vider trois tables.
  • ⚠️ SET NULL exige que la colonne FK soit nullable (pas de NOT NULL)
  • ON UPDATE CASCADE est rarement utile avec des PK auto-incrémentées (on ne change jamais un id)... mais devient précieux avec des clés naturelles (un code, une référence)

Syntaxe

CREATE TABLE commandes (
    id INT AUTO_INCREMENT PRIMARY KEY,
    client_id INT,
    date_commande DATETIME DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (client_id) REFERENCES clients(id)
        ON DELETE SET NULL
        ON UPDATE CASCADE
);
 
CREATE TABLE lignes_commande (
    commande_id INT NOT NULL,
    produit_id  INT NOT NULL,
    quantite INT NOT NULL,
    PRIMARY KEY (commande_id, produit_id),
    FOREIGN KEY (commande_id) REFERENCES commandes(id)
        ON DELETE CASCADE,          -- une ligne sans commande n'existe pas
    FOREIGN KEY (produit_id) REFERENCES produits(id)
        ON DELETE RESTRICT          -- on protège le catalogue
);

Différences entre moteurs

Aspect MySQL / MariaDB (InnoDB) PostgreSQL SQLite
Défaut si rien n'est écrit RESTRICT NO ACTION NO ACTION
SET DEFAULT ❌ (InnoDB refuse)
FK actives par défaut ⚠️ NON : PRAGMA foreign_keys = ON; à chaque connexion
Modifier l'action après coup DROP FOREIGN KEY puis ADD DROP CONSTRAINT puis ADD Impossible → recréer la table
  • ⚠️ Piège SQLite classique : vos FK (et donc vos cascades) sont ignorées tant que le PRAGMA n'est pas activé. Tabularis et sqlite-web ne le font pas forcément pour vous.

Cascades × Transactions : le duo

  • Une cascade est atomique par nature : parent + enfants disparaissent dans la même instruction. Si un enfant ne peut pas être supprimé (FK RESTRICT plus bas), tout est annulé.
  • Mais une cascade est aussi silencieuse : le DELETE ne vous dit pas combien de lignes filles ont disparu. D'où le réflexe pro :
sequenceDiagram
    participant Vous
    participant DB
    Vous->>DB: BEGIN
    Vous->>DB: DELETE FROM clients WHERE id = 3
    Vous->>DB: SELECT COUNT(*) FROM commandes / lignes_commande
    alt dégâts conformes aux attentes
        Vous->>DB: COMMIT ✅
    else surprise (trop de lignes disparues)
        Vous->>DB: ROLLBACK ↩️ tout revient
    end
Loading
  • C'est le pattern « viser / tirer » de la Notion 3, version destruction massive : la transaction est votre filet, le ROLLBACK votre bouton annuler

Bonus dans le bonus : SAVEPOINT

  • Un ROLLBACK annule toute la transaction. Un SAVEPOINT pose un point de sauvegarde intermédiaire :
BEGIN;
DELETE FROM clients WHERE id = 3;          -- cascade OK
SAVEPOINT apres_client;
DELETE FROM produits WHERE stock = 0;      -- oups, erreur FK RESTRICT
ROLLBACK TO SAVEPOINT apres_client;        -- annule SEULEMENT le 2e DELETE
COMMIT;                                    -- le 1er DELETE est validé
  • Supporté par MySQL, PostgreSQL et SQLite — enfin une syntaxe commune 🎉

Notion Bonus — 👥 On fait ensemble

On repart de boutique (MySQL, phpMyAdmin ou Tabularis). Nos FK actuelles sont en RESTRICT implicite : modifions-les.

1. Passer lignes_commande en cascade

-- Retrouver le nom de la contrainte existante
SHOW CREATE TABLE lignes_commande;
 
-- La remplacer (adaptez le nom : lignes_commande_ibfk_1, etc.)
ALTER TABLE lignes_commande DROP FOREIGN KEY lignes_commande_ibfk_1;
ALTER TABLE lignes_commande
    ADD CONSTRAINT fk_lc_commande
    FOREIGN KEY (commande_id) REFERENCES commandes(id)
    ON DELETE CASCADE;

2. Tester la cascade... sous transaction

BEGIN;   -- START TRANSACTION sous MySQL
 
SELECT COUNT(*) FROM lignes_commande;              -- avant : notez le chiffre
 
DELETE FROM commandes WHERE id = 1;                -- plus d'erreur FK !
 
SELECT COUNT(*) FROM lignes_commande;              -- les lignes ont suivi 🌊
SELECT * FROM lignes_commande WHERE commande_id = 1;  -- 0 ligne
 
ROLLBACK;                                          -- on annule tout
 
SELECT COUNT(*) FROM lignes_commande;              -- tout est revenu ✅
  • Observation clé : le ROLLBACK restaure aussi les lignes supprimées par cascade — l'atomicité couvre les effets en chaîne

3. Démo SET NULL sur commandes

ALTER TABLE commandes DROP FOREIGN KEY commandes_ibfk_1;
ALTER TABLE commandes
    ADD CONSTRAINT fk_cmd_client
    FOREIGN KEY (client_id) REFERENCES clients(id)
    ON DELETE SET NULL;
 
BEGIN;
DELETE FROM clients WHERE email = 'karim@mail.fr';
SELECT id, client_id FROM commandes;   -- ses commandes existent, client_id = NULL
ROLLBACK;
  • Discussion : dans une vraie boutique, que préférez-vous pour un client qui exerce son droit RGPD à l'effacement — CASCADE (perdre l'historique de CA) ou SET NULL (anonymiser) ?

Notion Bonus — 🎯 Vous faites

  1. Cartographiez vos FK : pour chacune des 4 relations de boutique, choisissez RESTRICT, CASCADE ou SET NULL et justifiez en une phrase. Appliquez vos choix par ALTER TABLE.
  2. Cascade à deux étages : mettez clients → commandes en CASCADE (temporairement), puis dans une transaction, supprimez un client et prouvez avec des COUNT(*) que trois tables ont été touchées par un seul DELETE. Terminez par ROLLBACK.
  3. Le piège SQLite : sur ./sqlite-data/DB_Cours.db, créez deux tables liées avec ON DELETE CASCADE, supprimez un parent sans le PRAGMA, constatez, puis refaites avec PRAGMA foreign_keys = ON;. Que concluez-vous pour vos scripts ?
  4. SAVEPOINT : réécrivez la transaction « passer une commande » de la Notion 9 avec un SAVEPOINT après la création de la commande : si l'insertion des lignes échoue, revenez au savepoint et insérez une ligne dans une table commandes_en_erreur avant de COMMIT.
  5. Portage : rejouez l'exercice 1 sur PostgreSQL (DROP CONSTRAINT au lieu de DROP FOREIGN KEY). Ajoutez la ligne « actions référentielles » à votre tableau de différences de la Notion 10.
  6. Question débat (lien avec le module MCP) : faut-il autoriser un agent IA à exécuter des DELETE sur des tables portant des ON DELETE CASCADE ? Quelles protections cumuleriez-vous ?
  • Livrable : un fichier cascade.sql rejouable + 5 lignes de justification de vos choix d'actions référentielles

Récap éclair

  • Le défaut (RESTRICT) protège, CASCADE propage, SET NULL détache — le bon choix est métier, pas technique
  • Une cascade est atomique mais silencieuse → toujours l'essayer sous transaction avec des COUNT(*) avant/après
  • ROLLBACK annule aussi les suppressions en chaîne ; SAVEPOINT permet un retour partiel
  • SQLite n'applique les FK qu'avec PRAGMA foreign_keys = ON; — à mettre en tête de tous vos scripts
  • Réflexe agent IA : jamais de CASCADE accessible en écriture à un LLM sans approval gate

Notion Bonus II — Procédures stockées · 📖 Théorie

  • Jusqu'ici, la logique vivait côté client : c'est vous (ou votre code Python) qui enchaînez les requêtes
  • Une procédure stockée déplace cette logique dans la base : un bloc de SQL nommé, paramétrable, exécuté par un simple CALL
  • Lien direct avec la Notion 9 : la transaction « passer une commande » (créer la commande, insérer les lignes, décrémenter le stock) est exactement le genre de logique qu'on encapsule dans une procédure
flowchart LR
    subgraph SANS[Sans procédure]
        A[App] -->|BEGIN| DB1[(DB)]
        A -->|INSERT commande| DB1
        A -->|INSERT lignes| DB1
        A -->|UPDATE stock| DB1
        A -->|COMMIT| DB1
    end
    subgraph AVEC[Avec procédure]
        B[App] -->|"CALL passer_commande(3, 1, 2)"| DB2[(DB)]
    end
Loading

Procédure vs Fonction : ne pas confondre

Procédure (PROCEDURE) Fonction (FUNCTION)
Appel CALL ma_proc(...) Dans une requête : SELECT ma_fonction(...)
Retour Rien (ou via paramètres OUT) Une valeur (ou une table)
Transactions internes ✅ (PG 11+ : COMMIT/ROLLBACK dedans)
Vocation Faire (écrire, orchestrer) Calculer (renvoyer un résultat)

Les paramètres

  • IN : entrée (défaut) — la procédure lit la valeur
  • OUT : sortie — la procédure écrit dedans, l'appelant récupère
  • INOUT : les deux

Différences entre moteurs

Aspect MySQL / MariaDB PostgreSQL SQLite
Procédures stockées ✅ (v11+) aucune
Langage SQL/PSM (BEGIN ... END) PL/pgSQL (+ Python, Perl...)
Séparateur ⚠️ DELIMITER $$ obligatoire dans les clients Corps entre $$ ... $$ (dollar-quoting)
Appel CALL proc(...) CALL proc(...)
Variables DECLARE x INT; DECLARE x INT; (section dédiée)
  • ⚠️ Le piège n°1 sous MySQL : le client coupe votre procédure au premier ; interne. D'où le DELIMITER $$ avant, DELIMITER ; après. PostgreSQL contourne élégamment avec le dollar-quoting $$ ... $$
  • ⚠️ SQLite n'a pas de procédures : la logique reste côté application (Python). C'est un critère de choix de SGBD !

Structures de contrôle (les deux dialectes)

IF condition THEN ... ELSEIF ... ELSE ... END IF;
WHILE condition DO ... END WHILE;      -- MySQL
WHILE condition LOOP ... END LOOP;     -- PostgreSQL

Pourquoi (et pourquoi pas) ?

  • Atomicité garantie côté serveur : impossible d'oublier le COMMIT ou de sauter une étape
  • ✅ Un seul aller-retour réseau au lieu de 5 → performance
  • ✅ Sécurité : on peut donner le droit EXECUTE sur la procédure sans donner UPDATE sur les tables — une ceinture de plus pour un agent IA 👀
  • ❌ Logique métier éparpillée entre app et base → difficile à versionner, tester, déboguer
  • ❌ Dialecte non portable : une procédure MySQL ne tourne pas telle quelle sur PostgreSQL
  • Verdict d'équipe : parfait pour les opérations transactionnelles critiques proches des données ; à éviter pour la logique métier qui évolue souvent

Notion Bonus II — 👥 On fait ensemble

1. Première procédure (MySQL, dans boutique)

DELIMITER $$

CREATE PROCEDURE restocker(IN p_produit_id INT, IN p_quantite INT)
BEGIN
    UPDATE produits
    SET stock = stock + p_quantite
    WHERE id = p_produit_id;
END $$

DELIMITER ;

-- Utilisation
SELECT stock FROM produits WHERE id = 1;
CALL restocker(1, 50);
SELECT stock FROM produits WHERE id = 1;   -- +50 ✅

2. Paramètre OUT : récupérer un résultat

DELIMITER $$

CREATE PROCEDURE valeur_stock(OUT p_total DECIMAL(12,2))
BEGIN
    SELECT SUM(prix * stock) INTO p_total FROM produits;
END $$

DELIMITER ;

CALL valeur_stock(@total);   -- @total = variable de session MySQL
SELECT @total;

3. La vraie : passer_commande (Notion 9 encapsulée)

DELIMITER $$

CREATE PROCEDURE passer_commande(
    IN p_client_id INT,
    IN p_produit_id INT,
    IN p_quantite INT
)
BEGIN
    DECLARE v_stock INT;

    -- En cas d'erreur SQL : tout annuler puis propager l'erreur
    DECLARE EXIT HANDLER FOR SQLEXCEPTION
    BEGIN
        ROLLBACK;
        RESIGNAL;
    END;

    START TRANSACTION;

    SELECT stock INTO v_stock FROM produits WHERE id = p_produit_id FOR UPDATE;

    IF v_stock IS NULL THEN
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Produit inconnu';
    ELSEIF v_stock < p_quantite THEN
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Stock insuffisant';
    END IF;

    INSERT INTO commandes (client_id) VALUES (p_client_id);

    INSERT INTO lignes_commande (commande_id, produit_id, quantite)
    VALUES (LAST_INSERT_ID(), p_produit_id, p_quantite);

    UPDATE produits SET stock = stock - p_quantite WHERE id = p_produit_id;

    COMMIT;
END $$

DELIMITER ;
  • On teste les trois chemins :
CALL passer_commande(1, 2, 1);      -- ✅ commande créée, stock décrémenté
CALL passer_commande(1, 999, 1);    -- ❌ 'Produit inconnu'
CALL passer_commande(1, 2, 9999);   -- ❌ 'Stock insuffisant'

-- Vérification : les échecs n'ont laissé AUCUNE trace
SELECT * FROM commandes ORDER BY id DESC LIMIT 3;
  • Trois nouveautés au passage :
    • SELECT ... INTO variable : stocker un résultat
    • FOR UPDATE : verrouille la ligne le temps de la transaction (lien Notion 9, isolation)
    • SIGNAL : lever une erreur métier · le HANDLER = le try/except du SQL

4. Même chose côté PostgreSQL (PL/pgSQL)

CREATE OR REPLACE PROCEDURE restocker(p_produit_id INT, p_quantite INT)
LANGUAGE plpgsql
AS $$
BEGIN
    UPDATE produits
    SET stock = stock + p_quantite
    WHERE id = p_produit_id;

    IF NOT FOUND THEN
        RAISE EXCEPTION 'Produit % inconnu', p_produit_id;
    END IF;
END;
$$;

CALL restocker(1, 50);
  • À noter : pas de DELIMITER (merci le dollar-quoting), RAISE EXCEPTION remplace SIGNAL, et FOUND teste si l'UPDATE a touché une ligne
  • On compare les deux versions côte à côte dans Tabularis : même intention, deux dialectes — l'écart est plus grand qu'en SQL « classique »

Notion Bonus II — 🎯 Vous faites

  1. archiver_client(p_client_id) : procédure MySQL qui passe la ville du client à '--archivé--' et son email à CONCAT('archive_', id, '@nulle.part'). Levez une erreur SIGNAL si le client n'existe pas.
  2. stats_client(IN p_client_id, OUT p_nb_commandes, OUT p_ca) : renvoie le nombre de commandes et le CA du client (réutilisez votre requête CA de la Notion 6). Testez avec CALL stats_client(1, @nb, @ca); SELECT @nb, @ca;
  3. Améliorez passer_commande : ajoutez un paramètre OUT p_commande_id qui renvoie l'id de la commande créée, puis un second produit (deux INSERT de lignes) — vérifiez qu'un échec sur le 2e produit annule bien le 1er.
  4. Portage : réécrivez stats_client en PostgreSQL. Question : en PG, serait-ce plutôt une FUNCTION qu'une PROCEDURE ? Pourquoi ?
  5. Sécurité & IA : créez un utilisateur MySQL qui a uniquement EXECUTE sur passer_commande (aucun droit sur les tables) :
CREATE USER 'app_commande'@'%' IDENTIFIED BY '********';
GRANT EXECUTE ON PROCEDURE boutique.passer_commande TO 'app_commande'@'%';

Connectez-vous avec ce compte : SELECT * FROM produits échoue, mais CALL passer_commande(...) fonctionne. Expliquez en 3 lignes pourquoi c'est une excellente ceinture de sécurité pour un agent IA connecté via MCP. 6. Débat (5 min) : votre binôme soutient que « toute la logique doit être dans des procédures stockées ». Trouvez deux arguments pour, deux contre, en pensant au versionning Git et aux tests.

  • Livrable : un fichier procedures.sql rejouable (avec DROP PROCEDURE IF EXISTS ...) + vos réponses aux questions 4 et 5

Récap éclair

  • Une procédure = de la logique SQL nommée et paramétrée, exécutée par CALL, au plus près des données
  • IN / OUT / INOUT pour dialoguer · SIGNAL/RAISE + HANDLER pour les erreurs métier
  • Le trio gagnant : procédure + transaction + FOR UPDATE = opération métier atomique et concurrente-safe
  • MySQL exige DELIMITER, PostgreSQL préfère PL/pgSQL et $$, SQLite n'en a pas du tout
  • GRANT EXECUTE sans droit sur les tables : le principe du moindre privilège, taillé pour les agents IA
  • Contrepartie : dialecte non portable et logique hors de Git — à réserver aux opérations critiques

Tabularis + Claude : SQL à l'ère des agents · 📖 Théorie

  • MCP (Model Context Protocol) : protocole standard pour connecter un LLM à des outils
  • Tabularis embarque un serveur MCP : Claude peut lire le schéma et exécuter des requêtes via Tabularis
sequenceDiagram
    participant U as Vous
    participant C as Claude Desktop
    participant T as Tabularis (mode --mcp, stdio)
    participant DB as MySQL / PostgreSQL
    U->>C: "Quel est le CA par client ?"
    C->>T: list_tables / lecture du schéma
    T->>DB: requêtes de métadonnées
    C->>T: execute_query(SELECT ...)
    T->>DB: SQL
    DB-->>T: résultats
    T-->>C: résultats
    C-->>U: réponse + SQL utilisé
Loading
  • Tout est local (stdio, pas de port réseau) ; les identifiants restent dans Tabularis, jamais dans le chat
  • ⚠️ L'agent peut exécuter du SQL destructeur → Read-only Mode, Approval Gates, ou utilisateur SQL en lecture seule

Tabularis + Claude — 👥 On fait ensemble (démo)

  1. Tabularis → Settings → MCP Server Integration
  2. Install Config en un clic pour Claude Desktop → redémarrer Claude
  3. Activer le Read-only Mode (on est prudents)
  4. Dans Claude : « Liste les tables de ma base boutique » → il appelle list_tables
  5. Puis : « Quel est le chiffre d'affaires par client ? Montre-moi le SQL »
  6. On relit ensemble la requête générée : jointures correctes ? GROUP BY complet ?
  7. On la rejoue à la main dans l'éditeur pour valider
  • Message clé : l'IA rédige, l'humain valide — votre SQL sert à auditer le sien

Tabularis + Claude — 🎯 Vous faites

  1. Configurez le serveur MCP sur votre poste (mode read-only obligatoire)
  2. Demandez à Claude 3 requêtes de difficulté croissante sur boutique :
    • une simple (filtre), une agrégation, une multi-jointures
  3. Pour chacune : la requête est-elle correcte ? optimale ? Corrigez-la si besoin
  4. Créez un utilisateur MySQL en lecture seule et expliquez pourquoi c'est une seconde ceinture de sécurité :
CREATE USER 'claude_ro'@'%' IDENTIFIED BY '********';
GRANT SELECT ON boutique.* TO 'claude_ro'@'%';
  1. Débat (5 min) : que doit-on interdire à un agent IA sur une base de production ?

Mini-projet d'évaluation 🏁

  • En binôme, 3 h — base au choix : bibliothèque, covoiturage, suivi sportif, capteurs IoT...
  • Livrables :
    • schéma ER (export Tabularis) : 4 tables minimum, FK, contraintes
    • schema.sql + seed.sql rejouables sur MySQL et PostgreSQL
    • 8 requêtes commentées : filtres, agrégation, 2 jointures min., 1 CTE, 1 vue
    • 1 transaction métier justifiée
    • 1 EXPLAIN avant/après index avec analyse
    • bonus : notebook Tabularis avec graphique + 1 requête co-écrite avec Claude (et critiquée !)

Récap & ressources

  • SQL : 50 ans, toujours central — surtout à l'ère des agents IA
  • Réflexes acquis : WHERE avant UPDATE · CTE pour la lisibilité · EXPLAIN avant d'optimiser · transactions pour l'intégrité · IA supervisée, jamais autonome sur les données
  • Pour aller plus loin :
    • docs officielles PostgreSQL & MySQL (références absolues)
    • sqlbolt.com, pgexercises.com : entraînement interactif
    • Tabularis : tabularis.dev (wiki MCP inclus)
    • spec MCP : modelcontextprotocol.io
  • Prochain module : SQL analytique (fonctions de fenêtrage) & intégration Python (sqlalchemy, pandas.read_sql)

About

Le SQL des bases jusqu'à l'IA

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors