Slot Time Challenge - Schema der Datenbank für Arzttermine

8

Ich versuche, ein Arztterminsystem zu schaffen, bei dem der Patient online zwei Möglichkeiten hat:

  1. Erster Besuch (dieser Besuch sollte 20 Minuten dauern)
  2. Follow-up-Besuch (dieser Besuch sollte 10 Minuten dauern)

Einschränkungen:

  1. Der Preis ist unterschiedlich, je nachdem, ob es sich um ein erstes Mal oder eine Nachuntersuchung handelt
  2. Der Arzt hat möglicherweise Pausen zwischen den Slots
  3. Die Systemschnittstelle unterstützt beide Optionen bei der Buchung des Slots
  4. Wir brauchen die geringste Lückenzeit zwischen den Slots, um max. Nutzung der Verfügbarkeit von Ärzten

Das Modell, das wir bisher fertiggestellt haben, basierte auf der Erstellung einer Doctor Slot-Tabelle, die der folgenden entspricht Doctor Slots-Tabellenschema

Betrachten Sie das folgende Beispiel, wenn DoctorSlot N und N = 10 Minuten ist

Erster Fall, wenn (N * 2) == Kostenlos Reservierungsblock zwei Slots anstelle von einem

Fall-Follow-up, wenn (N) == Free Reservierungsblock einen Steckplatz blockieren

Beispiel Arzt: 9:00 bis 9:10 Arzt: 9:10 bis 9:20 Arzt: 9:20 bis 9:30

Fall zum ersten Mal Zeigen Sie dem Patienten von 9:00 bis 9:20 Uhr

Herausforderungen:

  • Fügen Sie die Zeit von 9:00 bis 9:20 Uhr hinzu (es kann eine Pufferzeit (Pause für den Arzt) zwischen den Slots für Ärzte geben).
  • Wir haben zwei Slot-IDs aus der Datenbank anstelle von einer (welche SlotID wird mit Order verwendet).
  • Wie man dem Benutzer in der Laufzeit basierend auf seinem Fall zeigt, welches zeitgenerische Modell wir verwenden und die Preise später entsprechend aktualisieren
  • Wenn der Benutzer ein erstes Zeitfenster gebucht hat und ein anderer Benutzer ein Follow-up gebucht hat, gibt es Lücken und den Umgang mit der Zeit in SQL Server in der Datenbank

Fragen:

  1. Was ist das beste Datenbankschema, um eine Lösung zu erzielen, die alle möglichen Szenarien erfüllt?
  2. Was ist die beste Methode, um mit Zeitentitäten aus SQL Server / ASP.NET POV umzugehen?
Das K
quelle

Antworten:

5

Ich würde eine AppointmentTabelle vorschlagen, in der die aktuellen Termine für jeden Arzt gespeichert sind. Wir können einige Einschränkungen auf dieser Tabelle hinzufügen , die die Terminstartzeiten begrenzen , um sogar zehn Minuten Zeiten (zB 9,00, 9,10, 9,20) sowie einige andere gesunde Menschenverstand Prüfungen hinzufügen , wie EndTimenach StartTimeund Arzt kann nicht gleichzeitig zwei Termine haben Start . Angenommen, Sie möchten auch, dass Ärzte nur zwischen 9 und 17 Uhr arbeiten, da jeder eine gewisse Work-Life-Balance benötigt.

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);

Wir können einige Daten in diese Tabelle einfügen, um zu sehen, wie sie aussieht. Beachten Sie, dass die dritte Einfügung fehlschlägt, da sie durch unsere Einschränkung verhindert wird. Der Arzt kann nicht zwei Termine gleichzeitig haben.

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');

Nehmen wir an, Sie haben eine Zahlentabelle. Wenn Sie nicht viele andere Leute beschrieben haben, wie man eine erstellt. Wenn alles andere fehlschlägt, könnte dies eine für Sie erstellen, aber es ist wahrscheinlich nicht der beste Weg.

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 

Wenn wir nun freie Slots für einen bestimmten Arzt sehen möchten, müssen wir nur angeben, welcher Arzt und wie lange der Slot ist, den wir suchen:

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;

Das sieht etwas umständlich aus. Wenn also jemand diese Datumslogik verbessern kann, nimmt er gerne Vorschläge entgegen.

Wenn ein Arzt eine Pause wünscht, geben Sie die Pause als Termin ein und sie kann nicht gebucht werden.

Beachten Sie, dass die Tabelleneinschränkungen keine nicht überlappenden Termine erzwingen. Dies ist möglich, aber komplizierter. Wenn dies mein System wäre, würde ich über ein System (z. B. Auslöser) nachdenken, um endgültig zu überprüfen, ob sich der Termin zum Zeitpunkt des Einfügens nicht mit einem vorhandenen überschneidet, aber das liegt bei Ihnen.

