web-dev-qa-db-fra.com

Slot Time Challenge - Schéma de la base de données Doctor Appointment

J'essaie de créer un système de rendez-vous chez le médecin où le patient en ligne aura deux options:

  1. Première visite (cette visite devrait durer 20 minutes)
  2. Visite de suivi (cette visite devrait durer 10 minutes)

Contraintes:

  1. Le prix sera différent en fonction du flétrissement, c'est une première fois/suivi
  2. Le médecin peut avoir des temps de pause entre les machines
  3. L'interface système prendra en charge les deux options lors de la réservation de l'emplacement
  4. nous avons besoin du moins d'espace entre les créneaux horaires pour faire max. utilisation de la disponibilité des médecins

Le modèle que nous avons terminé jusqu'à présent était basé sur la création d'une table de Docteur Slot qui est comme ci-dessous Doctor Slots Table Schema

Considérez l'exemple ci-dessous si DoctorSlot est N et N = 10 minutes

Case First Time if (N * 2) == Free Faites une réservation bloquer deux emplacements au lieu d'un

Suivi de cas si (N) == Gratuit Faire une réservation Bloquer un emplacement

Exemple Docteur: 9h00 à 9h10 Médecin: 9h10 à 9h20 Médecin: 9h20 à 9h30

Premier cas présenté au patient de 9h00 à 9h20

Défis:

  • Ajouter l'heure de 9h00 à 9h20 (il pourrait y avoir un temps tampon (pause pour le médecin) entre les créneaux horaires pour les médecins)
  • Nous aurons deux ID d'emplacements de la base de données au lieu d'un (quel SlotID sera utilisé avec Order)
  • Comment montrer à l'utilisateur en temps d'exécution en fonction de son cas quel modèle générique de temps nous utiliserons et mettre à jour les prix plus tard en conséquence
  • Si l'utilisateur a réservé un premier créneau horaire, puis un autre utilisateur a réservé un suivi, il y aura des lacunes et comment gérer le temps dans SQL Server dans la base de données

Des questions:

  1. Quel est le meilleur schéma de base de données pour obtenir une solution qui satisfasse tous les scénarios possibles?
  2. Quelle est la meilleure méthode pour gérer l'entité de temps à partir de SQL Server/ASP.NET POV?
8
theK

Je suggère une table Appointment qui stocke les rendez-vous actuels pour chaque médecin. Nous pouvons ajouter quelques contraintes sur ce tableau qui limitent les heures de début de rendez-vous à des heures égales à dix minutes (par exemple 9.00, 9.10, 9.20) plus ajouter quelques autres vérifications de bon sens comme EndTime après StartTime et le médecin ne peut pas avoir deux rendez-vous à la fois. Supposons que vous souhaitiez également que les médecins ne travaillent qu'entre 9 h et 17 h, car tout le monde a besoin d'un équilibre entre vie professionnelle et vie privée.

CREATE TABLE Appointment (
    DoctorID char(1) NOT NULL,
    [Date] date NOT NULL,
    StartTime time(0) NOT NULL CONSTRAINT CHK_StartTime_TenMinute CHECK (DATEPART(MINUTE, StartTime)%10 = 0 AND DATEPART(SECOND, StartTime) = 0),
    EndTime time(0) NOT NULL CONSTRAINT CHK_EndTime_TenMinute CHECK (DATEPART(MINUTE, EndTime)%10 = 0 AND DATEPART(SECOND, EndTime) = 0),
    Status char(1) NOT NULL,
    UserID char(1) NOT NULL,
    Price int NOT NULL,
    CONSTRAINT PK_Appointment PRIMARY KEY CLUSTERED (DoctorID, [Date], StartTime),
    CONSTRAINT CHK_StartTime_BusinessHours CHECK (DATEPART(HOUR, StartTime) > = 9 AND DATEPART(HOUR, StartTime) < = 16),
    CONSTRAINT CHK_EndTime_BusinessHours CHECK (DATEPART(HOUR, EndTime) > = 9 AND DATEPART(HOUR, DATEADD(SECOND, -1, EndTime)) < = 16),
    CONSTRAINT CHK_EndTime_After_StartTime CHECK (EndTime > StartTime));
CREATE INDEX iDoctor_End ON Appointment (DoctorID, [Date], EndTime);

Nous pouvons insérer des données dans ce tableau pour voir à quoi il ressemble. Notez que la troisième insertion échouera car elle est empêchée par notre contrainte. Le médecin ne peut pas avoir deux rendez-vous commençant en même temps.

