Files
TermNSI/SQL/TP_Dossier_Dudley.md
T

15 KiB

TP : Dossier Dudley — une enquête épidémiologique

Contexte

Vous êtes analyste de données au Centre de veille sanitaire. On vous confie un dossier concernant Dudley, une petite commune rurale de l'Arkansas organisée autour d'une seule entreprise : l'usine de transformation de volaille Chaco Chicken.

Le signalement est bref :

Plusieurs habitants de Dudley sont décédés récemment d'une maladie neurologique rare, la maladie de Creutzfeldt-Jakob. Le nombre de cas paraît anormalement élevé pour une commune de cette taille. Un inspecteur fédéral de l'agriculture envoyé sur place au mois de mars a disparu.

Vous disposez de six tables : l'état civil, les emplois à l'usine, les diagnostics enregistrés par le cabinet médical, les repas communautaires organisés dans la commune, la liste des participants à ces repas, et les disparitions signalées.

Votre mission : trouver la source de la contamination, uniquement avec des requêtes SQL.

Ce TP s'inspire librement de l'épisode « Our Town » (X-Files, saison 2). Le mécanisme biologique, lui, est réel : les maladies à prions se transmettent par l'alimentation. C'est ce mécanisme qui a provoqué la crise de la « vache folle » en Europe dans les années 1990.


Objectifs

  • Interroger une base de données relationnelle à six tables
  • Maîtriser les jointures, y compris à travers une table d'association
  • Utiliser les fonctions d'agrégation (COUNT, AVG, MIN, MAX) et la clause HAVING
  • Comprendre qu'une requête n'est pas seulement un exercice de syntaxe : c'est un instrument d'enquête

Partie 1 : Mise en place de la base

1.1. Création des tables

Ouvrez DB Browser for SQLite, créez une base dudley.db, puis exécutez :

-- État civil de la commune et des environs
CREATE TABLE HABITANTS (
    id INTEGER PRIMARY KEY,
    nom TEXT NOT NULL,
    prenom TEXT NOT NULL,
    annee_naissance INTEGER,
    annee_deces INTEGER,          -- NULL si la personne est vivante
    commune TEXT
);

-- Emplois occupés à l'usine Chaco Chicken
CREATE TABLE EMPLOIS (
    id INTEGER PRIMARY KEY,
    id_habitant INTEGER,
    poste TEXT,
    annee_embauche INTEGER,
    annee_fin INTEGER,            -- NULL si toujours en poste
    FOREIGN KEY (id_habitant) REFERENCES HABITANTS(id)
);

-- Diagnostics enregistrés par le cabinet médical
CREATE TABLE DIAGNOSTICS (
    id INTEGER PRIMARY KEY,
    id_habitant INTEGER,
    annee INTEGER,
    symptome TEXT,
    diagnostic TEXT,
    FOREIGN KEY (id_habitant) REFERENCES HABITANTS(id)
);

-- Repas communautaires organisés dans la commune
CREATE TABLE REPAS (
    id INTEGER PRIMARY KEY,
    date DATE,
    lieu TEXT,
    occasion TEXT
);

-- Table d'association : qui a participé à quel repas
CREATE TABLE PARTICIPATIONS (
    id INTEGER PRIMARY KEY,
    id_habitant INTEGER,
    id_repas INTEGER,
    FOREIGN KEY (id_habitant) REFERENCES HABITANTS(id),
    FOREIGN KEY (id_repas) REFERENCES REPAS(id)
);

-- Disparitions signalées à la gendarmerie
CREATE TABLE DISPARITIONS (
    id INTEGER PRIMARY KEY,
    id_habitant INTEGER,
    date_disparition DATE,
    statut_enquete TEXT,
    FOREIGN KEY (id_habitant) REFERENCES HABITANTS(id)
);

1.2. Insertion des données

