Sql Server根据时间值将选择行转换为多行

时间:2012-06-08 15:25:14

标签: sql-server sql-server-2005

请查看以下查询:

SELECT ID, START, END FROM TABLEA

结果是:

ID       START                      END
1        2012-06-07 19:32:00        2012-06-08 06:00:00
2        2012-06-08 06:00:00        NULL

但我真的想要这样的结果:

START                      END
2012-06-07 19:32:00        2012-06-07 23:59:59
2012-06-08 00:00:00        NULL

id无关紧要。

看看这个例子:

 ID       START                      END
    1        2012-06-06 19:32:00        2012-06-08 06:00:00
    2        2012-06-08 06:00:00        NULL

必须是:

        START                      END
        2012-06-06 19:32:00        2012-06-06 23:59:59
        2012-06-07 00:00:00        2012-06-07 23:59:59
        2012-06-08 00:00:00        NULL

我们的想法是在同一天内生成包含起始值和结束值的行。

提前致谢。

2 个答案:

答案 0 :(得分:1)

以下解决方案可以在SQL中完成您想要的所有操作。它合并了重叠和连续的时间跨度(包括完全重叠的时间跨度),并且还使用日历表按日期打破了多日时间跨度。

declare @TimeSheetEntries table 
    ( 
    ID int identity not null primary key, 
    [Start] datetime not null, 
    [End] datetime null 
    ); 

declare @CalendarTable table
(
    CalDate datetime
)

DECLARE @CurDate datetime = '2000-01-01 00:00:00';

WHILE @CurDate <= '2020-12-31'
BEGIN
    INSERT INTO @CalendarTable
    SELECT @CurDate;

    SELECT @CurDate = DATEADD(day,1,@CurDate);
END

insert into @TimeSheetEntries 
    ( 
    [Start], 
    [End] 
    ) 
select 
    '2012-06-06 19:32:00', 
    '2012-06-08 06:00:00' 
union all select 
    '2012-06-08 06:00:00', 
    NULL
union all select 
    '2012-06-08 02:00:00', 
    '2012-06-08 03:00:00' 
union all select 
    '2012-01-01 14:09:00', 
    '2012-01-01 17:30:00'
union all select
    '2012-01-01 18:30:00', 
    '2012-01-01 19:30:00';

WITH ClockData AS
(
    SELECT ID, [Start], [End] AS EffectiveEnd, [Start] AS LastStart, 1 AS NumTimespans
    FROM @TimeSheetEntries ts
    WHERE NOT EXISTS 
    (
        SELECT tsWhere.ID FROM @TimeSheetEntries tsWhere 
        WHERE
            tsWhere.ID <> ts.ID -- don't match yourself!
            AND tsWhere.[Start] < ts.[Start]
            AND
            (
                -- Three types of things we don't want our outer SELECT to return
                -- 1) Completed timespans with another appointment's end date sitting inside;
                -- 2) Incomplete timespans (NULL ClockedOut) with another appointment's end date after ClockedIn
                -- 3) Any timespan wholly inside another one
                (ts.[End] IS NULL AND tsWhere.[End] >= ts.[Start]) OR
                (ts.[End] IS NOT NULL AND tsWhere.[End] BETWEEN ts.[Start] AND ts.[End]) OR
                (tswhere.[End] > ts.[End])
            )
    )

    UNION ALL

    SELECT cd.ID, cd.[Start], ts.[End] AS EffectiveClockout, ts.Start AS LastStart, cd.NumTimespans + 1 AS NumTimespans
    FROM @TimeSheetEntries ts
    INNER JOIN ClockData cd
            ON ts.[Start] BETWEEN cd.[Start] AND cd.EffectiveEnd AND ts.ID <> cd.ID 
                AND (ts.[End] IS NULL OR ts.[End] > cd.EffectiveEnd)
),
MergedTimespans AS
(
    SELECT [ID], [Start], [EffectiveEnd], [LastStart]
    FROM ClockData cd
    WHERE NumTimespans = (SELECT MAX(NumTimespans) FROM ClockData WHERE ID = cd.ID)
)
SELECT 
    CASE 
        WHEN mt.Start > cal.CalDate 
            THEN mt.Start 
        ELSE cal.CalDate 
    END AS [Start],
    CASE 
        WHEN (mt.EffectiveEnd IS NOT NULL AND mt.EffectiveEnd >= DATEADD(day,1,cal.CalDate)) OR (mt.EffectiveEnd IS NULL AND cal.CalDate < CAST(mt.LastStart AS date)) THEN DATEADD(second, -1,DATEADD(day,1,cal.CalDate))
        ELSE mt.EffectiveEnd
    END AS [End]
FROM MergedTimespans mt
    INNER JOIN @CalendarTable cal
        ON (mt.EffectiveEnd IS NULL AND cal.CalDate BETWEEN CAST(mt.Start AS date) AND mt.LastStart) -- Handle NULL end dates
         OR (mt.EffectiveEnd IS NOT NULL AND cal.CalDate BETWEEN CAST(mt.Start AS date) AND mt.EffectiveEnd)
ORDER BY Start

答案 1 :(得分:0)

 with       cte
      AS ( SELECT   MIN(START) AS MinStart ,
                    MAX(START) AS MaxStart
           FROM     TableA
           UNION ALL
           SELECT   DATEADD(DAY, 1, MinStart) ,
                    MaxStart
           FROM     cte
           WHERE    DATEADD(DAY, 1, MinStart) BETWEEN MinStart AND MaxStart
         )
SELECT  MinStart AS START ,
        DATEADD(millisecond, -3, FLOOR(CAST(MinStart AS FLOAT)) + 1) AS [End]
FROM    cte