我正在建立一个调度系统来跟踪专业人员的日程安排和可用性。用户应该能够输入一些标准,并查看按专业人员下次预约时间排序的最佳匹配专业人员列表。返回这些专业人员的查询有效,但随着管理专业人员数量的增加,返回结果所需的时间变得荒谬。
系统使用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上的方法(对我的实际设置进行一些修改)来查找下一个可用性,但就像我说的那样,是专业人员的数量,以及他们的可用性和约会,数量增加,返回结果所需的时间变得不合理。
而且,另外一个要求,只是为了使这更加困难,结果不应该包括任何没有任何即将到来的可用性的专业人士。
我认为,瓶颈在于,必须为所有这些专业人士计算下一个可用性。我已经考虑建立一个可用的约会表,但是对该表的维护将是令人望而却步的。关于如何有效地工作的任何想法?
提前感谢您的协助。
已编辑包含工作查询:
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_date_time,则“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结果,您可以看到Bob在3月6日9:00开始工作,并且在该日期预订了2次预约,一次是9:00-9:30,另一次是10:00-10 :30。外部查询正在做的是获取上面的表并将其连接到自身,偏移一行,以便Bob的上述数据看起来像(所有日期都是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 :(得分: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