6.2 - Exercices


Analyse

Analyser la base de données ci-dessous pour identifier les tables, relations et multiplicités.

CREATE DATABASE IF NOT EXISTS tweeties;

DROP TABLE IF EXISTS likes, tweeties, users;

-- 
-- USERS
-- 

CREATE TABLE users (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) NOT NULL UNIQUE,
    pwd VARCHAR(64) NOT NULL
);

INSERT INTO 
    users(username, pwd) 
VALUES  
    ('alice', 'pwda'), 
    ('bob', 'pwdb'), 
    ('charlie', 'pwdc');


--
-- TWEETIES
--

CREATE TABLE tweeties(
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    text VARCHAR(240) NOT NULL,
    created_at DATETIME NOT NULL DEFAULT NOW(),
    updated_at DATETIME,
    user_id INT UNSIGNED NOT NULL,

    FOREIGN KEY (user_id) REFERENCES users(id) 
    ON DELETE CASCADE
);

INSERT INTO tweeties (text, created_at, user_id)
VALUES  
    ('My first tweetie', DATE_ADD(NOW(), INTERVAL -36 HOUR), 1),
    ("Another tweetie", DEFAULT, 1),
    ("Tweetie, here I am!!!1!11!!", DATE_ADD(NOW(), INTERVAL -49 HOUR), 2);

--
-- LIKES
--

CREATE TABLE likes (
    user_id INT UNSIGNED NOT NULL,
    tweety_id INT UNSIGNED NOT NULL,

    PRIMARY KEY (user_id, tweety_id),

    FOREIGN KEY (user_id) REFERENCES users(id)
    ON DELETE CASCADE,

    FOREIGN KEY (tweety_id) REFERENCES tweeties(id)
    ON DELETE CASCADE
);

INSERT INTO 
    likes
VALUES 
    (1, 1), 
    (2, 1), 
    (2, 2);

Diagramme

SOLUTION

              1     1
      +--------users------+
      |                   |
     *|                   |*
    tweets--------------likes
          1            *
    

Modifier la base de données pour y intégrer les éléments ci-dessous.

Implémentation

CHECK

users

tweeties

  • text ne doit pas être vide
  • updated_at > created_at

Déclencheur

users

  • pwd respectant les conditions suivantes lors de l'insertion
    • Au moins 1 chiffre '\\d+'
    • Au moins 1 lettre minuscule '[a-z]+'
    • Au moins 1 lettre majuscule '[A-Z]+'
    • Exploiter la comparaison binaire pour respecter la case
    • Minimum 8 charactères
    • Il est plus facile de valider plusieurs conditions que de créer une seule expression régulière complexe
    • pwd devra également être hashé dans le déclencheur si le format est valide
    • SHA2(value, 256)

tweeties

  • updated_at
    • Initialisée à NULL à la création
    • Assigné à NOW() lors de la mise à jour

Vue

  • Encapsuler l'affichage des tweeties pour faciliter la réutilisation
  • La requête récupère tous les tweeties, le nom d'utilisateur de l'auteur et calcule
    • le nombre de like,
    • la dernière édition, soit la création ou la mise à jour si non nulle
  • trié du plus récent au plus ancien
+----+-----------------------------+---------+----------------+---------------------+-------------+
| id | text                        | user_id | username       | last_edit           | likes_count |
+----+-----------------------------+---------+----------------+---------------------+-------------+
|  1 | MY FIRST TWEETIE            |       1 | alice@mail.com | 2023-11-17 13:29:53 |           2 |
|  2 | Another tweetie             |       1 | alice@mail.com | 2023-11-17 13:29:48 |           1 |
|  3 | Tweetie, here I am!!!1!11!! |       2 | bob@mail.com   | 2023-11-15 12:29:53 |           0 |
+----+-----------------------------+---------+----------------+---------------------+-------------+

Solution

Voir 👀
CREATE DATABASE IF NOT EXISTS tweeties;

USE tweeties;

DROP TABLE IF EXISTS likes, tweeties, users;

-- 
-- USERS
-- 

CREATE TABLE users (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) NOT NULL UNIQUE CHECK(username REGEXP '^[^@]+@[^@]+\\.[^@]+$'), -- CHECK
    pwd VARCHAR(64) NOT NULL
);

--
-- TRIGGER
--

