计算时间戳重叠持续时间

时间:2016-06-15 19:55:51

标签: sql timestamp overlap duration

我有一家公司出租一种特定产品。我想知道特定地点缺货的持续时间(以分钟为单位)。

第一个数据集具有交易历史记录,包括位置ID,租赁开始时间戳和租赁结束时间戳。

第二个数据集包含当天可用于租借的位置ID,日期和数量。随着频繁添加/删除单位,单位数量可以每天更改。

我需要计算每个位置每天所有可用单位租金的分钟数。

例如:2016年2月1日,地点A有3个单位。 2016年2月1日多少分钟(如果有的话)有3个单位同时出租?

SQL是我需要使用的语言。

见下面的样本数据集Y:

LOC_ID, ACT_RNTL_BGN_TS, ACT_RNTL_END_TS

A   30Jan2016 19:54:37.000  01Feb2016 10:00:24.053

A   31Jan2016 16:30:23.000  01Feb2016 9:07:06.588

A   01Feb2016 9:22:22.000   02Feb2016 10:00:23.716

A   01Feb2016 9:36:11.000   01Feb2016 11:05:34.249

A   01Feb2016 10:27:34.000  01Feb2016 12:59:29.883

A   01Feb2016 10:40:38.000  01Feb2016 15:36:27.119

A   01Feb2016 12:43:10.000  01Feb2016 14:23:15.914

A   01Feb2016 13:28:20.000  01Feb2016 14:40:15.573

A   01Feb2016 17:03:13.000  01Feb2016 19:02:57.413

A   01Feb2016 17:17:12.000  01Feb2016 18:54:14.708

以下示例数据集Z:

LOC_ID, Date, Unit_Count

A 01Feb2016 3

A 02Feb2016 4

A 03Feb2016 3

B 01Feb2016 2

B 02Feb2016 2

B 03Feb2016 2

由于位置A在2月1日总共有3个单位,因此所需的输出将是25分钟,这是2月1日在位置A同时出租3个单位的总时间。上午10:40上午11点05分,3个单位同时出租。

1 个答案:

答案 0 :(得分:0)

这是一些T-SQL代码(您没有说明您使用的是哪个DBMS)

-- DECLARING SAMPLE DATA
DECLARE @Z table (LOC_ID char, Date date, Unit_Count int)

INSERT @Z ( LOC_ID, Date, Unit_Count ) VALUES  ( 'A', '2016-02-01', 3)
INSERT @Z ( LOC_ID, Date, Unit_Count ) VALUES  ( 'A', '2016-02-02', 4)
INSERT @Z ( LOC_ID, Date, Unit_Count ) VALUES  ( 'A', '2016-02-03', 3)
INSERT @Z ( LOC_ID, Date, Unit_Count ) VALUES  ( 'B', '2016-02-01', 2)
INSERT @Z ( LOC_ID, Date, Unit_Count ) VALUES  ( 'B', '2016-02-02', 2)
INSERT @Z ( LOC_ID, Date, Unit_Count ) VALUES  ( 'B', '2016-02-03', 2)

DECLARE @Y table (LOC_ID char, ACT_RNTL_BGN_TS datetime, ACT_RNTL_END_TS datetime)

INSERT @Y ( LOC_ID, ACT_RNTL_BGN_TS, ACT_RNTL_END_TS ) VALUES ('A', '2016-01-30 19:54:37.000', '2016-02-01 10:00:24.053')
INSERT @Y ( LOC_ID, ACT_RNTL_BGN_TS, ACT_RNTL_END_TS ) VALUES ('A', '2016-01-31 16:30:23.000', '2016-02-01 09:07:06.588')
INSERT @Y ( LOC_ID, ACT_RNTL_BGN_TS, ACT_RNTL_END_TS ) VALUES ('A', '2016-02-01 09:22:22.000', '2016-02-02 10:00:23.716')
INSERT @Y ( LOC_ID, ACT_RNTL_BGN_TS, ACT_RNTL_END_TS ) VALUES ('A', '2016-02-01 09:36:11.000', '2016-02-01 11:05:34.249')
INSERT @Y ( LOC_ID, ACT_RNTL_BGN_TS, ACT_RNTL_END_TS ) VALUES ('A', '2016-02-01 10:27:34.000', '2016-02-01 12:59:29.883')
INSERT @Y ( LOC_ID, ACT_RNTL_BGN_TS, ACT_RNTL_END_TS ) VALUES ('A', '2016-02-01 10:40:38.000', '2016-02-01 15:36:27.119')
INSERT @Y ( LOC_ID, ACT_RNTL_BGN_TS, ACT_RNTL_END_TS ) VALUES ('A', '2016-02-01 12:43:10.000', '2016-02-01 14:23:15.914')
INSERT @Y ( LOC_ID, ACT_RNTL_BGN_TS, ACT_RNTL_END_TS ) VALUES ('A', '2016-02-01 13:28:20.000', '2016-02-01 14:40:15.573')
INSERT @Y ( LOC_ID, ACT_RNTL_BGN_TS, ACT_RNTL_END_TS ) VALUES ('A', '2016-02-01 17:03:13.000', '2016-02-01 19:02:57.413')
INSERT @Y ( LOC_ID, ACT_RNTL_BGN_TS, ACT_RNTL_END_TS ) VALUES ('A', '2016-02-01 17:17:12.000', '2016-02-01 18:54:14.708')
;

