tre*_*nja 5 sql-server sql-server-2008-r2
我正在尝试准确计算工具出租的总天数。
这是一个数据示例:
CREATE Table #tmpToolRentalDays
(
ToolId BIGINT,
StartDate DATETIME,
EndDate DATETIME,
RentalDays FLOAT
)
INSERT INTO #tmpToolRentalDays(ToolId, StartDate, EndDate, RentalDays)
values
(39, '2016-02-01 00:00:00.000', '2016-02-01 00:00:00.000', 1),
(39, '2016-02-01 00:00:00.000', '2016-02-02 00:00:00.000', 2),
(39, '2016-02-04 00:00:00.000', '2016-02-05 00:00:00.000', 2),
(39, '2016-02-05 00:00:00.000', '2016-02-06 00:00:00.000', 2),
(39, '2016-02-06 00:00:00.000', '2016-02-07 00:00:00.000', 2),
(36, '2016-02-07 00:00:00.000', '2016-02-28 00:00:00.000', 22),
(39, '2016-02-08 00:00:00.000', '2016-02-09 00:00:00.000', 2),
(39, '2016-02-09 00:00:00.000', '2016-02-10 00:00:00.000', 2),
(11, '2016-02-14 00:00:00.000', '2016-02-28 00:00:00.000', 15),
(39, '2016-02-18 00:00:00.000', '2016-02-21 00:00:00.000', 4)
SELECT * from #tmpToolRentalDays
Run Code Online (Sandbox Code Playgroud)
一个工具可以出去一天,当天返回,然后在同一天再次出去。这应该是 1 天。我试图避免将像 02-01 这样的日期计算两次。
我的目的是获得两列:
ToolID Rental Days
39 13
36 22
11 15
Run Code Online (Sandbox Code Playgroud)
我怎样才能做到这一点?
我认为这会让你得到你想要的。
它创建一个递归 CTE(公用表表达式),生成 1 月 1 日到 3 月 1 日之间的所有日期。
然后将这些日期与工具租赁数据连接起来,检查每个日期记录是否在租赁日期之间。这为您在租赁日期范围内的每一天为每个工具 ID 提供一个记录。
最后,它按工具 ID 分组,并计算租用工具的不同日期以去除重复的日期值。
;WITH Dates(Date_Day) AS
(
SELECT Convert(DateTime, '2016-01-01') AS Date_Day
UNION ALL
SELECT DateAdd(day, 1, Date_Day) FROM Dates
WHERE Date_Day < '2016-03-01'
)
SELECT
Rental_Dates.ToolId,
Count(DISTINCT Calendar_Dates.Date_Day)
FROM
Dates Calendar_Dates
Inner Join
#tmpToolRentalDays Rental_Dates
ON
Calendar_Dates.Date_Day BETWEEN Rental_Dates.StartDate AND Rental_Dates.EndDate
GROUP BY
Rental_Dates.ToolId;
Run Code Online (Sandbox Code Playgroud)
CROSS JOIN
您可以使用这种方法,而不是进行昂贵的SUM
加法聚合潜在的大量行:包括重复的天数并减去具有重复日期的行数:
;with cte as
( select *,
row_number()
over (partition by ToolId
order by StartDate, EndDate) as rn
from #tmpToolRentalDays
)
select t1.ToolId,
-- sum including duplicate days
SUM(t1.RentalDays)
-- number of duplicate dates
- COUNT(T2.RentalDays) As RentalDaysSum
from cte as t1
left join cte as t2
on t1.ToolId = t2.ToolId
and t2.StartDate = t1.EndDate -- same day
and t2.rn = t1.rn + 1 -- on the "next" row?
group by t1.ToolId;
Run Code Online (Sandbox Code Playgroud)
SQL Server 将计算ROW_NUMBER
两次。为了避免这种情况,您可以在创建临时表时简单地添加该列(您编写的表是生成的)。
ROW_NUMBER
这里需要,因为可能有多个单日租用的迷你笛卡尔积。如果没有行号,每个单日租金都会匹配每个当日租金,因此例如,对于两行,您将获得四个作为连接的结果。SUMs
如果另一个多日租赁与许多单日租赁在同一天开始或结束,则偏差会特别大,因为这样SUM
也会成倍增加。
您可以通过使用日期表来避免昂贵的递归 CTE。这些很容易构建并且非常有用。我在这里使用了代码的变体来生成一个.
注意:我在这里使用临时表作为日期。这仅用于演示代码。永久解决方案应使用永久日期表。它可能还应该包括我从原始代码中删除的一些/所有列。(日、周等)这些对于各种与日期相关的查询都非常方便。
DECLARE @StartDate DATE = '20000101', @NumberOfYears INT = 30;
-- prevent set or regional settings from interfering with
-- interpretation of dates / literals
SET DATEFIRST 7;
SET DATEFORMAT mdy;
SET LANGUAGE US_ENGLISH;
DECLARE @CutoffDate DATE = DATEADD(YEAR, @NumberOfYears, @StartDate);
-- this is just a holding table for intermediate calculations:
CREATE TABLE #dim
(
[date] DATE PRIMARY KEY
);
-- use the catalog views to generate as many rows as we need
INSERT #dim([date])
SELECT d
FROM
(
SELECT d = DATEADD(DAY, rn - 1, @StartDate)
FROM
(
SELECT TOP (DATEDIFF(DAY, @StartDate, @CutoffDate))
rn = ROW_NUMBER() OVER (ORDER BY s1.[object_id])
FROM sys.all_objects AS s1
CROSS JOIN sys.all_objects AS s2
-- on my system this would support > 5 million days
ORDER BY s1.[object_id]
) AS x
) AS y;
Run Code Online (Sandbox Code Playgroud)
加上你上面的代码,我们得到了这个非常简单的代码来生成你的解决方案。
SELECT #tmpToolRentalDays.ToolId, COUNT(DISTINCT #dim.[Date]) AS Cnt
FROM #tmpToolRentalDays
JOIN #dim
ON #dim.[Date] >= #tmpToolRentalDays.StartDate
AND #dim.[Date] <= #tmpToolRentalDays.EndDate
GROUP BY #tmpToolRentalDays.ToolId
Run Code Online (Sandbox Code Playgroud)
在COUNT DISTINCT
摆脱了任何重复的日期。