2017-03-06 27 views
2

我正在构建一个调度系统来跟踪专业人员的时间表和可用性。用户应该能够输入一些标准并查看专业人员下一次可用预约时间的最佳匹配专业人员列表。查询返回这些专业人员的工作,但随着管理专业人员数量的增长,返回结果所需的时间变得荒谬。找到下一个可用的专业人员

该系统工作过3个表的:专业定义了专业人士,可用性定义的可用性块相关专业,预约规定安排相关专业的约会。因此,一些样本数据可能看起来像:

INSERT INTO professional (id, name) 
VALUES 
    (1, 'Bob'), 
    (2, 'Frank'), 
    (3, 'Joe'); 

INSERT INTO availability (id, professional_id, start_date_time, end_date_time) 
VALUES 
    (1, 1, '03/06/2017 09:00:00', '03/06/2017 12:30:00'), 
    (2, 1, '03/06/2017 13:30:00', '03/06/2017 18:00:00'), 
    (3, 2, '03/06/2017 10:00:00', '03/06/2017 14:00:00'), 
    (4, 3, '03/07/2017 08:30:00', '03/07/2017 16:30:00'); 

INSERT INTO appointment (id, professional_id, start_date_time, end_date_time) 
VALUES 
    (1, 1, '03/06/2017 09:00:00', '03/06/2017 09:30:00'), 
    (2, 1, '03/06/2017 10:00:00', '03/06/2017 10:30:00'), 
    (3, 2, '03/06/2017 10:00:00', '03/06/2017 10:30:00'), 
    (4, 2, '03/06/2017 10:30:00', '03/06/2017 11:00:00'), 
    (5, 2, '03/06/2017 11:00:00', '03/06/2017 11:30:00'); 

查询应返回的线沿线的一个结果:

name | next_availability 
----- | ----------------- 
Bob | 03/06/2017 09:30:00 
Frank | 03/06/2017 11:30:00 
Joe | 03/07/2017 08:30:00 

我使用Technet找到方法(有一些修改,以与合作我的实际设置)找到下一个可用性,但正如我所说的,随着专业人员数量及其可用性和约会数量的增加,返回结果所需的时间变得不合理。

另外还有一个要求,为了让这更加困难,结果不应该包括任何即将到来的可用性的专业人员。

正如我所看到的,瓶颈在于,所有这些专业人员都需要计算下一个可用性。我考虑制定一份可用的预约表格,但维护该表格会令人望而却步。关于如何有效地完成这项工作的任何想法?

非常感谢您的帮助。

编辑成包括WORKING QUERY:

DECLARE @buffer tinyint; 
DECLARE @duration tinyint; 

SET @buffer = 15; 
SET @duration = 30; 

