Cours SQL Server pour Débutants
Formation de 45 heures – Bases du langage SQL
Niveau : Débutant • Théorie + Exercices pratiques + Corrigés détaillés
Présentation de la formation
Objectif général : à l'issue de cette formation, l'apprenant sera capable de comprendre les bases de données
relationnelles, d'installer SQL Server, d'écrire des requêtes SQL (SELECT, INSERT, UPDATE, DELETE), de
concevoir des tables simples, et de comprendre les jointures et les fonctions de base.
Prérequis : aucun. Des notions de base en informatique suffisent.
Format suggéré : 15 séances de 3h (théorie + pratique), ou 9 séances de 5h.
Chaque module ci-dessous contient : la théorie, les énoncés d'exercices, puis un corrigé détaillé avec le code
SQL commenté et expliqué.
Module 1 – Introduction aux bases de données et à SQL Server (3h)
Théorie
Base de données relationnelle
Une base de données relationnelle est un ensemble de données organisées en tables reliées entre elles par
des relations logiques (clés). Le modèle relationnel, théorisé par Edgar Codd, repose sur l'algèbre ensembliste
et permet de représenter des données structurées de façon cohérente et sans redondance excessive. C'est le
modèle utilisé par SQL Server, Oracle, MySQL, PostgreSQL, etc.
Table, ligne, colonne
Une table (ou « relation ») est une structure en deux dimensions qui stocke des données sur un même sujet
(ex. Employes). Une ligne (ou « enregistrement », « tuple ») représente une occurrence unique de ce sujet (un
employé précis). Une colonne (ou « champ », « attribut ») représente une caractéristique commune à toutes
les lignes (le nom, le salaire, etc.).
Clé primaire (PRIMARY KEY)
Une clé primaire est une colonne (ou un ensemble de colonnes) qui identifie de façon unique et non ambiguë
chaque ligne d'une table. Elle ne peut jamais contenir de valeur NULL ni de doublon. C'est elle qui garantit
qu'on peut toujours retrouver une ligne précise.
Clé étrangère (FOREIGN KEY)
Une clé étrangère est une colonne d'une table qui référence la clé primaire d'une autre table (ou de la même
table). Elle matérialise une relation entre deux tables et garantit l'intégrité référentielle : on ne peut pas faire
référence à une ligne qui n'existe pas.
SQL Server et ses éditions
SQL Server est le système de gestion de base de données relationnelles (SGBDR) développé par Microsoft. Il
existe en plusieurs éditions : Express (gratuite, limitée en taille et en ressources, idéale pour l'apprentissage),
Standard (payante, pour les PME) et Enterprise (payante, pour les grandes entreprises, avec des
fonctionnalités avancées de haute disponibilité et de performance).
SQL Server Management Studio (SSMS)
SSMS est l'outil graphique officiel de Microsoft permettant de se connecter à une instance SQL Server,
d'explorer les bases de données, d'écrire et d'exécuter des requêtes SQL, et d'administrer le serveur (sécurité,
sauvegardes, etc.). C'est l'environnement de travail principal utilisé tout au long de cette formation.
Installation de SQL Server Express et SSMS
L'installation se fait en deux temps : d'abord le moteur de base de données (SQL Server Express), qui tourne en
tant que service Windows en arrière-plan, puis l'outil client (SSMS) qui permet de s'y connecter et de l'utiliser.
Les deux sont téléchargeables gratuitement depuis le site officiel de Microsoft.
Pratique
● Exercice 1.1 : Installer SQL Server Express et SSMS sur son poste
● Exercice 1.2 : Se connecter à une instance locale, explorer l'arborescence (bases système, sécurité,
etc.)
● Exercice 1.3 : Créer une base de données EntrepriseDB via l'interface graphique
Corrigé des exercices
✔ Exercice 1.1 – Installation
Télécharger « SQL Server Express » depuis le site officiel Microsoft, lancer l'installateur en choisissant le mode
« Basique », puis installer SSMS (SQL Server Management Studio) séparément. Aucune commande SQL n'est
nécessaire ici : il s'agit d'une installation graphique. Une fois terminé, vérifier que le service « SQL Server
(MSSQLSERVER) » est démarré dans les Services Windows.
-- Vérifier la version installée une fois connecté dans SSMS :
SELECT @@VERSION;
Remarque : @@VERSION est une variable système qui renvoie la version exacte du moteur SQL Server installé.
✔ Exercice 1.2 – Connexion et exploration
Dans SSMS, se connecter avec le nom du serveur (souvent .\SQLEXPRESS en local) et l'authentification
Windows. Dans l'Explorateur d'objets, déplier « Bases de données » puis « Bases de données système »
(master, model, msdb, tempdb) et « Sécurité » (Logins, Rôles serveur).
-- Lister toutes les bases de données présentes sur l'instance
SELECT name, database_id, create_date
FROM [Link];
-- Lister les logins de sécurité existants
SELECT name, type_desc
FROM sys.server_principals;
Remarque : [Link] et sys.server_principals sont des vues système permettant d'explorer l'instance en SQL plutôt
qu'à la souris.
✔ Exercice 1.3 – Création de la base EntrepriseDB
Via l'interface graphique : clic droit sur « Bases de données » > Nouvelle base de données > saisir EntrepriseDB
> OK. Équivalent en T-SQL :
CREATE DATABASE EntrepriseDB;
GO
-- Se positionner dans cette base pour les requêtes suivantes
USE EntrepriseDB;
GO
Remarque : Le mot-clé GO n'est pas une instruction SQL : c'est un séparateur de lot (batch) reconnu par SSMS.
Module 2 – Création de tables et types de données (3h)
Théorie
Types de données courants
INT
Type numérique entier (sans virgule), sur 4 octets, de -2 147 483 648 à 2 147 483 647. Utilisé pour les
identifiants, compteurs, quantités.
VARCHAR(n)
Chaîne de caractères de longueur variable (jusqu'à n caractères), codée sur 1 octet par caractère. Adapté au
texte ne contenant que des caractères ASCII (anglais).
NVARCHAR(n)
Identique à VARCHAR mais codée en Unicode (2 octets par caractère) : permet de stocker des accents et des
caractères de toutes les langues. Recommandé par défaut pour le texte.
DATE
Stocke uniquement une date (année, mois, jour), sans heure. Format ISO recommandé : 'AAAA-MM-JJ'.
DATETIME
Stocke une date ET une heure (jusqu'à la milliseconde). Utile pour horodater précisément un événement.
DECIMAL(p,s)
Type numérique exact à virgule fixe, où p est le nombre total de chiffres et s le nombre de chiffres après la
virgule (ex. DECIMAL(10,2) pour des montants monétaires). Contrairement à FLOAT, il ne génère pas d'erreurs
d'arrondi.
BIT
Type booléen : ne peut contenir que 0 (faux), 1 (vrai) ou NULL. Utilisé pour les indicateurs oui/non (ex.
EstActif).
Contraintes
PRIMARY KEY
Contrainte qui désigne la clé primaire de la table : garantit l'unicité et l'absence de NULL sur la ou les colonnes
concernées.
Syntaxe :
colonne TYPE PRIMARY KEY
-- ou, pour une clé composée :
CONSTRAINT PK_NomTable PRIMARY KEY (col1, col2)
NOT NULL
Contrainte qui rend une colonne obligatoire : impossible d'insérer une ligne sans fournir de valeur pour cette
colonne.
Syntaxe :
colonne TYPE NOT NULL
UNIQUE
Contrainte qui interdit les valeurs en double dans une colonne (contrairement à PRIMARY KEY, plusieurs
colonnes UNIQUE sont possibles, et NULL y est toléré une fois).
Syntaxe :
colonne TYPE UNIQUE
DEFAULT
Définit une valeur par défaut automatiquement insérée si aucune valeur n'est fournie explicitement lors d'un
INSERT.
Syntaxe :
colonne TYPE DEFAULT valeur_par_defaut
CHECK
Définit une règle métier que doit respecter la valeur d'une colonne (ex. un salaire toujours positif). Toute
tentative d'insertion ou de mise à jour qui viole la règle est rejetée.
Syntaxe :
colonne TYPE CHECK (condition)
-- ou au niveau table :
CONSTRAINT CK_NomTable CHECK (condition)
Syntaxe CREATE TABLE
CREATE TABLE crée une nouvelle table dans la base de données courante, en définissant ses colonnes, leurs
types et leurs contraintes.
CREATE TABLE NomTable (
NomColonne1 TYPE [CONTRAINTES],
NomColonne2 TYPE [CONTRAINTES],
...
CONSTRAINT PK_NomTable PRIMARY KEY (NomColonne1)
);
Pratique
● Exercice 2.1 : Créer une table Employes (Id, Nom, Prenom, DateNaissance, Salaire, DateEmbauche)
● Exercice 2.2 : Créer une table Departements (Id, NomDepartement)
● Exercice 2.3 : Ajouter une clé étrangère entre Employes et Departements
● Exercice 2.4 : Modifier une table existante avec ALTER TABLE (ajouter une colonne Email)
Corrigé des exercices
✔ Exercice 2.1 – Table Employes
Id est la clé primaire, auto-incrémentée grâce à IDENTITY(1,1). Nom et Prenom sont obligatoires (NOT NULL).
Salaire utilise DECIMAL pour éviter les erreurs d'arrondi des types flottants.
CREATE TABLE Employes (
Id INT IDENTITY(1,1) PRIMARY KEY,
Nom NVARCHAR(50) NOT NULL,
Prenom NVARCHAR(50) NOT NULL,
DateNaissance DATE NOT NULL,
Salaire DECIMAL(10,2) NOT NULL,
DateEmbauche DATE NOT NULL DEFAULT GETDATE()
);
Remarque : NVARCHAR est préféré à VARCHAR car il gère les caractères Unicode (accents, langues étrangères). DEFAULT
GETDATE() insère automatiquement la date du jour si aucune valeur n'est fournie.
✔ Exercice 2.2 – Table Departements
Même logique : une clé primaire auto-incrémentée et un nom obligatoire et unique (deux départements ne
devraient pas porter le même nom).
CREATE TABLE Departements (
Id INT IDENTITY(1,1) PRIMARY KEY,
NomDepartement NVARCHAR(100) NOT NULL UNIQUE
);
✔ Exercice 2.3 – Clé étrangère Employes → Departements
On ajoute d'abord la colonne DepartementId à Employes, puis on la lie à la clé primaire de Departements avec
une contrainte FOREIGN KEY.
ALTER TABLE Employes
ADD DepartementId INT NULL;
ALTER TABLE Employes
ADD CONSTRAINT FK_Employes_Departements
FOREIGN KEY (DepartementId)
REFERENCES Departements(Id);
Remarque : La clé étrangère est laissée NULL-able ici pour autoriser temporairement des employés sans département
assigné.
✔ Exercice 2.4 – Ajout de la colonne Email
ALTER TABLE ... ADD permet d'ajouter une colonne à une table déjà existante et déjà peuplée.
ALTER TABLE Employes
ADD Email NVARCHAR(150) NULL;
Remarque : La colonne est ajoutée en NULL par défaut car les lignes existantes n'ont pas encore d'adresse email.
Module 3 – Insertion, modification et suppression de données (3h)
Théorie
INSERT INTO
Instruction du langage de manipulation de données (DML) qui ajoute une ou plusieurs nouvelles lignes dans
une table.
Syntaxe :
INSERT INTO NomTable (Colonne1, Colonne2, ...)
VALUES (Valeur1, Valeur2, ...);
-- Plusieurs lignes en une seule instruction :
INSERT INTO NomTable (Colonne1, Colonne2)
VALUES (ValA1, ValA2), (ValB1, ValB2);
UPDATE ... SET ... WHERE
Instruction DML qui modifie la valeur d'une ou plusieurs colonnes pour les lignes existantes correspondant à
une condition. SET indique les colonnes à modifier et leurs nouvelles valeurs ; WHERE cible les lignes
concernées.
Syntaxe :
UPDATE NomTable
SET Colonne1 = NouvelleValeur1, Colonne2 = NouvelleValeur2
WHERE Condition;
DELETE FROM ... WHERE
Instruction DML qui supprime définitivement une ou plusieurs lignes d'une table correspondant à une
condition.
Syntaxe :
DELETE FROM NomTable
WHERE Condition;
Importance de la clause WHERE
La clause WHERE filtre les lignes concernées par UPDATE ou DELETE. Si elle est omise, l'instruction s'applique à
TOUTES les lignes de la table, ce qui peut écraser ou supprimer l'intégralité des données de façon irréversible.
Il est fortement recommandé de toujours vérifier sa condition avec un SELECT identique avant d'exécuter un
UPDATE ou un DELETE.
Pratique
● Exercice 3.1 : Insérer 10 employés et 3 départements dans les tables créées
● Exercice 3.2 : Mettre à jour le salaire de deux employés
● Exercice 3.3 : Supprimer un employé précis
● Exercice 3.4 (piège) : Observer ce qui se passe si on exécute un UPDATE sans WHERE (sur une copie de
table !)
Corrigé des exercices
✔ Exercice 3.1 – Insertion des données
On insère d'abord les départements (table référencée), puis les employés (table qui référence), pour respecter
la contrainte de clé étrangère.
INSERT INTO Departements (NomDepartement) VALUES
('Informatique'),
('Ressources Humaines'),
('Comptabilité');
INSERT INTO Employes (Nom, Prenom, DateNaissance, Salaire, DateEmbauche,
DepartementId) VALUES
('Diarra', 'Awa', '1990-05-12', 3200.00, '2018-03-01', 1),
('Traore', 'Ibrahim', '1988-11-23', 3800.00, '2016-07-15', 1),
('Kone', 'Fatou', '1995-02-08', 2900.00, '2020-01-10', 2),
('Sanogo', 'Moussa', '1992-09-30', 4100.00, '2015-06-01', 1),
('Coulibaly', 'Aissata', '1991-12-19', 3300.00, '2019-04-22', 3),
('Keita', 'Boubacar', '1987-03-05', 4500.00, '2012-10-11', 1),
('Toure', 'Mariam', '1993-07-17', 3000.00, '2021-02-14', 2),
('Camara', 'Seydou', '1996-01-25', 2800.00, '2022-05-30', 3),
('Doumbia', 'Kadiatou', '1989-08-09', 3900.00, '2014-09-01', 1),
('Sidibe', 'Oumar', '1994-04-14', 3100.00, '2020-11-03', 2);
Remarque : L'ordre des insertions respecte la contrainte FK : Departements avant Employes.
✔ Exercice 3.2 – Mise à jour de salaires
On cible précisément les employés à modifier via leur Id, jamais leur nom seul (risque d'homonymes).
UPDATE Employes SET Salaire = 3500.00 WHERE Id = 1;
UPDATE Employes SET Salaire = 4000.00 WHERE Id = 3;
✔ Exercice 3.3 – Suppression d'un employé
On supprime un employé précis en filtrant sur sa clé primaire.
DELETE FROM Employes WHERE Id = 8;
✔ Exercice 3.4 (piège) – UPDATE sans WHERE
Sur une copie de la table (jamais sur la table réelle), on exécute un UPDATE sans clause WHERE pour observer
que TOUTES les lignes sont modifiées.
-- Créer une copie de test
SELECT * INTO Employes_Copie FROM Employes;
-- UPDATE dangereux : pas de WHERE => tous les salaires sont écrasés
UPDATE Employes_Copie SET Salaire = 0;
-- Vérifier les dégâts
SELECT * FROM Employes_Copie;
-- Nettoyage
DROP TABLE Employes_Copie;
Remarque : Ceci illustre pourquoi il faut toujours vérifier la clause WHERE avant d'exécuter un UPDATE ou un DELETE en
production ; on peut aussi exécuter un SELECT avec la même clause WHERE au préalable pour vérifier les lignes ciblées.
Module 4 – La requête SELECT de base (3h)
Théorie
SELECT
Instruction du langage de requêtage (DQL) qui permet de lire et d'afficher des données depuis une ou
plusieurs tables. C'est l'instruction SQL la plus utilisée.
Syntaxe :
SELECT Colonne1, Colonne2
FROM NomTable;
SELECT *
Variante de SELECT qui renvoie toutes les colonnes de la table sans avoir à les nommer une par une. Pratique
pour explorer rapidement des données, mais déconseillé en production (moins lisible, moins performant,
sensible aux changements de structure de table).
Syntaxe :
SELECT * FROM NomTable;
Alias de colonnes (AS)
AS permet de renommer temporairement une colonne (ou une table) dans le résultat affiché, sans modifier
son nom réel dans la base. Utile pour clarifier l'affichage ou nommer une colonne calculée.
Syntaxe :
SELECT Colonne1 AS NomAffiche
FROM NomTable;
WHERE et opérateurs de comparaison
La clause WHERE filtre les lignes retournées par un SELECT selon une condition. Les opérateurs de
comparaison disponibles sont : = (égal), <> ou != (différent), > (supérieur), < (inférieur), >= (supérieur ou égal),
<= (inférieur ou égal).
Syntaxe :
SELECT Colonne1
FROM NomTable
WHERE Colonne2 > Valeur;
ORDER BY
Clause qui trie les lignes du résultat selon une ou plusieurs colonnes. ASC (ascendant, croissant) est l'ordre par
défaut ; DESC (descendant, décroissant) doit être précisé explicitement.
Syntaxe :
SELECT Colonne1
FROM NomTable
ORDER BY Colonne1 ASC; -- ou DESC
DISTINCT
Mot-clé qui élimine les doublons du résultat, pour n'afficher chaque combinaison de valeurs qu'une seule fois.
Syntaxe :
SELECT DISTINCT Colonne1
FROM NomTable;
Pratique
● Exercice 4.1 : Lister tous les employés triés par nom
● Exercice 4.2 : Lister les employés avec un salaire supérieur à 3000
● Exercice 4.3 : Lister les départements distincts présents dans la table employés
Corrigé des exercices
✔ Exercice 4.1 – Tri par nom
ORDER BY Nom trie les résultats par ordre alphabétique croissant (ASC est le comportement par défaut).
SELECT Id, Nom, Prenom, Salaire
FROM Employes
ORDER BY Nom ASC;
✔ Exercice 4.2 – Filtrage sur le salaire
La clause WHERE filtre les lignes avant leur affichage.
SELECT Nom, Prenom, Salaire
FROM Employes
WHERE Salaire > 3000;
✔ Exercice 4.3 – Départements distincts
DISTINCT élimine les doublons pour n'afficher chaque valeur qu'une seule fois.
SELECT DISTINCT DepartementId
FROM Employes;
Remarque : Pour afficher directement les noms des départements plutôt que leurs identifiants, il faudra utiliser une
jointure (voir Module 7).
Module 5 – Filtrage avancé (3h)
Théorie
AND / OR / NOT
Opérateurs logiques permettant de combiner plusieurs conditions dans une clause WHERE. AND exige que
toutes les conditions soient vraies, OR exige qu'au moins une le soit, NOT inverse une condition. Les
parenthèses permettent de contrôler l'ordre d'évaluation.
Syntaxe :
SELECT * FROM NomTable
WHERE Condition1 AND Condition2;
SELECT * FROM NomTable
WHERE Condition1 OR Condition2;
SELECT * FROM NomTable
WHERE NOT Condition1;
BETWEEN
Opérateur qui teste si une valeur se situe dans un intervalle donné, bornes incluses. Fonctionne avec des
nombres, des dates ou du texte.
Syntaxe :
SELECT * FROM NomTable
WHERE Colonne BETWEEN ValeurMin AND ValeurMax;
IN
Opérateur qui teste si une valeur appartient à une liste explicite de valeurs, en remplacement d'une
succession de conditions OR.
Syntaxe :
SELECT * FROM NomTable
WHERE Colonne IN (Valeur1, Valeur2, Valeur3);
LIKE (avec % et _)
Opérateur de recherche par motif (pattern matching) sur du texte. Le caractère % remplace une suite de zéro,
un ou plusieurs caractères quelconques ; le caractère _ remplace exactement un seul caractère quelconque.
Syntaxe :
SELECT * FROM NomTable
WHERE Colonne LIKE 'A%'; -- commence par A
WHERE Colonne LIKE '%A'; -- se termine par A
WHERE Colonne LIKE '%A%'; -- contient A
WHERE Colonne LIKE '_A%'; -- A en 2e position
Gestion des valeurs NULL (IS NULL / IS NOT NULL)
NULL représente l'absence de valeur (une donnée inconnue ou non renseignée) et n'est égal à rien, pas même
à lui-même : les opérateurs = et <> ne fonctionnent donc pas avec NULL. Il faut utiliser IS NULL pour tester
l'absence de valeur, et IS NOT NULL pour tester sa présence.
Syntaxe :
SELECT * FROM NomTable WHERE Colonne IS NULL;
SELECT * FROM NomTable WHERE Colonne IS NOT NULL;
Pratique
● Exercice 5.1 : Trouver les employés dont le nom commence par « M »
● Exercice 5.2 : Trouver les employés embauchés entre deux dates
● Exercice 5.3 : Trouver les employés sans adresse email renseignée
● Exercice 5.4 : Combiner plusieurs conditions avec AND/OR
Corrigé des exercices
✔ Exercice 5.1 – LIKE
Le symbole % remplace n'importe quelle suite de caractères (0 ou plus). 'M%' signifie « commence par M ».
SELECT Nom, Prenom
FROM Employes
WHERE Nom LIKE 'M%';
✔ Exercice 5.2 – BETWEEN
BETWEEN ... AND ... inclut les deux bornes.
SELECT Nom, Prenom, DateEmbauche
FROM Employes
WHERE DateEmbauche BETWEEN '2015-01-01' AND '2020-12-31';
✔ Exercice 5.3 – IS NULL
On ne peut pas tester une valeur NULL avec = ; il faut obligatoirement IS NULL ou IS NOT NULL.
SELECT Nom, Prenom
FROM Employes
WHERE Email IS NULL;
✔ Exercice 5.4 – Combinaison AND / OR
Les parenthèses sont essentielles pour contrôler la priorité entre AND et OR (AND est évalué avant OR par
défaut).
SELECT Nom, Prenom, Salaire, DepartementId
FROM Employes
WHERE Salaire > 3000
AND (DepartementId = 1 OR DepartementId = 2);
Module 6 – Fonctions d'agrégation et regroupement (3h)
Théorie
COUNT
Fonction d'agrégation qui compte le nombre de lignes (COUNT(*)) ou le nombre de valeurs non NULL d'une
colonne (COUNT(colonne)).
Syntaxe :
SELECT COUNT(*) FROM NomTable;
SELECT COUNT(Colonne) FROM NomTable;
SUM
Fonction d'agrégation qui calcule la somme des valeurs numériques d'une colonne.
Syntaxe :
SELECT SUM(Colonne) FROM NomTable;
AVG
Fonction d'agrégation qui calcule la moyenne des valeurs numériques d'une colonne.
Syntaxe :
SELECT AVG(Colonne) FROM NomTable;
MIN / MAX
Fonctions d'agrégation qui renvoient respectivement la plus petite et la plus grande valeur d'une colonne
(fonctionnent aussi sur des dates ou du texte).
Syntaxe :
SELECT MIN(Colonne), MAX(Colonne) FROM NomTable;
GROUP BY
Clause qui regroupe les lignes ayant la même valeur dans une ou plusieurs colonnes, afin de calculer une
fonction d'agrégation (COUNT, SUM, AVG...) pour chaque groupe plutôt que pour toute la table.
Syntaxe :
SELECT ColonneGroupe, COUNT(*)
FROM NomTable
GROUP BY ColonneGroupe;
HAVING
Clause qui filtre les groupes créés par GROUP BY, en fonction du résultat d'une fonction d'agrégation.
Contrairement à WHERE, qui filtre les lignes AVANT le regroupement et ne peut pas utiliser de fonction
d'agrégation, HAVING filtre APRÈS le regroupement.
Syntaxe :
SELECT ColonneGroupe, COUNT(*) AS Total
FROM NomTable
GROUP BY ColonneGroupe
HAVING COUNT(*) > Valeur;
Pratique
● Exercice 6.1 : Compter le nombre d'employés par département
● Exercice 6.2 : Calculer le salaire moyen par département
● Exercice 6.3 : Trouver les départements ayant plus de 3 employés (HAVING)
Corrigé des exercices
✔ Exercice 6.1 – COUNT par département
GROUP BY regroupe les lignes par valeur de DepartementId ; COUNT(*) compte le nombre de lignes dans
chaque groupe.
SELECT DepartementId, COUNT(*) AS NombreEmployes
FROM Employes
GROUP BY DepartementId;
✔ Exercice 6.2 – AVG par département
AVG calcule la moyenne des salaires pour chaque groupe.
SELECT DepartementId, AVG(Salaire) AS SalaireMoyen
FROM Employes
GROUP BY DepartementId;
✔ Exercice 6.3 – HAVING
HAVING s'applique après le GROUP BY, contrairement à WHERE qui ne peut pas filtrer sur un résultat agrégé
comme COUNT(*).
SELECT DepartementId, COUNT(*) AS NombreEmployes
FROM Employes
GROUP BY DepartementId
HAVING COUNT(*) > 3;
Module 7 – Les jointures (JOIN) – Partie 1 (3h)
Théorie
Pourquoi utiliser des jointures ?
Dans un modèle relationnel, les données sont réparties sur plusieurs tables pour éviter la redondance (chaque
information n'est stockée qu'à un seul endroit). Une jointure permet de recombiner ces tables au moment de
la lecture, en reliant les lignes grâce aux clés primaires et étrangères, pour reconstituer une vue complète de
l'information.
INNER JOIN
Jointure interne : ne renvoie que les lignes pour lesquelles une correspondance existe dans les deux tables. Si
une ligne d'une table n'a pas de correspondance dans l'autre, elle est exclue du résultat.
Syntaxe :
SELECT ...
FROM TableA a
INNER JOIN TableB b ON [Link] = [Link];
LEFT JOIN
Jointure externe gauche : renvoie TOUTES les lignes de la table de gauche (celle citée juste après FROM),
qu'elles aient ou non une correspondance dans la table de droite. Quand il n'y a pas de correspondance, les
colonnes de la table de droite apparaissent à NULL.
Syntaxe :
SELECT ...
FROM TableA a
LEFT JOIN TableB b ON [Link] = [Link];
Schéma visuel des jointures
On représente souvent les jointures avec des diagrammes de Venn : INNER JOIN correspond à l'intersection
des deux ensembles ; LEFT JOIN correspond à l'ensemble de gauche en entier, plus son intersection avec
l'ensemble de droite.
Pratique
● Exercice 7.1 : Lister les employés avec le nom de leur département (INNER JOIN)
● Exercice 7.2 : Lister tous les départements, même ceux sans employés (LEFT JOIN)
● Exercice 7.3 : Identifier les départements sans aucun employé
Corrigé des exercices
✔ Exercice 7.1 – INNER JOIN
On relie [Link] à [Link] pour afficher le nom du département au lieu de son
simple identifiant.
SELECT [Link], [Link], [Link]
FROM Employes e
INNER JOIN Departements d ON [Link] = [Link];
✔ Exercice 7.2 – LEFT JOIN
En partant de Departements (table de gauche), on récupère tous les départements même ceux qui n'ont
aucun employé correspondant (colonnes employé à NULL dans ce cas).
SELECT [Link], [Link], [Link]
FROM Departements d
LEFT JOIN Employes e ON [Link] = [Link];
✔ Exercice 7.3 – Départements sans employé
On combine LEFT JOIN et IS NULL : seules les lignes de Departements sans correspondance dans Employes ont
[Link] = NULL.
SELECT [Link]
FROM Departements d
LEFT JOIN Employes e ON [Link] = [Link]
WHERE [Link] IS NULL;
Module 8 – Les jointures (JOIN) – Partie 2 (3h)
Théorie
RIGHT JOIN
Jointure externe droite : symétrique du LEFT JOIN, elle renvoie toutes les lignes de la table de droite, avec
NULL pour les colonnes de la table de gauche en l'absence de correspondance. Peu utilisée en pratique car on
peut toujours reformuler un RIGHT JOIN en LEFT JOIN en inversant l'ordre des tables.
Syntaxe :
SELECT ...
FROM TableA a
RIGHT JOIN TableB b ON [Link] = [Link];
FULL OUTER JOIN
Jointure externe complète : renvoie toutes les lignes des deux tables, qu'elles aient une correspondance ou
non de part et d'autre. Les colonnes sans correspondance apparaissent à NULL.
Syntaxe :
SELECT ...
FROM TableA a
FULL OUTER JOIN TableB b ON [Link] = [Link];
Jointures sur plusieurs tables
On peut enchaîner plusieurs clauses JOIN dans une même requête pour combiner trois tables ou plus,
typiquement lorsqu'une table de liaison relie deux autres tables dans une relation plusieurs-à-plusieurs.
Syntaxe :
SELECT ...
FROM TableA a
INNER JOIN TableB b ON [Link] = [Link]
INNER JOIN TableC c ON [Link] = [Link];
CROSS JOIN
Produit cartésien : combine chaque ligne de la première table avec chaque ligne de la seconde, sans condition
de correspondance. Le nombre de lignes obtenu est le produit du nombre de lignes des deux tables. À utiliser
avec précaution (peut générer un très grand nombre de résultats).
Syntaxe :
SELECT ...
FROM TableA
CROSS JOIN TableB;
Pratique
● Exercice 8.1 : Créer une table Projets et une table de liaison EmployesProjets
● Exercice 8.2 : Lister les employés et leurs projets associés
● Exercice 8.3 : Lister les employés n'ayant aucun projet assigné
Corrigé des exercices
✔ Exercice 8.1 – Tables Projets et EmployesProjets
EmployesProjets est une table de liaison (relation many-to-many) : chaque ligne associe un employé à un
projet.
CREATE TABLE Projets (
Id INT IDENTITY(1,1) PRIMARY KEY,
NomProjet NVARCHAR(100) NOT NULL
);
CREATE TABLE EmployesProjets (
EmployeId INT NOT NULL,
ProjetId INT NOT NULL,
CONSTRAINT PK_EmployesProjets PRIMARY KEY (EmployeId, ProjetId),
CONSTRAINT FK_EP_Employe FOREIGN KEY (EmployeId) REFERENCES Employes(Id),
CONSTRAINT FK_EP_Projet FOREIGN KEY (ProjetId) REFERENCES Projets(Id)
);
Remarque : La clé primaire composée (EmployeId, ProjetId) empêche d'associer deux fois le même employé au même
projet.
✔ Exercice 8.2 – Employés et projets
Jointure sur trois tables : Employes → EmployesProjets → Projets.
SELECT [Link], [Link], [Link]
FROM Employes e
INNER JOIN EmployesProjets ep ON [Link] = [Link]
INNER JOIN Projets p ON [Link] = [Link];
✔ Exercice 8.3 – Employés sans projet
LEFT JOIN depuis Employes vers EmployesProjets, puis filtre sur les lignes sans correspondance.
SELECT [Link], [Link]
FROM Employes e
LEFT JOIN EmployesProjets ep ON [Link] = [Link]
WHERE [Link] IS NULL;
Module 9 – Sous-requêtes (3h)
Théorie
Sous-requête
Une sous-requête (ou requête imbriquée) est une requête SELECT placée à l'intérieur d'une autre requête. Elle
est exécutée en premier et son résultat est utilisé par la requête principale (dite « requête externe »).
Sous-requête dans WHERE
Une sous-requête placée dans la clause WHERE permet de filtrer les lignes de la requête externe en fonction
d'un résultat calculé dynamiquement, plutôt qu'une valeur fixe écrite en dur.
Syntaxe :
SELECT Colonne1
FROM TableA
WHERE Colonne2 = (SELECT Colonne2 FROM TableB WHERE Condition);
Sous-requête dans SELECT
Une sous-requête placée dans la liste des colonnes d'un SELECT calcule une valeur supplémentaire pour
chaque ligne de la requête externe (elle doit renvoyer une seule valeur par ligne).
Syntaxe :
SELECT Colonne1,
(SELECT COUNT(*) FROM TableB WHERE [Link] = [Link]) AS Total
FROM TableA;
IN
Teste si une valeur appartient à l'ensemble de valeurs renvoyées par une sous-requête (équivalent d'une liste
dynamique).
Syntaxe :
SELECT * FROM TableA
WHERE Colonne IN (SELECT ColonneCorrespondante FROM TableB WHERE Condition);
EXISTS
Teste uniquement l'existence d'au moins une ligne renvoyée par une sous-requête corrélée, sans en récupérer
la valeur. Renvoie VRAI ou FAUX. Souvent plus performant que IN sur de gros volumes de données.
Syntaxe :
SELECT * FROM TableA a
WHERE EXISTS (SELECT 1 FROM TableB b WHERE [Link] = [Link]);
ANY / ALL
ANY teste si la condition est vraie pour au moins une des valeurs renvoyées par la sous-requête ; ALL teste si
elle est vraie pour toutes les valeurs renvoyées.
Syntaxe :
SELECT * FROM TableA
WHERE Colonne > ANY (SELECT Colonne FROM TableB);
SELECT * FROM TableA
WHERE Colonne > ALL (SELECT Colonne FROM TableB);
Pratique
● Exercice 9.1 : Trouver les employés gagnant plus que la moyenne générale
● Exercice 9.2 : Trouver les départements ayant au moins un employé (EXISTS)
● Exercice 9.3 : Comparer sous-requête vs jointure sur un même résultat
Corrigé des exercices
✔ Exercice 9.1 – Sous-requête scalaire dans WHERE
La sous-requête interne (SELECT AVG(Salaire) FROM Employes) renvoie une seule valeur, comparée ensuite au
salaire de chaque employé.
SELECT Nom, Prenom, Salaire
FROM Employes
WHERE Salaire > (SELECT AVG(Salaire) FROM Employes);
✔ Exercice 9.2 – EXISTS
EXISTS teste seulement la présence d'au moins une ligne correspondante, sans en récupérer les valeurs : c'est
souvent plus performant qu'un IN.
SELECT [Link]
FROM Departements d
WHERE EXISTS (
SELECT 1 FROM Employes e WHERE [Link] = [Link]
);
✔ Exercice 9.3 – Sous-requête vs jointure
Les deux requêtes suivantes donnent le même résultat (départements ayant au moins un employé) mais avec
des approches différentes : la sous-requête est souvent plus lisible, la jointure peut être plus rapide sur de
gros volumes.
-- Version sous-requête (IN)
SELECT NomDepartement
FROM Departements
WHERE Id IN (SELECT DepartementId FROM Employes);
-- Version jointure (équivalente)
SELECT DISTINCT [Link]
FROM Departements d
INNER JOIN Employes e ON [Link] = [Link];
Remarque : Le DISTINCT est nécessaire dans la version JOIN car un département apparaît une fois par employé, alors que
IN ne renvoie chaque département qu'une seule fois.
Module 10 – Fonctions courantes (3h)
Théorie
Fonctions texte
LEN
Renvoie le nombre de caractères d'une chaîne de texte.
Syntaxe :
SELECT LEN(Colonne) FROM NomTable;
UPPER / LOWER
Convertissent respectivement une chaîne en majuscules ou en minuscules.
Syntaxe :
SELECT UPPER(Colonne), LOWER(Colonne) FROM NomTable;
SUBSTRING
Extrait une sous-chaîne à partir d'une position donnée, sur une longueur donnée.
Syntaxe :
SELECT SUBSTRING(Colonne, PositionDepart, Longueur) FROM NomTable;
CONCAT
Assemble (concatène) plusieurs chaînes de caractères en une seule.
Syntaxe :
SELECT CONCAT(Colonne1, ' ', Colonne2) FROM NomTable;
TRIM
Supprime les espaces inutiles en début et fin de chaîne.
Syntaxe :
SELECT TRIM(Colonne) FROM NomTable;
Fonctions date
GETDATE
Renvoie la date et l'heure système actuelles du serveur.
Syntaxe :
SELECT GETDATE();
DATEADD
Ajoute (ou soustrait, avec une valeur négative) un intervalle de temps à une date.
Syntaxe :
SELECT DATEADD(DAY, 15, DateColonne) FROM NomTable;
DATEDIFF
Calcule la différence entre deux dates, exprimée dans l'unité choisie (YEAR, MONTH, DAY...).
Syntaxe :
SELECT DATEDIFF(YEAR, DateDebut, DateFin) FROM NomTable;
YEAR / MONTH
Extraient respectivement l'année ou le mois d'une date.
Syntaxe :
SELECT YEAR(DateColonne), MONTH(DateColonne) FROM NomTable;
Fonctions numériques
ROUND
Arrondit un nombre à un nombre de décimales donné.
Syntaxe :
SELECT ROUND(Colonne, NombreDecimales) FROM NomTable;
ABS
Renvoie la valeur absolue d'un nombre (toujours positive).
Syntaxe :
SELECT ABS(Colonne) FROM NomTable;
Logique conditionnelle
CASE WHEN
Expression conditionnelle qui fonctionne comme un enchaînement de « si...alors...sinon » directement dans
une requête SQL. Elle évalue chaque condition WHEN dans l'ordre et renvoie le résultat THEN correspondant à
la première condition vraie, sinon la valeur ELSE.
Syntaxe :
SELECT Colonne,
CASE
WHEN Condition1 THEN 'Resultat1'
WHEN Condition2 THEN 'Resultat2'
ELSE 'ResultatParDefaut'
END AS NouvelleColonne
FROM NomTable;
Pratique
● Exercice 10.1 : Afficher le nom complet des employés en majuscules
● Exercice 10.2 : Calculer l'ancienneté de chaque employé en années
● Exercice 10.3 : Classer les employés en « Junior/Senior » selon leur ancienneté avec CASE WHEN
Corrigé des exercices
✔ Exercice 10.1 – CONCAT + UPPER
CONCAT assemble plusieurs chaînes ; UPPER convertit le résultat en majuscules.
SELECT UPPER(CONCAT(Prenom, ' ', Nom)) AS NomComplet
FROM Employes;
✔ Exercice 10.2 – DATEDIFF
DATEDIFF(YEAR, date_debut, date_fin) calcule la différence en années entre deux dates.
SELECT Nom, Prenom,
DATEDIFF(YEAR, DateEmbauche, GETDATE()) AS AncienneteAnnees
FROM Employes;
✔ Exercice 10.3 – CASE WHEN
CASE WHEN fonctionne comme un if/else in-line dans le SELECT.
SELECT Nom, Prenom,
DATEDIFF(YEAR, DateEmbauche, GETDATE()) AS Anciennete,
CASE
WHEN DATEDIFF(YEAR, DateEmbauche, GETDATE()) >= 5 THEN 'Senior'
ELSE 'Junior'
END AS Niveau
FROM Employes;
Module 11 – Modification de structure et contraintes avancées (3h)
Théorie
ALTER TABLE
Instruction du langage de définition de données (DDL) qui modifie la structure d'une table déjà existante :
ajout ou suppression de colonnes, ajout ou suppression de contraintes, sans perdre les données déjà
présentes.
Syntaxe :
ALTER TABLE NomTable ADD NouvelleColonne TYPE;
ALTER TABLE NomTable DROP COLUMN NomColonne;
ALTER TABLE NomTable ADD CONSTRAINT NomContrainte ...;
ALTER TABLE NomTable DROP CONSTRAINT NomContrainte;
DROP TABLE
Instruction DDL qui supprime définitivement une table et toutes ses données de la base. Opération
irréversible, à utiliser avec la plus grande prudence.
Syntaxe :
DROP TABLE NomTable;
Clé primaire composée
Une clé primaire composée est une clé primaire formée de plusieurs colonnes combinées : c'est la
combinaison des valeurs qui doit être unique, et non chaque colonne prise isolément. Utilisée typiquement
dans les tables de liaison (relations plusieurs-à-plusieurs).
Syntaxe :
CONSTRAINT PK_NomTable PRIMARY KEY (Colonne1, Colonne2)
Index
Un index est une structure de données annexe qui accélère la recherche et le tri sur une ou plusieurs colonnes,
un peu comme l'index d'un livre permet de retrouver rapidement un mot sans lire toutes les pages. Il a un coût
: chaque écriture (INSERT/UPDATE/DELETE) doit aussi mettre à jour l'index, donc on n'indexe que les colonnes
fréquemment utilisées dans des recherches, jointures ou tris.
Syntaxe :
CREATE INDEX NomIndex ON NomTable (NomColonne);
Pratique
● Exercice 11.1 : Ajouter une contrainte CHECK sur le salaire (doit être positif)
● Exercice 11.2 : Créer un index sur la colonne Nom
● Exercice 11.3 : Supprimer une contrainte existante
Corrigé des exercices
✔ Exercice 11.1 – Contrainte CHECK
CHECK impose une règle métier au niveau de la base : ici, empêcher tout salaire négatif ou nul.
ALTER TABLE Employes
ADD CONSTRAINT CK_Employes_SalairePositif CHECK (Salaire > 0);
✔ Exercice 11.2 – Index
Un index accélère les recherches et tris sur la colonne indexée, au prix d'un léger coût supplémentaire lors des
écritures.
CREATE INDEX IX_Employes_Nom
ON Employes (Nom);
✔ Exercice 11.3 – Suppression d'une contrainte
On retire la contrainte CHECK créée précédemment, en la désignant par son nom.
ALTER TABLE Employes
DROP CONSTRAINT CK_Employes_SalairePositif;
Remarque : Nommer explicitement chaque contrainte (CONSTRAINT nom ...) est indispensable pour pouvoir la retrouver
et la supprimer facilement.
Module 12 – Vues (VIEWS) (3h)
Théorie
Vue (VIEW)
Une vue est une requête SELECT enregistrée sous un nom, qui se comporte ensuite comme une table
virtuelle : elle ne stocke aucune donnée elle-même, mais exécute sa requête sous-jacente à chaque
interrogation. Elle est utile pour simplifier des requêtes complexes récurrentes, masquer certaines colonnes
sensibles, ou offrir une vue métier stable même si la structure des tables sous-jacentes évolue.
CREATE VIEW
Instruction qui crée une nouvelle vue à partir d'une requête SELECT.
Syntaxe :
CREATE VIEW NomVue AS
SELECT Colonne1, Colonne2
FROM NomTable
WHERE Condition;
ALTER VIEW / DROP VIEW
ALTER VIEW remplace la définition d'une vue existante par une nouvelle requête. DROP VIEW supprime
définitivement une vue (sans affecter les tables sous-jacentes, dont les données restent intactes).
Syntaxe :
ALTER VIEW NomVue AS
SELECT ... ;
DROP VIEW NomVue;
Pratique
● Exercice 12.1 : Créer une vue VueEmployesDepartements combinant les infos essentielles
● Exercice 12.2 : Interroger la vue comme une table normale
● Exercice 12.3 : Modifier puis supprimer la vue
Corrigé des exercices
✔ Exercice 12.1 – CREATE VIEW
Une vue est une requête SELECT enregistrée sous un nom, réutilisable comme une table virtuelle.
CREATE VIEW VueEmployesDepartements AS
SELECT [Link], [Link], [Link], [Link], [Link]
FROM Employes e
INNER JOIN Departements d ON [Link] = [Link];
✔ Exercice 12.2 – Interroger la vue
Une vue s'utilise exactement comme une table dans un SELECT.
SELECT * FROM VueEmployesDepartements
WHERE Salaire > 3000
ORDER BY NomDepartement;
✔ Exercice 12.3 – Modifier puis supprimer la vue
ALTER VIEW remplace la définition existante ; DROP VIEW la supprime définitivement.
ALTER VIEW VueEmployesDepartements AS
SELECT [Link], [Link], [Link], [Link], [Link], [Link]
FROM Employes e
INNER JOIN Departements d ON [Link] = [Link];
DROP VIEW VueEmployesDepartements;
Module 13 – Introduction aux procédures stockées (3h)
Théorie
Procédure stockée
Une procédure stockée est un ensemble d'instructions SQL enregistrées sous un nom dans la base de données,
que l'on peut exécuter à la demande. Elle permet de centraliser une logique métier réutilisable (au lieu de la
réécrire dans chaque application), d'améliorer la sécurité (accès contrôlé, protection contre les injections SQL)
et d'améliorer les performances (le plan d'exécution peut être mis en cache).
CREATE PROCEDURE et paramètres d'entrée
CREATE PROCEDURE définit une nouvelle procédure stockée. Les paramètres d'entrée (préfixés par @)
permettent de fournir des valeurs variables à chaque exécution, rendant la procédure réutilisable avec des
données différentes.
Syntaxe :
CREATE PROCEDURE NomProcedure
@Parametre1 TYPE,
@Parametre2 TYPE
AS
BEGIN
SET NOCOUNT ON;
-- instructions SQL utilisant @Parametre1, @Parametre2
END;
EXEC
Instruction (ou EXECUTE) qui appelle et exécute une procédure stockée existante, en lui fournissant les valeurs
de ses paramètres.
Syntaxe :
EXEC NomProcedure @Parametre1 = Valeur1, @Parametre2 = Valeur2;
Pratique
● Exercice 13.1 : Créer une procédure qui retourne les employés d'un département donné
● Exercice 13.2 : Créer une procédure d'insertion d'un nouvel employé
● Exercice 13.3 : Exécuter les procédures avec différents paramètres
Corrigé des exercices
✔ Exercice 13.1 – Procédure de lecture avec paramètre
@DepartementId est un paramètre d'entrée : sa valeur est fournie au moment de l'exécution (EXEC).
CREATE PROCEDURE [Link]
@DepartementId INT
AS
BEGIN
SET NOCOUNT ON;
SELECT Id, Nom, Prenom, Salaire
FROM Employes
WHERE DepartementId = @DepartementId;
END;
Remarque : SET NOCOUNT ON évite l'envoi de messages 'x lignes affectées' inutiles au client, ce qui améliore légèrement
la performance.
✔ Exercice 13.2 – Procédure d'insertion
Plusieurs paramètres sont passés en entrée pour créer un nouvel employé.
CREATE PROCEDURE [Link]
@Nom NVARCHAR(50),
@Prenom NVARCHAR(50),
@DateNaissance DATE,
@Salaire DECIMAL(10,2),
@DepartementId INT
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO Employes (Nom, Prenom, DateNaissance, Salaire, DateEmbauche,
DepartementId)
VALUES (@Nom, @Prenom, @DateNaissance, @Salaire, GETDATE(), @DepartementId);
END;
✔ Exercice 13.3 – Exécution avec EXEC
EXEC (ou EXECUTE) appelle la procédure en fournissant les valeurs des paramètres, soit par position, soit par
nom (recommandé pour la lisibilité).
EXEC [Link] @DepartementId = 1;
EXEC [Link]
@Nom = 'Diakite',
@Prenom = 'Salif',
@DateNaissance = '1997-06-20',
@Salaire = 2950.00,
@DepartementId = 2;
Module 14 – Transactions et bonnes pratiques (3h)
Théorie
Transaction
Une transaction est un ensemble d'une ou plusieurs instructions SQL traitées comme une seule unité
indivisible : soit toutes les instructions réussissent et sont validées, soit aucune ne l'est. Cette propriété (dite «
atomicité ») garantit qu'une opération complexe (ex. un virement bancaire, qui débite un compte et en crédite
un autre) ne peut jamais rester à moitié terminée.
BEGIN TRANSACTION / COMMIT / ROLLBACK
BEGIN TRANSACTION démarre une transaction. COMMIT TRANSACTION valide définitivement toutes les
modifications effectuées depuis le début de la transaction. ROLLBACK TRANSACTION annule toutes ces
modifications et remet la base dans l'état où elle était avant le BEGIN TRANSACTION.
Syntaxe :
BEGIN TRANSACTION;
-- instructions SQL (INSERT, UPDATE, DELETE...)
COMMIT TRANSACTION; -- valide
-- ou
ROLLBACK TRANSACTION; -- annule
Pourquoi les transactions sont importantes
Sans transaction, une erreur survenant au milieu d'une série d'opérations liées peut laisser la base de données
dans un état incohérent (ex. un compte débité sans que l'autre soit crédité). Les transactions garantissent
l'intégrité des données même en cas d'erreur, de panne ou d'accès concurrent.
Bonnes pratiques d'écriture SQL
Nommer explicitement les tables, colonnes et contraintes de façon claire et cohérente ; indenter et structurer
les requêtes pour la lisibilité ; utiliser des alias explicites plutôt que des lettres isolées sur les requêtes
complexes ; commenter les requêtes non triviales (-- pour une ligne, /* */ pour un bloc) ; éviter SELECT * en
production.
Sécurité de base : éviter les injections SQL
Une injection SQL est une attaque qui consiste à insérer du code SQL malveillant dans une entrée utilisateur
pour manipuler ou détourner une requête. La principale protection consiste à ne jamais construire une
requête par concaténation directe de texte utilisateur, et à toujours utiliser des requêtes paramétrées (comme
les paramètres @param des procédures stockées), qui séparent strictement le code SQL des données fournies.
Pratique
● Exercice 14.1 : Simuler un virement entre deux comptes avec transaction (COMMIT si succès,
ROLLBACK si échec)
● Exercice 14.2 : Provoquer volontairement une erreur pour observer le ROLLBACK
● Exercice 14.3 : Réécrire une requête du module 5 en appliquant les bonnes pratiques de nommage
Corrigé des exercices
✔ Exercice 14.1 – Transaction de virement
TRY...CATCH capture toute erreur survenant pendant la transaction ; en cas de succès on valide avec COMMIT,
en cas d'échec on annule tout avec ROLLBACK, garantissant qu'aucun virement partiel n'est enregistré.
CREATE TABLE Comptes (
Id INT IDENTITY(1,1) PRIMARY KEY,
Titulaire NVARCHAR(50),
Solde DECIMAL(10,2)
);
INSERT INTO Comptes (Titulaire, Solde) VALUES ('Compte A', 1000), ('Compte B', 500);
BEGIN TRY
BEGIN TRANSACTION;
UPDATE Comptes SET Solde = Solde - 200 WHERE Id = 1; -- débit
UPDATE Comptes SET Solde = Solde + 200 WHERE Id = 2; -- crédit
COMMIT TRANSACTION;
PRINT 'Virement effectué avec succès.';
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
PRINT 'Erreur : virement annulé.';
END CATCH;
✔ Exercice 14.2 – Provoquer une erreur volontaire
On force une division par zéro (ou toute violation de contrainte) au milieu de la transaction pour vérifier que le
ROLLBACK annule bien le premier UPDATE déjà exécuté.
BEGIN TRY
BEGIN TRANSACTION;
UPDATE Comptes SET Solde = Solde - 200 WHERE Id = 1;
SELECT 1 / 0; -- erreur provoquée volontairement
UPDATE Comptes SET Solde = Solde + 200 WHERE Id = 2;
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
PRINT 'Erreur détectée : ' + ERROR_MESSAGE();
END CATCH;
-- Vérification : le solde du Compte A doit être resté inchangé (1000)
SELECT * FROM Comptes;
Remarque : ERROR_MESSAGE() renvoie le texte de l'erreur capturée dans le bloc CATCH.
✔ Exercice 14.3 – Bonnes pratiques de nommage
Requête originale du module 5 réécrite avec alias explicites, indentation cohérente et commentaire d'en-tête.
-- Objectif : lister les employés embauchés entre 2015 et 2020, triés par date
d'embauche
SELECT
[Link] AS NomEmploye,
[Link] AS PrenomEmploye,
[Link] AS DateEmbauche
FROM Employes AS e
WHERE [Link] BETWEEN '2015-01-01' AND '2020-12-31'
ORDER BY [Link] ASC;
Remarque : L'utilisation de paramètres (@param) dans les procédures stockées, plutôt que la concaténation de texte, est
la meilleure protection contre les injections SQL.
Module 15 – Projet final récapitulatif (3h)
Objectif
Mettre en pratique l'ensemble des notions vues durant la formation sur un mini-projet complet.
Projet : Gestion d'une petite bibliothèque
● Créer les tables : Livres, Auteurs, Membres, Emprunts
● Insérer un jeu de données de test
● Lister les livres empruntés actuellement
● Lister les membres n'ayant jamais emprunté de livre
● Calculer le nombre d'emprunts par membre
● Créer une vue des emprunts en retard
● Créer une procédure stockée pour enregistrer un nouvel emprunt
● Présentation / correction collective du projet
Corrigé des exercices
✔ Étape 1 – Création des tables
Modèle relationnel simple : un Auteur peut avoir plusieurs Livres ; un Membre peut avoir plusieurs Emprunts ;
un Emprunt relie un Livre et un Membre.
CREATE TABLE Auteurs (
Id INT IDENTITY(1,1) PRIMARY KEY,
NomAuteur NVARCHAR(100) NOT NULL
);
CREATE TABLE Livres (
Id INT IDENTITY(1,1) PRIMARY KEY,
Titre NVARCHAR(150) NOT NULL,
AuteurId INT NOT NULL,
CONSTRAINT FK_Livres_Auteurs FOREIGN KEY (AuteurId) REFERENCES Auteurs(Id)
);
CREATE TABLE Membres (
Id INT IDENTITY(1,1) PRIMARY KEY,
NomMembre NVARCHAR(100) NOT NULL
);
CREATE TABLE Emprunts (
Id INT IDENTITY(1,1) PRIMARY KEY,
LivreId INT NOT NULL,
MembreId INT NOT NULL,
DateEmprunt DATE NOT NULL DEFAULT GETDATE(),
DateRetourPrevue DATE NOT NULL,
DateRetourEffective DATE NULL,
CONSTRAINT FK_Emprunts_Livres FOREIGN KEY (LivreId) REFERENCES Livres(Id),
CONSTRAINT FK_Emprunts_Membres FOREIGN KEY (MembreId) REFERENCES Membres(Id)
);
Remarque : DateRetourEffective reste NULL tant que le livre n'a pas été rendu : c'est ce champ qui permet de savoir si un
emprunt est en cours.
✔ Étape 2 – Jeu de données de test
Quelques lignes suffisent pour tester toutes les requêtes suivantes.
INSERT INTO Auteurs (NomAuteur) VALUES ('Amadou Hampâté Bâ'), ('Ahmadou Kourouma');
INSERT INTO Livres (Titre, AuteurId) VALUES
('Amkoullel, l''enfant peul', 1),
('Les Soleils des indépendances', 2);
INSERT INTO Membres (NomMembre) VALUES ('Awa Diarra'), ('Ibrahim Traore'), ('Fatou
Kone');
INSERT INTO Emprunts (LivreId, MembreId, DateEmprunt, DateRetourPrevue,
DateRetourEffective) VALUES
(1, 1, '2026-06-01', '2026-06-15', NULL), -- en cours
(2, 2, '2026-05-01', '2026-05-15', '2026-05-10'), -- rendu à temps
(1, 2, '2026-04-01', '2026-04-10', NULL); -- en retard (non rendu, date
dépassée)
✔ Étape 3 – Livres empruntés actuellement
Un emprunt est « en cours » quand DateRetourEffective est encore NULL.
SELECT [Link], [Link], [Link]
FROM Emprunts e
INNER JOIN Livres l ON [Link] = [Link]
INNER JOIN Membres m ON [Link] = [Link]
WHERE [Link] IS NULL;
✔ Étape 4 – Membres n'ayant jamais emprunté
LEFT JOIN + IS NULL, la même technique que pour les départements sans employé (Module 7).
SELECT [Link]
FROM Membres m
LEFT JOIN Emprunts e ON [Link] = [Link]
WHERE [Link] IS NULL;
✔ Étape 5 – Nombre d'emprunts par membre
GROUP BY sur le membre, avec LEFT JOIN pour inclure aussi les membres à 0 emprunt.
SELECT [Link], COUNT([Link]) AS NombreEmprunts
FROM Membres m
LEFT JOIN Emprunts e ON [Link] = [Link]
GROUP BY [Link];
✔ Étape 6 – Vue des emprunts en retard
Un emprunt est en retard si la date de retour prévue est dépassée et que le livre n'a pas encore été rendu.
CREATE VIEW VueEmpruntsEnRetard AS
SELECT [Link], [Link], [Link], [Link]
FROM Emprunts e
INNER JOIN Livres l ON [Link] = [Link]
INNER JOIN Membres m ON [Link] = [Link]
WHERE [Link] IS NULL
AND [Link] < GETDATE();
✔ Étape 7 – Procédure d'enregistrement d'un emprunt
La procédure reçoit un livre et un membre, et calcule automatiquement une date de retour prévue à 15 jours.
CREATE PROCEDURE [Link]
@LivreId INT,
@MembreId INT
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO Emprunts (LivreId, MembreId, DateEmprunt, DateRetourPrevue)
VALUES (@LivreId, @MembreId, GETDATE(), DATEADD(DAY, 15, GETDATE()));
END;
GO
-- Exemple d'exécution
EXEC [Link] @LivreId = 2, @MembreId = 3;
✔ Étape 8 – Présentation / correction collective
Chaque apprenant présente son script complet (tables, données, requêtes, vue, procédure) et le compare au
corrigé ci-dessus ; l'animateur revient sur les points de bonnes pratiques (nommage, contraintes,
transactions).
-- Script de vérification globale à exécuter en fin de projet
SELECT * FROM VueEmpruntsEnRetard;
EXEC [Link] @LivreId = 1, @MembreId = 3;
SELECT * FROM Emprunts ORDER BY DateEmprunt;
Récapitulatif des compétences acquises
Compétence Modules
Installation et prise en main de SQL Server 1
Création et modification de tables 2, 11
Manipulation de données (CRUD) 3
Requêtes SELECT et filtrage 4, 5
Agrégation et regroupement 6
Jointures 7, 8
Sous-requêtes 9
Fonctions SQL 10
Vues 12
Procédures stockées 13
Transactions 14
Projet complet 15
Conseils pédagogiques
● Prévoir un jeu de données commun (ex. base EntrepriseDB) réutilisé sur toute la formation pour la
cohérence
● Toujours faire pratiquer immédiatement après chaque notion théorique
● Prévoir un support de correction pour chaque exercice
● Encourager les apprenants à écrire leurs requêtes avant de vérifier la correction
● Les corrigés fournis dans ce document peuvent être adaptés : encourager les apprenants à comparer
leur propre syntaxe à celle proposée plutôt qu'à la copier telle quelle