我有一家公司出租一种特定产品。我想知道特定地点缺货的持续时间(以分钟为单位)。
第一个数据集具有交易历史记录,包括位置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个单位同时出租。
答案 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