Alf*_*sch 21 mysql database-design relational-database
我正在开发一个律师预订系统,一个人可以在某一天的某个特定时间(下一位律师的可用日期)预约.
让我们说它是律师的ZocDoc.相同的结构,基于时间的约会:http://goo.gl/djUZb
我正在使用MySQL和PHP.
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
);
Run Code Online (Sandbox Code Playgroud)
每位律师都有基于星期几的默认时段(状态= 0表示可用).插入默认插槽时,我不提供日期,只是day_of_week.示例数据:
+-----------+-------------+-----------+-----------+
| 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 |
+-----------+-------------+-----------+-----------+
Run Code Online (Sandbox Code Playgroud)
律师可以在特定日期添加一个时段(即使这一天是从他的默认时段开始的另一天)并且还可以锁定(status = -1)特定日期中的一个默认时段(即他正在开会或他病了):
+-----------+-------------+-----------+-----------+-----------+
| 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 |
+-----------+-------------+-----------+-----------+-----------+
Run Code Online (Sandbox Code Playgroud)
然后我们预约了约会.在这种情况下,我们填充slot_date和client_id:
+-----------+-------------+-----------+-----------+-----------+
| lawyer_id | day_of_week | slot_time | slot_date | client_id |
+-----------+-------------+-----------+-----------+-----------+
| 1 | 1 | 10:30 | 12/03/13 | 10 |
+-----------+-------------+-----------+-----------+-----------+
Run Code Online (Sandbox Code Playgroud)
例如,通过上述预订并假设它仍然是当天的6:30(2013年3月12日),必须打印的免费可用插槽是:
8:00 - default slot
8:30 - default slot
9:00 - default slot
16:00 - Specific slot inserted in point 2 for 12/03/13
Run Code Online (Sandbox Code Playgroud)
我必须返回下一个可用日期和相关的空闲时间(默认日期,特定日期减去锁定日期和预订日期).我不能只说"从星期一,10月10日的返回时间".
在搜索结果页面中,我将列出所有律师和每个律师的可用时间表.这意味着每次律师在每次搜索时都会有不同的时间表.
我不能简单地说"选择时间来自[串联] WHERE date = today".
我带来了这个查询,它忽略了被锁定(状态= -1)或预订(client_id不为空)的插槽,但当然它不会返回最近一天的可用时间(或从今天起)的空闲时间:
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;
Run Code Online (Sandbox Code Playgroud)
另一个问题:如果今天是day_of_week = 5,但是给定律师的下一个可用day_of_week是2,我该如何查询?
如何返回下一个最近且可用的day_of_week并汇总到从这一天开始的返回时间,而不是所有日子?
我带来的一件事是创建3个表而不是一个:
然后我将为每位律师在插槽表中存储实际日期的每一天的所有空闲时间长达一年.(从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 |
+-----------+-------------+-----------+-----------+-----------+
Run Code Online (Sandbox Code Playgroud)
我还将在每周运行一些cron作业,在表槽中添加另一周的空闲槽记录,并删除过去的记录以减少表大小和未使用的数据.
律师还可以将时间直接锁定到插槽中,并添加特定时间(第2点).
对于列表,由于每个日期的每个时间都有一行,因此将获得与今天相等或更大的日期的空闲时间.
对这个解决方案的影响:1)第一天我们将有2500名律师(第二个月约6000).假设8个可能的插槽/每天X 20天的工作/月X 12个月= 1920个插槽记录每位律师.
2500张记录x 1920记录=第一天的4,800万条记录.(第二个月约12M)
这些记录将一直更新,插入和删除.slot表有一些索引,所以我无法想象在具有12M +记录和一些索引的表上不断进行写操作.每秒更新的索引对我来说并不聪明.
我真的无法提供合理且可扩展的解决方案.我的解决方案只有一个表可以工作,但我想不出一种查询的方式.非规范化的槽表将是巨大的,同时需要不断的写操作.
有小费吗?
MWD*_*MWD 19
我做了类似于你尝试做的事情,所以我理解它有多复杂:)
这是在MSSQL中完成的,因此您需要将其转换为MySql.

这些是我们最终得到的表格:
时隙:
我们在此表中存储了每个职员的默认时间段和修改后的时隙(我们在此表中有一个名为"SlotType"的列.SlotType1 = DEFAULT TIMESLOTS&SlotType 2 = MODIFIED TIMESLOTS).如果您查看上图中的"星期二30/04/13",您会看到我们修改了当天的时间段,仅显示该特定工作人员的上午9点预约.
ClosedDays:
这是关闭日期的清单 - 例如,一名工作人员在他的生日和圣诞节那天不工作.
约会:
这是已预订(或等待预订确认)的约会列表.
用于获取可用约会的SQL查询:
为了检查约会,我们在存储过程中使用了以下SQL.它检查一名工作人员约会的指定日期.我们正在使用的最终存储过程通过页面上的每个工作人员循环,以获得所有约会.使用此查询在接下来的7天内获得10名工作人员约会=总共70个查询并且需要大约300毫秒,每个表中有100万条记录.我们正在通过ajax加载约会,因此300ms可供我们使用,并且可能会更改它以通过ajax(每次7个查询)单独获得每个工作人员的约会,以便在将来提高性能.
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
Run Code Online (Sandbox Code Playgroud)
希望这是有道理的,如果其他人对如何优化这一点有一些想法请告诉我!