INSERT INTO Appointment VALUES ('A', '20170420', '09:00:00', '09:10:00', 'P', '1', '0');
INSERT INTO Appointment VALUES ('A', '20170420', '09:20:00', '09:40:00', 'C', '2', '10');
INSERT INTO Appointment VALUES ('A', '20170420', '09:00:00', '09:20:00', 'C', '2', '10');

Supposons que vous ayez un tableau de nombres. Si vous n'avez pas beaucoup d'autres personnes ont décrit comment en créer un. Si tout le reste échoue, cela pourrait en créer un pour vous, mais ce n'est probablement pas la meilleure façon.

CREATE TABLE Numbers (Number int PRIMARY KEY CLUSTERED);
DECLARE @number int = 0;
WHILE @number < 1000
BEGIN
    INSERT INTO Numbers VALUES (@number);
    SET @number += 1;
END 

Maintenant, si nous voulons voir des créneaux libres pour un médecin particulier, tout ce que nous devons faire est de spécifier quel médecin, et combien de temps le créneau est que nous recherchons:

DECLARE @doctorID char(1) = 'A';
DECLARE @length tinyint = 20;
WITH Slots AS (
    SELECT StartTime = DATEADD(MINUTE, ((DATEPART(MINUTE, GETDATE())/10)+1+Number)*10, DATEADD(HOUR, DATEPART(HOUR, GETDATE()), CONVERT(smalldatetime, CONVERT(date, GETDATE())))),
           EndTime = DATEADD(MINUTE, @length, DATEADD(MINUTE, ((DATEPART(MINUTE, GETDATE())/10)+1+Number)*10, DATEADD(HOUR, DATEPART(HOUR, GETDATE()), CONVERT(smalldatetime, CONVERT(date, GETDATE())))))
      FROM Numbers)
SELECT TOP 15 DoctorID = @doctorID,
    s.StartTime,
    s.EndTime
  FROM Slots AS s
  WHERE NOT EXISTS (SELECT 1 
                      FROM Appointment AS a
                      WHERE (CONVERT(time(0), s.StartTime) < a.EndTime AND CONVERT(time(0), s.EndTime) > a.StartTime)
                        AND a.DoctorID = @doctorID
                        AND a.[Date] = CONVERT(date, s.StartTime))
    AND DATEPART(HOUR, s.StartTime) >= 9
    AND DATEPART(HOUR, DATEADD(MINUTE, -1, s.EndTime)) <= 16
ORDER BY s.StartTime;

Cela semble un peu gênant, donc si quelqu'un peut améliorer cette logique de date, heureux de prendre des suggestions.

Si un médecin souhaite une pause, entrez-la comme rendez-vous et elle ne sera pas disponible pour la réservation.

Notez que les contraintes de table n'appliquent pas de rendez-vous qui ne se chevauchent pas. C'est possible mais c'est plus compliqué. Si c'était mon système, je penserais à un système (par exemple, un déclencheur) pour enfin vérifier que le rendez-vous ne chevauche pas un rendez-vous existant au moment de l'insertion, mais c'est à vous de décider.

5
mendosi

Voici une version fonctionnellement équivalente (et IMO plus facile à lire) du code de mendosi pour MariaDB/MySQL, avec quelques explications supplémentaires et une logique légèrement simplifiée dans certains domaines.

Créez une table Numbers si vous n'en avez pas déjà une:

CREATE TABLE Numbers (number INT UNSIGNED PRIMARY KEY);

DELIMITER //

CREATE PROCEDURE populateNumbers()
BEGIN
    SET @x = 0;
    WHILE @x < 1024 DO
        INSERT INTO Numbers VALUES (@x);
        SET @x = @x + 1;
    END WHILE;
    SET @x = NULL;
END; //

DELIMITER ;

CALL populateNumbers;
DROP PROCEDURE populateNumbers;

Voici un schéma suffisant pour une table Appointment. Dans un instant, nous ajouterons également un déclencheur INSERT qui garantira que les nouvelles entrées ne se heurtent pas aux entrées existantes.