WITH CTE 
AS (
    SELECT timeSlots.*, RowNumber = ROW_NUMBER() OVER(ORDER BY start_date_time ASC) 
    FROM (
      -- Create an "appointment" to define the start of the block of availability. 
      -- If the start of the availability is still in the future, use the actual start_date_time 
      -- as both the start and end_date_time of the "appointment". If not, calculate the first 
      -- possible availability within this block based on the current time, a number of @buffer 
      -- minutes (to allow the user time to complete booking, etc., and increments based on the 
      -- duration of appointments (e.g., if current time + @buffer is 11:32, but appointments are 
      -- 15 minutes each on the hour, round up to 11:45). 
      -- Only look up to 2 weeks out. 
      SELECT 
       availability.id, 
       'Start' AS type, 
       availability.professional_id, 
       availability.start_date_time, 
       Iif(availability.start_date_time > DateAdd(Minute, @buffer, GetDate()), availability.start_date_time, DateAdd(Minute, (@duration - (DateDiff(Minute, availability.start_date_time, DateAdd(Minute, DateDiff(Minute, 0, DateAdd(Second, 30, DateAdd(Minute, @buffer, GetDate()))), 0)) % @duration)), DateAdd(Minute, DateDiff(Minute, 0, DateAdd(Second, 30, DateAdd(Minute, @buffer, GetDate()))), 0))) AS end_date_time 
      FROM 
       availability 
      WHERE 
       availability.end_date_time > GetDate() 
       AND availability.end_date_time <= DateAdd(Week, 2, GetDate()) 

     UNION 

      -- Create an "appointment" to define the end of the block of availability. 
      -- Only look up to 2 weeks out. 
      SELECT 
       availability.id, 
       'End' AS type, 
       availability.professional_id, 
       availability.end_date_time as start_date_time, 
       availability.end_date_time AS end_date_time 
      FROM 
       availability 
      WHERE 
       availability.end_date_time > GetDate() 
       AND availability.end_date_time <= DateAdd(Week, 2, GetDate()) 

     UNION 

      -- Get alreasy scheduled appointments up to 2 weeks out. 
      SELECT 
       appointment.id, 
       'Appointment' AS type, 
       appointment.professional_id, 
       appointment.start_date_time, 
       appointment.end_date_time 
      FROM 
       appointment 
      WHERE 
       start_date_time >= GetDate() 
       AND start_date_time <= DateAdd(Week, 2, GetDate()) 
    ) AS timeSlots 
) 
SELECT 
    TOP 5 
    a.professional_id, 
    min(a.end_date_time) AS next_availability 
FROM 
    CTE a 
    INNER JOIN CTE b 
     ON a.RowNumber = b.RowNumber - 1 
     AND a.professional_id = b.professional_id 
WHERE 
    dateDiff(Minute, a.end_date_time, b.start_date_time) >= @duration 
    -- Restrict results to those where the start of the gap is at least @buffer away from current time 
    AND a.end_date_time >= DateAdd(Minute, @buffer, GetDate()) 
    AND a.type <> 'End' 
GROUP BY 
    a.professional_id 
ORDER BY 
    next_availability ASC 

编辑,以说明什么WORKING QUERY DOES:

的CTE在上面的查询生成一个表,其基本上包括每1行预约预约。计划是找出这些任命之间有足够的时间差距。为了创建这些约会的边界,还根据约会发生的可用性块的开始和结束日期/时间,包括“开始”约会和“结束”约会。为了确保没有包含已经经过的时间间隔,如果可用性的start_date_time为0,那么“Start”的start_date_time为可用性的start_date_time或当前日期和时间(调整为下一个时间槽的start_date_time)已经过去了。

举个例子,上面给出的样本数据中,CTE将返回以下(表明专业的名称,而不是ID):

id | type  | name | start_date_time  | end_date_time 
--- | ----------- | ----- | ------------------- | ------------------- 
1 | Start  | Bob | 03/06/2017 09:00:00 | 03/06/2017 09:00:00 
1 | Appointment | Bob | 03/06/2017 09:00:00 | 03/06/2017 09:30:00 
1 | Appointment | Bob | 03/06/2017 10:00:00 | 03/06/2017 10:30:00 
1 | End   | Bob | 03/06/2017 12:30:00 | 03/06/2017 12:30:00 
2 | Start  | Bob | 03/06/2017 13:30:00 | 03/06/2017 13:30:00 
2 | End   | Bob | 03/06/2017 18:00:00 | 03/06/2017 18:00:00 
3 | Start  | Frank | 03/06/2017 10:00:00 | 03/06/2017 10:00:00 
3 | Appointment | Frank | 03/06/2017 10:00:00 | 03/06/2017 10:30:00 
4 | Appointment | Frank | 03/06/2017 10:30:00 | 03/06/2017 11:00:00 
5 | Appointment | Frank | 03/06/2017 11:00:00 | 03/06/2017 11:30:00 
3 | End   | Frank | 03/06/2017 14:00:00 | 03/06/2017 14:00:00 
4 | Start  | Joe | 03/07/2017 08:30:00 | 03/07/2017 08:30:00 
4 | End   | Joe | 03/07/2017 16:30:00 | 03/07/2017 16:30:00 

鉴于上述CTE的结果,你可以看到,鲍勃开始工作9月3日9:00,当天有两次预约,一次是9:00-9:30,另一次是10:00-10:30。什么外部查询正在做的是采取上述表,并将其加入到自身,由一个行偏移,这样从上面看起来像鲍勃的数据(所有日期是3/06):

a.type | a.start | a.end | b.type | b.start | b.end 
------ | -------- | -------- | ------ | -------- | -------- 
Start | 09:00:00 | 09:00:00 | Appt | 09:00:00 | 09:30:00 
Appt | 09:00:00 | 09:30:00 | Appt | 10:00:00 | 10:30:00 
Appt | 10:00:00 | 10:30:00 | End | 12:30:00 | 12:30:00 
End | 12:30:00 | 12:30:00 | Start | 13:30:00 | 13:30:00 

外部查询然后过滤这些结果,仅返回a.end和b.start之间的差至少持续时间长的那些行。第一行不起作用,因为第一行的a.end(9:00)和第一行的b.start(9:00)之间的差异小于持续时间。第二行确实工作,因为第二行的a.end(9:30)和第二行的b.start(10:00)之间的差异具有足够的持续时间。通过这种方式,外部查询仅返回那些持续时间足够长的时间段,然后仅返回每个专业人员的第一个时间段。

+0

因此,一个专业是可用的(他的一般可用性期间),如果对于一个给定的任命没有其他约会从第一个结束? – JimmyB

+1

什么是输入?例如,我需要在某天预约45分钟,或者需要在某天某天从9:00-9:45进行预约。编辑:另外,它可以成为使用datediff()与大量记录慢得出名。 – justiceorjustus

+0

你看过你的执行计划并建立了一些索引吗? – Hogan

回答

0

确定这里你去:

DECLARE @buffer tinyint; 
DECLARE @duration tinyint; 

SET @buffer = 15; 
SET @duration = 30; 

WITH TheDates AS 
(
    SELECT 
    availability.id, 
    availability.professional_id, 
    availability.start_date_time, 
    Iif(availability.start_date_time > DateAdd(Minute, @buffer, GetDate()), availability.start_date_time, DateAdd(Minute, (@duration - (DateDiff(Minute, availability.start_date_time, DateAdd(Minute, DateDiff(Minute, 0, DateAdd(Second, 30, DateAdd(Minute, @buffer, GetDate()))), 0)) % @duration)), DateAdd(Minute, DateDiff(Minute, 0, DateAdd(Second, 30, DateAdd(Minute, @buffer, GetDate()))), 0))) AS end_date_time, 
    availability.end_date_time AS end_date_time_real 
    FROM availability 
    WHERE 
    availability.end_date_time > GetDate() 
    AND availability.end_date_time <= DateAdd(Week, 2, GetDate()) 
), dates_filtered AS 
(
    SELECT 
    id, 
    professional_id, 
    start_date_time, 
    end_date_time, 
    end_date_time_real 
    FROM TheDates 
    WHERE dateDiff(Minute, end_date_time, start_date_time) >= @duration 
    AND end_date_time >= DateAdd(Minute, @buffer, GetDate()) 
), existingApts AS 
(
    -- Get alreasy scheduled appointments up to 2 weeks out. 
    SELECT 
    appointment.id, 
    appointment.professional_id, 
    appointment.start_date_time, 
    appointment.end_date_time 
    FROM appointment 
    WHERE 
    start_date_time >= GetDate() 
    AND start_date_time <= DateAdd(Week, 2, GetDate()) 
), dates_without_apt AS 
(-- filter out items with apt between start and end or starting or ending. 
    SELECT 
    id, 
    professional_id, 
-- start_date_time, 
-- end_date_time, 
    end_date_time_real 
    FROM dates_filtered D 
    LEFT JOIN existingApts A ON 
     -- apt between 
     (D.start_date_time <= A.start_date_time AND D.end_date_time_real >= A.end_date_time) OR 
     -- apt ends in range 
     (A.end_date_time >= D.start_date_time AND A.end_date_time <= D.end_date_time_real) OR 
     -- apt starts in range 
     (A.start_date_time >= D.start_date_time AND A.start_date_time <= D.end_date_time_real) OR 
    WHERE A.id is null 
) 
SELECT 
    TOP 5 
    a.professional_id, 
    min(a.end_date_time_real) AS next_availability 
FROM dates_without_apt a 
ORDER BY start_date_time ASC 
+0

谢谢,但这是行不通的。正如我所看到的,dates_without_apt返回没有安排applet的可用性块。但那不是我所追求的。根据availability_start和appt_start,appt_end和appt_start(下一个appt)以及appt_end和availability_end之间的差距,我需要知道至少持续几分钟的第一个可用时间。看看我发布的样本数据,Bob的第一个可用性是2017年3月3日9:30,因为即使该块在9:00开始,从9-9:30有一个appt,使得9:30成为第一个可用性。 – queevert