-- ------------------------------------------------------------------
-- HABITANTS
-- ------------------------------------------------------------------
INSERT INTO HABITANTS (id, nom, prenom, annee_naissance, annee_deces, commune) VALUES
-- Dudley : les anciens
(1,  'Chaco',    'Walter',    1902, NULL, 'Dudley'),
(2,  'Riddick',  'Doris',     1891, 1988, 'Dudley'),
(3,  'Bowman',   'Elias',     1888, 1985, 'Dudley'),
(4,  'Kingston', 'Martha',    1893, 1989, 'Dudley'),
(5,  'Prine',    'Howard',    1890, 1987, 'Dudley'),
(6,  'Sheldon',  'Rita',      1895, 1989, 'Dudley'),
(7,  'Farrow',   'Nelson',    1899, 1989, 'Dudley'),
-- Dudley : décès récents
(8,  'Gwynn',    'Paula',     1957, 1995, 'Dudley'),
(9,  'Kittel',   'Ray',       1949, 1995, 'Dudley'),
(10, 'Vance',    'Bonnie',    1962, 1994, 'Dudley'),
(11, 'Hooper',   'Jess',      1954, 1994, 'Dudley'),
(12, 'Traylor',  'Sam',       1960, 1995, 'Dudley'),
(13, 'Doyle',    'Nancy',     1951, 1995, 'Dudley'),
-- Dudley : vivants
(14, 'Ashby',    'Curtis',    1965, NULL, 'Dudley'),
(15, 'Lowell',   'Beatrice',  1970, NULL, 'Dudley'),
(16, 'Pruitt',   'Wendell',   1958, NULL, 'Dudley'),
(17, 'Kearns',   'George',    1948, NULL, 'Dudley'),
-- Millhaven : commune voisine, prise comme référence
(18, 'Sorrel',   'Agnes',     1912, 1988, 'Millhaven'),
(19, 'Buckley',  'Harold',    1918, 1994, 'Millhaven'),
(20, 'Neame',    'Constance', 1921, 1995, 'Millhaven'),
(21, 'Ferrell',  'Douglas',   1908, 1986, 'Millhaven'),
(22, 'Whitlock', 'Irene',     1925, 1993, 'Millhaven'),
(23, 'Amos',     'Leonard',   1915, 1989, 'Millhaven'),
(24, 'Bly',      'Marjorie',  1930, NULL, 'Millhaven'),
-- Dudley : deux dossiers anciens
(25, 'Guthrie',  'Samuel',    1935, NULL, 'Dudley'),
(26, 'Lomax',    'Edna',      1940, NULL, 'Dudley');

-- ------------------------------------------------------------------
-- EMPLOIS (usine Chaco Chicken)
-- ------------------------------------------------------------------
INSERT INTO EMPLOIS (id, id_habitant, poste, annee_embauche, annee_fin) VALUES
(1,  1,  'Directeur',                    1944, NULL),
(2,  2,  'Ouvriere de decoupe',          1932, 1972),
(3,  4,  'Contremaitresse',              1935, 1975),
(4,  5,  'Veterinaire',                  1930, 1970),
(5,  7,  'Ouvrier de decoupe',           1938, 1978),
(6,  8,  'Ouvriere de decoupe',          1978, 1994),
(7,  9,  'Conducteur de ligne',          1971, 1995),
(8,  10, 'Ouvriere de decoupe',          1984, 1994),
(9,  11, 'Agent d''entretien',           1976, 1994),
(10, 12, 'Conducteur de ligne',          1982, 1995),
(11, 13, 'Ouvriere de conditionnement',  1973, 1995),
(12, 14, 'Conducteur de ligne',          1988, NULL),
(13, 15, 'Ouvriere de conditionnement',  1992, NULL),
(14, 16, 'Chef d''equipe',               1980, NULL),
(15, 26, 'Ouvriere de conditionnement',  1962, 1988);

-- ------------------------------------------------------------------
-- DIAGNOSTICS
-- ------------------------------------------------------------------
INSERT INTO DIAGNOSTICS (id, id_habitant, annee, symptome, diagnostic) VALUES
(1,  8,  1993, 'Troubles de la coordination', 'Trouble neurologique non identifie'),
(2,  8,  1994, 'Demence rapide',              'Maladie de Creutzfeldt-Jakob'),
(3,  9,  1994, 'Pertes de memoire',           'Trouble neurologique non identifie'),
(4,  9,  1995, 'Myoclonies',                  'Maladie de Creutzfeldt-Jakob'),
(5,  10, 1993, 'Troubles de la vision',       'Trouble neurologique non identifie'),
(6,  10, 1994, 'Demence rapide',              'Maladie de Creutzfeldt-Jakob'),
(7,  11, 1993, 'Troubles de la coordination', 'Maladie de Creutzfeldt-Jakob'),
(8,  12, 1994, 'Myoclonies',                  'Maladie de Creutzfeldt-Jakob'),
(9,  13, 1994, 'Pertes de memoire',           'Maladie de Creutzfeldt-Jakob'),
(10, 14, 1994, 'Fracture du poignet',         'Traumatisme'),
(11, 15, 1995, 'Angine',                      'Infection ORL'),
(12, 19, 1993, 'Douleurs thoraciques',        'Insuffisance cardiaque'),
(13, 22, 1992, 'Toux persistante',            'Cancer du poumon'),
(14, 2,  1987, 'Fatigue',                     'Vieillissement'),
(15, 16, 1994, 'Lombalgie',                   'Trouble musculo-squelettique');

