Comment structurer et interroger un système de rendez-vous basé sur des créneaux horaires où chaque entité réservable a une table de temps quotidienne différente?
je développe un système de réservation d'avocat, où une personne peut réserver un rendez-vous à un moment donné dans une journée donnée (le jour de disponibilité de l'avocat suivant).
disons que c'est un ZocDoc pour les avocats. La même structure, avec des nominations fondées sur le temps: http://goo.gl/djUZb
J'utilise MySQL et PHP.
le schéma de La table:
CREATE TABLE `laywer_appointments` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`lawyer_id` INT unsigned,
`day_of_week` tinyint(3) unsigned DEFAULT '1',
`slot_date` date DEFAULT NULL,
`slot_time` time DEFAULT NULL,
`status` tinyint(4) NOT NULL DEFAULT '0',
`client_id` int(11) DEFAULT NULL, -- client_id = NULL means free slot
);
Point 1)
chaque avocat a des plages horaires par défaut basées sur le jour de la semaine (statut = 0 signifie disponible). En insérant des fentes par défaut, Je ne fournis pas une date, juste day_of_week. Les données de l'exemple:
+-----------+-------------+-----------+-----------+
| lawyer_id | day_of_week | slot_time | status |
+-----------+-------------+-----------+-----------+
| 1 | 1 | 08:00 | 0 |
| 1 | 1 | 08:30 | 0 |
| 1 | 1 | 09:00 | 0 |
| 1 | 1 | 10:30 | 0 |
| 1 | 4 | 14:30 | 0 |
| 1 | 4 | 16:40 | 0 |
| 2 | 1 | 10:20 | 0 |
| 2 | 1 | 14:00 | 0 |
| 2 | 3 | 15:50 | 0 |
+-----------+-------------+-----------+-----------+
Point 2)
Un avocat peut ajouter un créneau horaire à un jour spécifique (même si ce jour est d'un jour différent de la semaine de ses emplacements par défaut)et peut également verrouiller (statut = -1), l'un des défaut emplacements dans une journée spécifique (c'est à dire qu'il est sur une réunion ou qu'il est malade):
+-----------+-------------+-----------+-----------+-----------+
| lawyer_id | day_of_week | slot_time | slot_date | status |
+-----------+-------------+-----------+-----------+-----------+
| 1 | 1 | 16:00 | 12/03/13 | 0 |
| 1 | 6 | 11:00 | 26/04/13 | 0 |
| 1 | 6 | 12:00 | 26/04/13 | 0 |
| 2 | 1 | 10:00 | 01/01/13 | -1 |
+-----------+-------------+-----------+-----------+-----------+
Point 3)
alors nous avons des rendez-vous réservés. Dans ce cas, nous remplir le slot_date et le client_id:
+-----------+-------------+-----------+-----------+-----------+
| lawyer_id | day_of_week | slot_time | slot_date | client_id |
+-----------+-------------+-----------+-----------+-----------+
| 1 | 1 | 10:30 | 12/03/13 | 10 |
+-----------+-------------+-----------+-----------+-----------+
a titre d'exemple, avec la réservation ci-dessus et en supposant qu'il est encore 6: 30 du même jour( 12/03/13), les emplacements libres disponibles qui doivent être imprimés sont:
8:00 - default slot
8:30 - default slot
9:00 - default slot
16:00 - Specific slot inserted in point 2 for 12/03/13
Le problème:
j'ai du renvoyer la prochaine la date et les heures libres correspondantes (par défaut, celles spécifiques moins verrouillées et réservées). Je ne peux pas juste dire "heures de retour de lundi, 10/10/13".
Dans une page de résultats de recherche, je vais énumérer tous les avocats et le calendrier de disponibilité pour chacun. Cela signifie que chaque avocat aura un horaire différent chaque fois qu'une recherche est faite.
je ne peux pas simplement dire "SÉLECTIONNEZ le temps DE [tas de jointures] OÙ date = aujourd'hui".
je suis venu avec cette requête qui ignore les emplacements qui sont verrouillés (statut = -1) ou réservés (client_id pas null), mais bien sûr il ne retournera pas les heures libres pour le jour le plus proche avec les heures disponibles (ou à partir d'aujourd'hui):
SELECT p.day_of_week, p.slot_date, p.slot_time
FROM laywer_appointments p
WHERE p.client_id IS NULL AND p.status = 0
AND p.slot_time NOT IN (
SELECT s.slot_time FROM laywer_appointments s
WHERE (s.slot_date IS NOT NULL AND s.client_id IS NOT NULL
OR s.status = -1) AND s.day_of_week = p.day_of_week
)
GROUP BY p.day_of_week, p.slot_date, p.slot_time
ORDER BY p.day_of_week ASC, p.slot_time ASC;
un Autre problème: si aujourd'hui day_of_week = 5, mais que le prochain day_of_week disponible pour un avocat donné est 2, Comment puis-je interroger cela?
comment retourner la journée la plus proche et disponible suivante de la semaine et de l'agrégat pour simplement retourner les heures à partir de cette journée, pas tous les jours?
une solution possible
Une chose que je suis venu avec était de créer 3 tables au lieu d'un:
- default_slots: 3 colonnes: lawyer_id, day_of_week, time
- fentes: laywer_id, day_of_week, l'heure, la date, l'état
- rendez-vous: toutes les informations concernant un rendez-vous réservé
alors je vais stocker tous les emplacements de temps libre pour chaque jour de la date réelle jusqu'à un an dans la table de fentes pour chaque avocat. (prend les plages de temps de default_slots).
+-----------+-------------+-----------+-----------+-----------+
| lawyer_id | day_of_week | slot_time | slot_date | status |
+-----------+-------------+-----------+-----------+-----------+
| 1 | 1 | 16:00 | 12/03/13 | 0 |
| 1 | 1 | 16:00 | 12/03/13 | 0 |
| 1 | 2 | 08:00 | 13/03/13 | 0 |
| 1 | 2 | 09:00 | 13/03/13 | 0 |
... next week
| 1 | 1 | 16:00 | 19/03/13 | 0 |
| 1 | 1 | 16:00 | 19/03/13 | 0 |
| 1 | 2 | 08:00 | 20/03/13 | 0 |
| 1 | 2 | 09:00 | 20/03/13 | 0 |
... up to an year
| 1 | 1 | 16:00 | 20/03/14 | 0 |
| 1 | 1 | 16:00 | 20/03/14 | 0 |
| 1 | 2 | 08:00 | 21/03/14 | 0 |
| 1 | 2 | 09:00 | 21/03/14 | 0 |
+-----------+-------------+-----------+-----------+-----------+
je vais aussi avoir quelques jobs cron qui fonctionnent chaque semaine qui ajoute une autre semaine d'enregistrements de fente libre dans les fentes de table et aussi supprimer les enregistrements passés pour réduire la taille de la table et les données inutilisées.
un avocat pourra également verrouiller un temps directement dans les slots, ainsi qu'ajouter des temps spécifiques (point 2).
Pour l'inscription, il sera d'obtenir des créneaux horaires pour une date égale ou supérieure qu'aujourd'hui avec des temps libres, puisque chaque heure de chaque date aura une rangée.
Implications sur cette solution: 1) premier jour nous aurons 2500 avocats (2ème mois environ 6000). En supposant 8 emplacements possibles / jour X 20 jours de travail / mois X 12 mois = 1920 dossiers de machines à sous par avocat.
2500 laywers x 1920 dossiers = 4,8 millions de dossiers le premier jour. (~12M, le deuxième mois)
ces enregistrements seront mis à jour, insérés et La suppression de tous les temps. la table slots a quelques index, donc je ne peux pas imaginer que des opérations d'écriture soient faites constamment sur une table avec des enregistrements 12M+ et quelques index. les index mis à jour à chaque seconde ne me semblent pas intelligents.
je ne peux vraiment pas venir avec une solution évolutive et. Ma solution avec une table seulement pourrait fonctionner, mais je ne peux pas penser à un moyen de l'interrogation. Et la table des fentes dénormalisées sera énorme, tout en ayant besoin d'écriture constante opérations.
des conseils?
2 réponses
j'ai fait quelque chose de similaire à ce que vous essayez de faire pour que je comprenne à quel point c'est compliqué :)
cela a été fait en MSSQL donc vous aurez besoin de le convertir en MySql.
ce sont les tables que nous avons terminé avec:
les intervalles de temps:
nous stockons à la fois les timesslots par défaut et les timeslots modifiés pour chaque membre du personnel dans ce tableau (Nous avons une colonne appelée "SlotType" dans cette table. SlotType 1 = DEFAULT TIMESLOTS & SlotType 2 = MODIFIED TIMESLOTS). Si vous regardez "Mar 30/04/13" sur la photo ci-dessus, vous verrez que nous avons modifié les plages horaires pour cette journée afin de n'afficher qu'un rendez-vous de 9h pour ce fonctionnaire en particulier.
ClosedDays:
ceci est une liste de jours fermés - par exemple, un membre du personnel que je ne travaille pas le jour de son anniversaire et de Noël jour.
rendez-vous:
Ceci est une liste de rendez-vous qui ont été réservés (ou en attente de confirmation de réservation).
requête SQL pour obtenir des rendez-vous Disponibles:
pour vérifier les rendez-vous, nous avons ensuite utilisé le SQL suivant dans notre procédure stockée. Il vérifie qu'un fonctionnaire est nommé à la date indiquée. La procédure stockée finale que nous utilisons des boucles à travers chaque membre du personnel sur la page pour chaque jour de la semaine pour obtenir tous les rendez-vous. Utiliser cette requête pour obtenir 10 rendez-vous de membres du personnel pour les 7 prochains jours = un total de 70 requêtes & prend environ 300ms avec un million d'enregistrements dans chaque tableau. Nous chargeons les rendez-vous via ajax donc 300ms est acceptable pour notre usage et sera prob le changer pour obtenir des rendez-vous de chaque membre du personnel séparément via ajax (so 7 queries à la fois) pour améliorer les performances encore plus à l'avenir.
DECLARE @MyDate date, @MyDayName nvarchar(10);
IF @StartDate IS NULL
SET @StartDate = GETDATE();
SET @MyDate = CAST(@StartDate AS date);
SET @MyDayName = DATENAME(dw, @MyDate );
--NOTES:
--@SlotType = 1 (DEFAULT TIMESLOTS), 2 (MODIFIED TIMESLOTS)
--***CHECK TO SEE IF DOCTOR IS CLOSED TODAY***
IF NOT EXISTS (SELECT [ClosedDays].[ID] FROM [ClosedDays] WHERE [ClosedDays].[StaffID] = @StaffID AND [ClosedDays].[BusinessID] = @BusinessID AND [ClosedDays].[Active] = 1 AND @MyDate BETWEEN [ClosedDays].[StartDate] AND [ClosedDays].[EndDate])
BEGIN
--***THE DOCTOR IS NOT CLOSED TODAY SO GET THE AVAILABLE TIMESLOTS***
--***CHECK TO SEE IF DOCTOR IS HAS MODIED TIMESLOTS TODAY***
IF NOT EXISTS (SELECT [TimeSlots].[ID], @MyDate AS SlotDate FROM [TimeSlots] WHERE [TimeSlots].[StaffID] = @StaffID AND [TimeSlots].[BusinessID] = @BusinessID AND [TimeSlots].[Active] = 1 AND [TimeSlots].[SlotType] = 2 AND [TimeSlots].[SlotDay] = @MyDayName AND @MyDate BETWEEN [TimeSlots].[StartDate] AND [TimeSlots].[EndDate] AND [TimeSlots].[ID] NOT IN (SELECT [Appointments].[TimeSlotID] FROM [Appointments]) )
BEGIN
--***THE DOCTOR HAS NO MODIFIED TIMESLOTS FOR TODAY USE THE DEFAULT ONES***
SELECT [TimeSlots].[ID] AS SlotID, [TimeSlots].[StaffID], [TimeSlots].[BusinessID], CONVERT(nvarchar(10), @MyDate, 103) AS SlotDate, [TimeSlots].[SlotDay], LTRIM(RIGHT(CONVERT(nvarchar(10), [TimeSlots].[SlotTime], 100), 7))AS SlotTime FROM [TimeSlots]
WHERE [TimeSlots].[StaffID] = @StaffID AND [TimeSlots].[BusinessID] = @BusinessID AND [TimeSlots].[Active] = 1 AND [TimeSlots].[SlotType] = 1 AND [TimeSlots].[SlotDay] = @MyDayName AND @MyDate BETWEEN [TimeSlots].[StartDate] AND [TimeSlots].[EndDate] AND NOT EXISTS (SELECT [Appointments].[TimeSlotID] FROM [Appointments] WHERE [Appointments].[TimeSlotID] = [TimeSlots].[ID])
END
ELSE
BEGIN
--***THE DOCTOR HAS MODIFIED TODAYS TIMESLOTS SO USE THE MODIFIED TIMESLOTS***
SELECT [TimeSlots].[ID] AS SlotID, [TimeSlots].[StaffID], [TimeSlots].[BusinessID], CONVERT(nvarchar(10), @MyDate, 103) AS SlotDate, [TimeSlots].[SlotDay], LTRIM(RIGHT(CONVERT(nvarchar(10), [TimeSlots].[SlotTime], 100), 7))AS SlotTime FROM [TimeSlots]
WHERE [TimeSlots].[StaffID] = @StaffID AND [TimeSlots].[BusinessID] = @BusinessID AND [TimeSlots].[Active] = 1 AND [TimeSlots].[SlotType] = 2 AND [TimeSlots].[SlotDay] = @MyDayName AND @MyDate BETWEEN [TimeSlots].[StartDate] AND [TimeSlots].[EndDate] AND NOT EXISTS (SELECT [Appointments].[TimeSlotID] FROM [Appointments] WHERE [Appointments].[TimeSlotID] = [TimeSlots].[ID])
END
END
ELSE
BEGIN
--***NO APPOINTMENTS WERE FOUND***
--***DUMMY QUERY TO RETURN NO RECORDS***
SELECT [TimeSlots].[ID] AS SlotID, [TimeSlots].[StaffID], [TimeSlots].[BusinessID], CONVERT(nvarchar(10), @MyDate, 103) AS SlotDate, [TimeSlots].[SlotDay], LTRIM(RIGHT(CONVERT(nvarchar(10), [TimeSlots].[SlotTime], 100), 7))AS SlotTime FROM [TimeSlots]
WHERE [TimeSlots].[ID] = -0
END
Espérons que cela a du sens et si quelqu'un a une idée sur comment optimiser ce plus s'il vous plaît laissez-moi savoir!
Vous avez raison, vous aurez une grande table. Mais il n'est pas certain que votre demande échouera en conséquence. MySQL (et tous les logiciels de SGBD) est fait pour permettre l'accès des grandes tables rapidement.
bon matériel serveur MySQL dédié (qui a un OS 64 bits, deux ou quatre processeurs rapides, beaucoup de RAM, et excellent fichier I/O -- SAS-des disques rapides interfacés) et un logiciel serveur correctement configuré va gérer cette charge de travail.
vous pouvez vouloir fusionner slot_time et slot_date dans un seul champ DATETIME ou TIMESTAMP, qui peut être indexé pour faciliter la recherche. Si vous choisissez d'utiliser des éléments de données TIMESTAMP, vous obtiendrez de bons avantages de gestion du fuseau horaire si vous faites les choses correctement.
vous pouvez vouloir travailler sur la façon de cloisonner votre grande table en utilisant un schéma qui vous permet de prendre la valeur d'un mois, ou même la valeur d'une semaine, des données hors ligne quand ce mois ou cette semaine est passé.
avec 2500 avocats utilisant votre système, vous allez vouloir obtenir ce droit. Pourquoi ne pas dépenser un peu d'argent pour un administrateur de base de données décent? Ils coûtent moins cher à l'heure que la plupart des avocats. Sheeri Cabral a écrit un bon résumé de la façon d'en trouver un. http://www.sheeri.org/how-to-find-a-dba/