Đây là phiên bản tương đương về chức năng (và IMO dễ đọc hơn) của mã mendosi cho MariaDB / MySQL, với một số giải trình bổ sung và logic hơi đơn giản hóa trong một số lĩnh vực.
Tạo một Numbers
bảng nếu bạn chưa có:
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;
Đây là một lược đồ đủ cho một Appointment
bảng. Trong giây lát, chúng tôi cũng sẽ thêm một trình INSERT
kích hoạt để đảm bảo rằng các mục mới không xung đột với các mục hiện có.
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
)
);
Đầu tiên, chúng tôi xác định một chức năng để xác định xem một khe thời gian nhất định có thể được phân bổ như một cuộc hẹn mới hay không:
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 ;
Bây giờ đây là trình INSERT
kích hoạt được đề cập trước đó để đảm bảo không có cuộc hẹn xung đột nào được lưu trữ:
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 ;
Bây giờ Appointment
bảng đã được thiết lập đúng, chúng ta có thể chèn một số mục mẫu hợp lệ:
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');
Nếu bạn cố gắng chèn một mục cuộc hẹn không hợp lệ, một lỗi sẽ được đưa ra do kết quả của trình ensureNewAppointmentsDoNotClash
kích hoạt. Trong thực tế, trình kích hoạt này sẽ đưa ra một lỗi ngay cả trước khi ràng buộc khóa chính được kiểm tra, do đó có thể được coi là dư thừa; đối với giải pháp của mình, tôi đã chọn có một trường ID cho Appointment
bảng, thay vì sử dụng khóa chính ghép.
Bây giờ đây là thủ tục để có được một tập hợp các khe thời gian có sẵn với độ dài nhất định, với một bác sĩ nhất định. Lưu ý rằng chúng tôi sử dụng slotIsAvailable
chức năng của chúng tôi mà chúng tôi đã xác định trước đó và cũng được sử dụng trong trình INSERT
kích hoạt của chúng tôi .
-- 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;
Truy vấn trên, với các bản ghi mẫu ở trên cho Appointment
và @searchStart
bằng với '2019-10-06 06:00'
, mang lại:
+----------+---------------------+---------------------+
| 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 |
+----------+---------------------+---------------------+