私は、オンラインの患者が2つの選択肢を持つ医師の予約システムを作成しようとしています。
制約:
これまでに完成したモデルは、次のようなDoctor Slotテーブルの作成に基づいています
DoctorSlotがNでN = 10の場合、以下の例を検討してください。
Case First Time if(N * 2)== Free予約ブロックを1つではなく2つのスロットでブロックする
ケースフォローアップif(N)==無料予約ブロックを1スロット作成
例:医師:9:00から9:10医師:9:10から9:20医師:9:20から9:30
ケース9:00から9:20までの患者への初回ショー
課題:
質問:
各医師の現在の予定を格納するAppointment
テーブルをお勧めします。このテーブルにいくつかの制約を追加して、アポイントメントの開始時間を10分の時間(たとえば9.00、9.10、9.20)にさえ制限し、EndTime
の後にStartTime
のような他の常識チェックを追加できます。医師は同時に2つの予定を開始することはできません。誰もがワークライフバランスを必要としているため、医師も午前9時から午後5時までしか働かないようにするとします。
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);
このテーブルにデータを挿入して、どのように見えるかを確認できます。 3番目の挿入は、制約によって妨げられているため失敗します。医師は、2つの予定を同時に開始することはできません。
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');
数値テーブルがあるとしましょう。あなたが他の多くの人がそれを作成する方法を説明していない場合。他のすべてが失敗した場合、これはあなたのためにそれを作成する可能性がありますが、それはおそらく最良の方法ではありません。
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
ここで、特定のドクターの空きスロットを確認したい場合は、必要なのは、どのドクターか、およびスロットが探している長さを指定することだけです。
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;
それは少し厄介に見えますので、誰かがその日付ロジックを改善して提案を喜んで受け入れることができれば。
医師が休憩を希望する場合は、予約として休憩を入力すると予約できなくなります。
テーブルの制約は重複しない予定を強制しないことに注意してください。これは可能ですが、より複雑です。これが私のシステムである場合、最終的にアポイントメントが挿入時に既存のアポイントメントと重複しないことを確認するために、いくつかのシステム(トリガーなど)について考えますが、それはあなた次第です。
以下は、MariaDB/MySQL用の mendosi のコードの機能的に同等な(およびIMOの方が読みやすい)バージョンです。
Numbers
テーブルがない場合は作成します。
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;
Appointment
テーブルの十分なスキーマを以下に示します。すぐに、INSERT
トリガーも追加して、新しいエントリが既存のエントリと競合しないようにします。
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
)
);
まず、特定のタイムスロットを新しい予定として割り当てることができるかどうかを決定する関数を定義します。
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 ;
次に、衝突する予定が保存されないようにするために、前述のINSERT
トリガーを示します。
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 ;
Appointment
テーブルが適切に設定されたので、いくつかの有効なサンプルエントリを挿入できます。
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');
無効な予定エントリを挿入しようとすると、ensureNewAppointmentsDoNotClash
トリガーの結果としてエラーがスローされます。実際、このトリガーは、主キー制約がチェックされる前でもエラーをスローするため、冗長と見なされる可能性があります。私のソリューションでは、複合主キーを使用するのではなく、Appointment
テーブルのIDフィールドを選択しました。
ここで、指定されたドクターを使用して、指定された長さの利用可能なタイムスロットの結果セットを取得する手順を示します。以前に定義し、slotIsAvailable
トリガーでも使用した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;
上記のクエリは、上記のAppointment
のサンプルレコードと@searchStart
に等しい '2019-10-06 06:00'
、利回り:
+----------+---------------------+---------------------+
| 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 |
+----------+---------------------+---------------------+
まず、なぜあなたのタイムスロットは固定長なのですか?現在のデザインでも、start_time = 9:00およびend_time = 9:20のスロットを使用でき、IDを1つにすることができます。さらに、次のようにブリッジテーブルを作成して、試験のタイプ(タイプ1 =最初の試験、タイプ2 =対照試験など)をスロットの長さに接続できます。
exam_type slot_length_in_minutes
1 20
2 10
3 15
この場合、試験の時間を予約するときに、UIである種の選択ボックス(コンボボックス)から試験の種類を選択し、適切なサイズの空きスロットを検索します。
また、予約テーブルの構造を教えてくれなかったので、それをお手伝いします。しかし、私はあなたがそこにある種のタイムスロットIDを持っていると思います、そしてまた選択された医者のIDも...?その場合、タイムスロットと予約に参加するだけで、完全なタイムテーブルを表示できます。お役に立てば幸いです。そうでない場合は、遠慮なく質問してください。