我们有一个表,用于将培训师分配给计划的客户安装。在表格中,每个培训师在一年中的每一天都有一个记录。(我们可以,有时也会在周末工作。)我正在构建一个搜索工具,允许我们的调度程序搜索日期Y和z之间可用的培训师X天。
Table mySchedule
Trainer Date Dirty (Bit)
------------------------------------------------
Joe 06/01/2013 0
Jessica 06/01/2013 0
Alan 06/01/2013 0
Heather 06/01/2013 0
Joe 06/02/2013 1
Jessica 06/02/2013 1
Alan 06/02/2013 0
Heather 06/02/2013 0
Joe 06/03/2013 1
Jessica 06/03/2013 1
Alan 06/03/2013 1
Heather 06/03/2013 0
这是我的表的简化版本,涵盖4个训练者在3天内。如果他们有计划,Dirty = 1。如果它们可以自由调度,则Dirty = 0。
我想构建的是一个允许以下操作的查询:
定义工作需要发生的开始和结束日期。
规定要求培训师连续培训的天数。
返回每个匹配的培训师,以及他们的第一次可用日期,至少等于请求的天数。
纯文本示例:
客户要求培训师在6月份的任何时候到现场两天。查询应该返回:
Alan, 06/01/2013
Heather, 06/01/2013
如果客户将请求更改为6月的三天,查询将返回:
Heather, 06/01/2013
我已经找了几天了,我找到了一些看起来很接近的东西,但最终,我无法让它们工作。在大多数情况下,失败的形式是执行时间过长。这里有一些看起来很有希望的,也许可以被比我打包的SQL-Fu更强的人改编:
- 如何使用SQL 查找表中连续的N条记录
- Microsoft T-SQL连续记录计数
- 如何在具有连续日期和重复数据的记录中查找日期范围
不确定这对更大的数据集的性能如何,但它可以为所提供的数据集获得正确的结果。假设缺失的数据点是可用的。
declare @startDate datetime, @endDate datetime, @days int
select @startDate = '6/1/2013', @endDate='6/3/2013', @days=2
select trainer, min(date)
from
(
select trainer,date,
(select top 1 date
from mySchedule sInner
where sInner.date > sOuter.date
and sInner.trainer = sOuter.trainer
and sInner.Dirty = 1
and sInner.date between @startDate and @endDate
order by sInner.date) as nextDirtyDate
from mySchedule sOuter
where sOuter.dirty=0
and sOuter.date between @startDate and @endDate
) sub
group by trainer, nextDirtyDate
having dateDiff(d, min(date), isNull(nextDirtyDate,dateAdd(d,1,@endDate))) >= @days
假设一个缺失的时间表记录默认为"available",类似这样的事情应该可以工作:
WITH cteRawData As
(
SELECT
S.Trainer,
S.Date,
-- If there are no later busy dates within the period,
-- assume they're free until the end of the period:
IsNull(ED.EndDate, DateAdd(day, 1, @EndDate)) As EndDate
FROM
mySchedule As S
CROSS APPLY
(
SELECT
Min(Date) As EndDate
FROM
mySchedule As S2
WHERE
S2.Trainer = S.Trainer
And
S2.Date > S.Date
And
S2.Date <= @EndDate
And
S2.Dirty = 1
) As ED
WHERE
S.Date Between @StartDate And @EndDate
And
S.Dirty = 0
),
cteData As
(
SELECT
Trainer,
Date,
DateDiff(day, Date, EndDate) As NumberOfDays
FROM
cteRawData
)
SELECT
Trainer,
Min(Date) As EarliestStartDate
FROM
cteData
WHERE
NumberOfDays >= @NumberOfDays
GROUP BY
Trainer
;
http://www.sqlfiddle.com/!3/7b3e2/17
注意:我不知道性能有多好,因为它使用了运行总数。
当dirty = 0时,它会使用dirty标志来获得正数
例如:CASE WHEN B.Dirty = 0 THEN 1 ELSE -1 END
它使用查询
中两个位置的天数S.Date - NNN + 1 AS StartDate
HAVING Sum(CASE WHEN B.Dirty = 0 THEN 1 ELSE -1 END) = NNN
-- Note the need to put the value of 3 to be put at 2 places
SELECT S.Trainer, S.Date - 3 + 1 AS StartDate,
Sum(CASE WHEN B.Dirty = 0 THEN 1 ELSE -1 END) AS RunningAvl
FROM Schedule S INNER JOIN
Schedule B
ON S.Trainer = B.Trainer
WHERE B.Date <= S.Date
AND S.Date BETWEEN '2013-06-01' AND '2013-06-30'
AND B.Date BETWEEN '2013-06-01' AND '2013-06-30'
GROUP BY S.Trainer, S.Date
HAVING Sum(CASE WHEN B.Dirty = 0 THEN 1 ELSE -1 END) = 3
ORDER BY S.Trainer, S.Date ;
-- Note the need to put the value of 2 to be put at 2 places
SELECT S.Trainer, S.Date - 2 + 1 AS StartDate,
Sum(CASE WHEN B.Dirty = 0 THEN 1 ELSE -1 END) AS RunningAvl
FROM Schedule S INNER JOIN
Schedule B
ON S.Trainer = B.Trainer
WHERE B.Date <= S.Date
AND S.Date BETWEEN '2013-06-01' AND '2013-06-30'
AND B.Date BETWEEN '2013-06-01' AND '2013-06-30'
GROUP BY S.Trainer, S.Date
HAVING Sum(CASE WHEN B.Dirty = 0 THEN 1 ELSE -1 END) = 2
ORDER BY S.Trainer, S.Date
http://www.sqlfiddle.com/!3/99f2d/1