-- START OF QUERY
WITH SplittedRentedIntervals AS ( -- Intervals that span more than one day are splited.
    SELECT Z.LOC_ID
         , Z.Date
         , BGN = CASE WHEN Y.ACT_RNTL_BGN_TS > CAST(Z.Date AS datetime) THEN Y.ACT_RNTL_BGN_TS ELSE CAST(Z.Date AS datetime) END
         , [END] = CASE WHEN Y.ACT_RNTL_END_TS < CAST(DATEADD(DAY, 1, Z.Date) AS datetime) THEN Y.ACT_RNTL_END_TS ELSE CAST(DATEADD(DAY, 1, Z.Date) AS datetime) END
    FROM @Z Z
    JOIN @Y Y
    ON Y.LOC_ID = Z.LOC_ID
    AND (Z.Date = CAST(Y.ACT_RNTL_BGN_TS AS date)
      OR Z.Date = CAST(Y.ACT_RNTL_END_TS AS date)
      OR (Z.Date BETWEEN Y.ACT_RNTL_BGN_TS AND Y.ACT_RNTL_END_TS))
), Times AS ( -- All times starting and ending any interval, including start and end of day.
          SELECT DISTINCT LOC_ID, Date, TIME = BGN FROM SplittedRentedIntervals
    UNION SELECT DISTINCT LOC_ID, Date, TIME = [END] FROM SplittedRentedIntervals
    UNION SELECT LOC_ID, Date, TIME = CAST(Date AS datetime) FROM @Z
    UNION SELECT LOC_ID, Date, TIME = CAST(DATEADD(DAY, 1, Date) AS datetime) FROM @Z
), OrderedTimes AS (
    SELECT LOC_ID
         , Date
         , TIME
         , NUM = ROW_NUMBER() OVER (PARTITION BY LOC_ID, Date ORDER BY TIME ASC)
    FROM Times
), Intervals AS ( -- Intervals are conformed by two consecutives times.
    SELECT OT1.LOC_ID
         , OT1.Date
         , BGN = OT1.TIME
         , [END] = OT2.TIME
    FROM OrderedTimes OT1
    JOIN OrderedTimes OT2
    ON OT2.LOC_ID = OT1.LOC_ID
    AND OT2.Date = OT1.Date
    AND OT2.NUM = OT1.NUM + 1
), StockByInterval AS ( -- Intersect all time intervals with rented intervals to calculate rented units.
    SELECT I.LOC_ID
         , I.Date
         , I.BGN
         , I.[END]
         , STOCK = Z.Unit_Count
                 - (SELECT COUNT(*)
                    FROM SplittedRentedIntervals SRI
                    WHERE I.Date = SRI.Date
                    AND I.LOC_ID = SRI.LOC_ID
                    AND SRI.BGN < I.[END]
                    AND SRI.[END] > I.BGN)
    FROM Intervals I
    JOIN @Z Z
    ON Z.Date = I.Date
    AND Z.LOC_ID = I.LOC_ID
), WithuotStock AS ( -- Sum the minutes of intervals where there is no stock.
    SELECT LOC_ID
         , Date
         , MinutesWithoutStock = SUM(DATEDIFF(MINUTE, BGN, [END]))
    FROM StockByInterval
    WHERE STOCK <= 0 -- Sample data has some intervals where there are more items rented than are available.
    GROUP BY LOC_ID, Date
)
SELECT Z.LOC_ID
     , Z.Date
     , MinutesWithoutStock = ISNULL(WS.MinutesWithoutStock, 0)
FROM @Z Z
LEFT JOIN WithuotStock WS
ON WS.Date = Z.Date
AND WS.LOC_ID = Z.LOC_ID
ORDER BY Z.LOC_ID, Z.Date

示例输出

LOC_ID Date       MinutesWithoutStock
------ ---------- -------------------
A      2016-02-01 374
A      2016-02-02 0
A      2016-02-03 0
B      2016-02-01 0
B      2016-02-02 0
B      2016-02-03 0