-- Pour pouvoir faire des ; dans le trigger
DELIMITER &&

    -- Comment éviter de répéter le même code dans 2 triggers?
    -- https://mariadb.com/kb/en/stored-procedures/
    --
    --
    --
    -- CREATE OR REPLACE PROCEDURE validate (in pwd VARCHAR(100))
    -- BEGIN
    --     IF( pwd NOT REGEXP '\\d+' ) THEN
    --         SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Password must contain at least 1 number.';
    --     END IF;
    -- 
    --     IF( BINARY pwd NOT REGEXP '[a-z]+' ) THEN
    --         SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Password must contain at least 1 lower case letter.';
    --     END IF;
    -- 
    --     IF( BINARY pwd NOT REGEXP '[A-Z]+' ) THEN
    --         SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Password must contain at least 1 upper case letter.';
    --     END IF;
    -- 
    --     IF( CHAR_LENGTH(pwd) < 8) THEN
    --         SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Password must contain at least 8 characters long.';
    --     END IF;
    -- END&&
    --
    --
    --
    -- Puis dans le trigger
    --
    -- call validate(new.pwd);

    CREATE OR REPLACE TRIGGER users_insert_hash_password BEFORE INSERT ON users FOR EACH ROW 
    BEGIN

        IF( new.pwd NOT REGEXP '\\d+' ) THEN
            SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Password must contain at least 1 number.';
        END IF;

        IF( BINARY new.pwd NOT REGEXP '[a-z]+' ) THEN
            SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Password must contain at least 1 lower case letter.';
        END IF;

        IF( BINARY new.pwd NOT REGEXP '[A-Z]+' ) THEN
            SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Password must contain at least 1 upper case letter.';
        END IF;

        IF( CHAR_LENGTH(new.pwd) < 8) THEN
            SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Password must contain at least 8 characters long.';
        END IF;

        SET new.pwd = SHA2(new.pwd, 256);

    END&&

    CREATE OR REPLACE TRIGGER users_update_hash_password BEFORE UPDATE ON users FOR EACH ROW 
    BEGIN

        IF( new.pwd NOT REGEXP '\\d+' ) THEN
            SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Password must contain at least 1 number.';
        END IF;

        IF( BINARY new.pwd NOT REGEXP '[a-z]+' ) THEN
            SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Password must contain at least 1 lower case letter.';
        END IF;

        IF( BINARY new.pwd NOT REGEXP '[A-Z]+' ) THEN
            SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Password must contain at least 1 upper case letter.';
        END IF;

        IF( CHAR_LENGTH(new.pwd) < 8) THEN
            SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Password must contain at least 8 characters long.';
        END IF;

        SET new.pwd = SHA2(new.pwd, 256);
    END&&

DELIMITER ;



INSERT INTO 
    users(username, pwd) 
VALUES  
    ('alice@mail.com', 'pwdpwdA1'), 
    ('bob@mail.com', 'pwdpwdB2'), 
    ('charlie@mail.com', 'pwdpwdC3');


--
-- TWEETIES
--

CREATE TABLE tweeties(
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    text VARCHAR(240) NOT NULL CHECK(trim(text) != ''), -- CHECK
    created_at DATETIME NOT NULL DEFAULT NOW(),
    updated_at DATETIME,
    user_id INT UNSIGNED NOT NULL,

    -- CHECK
    CONSTRAINT created_less_than_updated CHECK(ifnull(created_at < updated_at, true)),

    FOREIGN KEY (user_id) REFERENCES users(id) 
    ON DELETE CASCADE
);

DELIMITER &&

CREATE OR REPLACE TRIGGER tweeties_create_created_at BEFORE INSERT ON tweeties FOR EACH ROW 
BEGIN
    SET new.created_at = NOW();
END&&

CREATE OR REPLACE TRIGGER tweeties_update_updated_at BEFORE UPDATE ON tweeties FOR EACH ROW 
BEGIN
    SET new.updated_at = NOW();
END&&

DELIMITER ;

INSERT INTO tweeties (text, created_at, user_id)
VALUES  
    ('My first tweetie', DATE_ADD(NOW(), INTERVAL -36 HOUR), 1),
    ("Another tweetie", DATE_ADD(NOW(), INTERVAL -5 SECOND), 1),
    ("Tweetie, here I am!!!1!11!!", DATE_ADD(NOW(), INTERVAL -49 HOUR), 2);

--
-- LIKES
--

CREATE TABLE likes (
    user_id INT UNSIGNED NOT NULL,
    tweety_id INT UNSIGNED NOT NULL,

    PRIMARY KEY (user_id, tweety_id),

    FOREIGN KEY (user_id) REFERENCES users(id)
    ON DELETE CASCADE,

    FOREIGN KEY (tweety_id) REFERENCES tweeties(id)
    ON DELETE CASCADE
);

INSERT INTO 
    likes
VALUES 
    (1, 1), 
    (2, 1), 
    (2, 2);

--
-- VIEW
-- 

CREATE OR REPLACE VIEW tweets AS
SELECT 
    tweeties.id,
    tweeties.text,
    tweeties.user_id,
    username,
    COALESCE(updated_at, created_at) AS `last_edit`,
    COUNT(likes.user_id) AS `likes_count` 
FROM tweeties 
INNER JOIN users ON user_id = users.id
LEFT JOIN likes ON tweeties.id = likes.tweety_id
GROUP BY tweeties.id
ORDER BY last_edit DESC;

--
-- TESTS
--

SELECT * FROM users;

-- Pas email
-- INSERT INTO users VALUES (DEFAULT, 'a', 'pwdPWD123');

-- Pas password valide
-- INSERT INTO users VALUES (DEFAULT, 'a@a.a', 'pwd');

-- Text vide
-- INSERT INTO tweeties(text, user_id) VALUES ('   ', 1);

-- Password update trigger
-- password invalide
-- UPDATE users SET pwd = 'non' WHERE id = 1;

-- password hash apres update
UPDATE 
    users
SET 
    pwd = 'pwdaAa111'
WHERE id = 1;

SELECT * FROM users WHERE id = 1;

-- Tweeties

SELECT * FROM tweets;

-- created_at trigger
-- insert into tweeties(text, created_at, user_id) values ('mauvaise created at', DATE_ADD(NOW(), INTERVAL 36 YEAR), 1);

-- updated_at trigger
UPDATE 
    tweeties
SET
    text = UPPER(text)
WHERE id = 1;

-- invalid created_at
-- UPDATE tweeties SET created_at = NOW() WHERE id = 1;

SELECT * FROM tweets;