有效地寻找下一个可用的专业人员



我正在建立一个调度系统,以跟踪专业人士的时间表和可用性。用户应该能够输入一些条件,并查看专业人士下一个可用约会时间的最佳匹配专业人员列表。返回这些专业人员的查询工作起作用,但是随着托管专业人员的数量的增加,返回结果所需的时间变得荒谬。

系统工作的3个表工作:专业定义了专业人员, availability 定义了相关专业人员的可用性块, piforment 定义安排了针对相关专业人员的约会。因此,一些示例数据可能看起来像:

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上找到的方法(有一些修改可以与我的实际设置一起使用)来找到下一个可用性,但是就像我说的那样,作为专业人士的数量及其可用性和约会,返回结果所需的时间变得不合理。

,还有其他要求,只是为了使这一点更加困难,结果不应包括任何没有任何可用性的专业人员。

我看到的瓶颈是必须为所有这些专业人员计算下一个可用性。我已经考虑建立一张可用的约会表,但是该表的维护将是令人望而却步的。关于如何有效地使这项工作有什么想法?

预先感谢您的帮助。

编辑为包括工作查询:

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

编辑以解释工作查询的作用:

上面查询中的CTE生成了一个表,该表本质上包括每个预订约会1行。该计划是在这些任命之间找到足够持续时间的差距。为了为这些任命创建界限,还根据开始和终点的日期/时间来包括"开始"任命和"结束"任命。为了确保不包括已经过去了的时间差距," start"的start_date_time是可用性的start_date_time,或者当前日期和时间(已调整到下一个slot shart_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结果,您可以看到鲍勃在3/06的9:00开始工作,并在该日期预定了2个约会,一个是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)之间的差异足够持续时间。这样,外部查询仅返回那些足够持续时间的时间插槽,然后仅返回每个专业的第一个。

好吧,您去:

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

最新更新