如何使用递归查询转换时间点的时间间隔

时间:2012-05-05 09:25:45

标签: sql-server-2008 recursion recursive-query

我正在尝试使用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作为参数提供给函数 有什么想法可以达到同样的效果吗?

2 个答案:

答案 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。