我正在尝试使用SQL SERVER 2008来创建一个函数,该函数在给定的时间间隔内为每个检查点返回一行。
换句话说,给出一个表格告诉我某个对象在某个时间间隔内是活跃的
[ID] [TIME FROM] [TIME TO]
23 12:34:00 13:14:00
作为结果集,我试图获得这样一个表:
[ID] [TimeCheck] [flag]
23 12:00:00 0
23 12:15:00 0
23 12:30:00 1
23 12:45:00 1
23 13:00:00 1
23 13:15:00 0
我可以用来链接到表格,为我提供每个ID的间隔(见下面的 finalgoal )
原因是我想计算一些统计数据来创建图表,我想计算在给定的一组检查点时间检查的ID的数量。
我可以使用光标,但这听起来很粗糙,我希望有人能提出更好的建议
我正在考虑CTE或递归函数,因为这听起来是一个理想的递归问题,但我无法解决它
感谢您的建议! ----编辑--- 当然我可以使用这样的函数:
CREATE FUNCTION test_GetTimePoints (@ID INT, @timeFrom TIME, @timeTo TIME ,@intervalMinutes INT)
RETURNS @result TABLE ( [ID] INT , LasTimeCheck TIME , flag BIT)
AS
BEGIN
DECLARE @curTick INT = DATEDIFF(MINUTE,'00:00:00',@timeFrom)/@intervalMinutes+1
DECLARE @endTick INT = DATEDIFF(MINUTE,'00:00:00',@timeTo)/@intervalMinutes
WHILE ( @curTick < = @endTick )
BEGIN
INSERT INTO @result
SELECT @ID, DATEADD(MINUTE,@curTick*@intervalMinutes,0),1
SET @curTick = @curTick + 1
END
RETURN
END
最终目标会做这样的事情:
select ID,TS_In,TS_end, A.* FROM VEH M LEFT OUTER JOIN (SELECT * FROM
dbo.test_GetTimePoints(ID,M.ts_In,M.ts_end,15) )A ON A.ID=M.ID
这显然无法正常工作,因为我无法将ID,M.ts_In,M.ts_end,15
作为参数提供给函数
有什么想法可以达到同样的效果吗?
答案 0 :(得分:1)
CREATE FUNCTION test_GetTimePoints
(
@ID INT,
@timeFrom TIME,
@timeTo TIME,
@intervalMinutes INT
)
RETURNS TABLE AS RETURN
(
WITH C (LasTimeCheck) AS
(
SELECT DATEADD(MINUTE, @intervalMinutes, @timeFrom)
UNION ALL
SELECT DATEADD(MINUTE, @intervalMinutes, LasTimeCheck)
FROM C
WHERE LasTimeCheck < @timeTo
)
SELECT @ID AS ID,
LasTimeCheck,
1 AS flag
FROM C
)
答案 1 :(得分:0)
如果您已有功能,最简单的方法就是使用OUTER APPLY:
SELECT ID,TS_In,TS_end, A.*
FROM VEH M
OUTER APPLY
(
SELECT *
FROM dbo.test_GetTimePoints(ID, M.ts_In, M.ts_end, 15) A
) A
但是该函数看起来很可疑,因为它没有考虑对象活动间隔之外的时间。您可以将其更改为仅在某个时间间隔(一天?)生成时间列表,与VEH进行交叉连接并在15分钟的开始和结束之间标记时间:
SELECT ID, TS_In, TS_end,
CASE WHEN M.TS_In <= dateadd (minute, 15, A.LastTimeCheck )
AND M.TS_end >= A.LastTimeCheck
THEN 1
ELSE 0
END Flag
FROM VEH M
CROSS JOIN dbo.test_GetTimePoints('07:00:00', '23:00:00', 15) A
要完全消除函数,可以使用递归CTE生成时间表:
; WITH test_GetTimePoints (LastTimeCheck) AS
(
SELECT CAST('07:00:00' as time)
UNION ALL
SELECT dateadd(minute, 15, LastTimeCheck)
FROM test_GetTimePoints
WHERE LastTimeCheck < CAST ('23:00:00' as time)
)
SELECT ID, TS_In, TS_end,
CASE WHEN M.TS_In <= dateadd (minute, 15, A.LastTimeCheck )
AND M.TS_end >= A.LastTimeCheck
THEN 1
ELSE 0
END Flag
FROM VEH M
CROSS JOIN test_GetTimePoints A
<强>更新强>
此查询与任何其他查询一样 - 您可以从应用程序发送参数,即@interval int
可以很好地执行。您仍然可以使用CTE变体,但是您应该过滤掉不需要的时间:
; WITH test_GetTimePoints (LastTimeCheck) AS
(
SELECT CAST('00:00:00' as time)
UNION ALL
SELECT dateadd(minute, @interval, LastTimeCheck)
FROM test_GetTimePoints
WHERE LastTimeCheck < dateadd (minute, - @interval,
CAST ('00:00:00' as time))
)
SELECT ID, TS_In, TS_end
FROM VEH M
CROSS JOIN test_GetTimePoints A
WHERE M.TS_In <= dateadd (minute, @interval, A.LastTimeCheck )
AND M.TS_end >= A.LastTimeCheck
-- Default = 100, but we need to produce up to 1440 records
-- So we turn the limit off with zero.
OPTION (maxrecursion 0)
更好的解决方案是拥有一个日历表(在这种情况下是一天中的所有分钟)并使用CROSS APPLY:
SELECT ID,TS_In,TS_end, A.LastTimeCheck
FROM VEH M
OUTER APPLY
(
SELECT LastTimeCheck
FROM TimeTable A
WHERE (datediff (minute, 0, LastTimeCheck) % @interval = 0)
AND M.TS_In <= dateadd (minute, @interval, A.LastTimeCheck )
AND M.TS_end >= A.LastTimeCheck
) A
可以使用上面的CTE填充TimeTable,并将@interval设置为1。