查找两个日期之间的总小时数

时间:2010-10-01 03:55:06

标签: sql sql-server-2005

我知道有很多重叠的查询问题已经得到解答,但没有一个能够解决我遇到的问题:

我们需要找出最低start_date和最高end_date之间的总小时数,同时考虑重叠范围。

Start_Date      End_Date
3/5/2010 11:27  3/5/2010 13:04 -  Need to include
3/5/2010 11:29  3/5/2010 11:55 -  Can exclude ( overlapping)
3/5/2010 13:13  3/5/2010 13:37 -  Need to include
3/5/2010 13:13  3/5/2010 13:37 -  Duplicate
3/5/2010 14:55  3/5/2010 15:22 -  Need to include
3/5/2010 14:55  3/5/2010 15:22 -  Duplicate
3/5/2010 15:15  3/5/2010 17:45 -  Overlapping with above since it starts at 15.15, 7 minutes before the previous end_date
提前谢谢 约翰D

enter code here

4 个答案:

答案 0 :(得分:1)

SELECT DATEDIFF(hour, MIN(Start_Date), MAX(End_Date)) AS [Diff]
  FROM [table]

MSDN datediff页面:http://msdn.microsoft.com/en-us/library/aa258269%28SQL.80%29.aspx

答案 1 :(得分:1)

这是一个有趣且有趣的问题。 1

让我们开始设置:

CREATE TABLE d (start_dt datetime, end_dt datetime)
INSERT INTO d VALUES ('3/5/2010 11:27', '3/5/2010 13:04')
INSERT INTO d VALUES ('3/5/2010 11:29', '3/5/2010 11:55')
INSERT INTO d VALUES ('3/5/2010 13:13', '3/5/2010 13:37')
INSERT INTO d VALUES ('3/5/2010 13:13', '3/5/2010 13:37')
INSERT INTO d VALUES ('3/5/2010 14:55', '3/5/2010 15:22')
INSERT INTO d VALUES ('3/5/2010 14:55', '3/5/2010 15:22')
INSERT INTO d VALUES ('3/5/2010 15:15', '3/5/2010 17:45')

这是通过TSQL实际计算它的代码:

-- Create a cursor that we will use to loop over all the items in the base table
DECLARE cur CURSOR FOR SELECT start_dt, end_dt FROM d
-- Temp variables to store each rows data
DECLARE @start_dt datetime, @end_dt datetime
-- Temp table to hold the "adjusted" rows
DECLARE @d TABLE (id int identity, start_dt datetime, end_dt datetime)
OPEN cur
FETCH NEXT FROM cur INTO @start_dt, @end_dt
WHILE @@FETCH_STATUS = 0
BEGIN
    -- Start by deleting any rows contained entirely within the current rows timeframe
    DELETE FROM @d WHERE start_dt BETWEEN @start_dt AND @end_dt AND end_dt BETWEEN @start_dt     AND @end_dt

    DECLARE @id_start_in int = -1, @id_end_in int = -1
    SELECT @id_start_in = id FROM @d WHERE @start_dt BETWEEN start_dt AND end_dt
    SELECT @id_end_in = id FROM @d WHERE @end_dt BETWEEN start_dt AND end_dt

    -- If our start and end dates are not contained in any other set, add it as a new row
    IF (@id_start_in = -1 AND @id_end_in = -1)
        INSERT INTO @d (start_dt, end_dt) VALUES (@start_dt, @end_dt)

    -- If our start date and end dates are both contained in the same row, ignore because we are overlapping that row

    -- If our start date and end dates are in two different rows, we combine those two
    -- For example if there are 3 rows, 1-3, 2-5, 4-6, we actually have full coverage from 1-6
    IF (@id_start_in != @id_end_in AND @id_start_in != -1 AND @id_end_in != -1)
    BEGIN
        -- Expand the start row to end at the same time the row our end time is in
        UPDATE @d SET end_dt = (SELECT end_dt FROM @d WHERE id = @id_end_in) WHERE id = @id_start_in
        -- Delete the row our end time is in
        DELETE FROM @d WHERE id = @id_end_in
    END

    -- If our start date is contained in a row but our end date isnt, extend the existing row
    -- to end at our end date
    IF (@id_start_in != -1 AND @id_end_in = -1)
        UPDATE @d SET end_dt = @end_dt WHERE id = @id_start_in

    -- If our end date is contained in a row but our start date isnt, extend the existing row
    -- to start at our start time
    IF (@id_start_in = -1 AND @id_end_in != -1)
        UPDATE @d SET start_dt = @start_dt WHERE id = @id_end_in

    FETCH NEXT FROM cur INTO @start_dt, @end_dt