-- ------------------------------------------------------------------
-- REPAS communautaires
-- ------------------------------------------------------------------
INSERT INTO REPAS (id, date, lieu, occasion) VALUES
(1, '1988-09-17', 'Ferme Chaco',      'Banquet annuel Chaco Chicken'),
(2, '1989-06-24', 'Salle des fetes',  'Fete de la moisson'),
(3, '1990-09-15', 'Ferme Chaco',      'Banquet annuel Chaco Chicken'),
(4, '1992-12-24', 'Eglise de Dudley', 'Reveillon de Noel'),
(5, '1995-03-11', 'Ferme Chaco',      'Banquet annuel Chaco Chicken');

-- ------------------------------------------------------------------
-- PARTICIPATIONS
-- ------------------------------------------------------------------
INSERT INTO PARTICIPATIONS (id, id_habitant, id_repas) VALUES
-- Repas 1 (17 septembre 1988)
(1, 8, 1), (2, 9, 1), (3, 10, 1), (4, 11, 1), (5, 12, 1), (6, 13, 1),
(7, 1, 1), (8, 2, 1), (9, 4, 1), (10, 14, 1), (11, 16, 1),
-- Repas 2 (24 juin 1989)
(12, 8, 2), (13, 9, 2), (14, 10, 2), (15, 11, 2),
(16, 1, 2), (17, 14, 2), (18, 15, 2), (19, 16, 2),
-- Repas 3 (15 septembre 1990)
(20, 8, 3), (21, 9, 3), (22, 12, 3), (23, 13, 3),
(24, 1, 3), (25, 14, 3), (26, 16, 3), (27, 17, 3),
-- Repas 4 (24 decembre 1992)
(28, 9, 4), (29, 10, 4), (30, 11, 4), (31, 12, 4), (32, 13, 4),
(33, 15, 4), (34, 16, 4),
-- Repas 5 (11 mars 1995)
(35, 1, 5), (36, 14, 5), (37, 15, 5), (38, 16, 5), (39, 24, 5);

-- ------------------------------------------------------------------
-- DISPARITIONS
-- ------------------------------------------------------------------
INSERT INTO DISPARITIONS (id, id_habitant, date_disparition, statut_enquete) VALUES
(1, 25, '1961-07-22', 'Classee sans suite'),
(2, 26, '1988-09-14', 'Classee sans suite'),
(3, 17, '1995-03-08', 'En cours');

Remarque. Les accents ont été retirés des données pour éviter tout problème d'encodage selon les postes. Les apostrophes des textes SQL sont doublées (d''entretien) : c'est la façon d'écrire une apostrophe à l'intérieur d'une chaîne.

1.3. Schéma relationnel

Dessinez le schéma de la base en identifiant les clés primaires (PK) et les clés étrangères (FK).

Question de mise en route : la table PARTICIPATIONS ne contient aucune information « propre » — que des clés étrangères. À quoi sert-elle ? Pourquoi ne peut-on pas simplement ajouter une colonne id_repas dans HABITANTS ?


Partie 2 : Prise en main (requêtes simples)

Pour chaque question, écrivez la requête et notez le résultat obtenu.

1. Affichez le nom, le prénom et l'année de naissance de tous les habitants de Dudley, triés du plus âgé au plus jeune.

2. Affichez le nom et le prénom des habitants encore en vie. (Indice : une valeur absente se teste avec IS NULL, pas avec = NULL.)

3. Affichez la liste des postes différents occupés à l'usine Chaco Chicken, sans doublon.

4. Affichez la date et l'occasion des repas qui se sont tenus à la Ferme Chaco.


Partie 3 : Croiser les tables (jointures)

5. Affichez le nom, le prénom et le poste de chaque personne ayant travaillé à l'usine.

6. Affichez le nom, le prénom et l'année du diagnostic de tous les habitants diagnostiqués 'Maladie de Creutzfeldt-Jakob'.