CREATE TABLE Appointment (
    doctorID    INT UNSIGNED    NOT NULL,
    `date`      DATE            NOT NULL,
    startTime   TIME(0)         NOT NULL,
    endTime     TIME(0)         NOT NULL,

    CONSTRAINT PRIMARY KEY (doctorID, `date`, startTime),

    CONSTRAINT mustStartOnTenMinuteBoundary CHECK (
        EXTRACT(MINUTE FROM startTime) % 10 = 0
        AND EXTRACT(SECOND FROM startTime) = 0
    ),
    CONSTRAINT mustEndOnTenMinuteBoundary CHECK (
        EXTRACT(MINUTE FROM endTime) % 10 = 0
        AND EXTRACT(SECOND FROM endTime) = 0
    ),
    CONSTRAINT cannotStartBefore0900 CHECK (
        EXTRACT(HOUR FROM startTime) >= 9
    ),
    CONSTRAINT cannotEndAfter1700 CHECK (
        EXTRACT(HOUR FROM (startTime - INTERVAL 1 SECOND)) < 17
    ),
    CONSTRAINT mustEndAfterStart CHECK (
        endTime > startTime
    )
);

Tout d'abord, nous définissons une fonction pour déterminer si un créneau horaire donné peut être attribué en tant que nouveau rendez-vous:

DELIMITER //

CREATE FUNCTION slotIsAvailable(
    doctorID            INT,
    slotStartDateTime   DATETIME,
    slotEndDateTime     DATETIME
) RETURNS BOOLEAN NOT DETERMINISTIC
BEGIN
    RETURN CASE WHEN EXISTS (
        -- This table will contain records iff the slot clashes with an existing appointment
        SELECT TRUE
        FROM Appointment AS a
        WHERE
                CONVERT(slotStartDateTime, TIME) < a.endTime   -- These two conditions will both hold iff the slot overlaps
            AND CONVERT(slotEndDateTime,   TIME) > a.startTime -- with the existing appointment that it's being compared to
            AND a.doctorID = doctorID
            AND a.date = CONVERT(slotStartDateTime, DATE)
    ) THEN FALSE ELSE TRUE
    END;
END; //

DELIMITER ;

Voici maintenant le déclencheur INSERT mentionné précédemment pour garantir qu'aucun rendez-vous conflictuel n'est stocké:

DELIMITER //

CREATE TRIGGER ensureNewAppointmentsDoNotClash
    BEFORE INSERT ON Appointment
    FOR EACH ROW
BEGIN
    IF NOT slotIsAvailable(
        NEW.doctorID,
        CAST( CONCAT(NEW.date, ' ', NEW.startTime)  AS DATETIME ),
        CAST( CONCAT(NEW.date, ' ', NEW.endTime)    AS DATETIME )
    ) THEN
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Appointment clashes with an existing appointment!';
    END IF;
END; //

DELIMITER ;

Maintenant que la table Appointment est correctement configurée, nous pouvons insérer des exemples d'entrées valides:

INSERT INTO Appointment VALUES (1, '2019-10-06', '09:20', '09:30');
INSERT INTO Appointment VALUES (1, '2019-10-06', '09:40', '09:50');
INSERT INTO Appointment VALUES (1, '2019-10-06', '11:00', '11:20');
INSERT INTO Appointment VALUES (1, '2019-10-06', '11:20', '11:40');
INSERT INTO Appointment VALUES (1, '2019-10-06', '11:40', '12:00');
INSERT INTO Appointment VALUES (1, '2019-10-06', '13:00', '14:00');
INSERT INTO Appointment VALUES (1, '2019-10-06', '16:00', '16:40');

Si vous essayez d'insérer une entrée de rendez-vous non valide, une erreur sera générée à la suite du déclencheur ensureNewAppointmentsDoNotClash. En fait, ce déclencheur générera une erreur avant même que la contrainte de clé primaire ne soit vérifiée, ce qui pourrait être considéré comme redondant; pour ma solution, j'ai choisi d'avoir un champ ID pour la table Appointment, plutôt que d'utiliser une clé primaire composée.

Voici maintenant la procédure pour obtenir un ensemble de résultats de plages horaires disponibles d'une durée donnée, avec un médecin donné. Notez que nous utilisons notre fonction slotIsAvailable que nous avons définie précédemment et également utilisée dans notre déclencheur INSERT.

-- The ID of the doctor to book the appointment with.
SET @doctorID = 1;

-- The moment from which to start searching for availble time slots
SET @searchStart = CURRENT_TIMESTAMP;

-- The duration of the appointment to book, in minutes.
SET @duration = 20;