Mendosi
quelle
Ich habe dieses Problem zwar gelöst, weil es eine alte Frage ist und ich einen anderen Ansatz gewählt habe, aber Ihre Antwort ist genau das, wonach ich gesucht habe. danke für die Hilfe :)
theK
1
@ user3291143 Ja, ich habe festgestellt, dass es sich um eine alte Frage handelt, die jedoch auf der Titelseite angezeigt wurde. Daher hielt ich es für sinnvoll, eine gründlichere Antwort zu finden, um später darauf zurückgreifen zu können. Danke für die Antwort.
Mendosi
@ theK.Bitte teilen Sie Ihren Code, wie Sie getan haben
iCoders
1

Hier ist eine funktional äquivalente (und IMO leichter zu lesende) Version von Mendosis Code für MariaDB / MySQL mit einigen zusätzlichen Darstellungen und einer leicht vereinfachten Logik in einigen Bereichen.

Erstellen Sie eine NumbersTabelle, falls Sie noch keine haben:

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;

Hier ist ein ausreichendes Schema für eine AppointmentTabelle. In INSERTKürze werden wir auch einen Trigger hinzufügen , der sicherstellt, dass neue Einträge nicht mit vorhandenen Einträgen in Konflikt geraten.

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
    )
);

Zunächst definieren wir eine Funktion, um zu bestimmen, ob ein bestimmtes Zeitfenster als neuer Termin zugewiesen werden kann:

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 ;

Hier ist der INSERTzuvor erwähnte Auslöser, um sicherzustellen, dass keine Kollisionstermine gespeichert werden:

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 ;

Nachdem die AppointmentTabelle ordnungsgemäß eingerichtet wurde, können wir einige gültige Beispieleinträge einfügen:

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');

Wenn Sie versuchen, einen ungültigen Termineintrag einzufügen, wird aufgrund des ensureNewAppointmentsDoNotClashAuslösers ein Fehler ausgegeben. Tatsächlich löst dieser Trigger einen Fehler aus, noch bevor die Primärschlüsseleinschränkung überprüft wird, sodass dies als redundant angesehen werden kann. Für meine Lösung habe ich mich für ein ID-Feld für die AppointmentTabelle entschieden, anstatt einen zusammengesetzten Primärschlüssel zu verwenden.

Hier ist das Verfahren, um mit einem bestimmten Arzt eine Ergebnismenge verfügbarer Zeitfenster einer bestimmten Länge zu erhalten. Beachten Sie, dass wir unsere slotIsAvailablezuvor definierte und auch in unserem INSERTTrigger verwendete Funktion verwenden .

-- 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;

Die obige Abfrage mit den obigen Beispieldatensätzen für Appointmentund mit @searchStartgleich '2019-10-06 06:00'ergibt:

+----------+---------------------+---------------------+
| 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 |
+----------+---------------------+---------------------+
Jivan Pal
quelle
0

Zunächst einmal, warum ist Ihr Zeitfenster von fester Länge? Selbst mit dem aktuellen Design können Sie einen Slot mit start_time = 9:00 und end_time = 9:20 haben, und es kann eine ID sein. Darüber hinaus können Sie eine Brückentabelle
erstellen, um den Prüfungstyp (dh Typ 1 = erste Prüfung, Typ 2 = Kontrollprüfung usw.) wie folgt mit der Länge der Zeitnischen zu verbinden : Prüfungstyp slot_length_in_minutes
1 20
2 10
3 15
In diesem Fall, wenn Sie Buchen Sie die Zeit für die Prüfung. Wählen Sie in der Benutzeroberfläche einen Prüfungstyp aus einem Auswahlfeld (Kombinationsfeld) aus und suchen Sie dann nach einem freien Platz mit der entsprechenden Größe.

Außerdem haben Sie uns Ihre Reservierungstabellenstruktur nicht mitgeteilt, damit wir Ihnen dabei helfen können. Aber ich denke, Sie haben dort eine Art Zeitfenster-ID und auch die ID eines ausgewählten Arztes ...? In diesem Fall können Sie durch den Beitritt zu Zeitfenstern und Reservierungen den vollständigen Zeitplan anzeigen. Ich hoffe, es hat geholfen. Wenn nicht, können Sie gerne mehr fragen.

Miloš
quelle
Ich möchte, dass dieser freie Slot mit der entsprechenden Größe dynamisch geändert wird, basierend auf dem vom Benutzer gewählten Typ. Wenn der Benutzer beispielsweise "Ersttermin" gewählt hat, sollte er in der Lage sein, Slots für eine Länge von 20 Minuten zu sehen, aber wenn er "Termin zum 2. Mal" gewählt hat '' er sollte einen 10 Minuten langen Slot sehen
theK