7. Affichez le nom et le prénom de toutes les personnes ayant participé au repas du 17 septembre 1988. (Indice : il faut passer par PARTICIPATIONS, donc joindre trois tables.)

8. Affichez le nom, le prénom, la date et le statut de l'enquête pour chaque disparition signalée.

9. Affichez le nom et le prénom des habitants de Dudley qui n'ont jamais travaillé à l'usine. (Indice : NOT IN (SELECT ...) ou une jointure externe.)


Partie 4 : Compter et mesurer (agrégations)

10. Combien y a-t-il d'habitants recensés dans chaque commune ?

11. Pour chaque repas, affichez sa date et son nombre de participants.

12. Pour chaque commune, calculez l'âge moyen au décès des personnes décédées. (Indice : l'âge au décès se calcule à partir de deux colonnes.)

13. Combien de fois chaque diagnostic apparaît-il dans la base ? Classez du plus fréquent au moins fréquent.

14. Quels habitants ont participé à au moins 3 repas ? Affichez leur nom et leur nombre de participations.


Partie 5 : Une anomalie

Les résultats de la question 12 méritent qu'on s'y arrête. Reprenons-les en séparant les époques.

15. Pour chaque commune, calculez l'âge moyen au décès des personnes décédées avant 1990. Que constatez-vous pour Dudley ?

16. Même question pour les personnes décédées à partir de 1990. Comparez les deux résultats commune par commune.

Rédigez deux ou trois phrases : que s'est-il passé à Dudley entre ces deux périodes ? Le même phénomène s'observe-t-il à Millhaven ?

17. Affichez le nom, le prénom et l'âge au décès de chaque personne diagnostiquée 'Maladie de Creutzfeldt-Jakob'. Combien sont-elles ? Toutes appartiennent-elles à la même commune ?


Partie 6 : La source

Vous avez maintenant un groupe de malades identifié. En épidémiologie, la question suivante est toujours la même : qu'ont-ils en commun que les autres n'ont pas ?

18. Trouvez le repas auquel ont participé toutes les personnes diagnostiquées 'Maladie de Creutzfeldt-Jakob', et uniquement celui-là.

Indice : comptez, pour chaque repas, le nombre de malades présents, puis ne gardez que les repas où ce nombre est égal au nombre total de malades. La clause qui filtre après un regroupement s'appelle HAVING.

19. Affichez chaque disparition signalée, accompagnée du repas qui a eu lieu dans les 7 jours suivants, s'il y en a un.

Indice : en SQLite, date(d.date_disparition, '+7 days') renvoie la date décalée de sept jours. Vous pouvez alors comparer directement les dates entre elles.

20. Le dernier banquet a eu lieu le 11 mars 1995. Affichez le nom et le prénom de ses participants, et indiquez pour chacun s'il figure ou non dans la table DIAGNOSTICS avec le diagnostic 'Maladie de Creutzfeldt-Jakob'.

Rédigez la conclusion de votre rapport (5 à 10 lignes). Quelle est la source probable de la contamination ? Quel lien faites-vous entre les dates des banquets et celles des disparitions ? Et surtout : au vu de la question 20, l'affaire est-elle close ?


Questions de réflexion

1. Pourquoi avoir séparé HABITANTS et EMPLOIS dans deux tables, plutôt que d'ajouter une colonne poste dans HABITANTS ?

2. À la question 18, on compte les malades présents à chaque repas. Un même habitant peut apparaître plusieurs fois dans DIAGNOSTICS. En quoi cela pourrait-il fausser le comptage, et comment s'en prémunir ?

3. L'incubation d'une maladie à prion dure souvent plus de dix ans entre la contamination et les premiers symptômes. En quoi ce délai rend-il ce type d'enquête difficile ? Quelles données faudrait-il conserver, et pendant combien de temps ?

4. La base ne contient que les repas déclarés et les disparitions signalées. Quelles conclusions cela vous interdit-il de tirer ? Formulez une phrase précisant ce que vos requêtes démontrent réellement, et ce qu'elles ne font que suggérer.


Barème indicatif

Partie Points
Partie 1 : schéma et question de mise en route 2
Partie 2 : requêtes simples 3
Partie 3 : jointures 4
Partie 4 : agrégations 4
Partie 5 : anomalie + rédaction 3
Partie 6 : source + conclusion 3
Questions de réflexion 1
Total 20

Auteur : Florian Mathieu

Licence CC BY NC

Licence Creative Commons
Ce cours est mis à disposition selon les termes de la Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International.