Les SGBD relationnels possèdent un mécanisme, clés primaires et clés étrangères, permettant d'assurer l'intégrité des associations qui existent entre 2 tables.
Une étape importante de conception d'une base de données relationnelle organisée est la normalisation
Une seule table Recipes
name | desc | ingr | steps | prep_time | category | cook_name | cook_email | cook_join_date |
a | ... | ... | ... | ... | ... | james | j@mail.ca | YYYY-MM-DD |
b | ... | ... | ... | ... | ... | james | j@mail.ca | YYYY-MM-DD |
c | ... | ... | ... | ... | ... | mathieu | m@mail.ca | YYYY-MM-DD |
d | ... | ... | ... | ... | ... | james | j@mail.ca | YYYY-MM-DD |
Devient
Cooks
id | name | join_date | |
1 | james | j@mail.ca | YYYY-MM-DD |
2 | mathieu | m@mail.ca | YYYY-MM-DD |
Recipes
name | desc | ingr | steps | prep_time | category | cook_id |
a | ... | ... | ... | ... | ... | 1 |
b | ... | ... | ... | ... | ... | 1 |
c | ... | ... | ... | ... | ... | 2 |
d | ... | ... | ... | ... | ... | 1 |
Mécanisme permettant d'assurer l'intégrité relationnelle
PRIMARY KEY
par tableFOREIGN KEYS
Identifie de façon UNIQUE et NOT NULL chaque enregistrement par la valeur d'une ou plusieurs colonnes
CREATE OR REPLACE TABLE teachers (
-- Raccourci directement à la création de la colonne
-- PRIMARY est optionnel, mais on précise pour être explicite
employee_number INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(200) NOT NULL
);
-- DESCRIBE teachers; pour constater le NOT NULL ajouté automatiquement
CREATE OR REPLACE TABLE courses (
code CHAR(10) NOT NULL,
name VARCHAR(200) NOT NULL,
teacher_employee_number INT UNSIGNED,
semester CHAR(5) NOT NULL,
-- Définition dédiée, permet de nommer PLUSIEURS colonnes: CLÉ COMPOSITE
PRIMARY KEY (code, teacher_employee_number, semester)
);
Force un lien vers un enregistrement identifié par une PRIMARY KEY
CREATE OR REPLACE TABLE courses (
code CHAR(10) NOT NULL,
name VARCHAR(200) NOT NULL,
semester CHAR(5) NOT NULL,
teacher_employee_number INT UNSIGNED, -- Interet de mettre NOT NULL ???
-- Le type des 2 colonnes doit correspondre
FOREIGN KEY (teacher_employee_number) REFERENCES teachers (employee_number)
);
CREATE OR REPLACE TABLE grades (
student_da VARCHAR(7),
grade SMALLINT UNSIGNED NOT NULL,
course_code CHAR(10) NOT NULL,
course_teacher INT UNSIGNED NOT NULL,
course_semester CHAR(5) NOT NULL,
PRIMARY KEY (student_da, course_code, course_teacher, course_semester),
FOREIGN KEY (course_code, course_teacher, course_semester)
REFERENCES courses (code, teacher_employee_number, semester)
-- , FOREIGN KEY (student_da) REFERENCES students (da) -- On peut définir plusieurs FK
);
On peut préciser le comportement à implémenter lors de la modification de la PRIMARY KEY
ou suppression d'un enregistrement parent
FOREIGN KEY (col[, ...])
REFERENCES table_name (col[, ...])
[ON DELETE { RESTRICT | CASCADE | SET NULL }]
[ON UPDATE { RESTRICT | CASCADE | SET NULL }]
CASCADE
L'action est propagée. Les enfants sont supprimés, la mise à jour de PRIMARY KEY
est répercutéeSET NULL
La valeur NULL est assignée à la FOREIGN KEY
Permet de récupérer des données de plusieurs tables en exploitant les relations établies entres elles
SELECT select_expr [, select_expr ...]
FROM table_ref [, table_ref ...]
table_ref:
table_factor
| CROSS JOIN
| INNER JOIN table_factor ON conditional_expr
| { LEFT | RIGHT } JOIN table_factor ON conditional_expr
-- Reference: https://fr.wikipedia.org/wiki/Alphabet_grec
DROP DATABASE IF EXISTS alphabet;
CREATE DATABASE alphabet;
-- Different de CREATE OR REPLACE DATABASE qui est en ordre alphabetique
-- Sinon DROP TABLES IF EXISTS alphabet.greek, alphabet.english;
CREATE TABLE alphabet.english (
id INT UNSIGNED PRIMARY KEY,
letter VARCHAR(1)
);
CREATE TABLE alphabet.greek (
id INT UNSIGNED PRIMARY KEY,
name VARCHAR(10),
english_id INT UNSIGNED,
FOREIGN KEY (english_id) REFERENCES english(id) ON DELETE CASCADE
);
INSERT INTO alphabet.english VALUES (1, 'a'), (2, 'b'), (3, 'c'), (4, 'd'), (5, 'e'), (6, 'f'), (7, 'g'), (8, 'h'), (9, 'i'), (10, 'j'), (11, 'k'), (12, 'l'), (13, 'm'), (14, 'n'), (15, 'o'), (16, 'p'), (17, 'q'), (18, 'r'), (19, 's'), (20, 't'), (21, 'u'), (22, 'v'), (23, 'w'), (24, 'x'), (25, 'y'), (26, 'z');
INSERT INTO alphabet.greek VALUES (1, 'alpha', 1), (2, 'beta', 2), (3, 'gamma', 7), (4, 'delta', 4), (5, 'epsilon', 5), (6, 'zeta', 26), (7, 'eta', NULL), (8, ' theta', NULL), (9, 'iota', 9), (10, 'kappa', 11), (11, 'lambda', 12), (12, 'mu', 13), (13, 'nu', 14), (14, 'xi', 24), (15, 'omnicron', 15), (16, 'pi', 16), (17, 'rho', 18), (18, 'sigma', 19), (19, 'tau', 20), (20, 'upsilon', 21), (21, 'phi', NULL), (22, 'chi', NULL), (23, 'psi', NULL), (24, 'omega', NULL);
english +----+------+
| id | name |
+----+------+
| 1 | a |
| 2 | b |
| 3 | c |
...
+----+------+
greek +-----+-------------+------------+
| id | name | english_id |
+-----+-------------+------------+
| 1 | alpha | 1 |
| 2 | beta | 2 |
...
| 24 | omega | NULL |
+-----+-------------+------------+
SELECT * FROM alphabet.english
CROSS JOIN alphabet.greek;
+----+--------+----+----------+------------+
| id | letter | id | name | english_id |
+----+--------+----+----------+------------+
| 1 | a | 1 | alpha | 1 |
| 1 | a | 2 | beta | 2 |
| 1 | a | 3 | gamma | 7 |
| 1 | a | 4 | delta | 4 |
...
| 26 | z | 19 | tau | 20 |
| 26 | z | 20 | upsilon | 21 |
| 26 | z | 21 | phi | NULL |
| 26 | z | 22 | chi | NULL |
| 26 | z | 23 | psi | NULL |
| 26 | z | 24 | omega | NULL |
+----+--------+----+----------+------------+
SELECT *
FROM alphabet.english
INNER JOIN alphabet.greek ON alphabet.english.id = alphabet.greek.english_id
ORDER BY letter;
+----+--------+----+----------+------------+
| id | letter | id | name | english_id |
+----+--------+----+----------+------------+
| 1 | a | 1 | alpha | 1 |
| 2 | b | 2 | beta | 2 |
'c' est absent, pas de correspondance grec
| 4 | d | 4 | delta | 4 |
| 5 | e | 5 | epsilon | 5 |
'f' est absent, pas de correspondance grec
| 7 | g | 3 | gamma | 7 |
...
| 26 | z | 6 | zeta | 26 |
+----+--------+----+----------+------------+
SELECT *
FROM alphabet.english
LEFT JOIN alphabet.greek ON alphabet.english.id = alphabet.greek.english_id;
+----+--------+------+----------+------------+
| id | letter | id | name | english_id |
+----+--------+------+----------+------------+
| 1 | a | 1 | alpha | 1 |
| 2 | b | 2 | beta | 2 |
| 3 | c | NULL | NULL | NULL |
| 4 | d | 4 | delta | 4 |
| 5 | e | 5 | epsilon | 5 |
| 6 | f | NULL | NULL | NULL |
| 7 | g | 3 | gamma | 7 |
...
| 24 | x | 14 | xi | 24 |
| 25 | y | NULL | NULL | NULL |
| 26 | z | 6 | zeta | 26 |
+----+--------+------+----------+------------+
SELECT *
FROM alphabet.english
LEFT JOIN alphabet.greek ON alphabet.english.id = alphabet.greek.english_id
WHERE alphabet.greek.id IS NULL; -- <== EXCLUSIF à english
+----+--------+------+------+------------+
| id | letter | id | name | english_id |
+----+--------+------+------+------------+
| 3 | c | NULL | NULL | NULL |
| 6 | f | NULL | NULL | NULL |
| 8 | h | NULL | NULL | NULL |
| 10 | j | NULL | NULL | NULL |
| 17 | q | NULL | NULL | NULL |
| 22 | v | NULL | NULL | NULL |
| 23 | w | NULL | NULL | NULL |
| 25 | y | NULL | NULL | NULL |
+----+--------+------+------+------------+
SELECT *
FROM alphabet.english
RIGHT JOIN alphabet.greek
ON alphabet.english.id = alphabet.greek.english_id
ORDER BY alphabet.greek.id;
+------+--------+----+----------+------------+
| id | letter | id | name | english_id |
+------+--------+----+----------+------------+
| 1 | a | 1 | alpha | 1 |
| 2 | b | 2 | beta | 2 |
...
| NULL | NULL | 23 | psi | NULL |
| NULL | NULL | 24 | omega | NULL |
+------+--------+----+----------+------------+
-- EXCLUSIF à grec
SELECT *
FROM alphabet.english
RIGHT JOIN alphabet.greek
ON alphabet.english.id = alphabet.greek.english_id
WHERE alphabet.english.id IS NULL;
+------+--------+----+--------+------------+
| id | letter | id | name | english_id |
+------+--------+----+--------+------------+
| NULL | NULL | 7 | eta | NULL |
| NULL | NULL | 8 | theta | NULL |
| NULL | NULL | 21 | phi | NULL |
| NULL | NULL | 22 | chi | NULL |
| NULL | NULL | 23 | psi | NULL |
| NULL | NULL | 24 | omega | NULL |
+------+--------+----+--------+------------+
-- CREATE OR REPLACE ok car courses < teachers
CREATE OR REPLACE DATABASE school;
USE school;
CREATE TABLE teachers (
employee_number INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(200) NOT NULL
);
CREATE TABLE courses (
code CHAR(10) NOT NULL,
teacher_employee_number INT UNSIGNED NOT NULL,
semester CHAR(5) NOT NULL,
PRIMARY KEY (code, teacher_employee_number, semester),
FOREIGN KEY (teacher_employee_number) REFERENCES teachers (employee_number)
);
INSERT INTO teachers (employee_number, name)
VALUES (1, 'James'), (2, 'Mathieu'), (3, 'Stevens'), (4, 'Marco'), (5, 'Lyne'), (6, 'Nicolas');
INSERT INTO courses
VALUES ('420-0Q4-SW', 1, 'A2021'), ('420-0Q4-SW', 1, 'A2020'), ('420-0Q4-SW', 1, 'A2019'), ('420-0Q7-SW', 1, 'A2021'), ('420-2SS-SW', 1, 'A2021'), ('420-2SS-SW', 1, 'A2020'), ('420-0Q7-SW', 2, 'A2021'), ('420-2SS-SW', 2, 'A2020'), ('420-0Q4-SW', 2, 'A2019'), ('420-0SU-SW', 2, 'A2021'), ('420-2SU-SW', 2, 'A2021'), ('420-1SY-SW', 2, 'A2021'), ('420-0Q4-SW', 3, 'A2021'), ('420-0SV-SW', 3, 'A2021'), ('420-1SX-SW', 3, 'A2021'), ('420-2SS-SW', 3, 'A2020'), ('420-0Q4-SW', 4, 'A2018'), ('420-0Q4-SW', 4, 'A2017');
SELECT *
FROM school.teachers
CROSS JOIN school.courses
ORDER BY name, code, semester;
+-----------------+---------+------------+-------------------------+----------+
| employee_number | name | code | teacher_employee_number | semester |
+-----------------+---------+------------+-------------------------+----------+
| 1 | James | 420-0Q4-SW | 4 | A2017 |
| 1 | James | 420-0Q4-SW | 4 | A2018 |
| 1 | James | 420-0Q4-SW | 1 | A2019 |
| 1 | James | 420-0Q4-SW | 2 | A2019 |
| 1 | James | 420-0Q4-SW | 1 | A2020 |
| 1 | James | 420-0Q4-SW | 3 | A2021 |
| 1 | James | 420-0Q4-SW | 1 | A2021 |
| 1 | James | 420-0Q7-SW | 2 | A2021 |
| 1 | James | 420-0Q7-SW | 1 | A2021 |
...
| 3 | Stevens | 420-2SS-SW | 1 | A2021 |
| 3 | Stevens | 420-2SU-SW | 2 | A2021 |
+-----------------+---------+------------+-------------------------+----------+
SELECT name, code
FROM school.teachers
CROSS JOIN (SELECT DISTINCT code FROM school.courses) AS codes
ORDER BY name;
+---------+------------+
| name | code |
+---------+------------+
| James | 420-2SU-SW |
| James | 420-2SS-SW |
| James | 420-1SY-SW |
| James | 420-1SX-SW |
| James | 420-0SV-SW |
| James | 420-0SU-SW |
| James | 420-0Q7-SW |
| James | 420-0Q4-SW |
...
| Stevens | 420-0Q4-SW |
| Stevens | 420-2SU-SW |
| Stevens | 420-2SS-SW |
| Stevens | 420-1SY-SW |
+---------+------------+
SELECT *
FROM school.teachers
INNER JOIN school.courses ON employee_number = teacher_employee_number
ORDER BY name, code, semester;
+-----------------+---------+------------+-------------------------+----------+
| employee_number | name | code | teacher_employee_number | semester |
+-----------------+---------+------------+-------------------------+----------+
| 1 | James | 420-0Q4-SW | 1 | A2019 |
| 1 | James | 420-0Q4-SW | 1 | A2020 |
| 1 | James | 420-0Q4-SW | 1 | A2021 |
| 1 | James | 420-0Q7-SW | 1 | A2021 |
| 1 | James | 420-2SS-SW | 1 | A2020 |
| 1 | James | 420-2SS-SW | 1 | A2021 |
| 4 | Marco | 420-0Q4-SW | 4 | A2017 |
| 4 | Marco | 420-0Q4-SW | 4 | A2018 |
...
| 3 | Stevens | 420-0Q4-SW | 3 | A2021 |
| 3 | Stevens | 420-0SV-SW | 3 | A2021 |
| 3 | Stevens | 420-1SX-SW | 3 | A2021 |
| 3 | Stevens | 420-2SS-SW | 3 | A2020 |
+-----------------+---------+------------+-------------------------+----------+
SELECT
employee_number,
name,
COUNT(DISTINCT code) AS `Nb cours`,
COUNT(semester) AS `Nb sessions`
FROM school.teachers
INNER JOIN school.courses ON teachers.employee_number = courses.teacher_employee_number
GROUP BY name;
+-----------------+---------+----------+-------------+
| employee_number | name | Nb cours | Nb sessions |
+-----------------+---------+----------+-------------+
| 1 | James | 3 | 6 |
| 4 | Marco | 1 | 2 |
| 2 | Mathieu | 6 | 6 |
| 3 | Stevens | 4 | 4 |
+-----------------+---------+----------+-------------+
SELECT
employee_number,
name,
code,
COUNT(DISTINCT code) AS `Nb cours (inutile)`,
COUNT(semester) AS `Nb sessions`
FROM school.teachers
INNER JOIN school.courses ON employee_number = teacher_employee_number
GROUP BY name, code; -- 2 niveaux name ET code
+-----------------+---------+------------+--------------------+-------------+
| employee_number | name | code | Nb cours (inutile) | Nb sessions |
+-----------------+---------+------------+--------------------+-------------+
| 1 | James | 420-0Q4-SW | 1 | 3 |
| 1 | James | 420-0Q7-SW | 1 | 1 |
| 1 | James | 420-2SS-SW | 1 | 2 |
| 4 | Marco | 420-0Q4-SW | 1 | 2 |
| 2 | Mathieu | 420-0Q4-SW | 1 | 1 |
...
+-----------------+---------+------------+--------------------+-------------+
SELECT *
FROM school.teachers
LEFT JOIN school.courses ON employee_number = teacher_employee_number;
+-----------------+---------+------------+-------------------------+----------+
| employee_number | name | code | teacher_employee_number | semester |
+-----------------+---------+------------+-------------------------+----------+
| 1 | James | 420-0Q4-SW | 1 | A2019 |
| 1 | James | 420-0Q4-SW | 1 | A2020 |
| 1 | James | 420-0Q4-SW | 1 | A2021 |
| 1 | James | 420-0Q7-SW | 1 | A2021 |
...
| 4 | Marco | 420-0Q4-SW | 4 | A2017 |
| 4 | Marco | 420-0Q4-SW | 4 | A2018 |
| 5 | Lyne | NULL | NULL | NULL |
| 6 | Nicolas | NULL | NULL | NULL |
+-----------------+---------+------------+-------------------------+----------+
SELECT *
FROM school.teachers
LEFT JOIN school.courses ON employee_number = teacher_employee_number
WHERE teacher_employee_number IS NULL;
+-----------------+---------+------+-------------------------+----------+
| employee_number | name | code | teacher_employee_number | semester |
+-----------------+---------+------+-------------------------+----------+
| 5 | Lyne | NULL | NULL | NULL |
| 6 | Nicolas | NULL | NULL | NULL |
+-----------------+---------+------+-------------------------+----------+
SELECT employee_number, name, code, COUNT(semester) AS `Nb sessions`
FROM school.teachers
LEFT JOIN school.courses ON teachers.employee_number = courses.teacher_employee_number
GROUP BY name, code
ORDER BY employee_number;
+-----------------+---------+------------+-------------+
| employee_number | name | code | Nb sessions |
+-----------------+---------+------------+-------------+
| 1 | James | 420-0Q4-SW | 3 |
| 1 | James | 420-0Q7-SW | 1 |
| 1 | James | 420-2SS-SW | 2 |
| 2 | Mathieu | 420-2SU-SW | 1 |
| 2 | Mathieu | 420-0Q4-SW | 1 |
...
| 3 | Stevens | 420-0SV-SW | 1 |
| 4 | Marco | 420-0Q4-SW | 2 |
| 5 | Lyne | NULL | 0 |
| 6 | Nicolas | NULL | 0 |
+-----------------+---------+------------+-------------+