J'essaie de créer un système de rendez-vous chez le médecin où le patient en ligne aura deux options:
Contraintes:
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
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:
Des questions:
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.
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 |
+----------+---------------------+---------------------+
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.