WITH
    SlotStart AS (
        -- This table will list all the 10-minute-aligned timestamps that occur after `@searchStart`
        SELECT
            CONVERT(@searchStart, DATE)
            + INTERVAL (EXTRACT(HOUR FROM @searchStart)) HOUR
            + INTERVAL ( EXTRACT(MINUTE FROM @searchStart) DIV 10 + number + 1 ) * 10 MINUTE
        AS startDateTime
        FROM Numbers
    ),
    Slot AS (
        SELECT
            startDateTime,
            startDateTime + INTERVAL @duration MINUTE   AS endDateTime
        FROM SlotStart
    ),
    AvailableSlot AS (
        SELECT
            @doctorID   AS doctorID,
            startDateTime,
            endDateTime
        FROM Slot AS s
        WHERE
                slotIsAvailable(@doctorID, s.startDateTime, s.endDateTime)
            AND EXTRACT(HOUR FROM s.startDateTime) >= 9
            AND EXTRACT(HOUR FROM (s.endDateTime - INTERVAL 1 MINUTE)) <= 16
    )
SELECT *
    FROM AvailableSlot
    WHERE
            CONVERT(startDateTime, DATE) = CONVERT(@searchStart, DATE)
        AND CONVERT(endDateTime,   DATE) = CONVERT(@searchStart, DATE)
    ORDER BY startDateTime ASC;

La requête ci-dessus, avec les exemples d'enregistrements ci-dessus pour Appointment, et avec @searchStart égal à '2019-10-06 06:00', donne:

+----------+---------------------+---------------------+
| doctorID | startDateTime       | endDateTime         |
+----------+---------------------+---------------------+
|        1 | 2019-10-06 09:00:00 | 2019-10-06 09:20:00 |
|        1 | 2019-10-06 09:50:00 | 2019-10-06 10:10:00 |
|        1 | 2019-10-06 10:00:00 | 2019-10-06 10:20:00 |
|        1 | 2019-10-06 10:10:00 | 2019-10-06 10:30:00 |
|        1 | 2019-10-06 10:20:00 | 2019-10-06 10:40:00 |
|        1 | 2019-10-06 10:30:00 | 2019-10-06 10:50:00 |
|        1 | 2019-10-06 10:40:00 | 2019-10-06 11:00:00 |
|        1 | 2019-10-06 12:00:00 | 2019-10-06 12:20:00 |
|        1 | 2019-10-06 12:10:00 | 2019-10-06 12:30:00 |
|        1 | 2019-10-06 12:20:00 | 2019-10-06 12:40:00 |
|        1 | 2019-10-06 12:30:00 | 2019-10-06 12:50:00 |
|        1 | 2019-10-06 12:40:00 | 2019-10-06 13:00:00 |
|        1 | 2019-10-06 14:00:00 | 2019-10-06 14:20:00 |
|        1 | 2019-10-06 14:10:00 | 2019-10-06 14:30:00 |
|        1 | 2019-10-06 14:20:00 | 2019-10-06 14:40:00 |
|        1 | 2019-10-06 14:30:00 | 2019-10-06 14:50:00 |
|        1 | 2019-10-06 14:40:00 | 2019-10-06 15:00:00 |
|        1 | 2019-10-06 14:50:00 | 2019-10-06 15:10:00 |
|        1 | 2019-10-06 15:00:00 | 2019-10-06 15:20:00 |
|        1 | 2019-10-06 15:10:00 | 2019-10-06 15:30:00 |
|        1 | 2019-10-06 15:20:00 | 2019-10-06 15:40:00 |
|        1 | 2019-10-06 15:30:00 | 2019-10-06 15:50:00 |
|        1 | 2019-10-06 15:40:00 | 2019-10-06 16:00:00 |
|        1 | 2019-10-06 16:40:00 | 2019-10-06 17:00:00 |
+----------+---------------------+---------------------+
1
Jivan Pal

Tout d'abord, pourquoi votre créneau horaire est-il de longueur fixe? Même avec la conception actuelle, vous pouvez avoir un emplacement avec start_time = 9:00 et end_time = 9:20, et il peut s'agir d'un seul identifiant. De plus, vous pouvez créer une table de transition pour connecter le type d'examens (c'est-à-dire type 1 = premier examen, type 2 = examen de contrôle, etc.) à la longueur des créneaux, comme ceci:
exam_type slot_length_in_minutes
1 20
2 10
3 15
.

De plus, vous ne nous avez pas donné la structure de votre tableau de réservations, nous pouvons donc vous aider. Mais je suppose que vous avez une sorte d'identification de créneau horaire, ainsi que l'ID d'un médecin sélectionné ...? Dans ce cas, le simple fait de rejoindre des plages horaires et des réservations pourrait vous montrer le calendrier complet. J'espère que cela vous sera utile. Sinon, n'hésitez pas à en demander plus.

0
Miloš