根据不同的日期汇总数据

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)

我怎样才能做到这一点?

Sha*_*lle 7

我认为这会让你得到你想要的。

它创建一个递归 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)


dno*_*eth 7

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也会成倍增加。


Ken*_*her 6

您可以通过使用日期表来避免昂贵的递归 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摆脱了任何重复的日期。