TP 3.01 — Premiers pas avec PostgreSQL
Prendre en main une base PostgreSQL : se connecter avec psql, écrire un script SQL ré-exécutable, insérer des données et répondre à neuf requêtes — corrigées et expliquées une par une.
Objectif du TP : prendre en main une base PostgreSQL, et pratiquer les ordres classiques du DDL (création de la structure) et du DML (insertion et interrogation des données).
Le support de ce TP est court — deux pages. C'est normal : un TP ne s'apprend pas en le lisant, il s'apprend en le tapant. Cette page reprend donc chaque consigne et le raisonnement qui mène à la commande.
1. PostgreSQL, c'est quoi ?
PostgreSQL est un SGBD relationnel : un logiciel qui stocke des données sous forme de tables et qui répond à des requêtes SQL. Il est réputé très robuste — il ne perd pas de données, même en cas d'arrêt brutal — et très complet.
Le support insiste sur un point de vocabulaire, parce que ces trois adjectifs sont constamment confondus.
Les trois liens à connaître
- Le site officiel : postgresql.org
- La documentation en français : docs.postgresqlfr.org
- Le SGBD est déjà installé sur la machine
psqlservde l'IUT — c'est celle que vous utiliserez.
Travailler ailleurs que sur psqlserv
Il est tout à fait possible d'installer PostgreSQL sur sa propre machine, d'utiliser un serveur du réseau local, ou de créer une base « dans le cloud ». Quelques services cités par le support :
| Service | Remarque |
|---|---|
| Clever Cloud | Offre un plan de test gratuit, suffisant pour les exercices. |
| Heroku | Idem — plan gratuit adapté à un usage pédagogique. |
| ElephantSQL | Idem, spécialisé PostgreSQL. |
| Amazon AWS, Google Cloud, Microsoft Azure | Les trois grands fournisseurs de cloud ; les offres gratuites sont plus limitées mais existent. |
2. Se connecter à la base
Sur psqlserv, une instance de PostgreSQL est installée, et une base nommée but2 est à votre
disposition. Un espace y est réservé à chacun, avec un compte à votre login habituel et le mot
de passe moi.
Le client en ligne de commande s'appelle psql. La commande de connexion se décompose en
quatre morceaux, et il vaut la peine de les connaître séparément.
psql -h psqlserv -U prenomnometu but2La syntaxe générale, à retenir sous cette forme, est toujours la même :
psql -h nomMachine -U login nomBase3. Les commandes élémentaires une fois connecté
Une fois la connexion établie, psql affiche une invite qui change de forme : elle devient
but2=>. C'est le signal que le programme attend vos ordres.
Il faut distinguer deux familles de commandes, et ne jamais les mélanger :
- les ordres SQL, qui se terminent par un point-virgule
;et qui sont envoyés au serveur ; - les méta-commandes de
psql, qui commencent par un antislash\et qui concernent l'outil lui-même, pas la base. Elles n'ont pas besoin de point-virgule.
| Commande | Signification |
|---|---|
\? | La liste des commandes psql disponibles |
\h | La liste des ordres SQL |
\h unOrdre | La description et la syntaxe d'un ordre précis |
\q | Quitte psql |
\d | Liste les tables de la base |
\d nomtable | Décrit la structure d'une table |
\i fichier | Exécute les commandes contenues dans le fichier |
Les deux commandes à connaître par cœur
\d pour vérifier ce qui existe, \i pour exécuter un fichier. Entre les deux, on couvre
90 % du travail de ce TP.
4. Exercice 1 — créer les tables du club LaRaquette
La consigne : « Les ordres DDL de création SQL des tables du TD 3.01 dans un fichier, que vous
interpréterez sous psql (\i). Préférez varchar(15) à text pour faciliter les traitements
ultérieurs. Faites en sorte que votre fichier puisse être ré-exécuté plusieurs fois. »
Pourquoi un fichier plutôt que des commandes tapées à la main
Un fichier se relit, se corrige, se rejoue et se partage. Une commande tapée dans un terminal disparaît dès qu'on ferme la fenêtre — et surtout, elle ne se rejoue pas. Sur un projet réel, tous les scripts de création de base vivent dans des fichiers versionnés avec le code.
Pourquoi varchar(15) plutôt que text
text accepte une chaîne de longueur quelconque : le SGBD ne peut rien prévoir. varchar(15)
borne la longueur à 15 caractères, ce qui :
- documente ce qu'on attend dans la colonne (un nom, un prénom, un libellé court) ;
- permet au SGBD de réserver la place de façon plus efficace ;
- fait échouer immédiatement une insertion aberrante, plutôt que de laisser entrer une chaîne de 4 000 caractères qu'il faudra nettoyer plus tard.
Rendre le fichier ré-exécutable
C'est le mot idempotent : une opération idempotente donne le même résultat qu'on l'exécute une fois ou dix fois. Ici, cela s'obtient en plaçant en tête du script une remise à zéro :
DROP TABLE IF EXISTS reservation, creneau, abonne, terrain, sport CASCADE;Sans IF EXISTS, la deuxième exécution s'arrêterait sur une erreur. Sans CASCADE, PostgreSQL
refuserait de détruire terrain tant que creneau la référence.
Le fichier complet
-- Remise à zéro : le script peut être relancé autant de fois qu'on veut.
DROP TABLE IF EXISTS reservation, creneau, abonne, terrain, sport CASCADE;
CREATE TABLE sport (
noSport INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
libelle VARCHAR(15) NOT NULL UNIQUE
);
CREATE TABLE terrain (
noTerrain INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
nom VARCHAR(15) NOT NULL UNIQUE,
noSport INTEGER NOT NULL,
CONSTRAINT fk_terrain_sport FOREIGN KEY (noSport)
REFERENCES sport (noSport)
ON DELETE RESTRICT ON UPDATE CASCADE
);
CREATE TABLE creneau (
noCreneau INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
dateCreneau DATE NOT NULL,
heureDebut INTEGER NOT NULL,
noTerrain INTEGER NOT NULL,
CONSTRAINT ck_creneau_heure CHECK (heureDebut >= 10 AND heureDebut <= 20),
CONSTRAINT uq_creneau UNIQUE (noTerrain, dateCreneau, heureDebut),
CONSTRAINT fk_creneau_terrain FOREIGN KEY (noTerrain)
REFERENCES terrain (noTerrain)
ON DELETE RESTRICT ON UPDATE CASCADE
);
CREATE TABLE abonne (
noAbo INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
nom VARCHAR(15) NOT NULL,
prenom VARCHAR(15) NOT NULL,
dateNaissance DATE NOT NULL,
dateDebutAbo DATE NOT NULL DEFAULT CURRENT_DATE,
dateFinAbo DATE NOT NULL DEFAULT (CURRENT_DATE + INTERVAL '1 year'),
noParrain INTEGER,
CONSTRAINT ck_abonne_dates CHECK (dateFinAbo > dateDebutAbo),
CONSTRAINT fk_abonne_parrain FOREIGN KEY (noParrain)
REFERENCES abonne (noAbo)
ON DELETE RESTRICT ON UPDATE CASCADE
);
CREATE TABLE reservation (
noResa INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
utilise BOOLEAN NOT NULL DEFAULT false,
noCreneau INTEGER NOT NULL,
noAboResp INTEGER NOT NULL,
noAboPart INTEGER NOT NULL,
CONSTRAINT ck_resa_distincts CHECK (noAboResp <> noAboPart),
CONSTRAINT uq_resa_creneau UNIQUE (noCreneau),
CONSTRAINT fk_resa_creneau FOREIGN KEY (noCreneau)
REFERENCES creneau (noCreneau)
ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT fk_resa_resp FOREIGN KEY (noAboResp)
REFERENCES abonne (noAbo)
ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT fk_resa_part FOREIGN KEY (noAboPart)
REFERENCES abonne (noAbo)
ON DELETE RESTRICT ON UPDATE CASCADE
);Vérifier que ça a marché
but2=> \i creer_laraquette.sql
CREATE TABLE
CREATE TABLE
CREATE TABLE
CREATE TABLE
CREATE TABLE
but2=> \d
List of relations
Schema | Name | Type | Owner
--------+-------------+-------+---------
public | abonne | table | yann
public | creneau | table | yann
public | reservation | table | yann
public | sport | table | yann
public | terrain | table | yann
but2=> \d creneau\d liste les tables ; \d nomtable montre les colonnes, les types et les contraintes d'une
table précise. Prendre l'habitude de vérifier après chaque création évite de chercher
longtemps pourquoi une requête échoue alors qu'une colonne n'a jamais été créée.
5. Exercice 2 — la première inscription
La consigne : « Rien n'est encore saisi dans les tables. On souhaite maintenant inscrire Paul Durand, né le 12/07/2002, pour une première réservation avec Jean Leclercq, né le 23/02/2000, sur le terrain 1 de Tennis le 16/09 à 14h. Donnez les ordres nécessaires à cette inscription. »
Le raisonnement
Ce qui est demandé, en clair : créer deux abonnés, puis une réservation qui les relie à un créneau.
Créer les deux abonnés. Paul Durand est le responsable (celui qui réserve), Jean
Leclercq est le partenaire invité. Les deux doivent exister dans la table abonne avant
toute réservation.
Créer le créneau. Une réservation ne contient ni date ni heure : elle renvoie vers un créneau. Il faut donc créer la ligne « terrain 1 de Tennis, le 16/09, à 14h ».
Créer la réservation. Elle référence le créneau et les deux abonnés, par leurs numéros.
Les ordres
INSERT INTO sport (libelle) VALUES ('Tennis'), ('Squash'), ('Badminton');INSERT INTO abonne (nom, prenom, dateNaissance, dateDebutAbo, dateFinAbo)
VALUES
('Durand', 'Paul', DATE '2002-07-12', DATE '2026-09-01', DATE '2027-09-01'),
('Leclercq', 'Jean', DATE '2000-02-23', DATE '2026-09-01', DATE '2027-09-01');INSERT INTO terrain (nom, noSport)
VALUES ('T1', (SELECT noSport FROM sport WHERE libelle = 'Tennis'));
INSERT INTO creneau (dateCreneau, heureDebut, noTerrain)
VALUES (
DATE '2026-09-16',
14,
(SELECT noTerrain FROM terrain WHERE nom = 'T1')
);INSERT INTO reservation (noCreneau, noAboResp, noAboPart)
VALUES (
(SELECT noCreneau FROM creneau
WHERE dateCreneau = DATE '2026-09-16'
AND heureDebut = 14
AND noTerrain = (SELECT noTerrain FROM terrain WHERE nom = 'T1')),
(SELECT noAbo FROM abonne WHERE nom = 'Durand'),
(SELECT noAbo FROM abonne WHERE nom = 'Leclercq')
);Deux pièges dans cet exercice
1. L'ordre compte. Insérer la réservation avant le créneau produit une erreur de clé
étrangère : Key (noCreneau)=(1) is not present in table « creneau ». Ce n'est pas une
malchance, c'est le SGBD qui fait son travail.
2. Ne jamais écrire les numéros en dur. VALUES (1, 2, 3) fonctionne tant que personne
d'autre n'a inséré de ligne avant vous. Avec les sous-requêtes ci-dessus, la commande reste
correcte quel que soit l'état de la base.
6. Exercice 3 — peupler la base
La consigne : « Saisir 6 salles, saisir 5 clients abonnés, saisir 5 créneaux par jour ouvré de la semaine prochaine et quelques réservations. »
« Salles » ou « terrains » ?
L'énoncé parle de « salles », alors que le modèle du club parle de terrains. Les deux désignent la même chose ici : lisez « 6 terrains ».
Les sports et les terrains
INSERT INTO sport (libelle) VALUES ('Tennis'), ('Squash'), ('Badminton');INSERT INTO terrain (nom, noSport) VALUES
('T1', (SELECT noSport FROM sport WHERE libelle = 'Tennis')),
('T2', (SELECT noSport FROM sport WHERE libelle = 'Tennis')),
('S1', (SELECT noSport FROM sport WHERE libelle = 'Squash')),
('S2', (SELECT noSport FROM sport WHERE libelle = 'Squash')),
('B1', (SELECT noSport FROM sport WHERE libelle = 'Badminton')),
('B2', (SELECT noSport FROM sport WHERE libelle = 'Badminton'));Cinq abonnés, dont deux parrainés
INSERT INTO abonne (nom, prenom, dateNaissance, dateDebutAbo, dateFinAbo) VALUES
('Dupont', 'Jean', DATE '1998-04-12', DATE '2026-09-01', DATE '2027-09-01'),
('Durand', 'Paul', DATE '2002-07-12', DATE '2026-09-01', DATE '2027-09-01'),
('Martin', 'Lucie', DATE '1995-11-30', DATE '2026-09-01', DATE '2027-09-01'),
('Bernard', 'Karim', DATE '2001-01-05', DATE '2026-09-01', DATE '2027-09-01'),
('Petit', 'Emma', DATE '1999-08-21', DATE '2026-09-01', DATE '2027-09-01');
-- Lucie et Karim ont été parrainés par Jean (noAbo = 1)
UPDATE abonne SET noParrain = 1 WHERE nom IN ('Martin', 'Bernard');
-- Emma a été parrainée par Paul (noAbo = 2)
UPDATE abonne SET noParrain = 2 WHERE nom = 'Petit';Pourquoi deux UPDATE plutôt qu'un seul INSERT ?
Parce qu'un abonné ne peut pas être son propre parrain au moment où il est créé : la clé
étrangère noParrain doit pointer vers une ligne déjà existante. On insère donc les
personnes, puis on renseigne les parrainages. C'est un cas très fréquent, et une bonne raison
pour que la colonne noParrain accepte NULL.
Cinq créneaux par jour ouvré
Saisir « à la main » cinq créneaux pour chacun des cinq jours ouvrés de la semaine prochaine ferait 25 lignes à taper. C'est exactement le genre de tâche qu'un SGBD fait mieux que nous.
INSERT INTO creneau (dateCreneau, heureDebut, noTerrain)
SELECT d::date, -- la date du jour généré
9 + n, -- 10h, 11h, 12h, 13h, 14h
(SELECT noTerrain FROM terrain WHERE nom = 'T1')
FROM generate_series(1, 5) AS n -- cinq créneaux par jour
CROSS JOIN generate_series(
date_trunc('week', CURRENT_DATE) + INTERVAL '7 days', -- lundi prochain
date_trunc('week', CURRENT_DATE) + INTERVAL '11 days', -- vendredi prochain
INTERVAL '1 day'
) AS d
WHERE EXTRACT(ISODOW FROM d) BETWEEN 1 AND 5; -- 1 = lundi … 5 = vendrediLes fonctions employées méritent d'être présentées :
| Élément | Ce qu'il fait |
|---|---|
generate_series(début, fin, pas) | Fabrique une suite de valeurs — ici des dates, jour par jour. |
date_trunc('week', …) | Ramène une date au lundi de sa semaine. |
EXTRACT(ISODOW FROM d) | Donne le numéro du jour dans la semaine : 1 = lundi, 7 = dimanche. |
CROSS JOIN | Associe chaque ligne de gauche à chaque ligne de droite : 5 heures × 5 jours = 25 lignes. |
d::date | Convertit l'horodatage produit par generate_series en date simple. |
Pour couvrir tous les terrains plutôt qu'un seul, il suffit de remplacer la sous-requête par
un CROSS JOIN terrain t et de sélectionner t.noTerrain.
Quelques réservations
INSERT INTO reservation (noCreneau, noAboResp, noAboPart, utilise) VALUES
( 1, 1, 2, true ),
( 2, 2, 3, true ),
( 3, 3, 4, false ),
( 6, 4, 5, true ),
( 7, 5, 1, false );Le booléen utilise est ce qui permettra de calculer la statistique demandée dans l'énoncé du TD :
combien de créneaux ont été réservés et non utilisés.
7. Les requêtes simples
Voici les neuf questions du TP, dans l'ordre. Pour chacune : ce qui est demandé en français, le raisonnement, puis la requête.
(a) Lister le contenu de la table Abonne
SELECT * FROM abonne;SELECT * veut dire « toutes les colonnes ». C'est pratique pour explorer une table, à éviter
dans une application : si on ajoute une colonne plus tard, le programme reçoit soudain une donnée
qu'il n'attendait pas.
(b) La liste des terrains de Squash
Le mot « Squash » n'est pas dans la table terrain : il est dans la table sport. Il faut donc
joindre les deux.
SELECT t.noTerrain, t.nom
FROM terrain t
JOIN sport s USING (noSport)
WHERE s.libelle = 'Squash'
ORDER BY t.nom;USING (noSport) est un raccourci de ON t.noSport = s.noSport : les deux tables ont une colonne
du même nom, on peut le dire en un mot.
(c) Les abonnés qui n'ont pas de parrain, triés par nom
SELECT noAbo, nom, prenom
FROM abonne
WHERE noParrain IS NULL
ORDER BY nom;`= NULL` ne marche pas
WHERE noParrain = NULL ne renvoie jamais de ligne, même quand la colonne est vide.
NULL n'est pas une valeur : c'est l'absence de valeur. Rien n'est « égal » à rien, pas même
NULL à NULL. Il faut utiliser IS NULL ou IS NOT NULL.
(d) Les réservations en Squash aujourd'hui, avec le nom de l'abonné, du terrain et l'heure
Trois informations viennent de trois tables différentes : l'abonné de abonne, le terrain de
terrain, l'heure de creneau. Il faut donc chaîner les jointures en suivant les clés
étrangères.
SELECT a.nom AS abonne,
t.nom AS terrain,
c.heureDebut
FROM reservation r
JOIN creneau c ON c.noCreneau = r.noCreneau -- la réservation → son créneau
JOIN terrain t ON t.noTerrain = c.noTerrain -- le créneau → son terrain
JOIN sport s ON s.noSport = t.noSport -- le terrain → son sport
JOIN abonne a ON a.noAbo = r.noAboResp -- la réservation → le responsable
WHERE s.libelle = 'Squash'
AND c.dateCreneau = CURRENT_DATE -- « aujourd'hui »
ORDER BY c.heureDebut;Le cheminement est toujours le même : on part de la table qui porte le fait (reservation),
et on remonte les clés étrangères jusqu'à l'information cherchée. CURRENT_DATE est une fonction
qui renvoie la date du jour — elle évite d'écrire la date en dur.
(e) Ajouter le nom du partenaire
Même requête, plus une jointure vers abonne — mais cette fois sur la colonne noAboPart :
SELECT a.nom AS abonne,
p.nom AS partenaire,
t.nom AS terrain,
c.heureDebut
FROM reservation r
JOIN creneau c ON c.noCreneau = r.noCreneau
JOIN terrain t ON t.noTerrain = c.noTerrain
JOIN sport s ON s.noSport = t.noSport
JOIN abonne a ON a.noAbo = r.noAboResp
JOIN abonne p ON p.noAbo = r.noAboPart -- ← la seule ligne qui change
WHERE s.libelle = 'Squash'
AND c.dateCreneau = CURRENT_DATE
ORDER BY c.heureDebut;C'est une auto-jointure : la table abonne apparaît deux fois, sous deux alias différents,
parce qu'elle joue deux rôles distincts.
(f) Les identifiants et noms des clients qui jouent le 10/09
Attention : « les clients qui jouent » désigne aussi bien le responsable que le partenaire. Il faut donc interroger les deux colonnes, et supprimer les doublons.
SELECT DISTINCT a.noAbo, a.nom, a.prenom
FROM abonne a
JOIN reservation r ON r.noAboResp = a.noAbo OR r.noAboPart = a.noAbo
JOIN creneau c ON c.noCreneau = r.noCreneau
WHERE c.dateCreneau = DATE '2026-09-10'
ORDER BY a.nom;SELECT a.noAbo, a.nom, a.prenom
FROM abonne a
JOIN reservation r ON r.noAboResp = a.noAbo
JOIN creneau c ON c.noCreneau = r.noCreneau
WHERE c.dateCreneau = DATE '2026-09-10'
UNION
SELECT a.noAbo, a.nom, a.prenom
FROM abonne a
JOIN reservation r ON r.noAboPart = a.noAbo
JOIN creneau c ON c.noCreneau = r.noCreneau
WHERE c.dateCreneau = DATE '2026-09-10';UNION empile deux résultats et supprime les doublons (contrairement à UNION ALL). Ici, un
abonné qui serait à la fois responsable et partenaire le même jour n'apparaîtrait qu'une fois.
(g) Les identifiants des clients qui ont parrainé quelqu'un
SELECT DISTINCT noParrain AS noAbo
FROM abonne
WHERE noParrain IS NOT NULL
ORDER BY noParrain;DISTINCT est indispensable : si Jean a parrainé trois personnes, il apparaît trois fois dans la
colonne noParrain, mais une seule fois dans la liste des parrains.
Une variante équivalente, en partant de la table des abonnés :
SELECT a.noAbo, a.nom
FROM abonne a
WHERE EXISTS (SELECT 1 FROM abonne f WHERE f.noParrain = a.noAbo);(h) Les clients parrainés, avec le nom de leur parrain, triés par nom de parrain
SELECT a.nom AS filleul, p.nom AS parrain
FROM abonne a
JOIN abonne p ON a.noParrain = p.noAbo
ORDER BY p.nom;Cette fois, la question dit bien « les clients qui ont été parrainés » : les abonnés sans
parrain ne font donc pas partie du résultat. Un JOIN ordinaire (INNER) fait exactement cela.
(i) Tous les clients, avec leur parrain s'il existe
Le « s'il existe » change tout : il faut conserver les abonnés sans parrain.
SELECT a.nom AS filleul, p.nom AS parrain
FROM abonne a
LEFT JOIN abonne p ON a.noParrain = p.noAbo
ORDER BY a.nom;La règle à retenir entre (h) et (i)
« seulement ceux qui ont… » → JOIN. « tous, avec … s'il existe » → LEFT JOIN.
Le second conserve les lignes orphelines en remplissant les colonnes de droite par NULL.
Compter les lignes du résultat est le moyen le plus rapide de vérifier qu'on a bien choisi :
2 lignes contre 4 dans l'exemple ci-dessus.
8. Auto-évaluation
Un ordre SQL se termine par un point-virgule et s'adresse au serveur : il manipule les
données. Une méta-commande commence par un antislash et s'adresse à l'outil psql
lui-même : elle sert à naviguer, afficher, exécuter un fichier. Exemples : SELECT … ; d'un
côté, \d, \i, \q de l'autre.
Parce qu'un script qu'on ne peut lancer qu'une seule fois oblige à repartir d'une base neuve à
chaque correction. En commençant par
DROP TABLE IF EXISTS … CASCADE, on garantit que relancer le script donne toujours le même
résultat : c'est l'idempotence.
\i demande à psql de lire un fichier et d'exécuter toutes les commandes qu'il contient,
l'une après l'autre, comme si on les avait tapées. C'est la façon normale de travailler : on
écrit dans un éditeur, on exécute dans le terminal.
Parce que la valeur insérée doit déjà exister dans la table visée. Si on insère une réservation qui référence le créneau 7 alors qu'aucun créneau 7 n'existe, la donnée serait orpheline : le SGBD la refuse. Cela impose un ordre d'insertion : les parents d'abord, les enfants ensuite.
Parce que NULL signifie « pas de valeur », et non « la valeur zéro » ou « la chaîne vide ».
Une comparaison avec NULL ne renvoie ni vrai ni faux, mais « inconnu » — donc aucune ligne
n'est retenue. Il faut écrire IS NULL.
Quand la question contient « tous » ou « s'il existe ». Un JOIN élimine les lignes sans
correspondance ; un LEFT JOIN les conserve en mettant NULL à la place des colonnes
manquantes. C'est la différence entre « les abonnés parrainés » et « tous les abonnés, avec
leur parrain s'il y en a un ».
Parce qu'un parrain qui a parrainé trois personnes apparaît trois fois dans la colonne
noParrain. Sans DISTINCT, il figurerait trois fois dans la liste des parrains, alors que
la question demande les identifiants des clients qui ont parrainé — chacun une fois.
À deux choses. D'abord à raccourcir l'écriture (a.nom au lieu de abonne.nom). Ensuite,
et c'est plus important, à distinguer deux usages de la même table : dans une auto-jointure,
a et p sont deux copies logiques de abonne, l'une dans le rôle du filleul, l'autre dans
celui du parrain.
tp-postgresql.pdf
TP 3.01 — Rappels BDD : PostgreSQL et le club LaRaquette · 2 pages
Chargement du document…
Dernière mise à jour le
TD 3.02 — LaRaquette, la suite
La suite du club LaRaquette : requêtes évoluées (GROUP BY, EXISTS, classements avec égalités), fonctions de dates, et mises à jour — corrigées et expliquées requête par requête.
TP 3.02 — ODBC et migration Access → PostgreSQL
Lier MS Access à PostgreSQL via ODBC, vérifier que les données circulent dans les deux sens, et migrer une base Access complète vers PostgreSQL sans casser les formulaires — les étapes expliquées.