END
CLOSE cur
DEALLOCATE cur

-- Show the end table
SELECT start_dt, end_dt, DATEDIFF(MINUTE, start_dt, end_dt) FROM @d
-- Sum up to get the minutes and calculate the hours
SELECT SUM(DATEDIFF(MINUTE, start_dt, end_dt)) AS MINUTES, CAST(SUM(DATEDIFF(MINUTE, start_dt, end_dt)) AS DECIMAL) / 60 AS HOURS FROM @d

答案 2 :(得分:0)

受到this answer about flattening date ranges的启发,我提出了这个问题:

-- Required function to flatten the time ranges
-- Assumes the end date is after the start.
CREATE FUNCTION Ranges(@startOfRange DATETIME, @endOfRange DATETIME)
RETURNS @rangesTable TABLE (
    [start] DATETIME NOT NULL,
    [end] DATETIME NOT NULL
    )
AS
BEGIN
    DECLARE @startTime DATETIME
    DECLARE @endTime DATETIME
    DECLARE @m_start DATETIME
    DECLARE @m_end DATETIME

    DECLARE cursorSpans CURSOR FAST_FORWARD FOR
        SELECT DISTINCT [Start_Time], [End_Time]
        FROM [YOUR_TABLE]
        ORDER BY [Start_Time], [End_Time]

    OPEN cursorSpans

    FETCH NEXT FROM cursorSpans INTO @startTime, @endTime

    SET @m_start = @startTime
    SET @m_end = @endTime

    WHILE @@FETCH_STATUS = 0
    BEGIN
        FETCH NEXT FROM cursorSpans INTO @startTime, @endTime

        IF @startTime > @m_end
        BEGIN
            -- Only insert a new record if the current date range does not overlap the previous one.
            INSERT INTO @rangesTable
            VALUES(@m_start, @m_end)

            SET @m_start = @startTime
        END
        SET @m_end = CASE WHEN @endTime > @m_end THEN @endTime ELSE @m_end END
    END

    -- Handle the final date range
    IF @m_start IS NOT NULL
    BEGIN
        INSERT INTO @rangesTable
        VALUES(@m_start, @m_end)
    END

    CLOSE cursorSpans
    DEALLOCATE cursorSpans

    RETURN
END
GO

-- Get the total minute difference between the start and end of each range, then total them up.
SELECT SUM(DATEDIFF(MINUTE, [Start], [End]))
FROM Ranges('2010-03-05 11:00:00', '2010-03-05 18:00:00')
GO

答案 3 :(得分:0)

使用Jeff Wright提供的表格:

CREATE TABLE d (start_dt datetime, end_dt datetime)
INSERT INTO d VALUES ('3/5/2010 11:27', '3/5/2010 13:04')
INSERT INTO d VALUES ('3/5/2010 11:29', '3/5/2010 11:55')
INSERT INTO d VALUES ('3/5/2010 13:13', '3/5/2010 13:37')
INSERT INTO d VALUES ('3/5/2010 13:13', '3/5/2010 13:37')
INSERT INTO d VALUES ('3/5/2010 14:55', '3/5/2010 15:22')
INSERT INTO d VALUES ('3/5/2010 14:55', '3/5/2010 15:22')
INSERT INTO d VALUES ('3/5/2010 15:15', '3/5/2010 17:45')

是时候享受Common Table Expressions

with ranges as (
    select d.start_dt,d.end_dt from d
    union all
    select r1.start_dt,r2.end_dt from d r1 inner join ranges r2 on r1.start_dt < r2.start_dt and r1.end_dt >= r2.start_dt
    union all
    select r1.start_dt,r2.end_dt from ranges r1 inner join d r2 on r2.end_dt > r1.end_dt and r2.start_dt <= r1.end_dt
), maxrange as (
    select start_dt,MAX(end_dt) as end_dt from ranges group by start_dt
), minmaxrange as (
    select MIN(start_dt) as start_dt,end_dt from maxrange group by end_dt
)
select SUM(DATEDIFF(MINUTE,start_dt,end_dt)) from minmaxrange

范围CTE查找所有重叠时段。然后,maxrange CTE找到特定start_dt的最新end_dt,minmaxrange找到特定end_dt的最早start_dt。这两者一起消除了重叠和重复。最后,我们以分钟为单位询问差异的总和。结果是291分钟。