【问题标题】:Query that is getting average, maximum, and minimum value of days between two dates in each year获取每年两个日期之间天数的平均值、最大值和最小值的查询
【发布时间】:2014-12-19 16:32:14
【问题描述】:

我有一张租船的桌子:
出租:
- 租期PK
- 返回日期,
- 船号
以及此处不需要的其他字段

我需要进行一个返回四列的查询:
- 年
- 今年平均租房天数
- 今年最多租用天数
- 今年最少租用天数

现在我有这个查询:

SELECT 
YEAR(RentDate), 
AVG(DATEDIFF(DD, RentDate, ReturnDate))  AS AVERAGE, 
MAX(DATEDIFF(DD, RentDate, ReturnDate)) AS MAXIMUM, 
MIN(DATEDIFF(DD, RentDate, ReturnDate)) AS MINIMUM  
FROM RENTING
WHERE YEAR(RentDate) = YEAR(ReturnDate)
GROUP BY YEAR(RentDate)

问题是我正在考虑租金在年底开始的可能性,并且在另一年结束 - RentDate Year != ReturnDate。我认为这个查询不包括这种可能性。

【问题讨论】:

  • 您的Renting 表需要RentDateBoatId 的复合主键,而不仅仅是RentDate。或者你每天只能租一艘船?
  • 我每天可以租很多船,但我需要的查询中的共同部分是什么?我不明白。
  • 道歉我没有回答你发布的问题,只是对桌子设计发表评论。如果 PK 只是日期字段,您将无法为该天输入多于一行;尽管我猜您的 RentDate 字段也包含时间,因此您每天可以插入多个租金。在规范化数据库中,您可能希望有一个复合键以允许完全同时进行多个租赁。我的评论更多是关于数据库设计的学术观点,但作为设计数据库的人,当我看到您的表格描述时,它脱颖而出:)

标签: sql max average min datediff


【解决方案1】:

我认为这个查询不包括这种可能性

不,它没有,但它很容易修复 - 只需删除您添加的 WHERE 子句:

SELECT 
YEAR(RentDate), 
AVG(DATEDIFF(DD, RentDate, ReturnDate))  AS AVERAGE, 
MAX(DATEDIFF(DD, RentDate, ReturnDate)) AS MAXIMUM, 
MIN(DATEDIFF(DD, RentDate, ReturnDate)) AS MINIMUM  
FROM RENTING
/*WHERE YEAR(RentDate) = YEAR(ReturnDate)*/
GROUP BY YEAR(RentDate)

DATEDIFF 工作正常,即使年份发生变化。

【讨论】:

  • 是的,没错,但是如果我将最后一个 GROUP BY 子句更改为 ReturnDate,它不会对计算产生影响吗?在这两种情况下,它都在计算一个可能最长的时间跨度,并且与一年结束和下一年开始有关。如果我认为错了,请纠正我。
  • 嗯,是的,如果你按 YEAR(ReturnDate) 分组,你会得到不同的结果。按照现在的情况,如果一艘船在 2013 年 12 月 31 日租用并在 2014 年 1 月 1 日归还,则在 2013 年将计为 2 天租期。这不是您想要的吗?
  • 是的,这就是我的意思,也是我不想要的。例如,如果一艘船于 2013 年 12 月 15 日租用并于 2014 年 1 月 10 日归还,则此期间将计为 2013 年或 2014 年的最大值(取决于 groupby 条款),此时它应该只占一部分到年底,或从年初开始。如何做到这一点?
  • @user2838197:实际上,如果一艘船在 13 年 12 月 31 日租用并于 14 年 1 月 1 日归还,DATEDIFF(DD, RentDate, ReturnDate) 会给你 1 而不是 2。所以我的问题是,应该ReturnDate真的算入天数吗?
【解决方案2】:

根据您对 D Stanley 回答的评论,您希望拆分任何跨越两年(或更多)年的租金,并将部分租金归于每年。

为此,您需要计算每年的租金天数。我知道的最简单的方法是使用Calendar Table。在您的情况下,您最感兴趣的是一年中的每一天的租金。

给定一个日历表:

CREATE TABLE Calendar
    ([CalendarDate] date, [CalendarYear] char(4))

您将租赁表加入日历,日历会将每个租赁期扩展到与租赁天数相等的行数。您可以COUNT 按年份分组的天数,按日历年拆分租金。

SELECT RentDate, COUNT(*) AS DayCount, CalendarYear
FROM Renting INNER JOIN Calendar ON CalendarDate >= RentDate 
  AND CalendarDate < ReturnDate
GROUP BY RentDate, CalendarYear

包含 PK RentDate 以区分每个不同的租金。否则,您每年都会得到一个数字,并且无法计算您的最小值、最大值和平均值。

要获取聚合值,请将第一个查询包装在另一个查询中:

SELECT CalendarYear,
       AVG(DayCount) AS AVERAGE,
       MAX(DayCount) AS MAXIMUM, 
       MIN(DayCount) AS MINIMUM  
FROM (
    SELECT RentDate, COUNT(*) AS DayCount, CalendarYear
    FROM Renting INNER JOIN Calendar ON CalendarDate >= RentDate 
      AND CalendarDate < ReturnDate
    GROUP BY RentDate, CalendarYear
) AS T

这是SQL Fiddle,您可以看到它的实际效果。

【讨论】:

    【解决方案3】:

    从表面上看,您需要在年底拆分覆盖年终的租金。我会使用我喜欢称之为测试驱动查询设计 (TDQD) 的方式来构建查询。

    一年的租金

    这可能涵盖了大部分数据:

    SELECT YEAR(RentalDate) AS RentalYear,
           DATEDIFF(dd, RentalDate, ReturnDate) AS RentalDays
      FROM Renting
     WHERE YEAR(RentalDate) = YEAR(ReturnDate)
    

    跨年的租金

    此查询处理起始年份的租金部分:

    SELECT YEAR(RentalDate) AS RentalYear,
           DATEDIFF(dd, RentalDate, DATEFROMPARTS(YEAR(RentalDate), 12, 31)) AS RentalDays
      FROM Renting
     WHERE YEAR(RentalDate) + 1 = YEAR(ReturnDate)
    

    这个查询处理的是下一年的租金部分:

    SELECT YEAR(ReturnDate) AS RentalYear,
           DATEDIFF(dd, DATEFROMPARTS(YEAR(ReturnDate), 1, 1), ReturnDate) AS RentalDays
      FROM Renting
     WHERE YEAR(RentalDate) + 1 = YEAR(ReturnDate)
    

    租期超过两年

    这还是比较棘手的。我要指出的是,理论上,单次租金可以从 2011-04-14 开始,到 2014-09-30 结束(为了争论),在这种情况下,2011 年有部分年租金,在 2012 年和 2013 年租了两次全年租金(但一个租了 366 天,另一个租了 365 天),然后是 2014 年的部分年租。但我不会解决这部分问题。

    不带聚合的查询

    前面三个查询需要结合 UNION ALL 来创建应该运行聚合的原始数据:

    SELECT YEAR(RentalDate) AS RentalYear,
           DATEDIFF(dd, RentalDate, ReturnDate) AS RentalDays
      FROM Renting
     WHERE YEAR(RentalDate) = YEAR(ReturnDate)
    UNION ALL
    SELECT YEAR(RentalDate) AS RentalYear,
           DATEDIFF(dd, RentalDate, DATEFROMPARTS(YEAR(RentalDate), 12, 31)) AS RentalDays
      FROM Renting
     WHERE YEAR(RentalDate) + 1 = YEAR(ReturnDate)
    UNION ALL
    SELECT YEAR(ReturnDate) AS RentalYear,
           DATEDIFF(dd, DATEFROMPARTS(YEAR(ReturnDate), 1, 1), ReturnDate) AS RentalDays
      FROM Renting
     WHERE YEAR(RentalDate) + 1 = YEAR(ReturnDate)
    

    聚合数据

    SELECT RentalYear,
           AVG(RentalDays) AS Average,
           MIN(RentalDays) AS Minimum,
           MAX(RentalDays) AS Maximum
      FROM (SELECT YEAR(RentalDate) AS RentalYear,
                   DATEDIFF(dd, RentalDate, ReturnDate) AS RentalDays
              FROM Renting
             WHERE YEAR(RentalDate) = YEAR(ReturnDate)
            UNION ALL
            SELECT YEAR(RentalDate) AS RentalYear,
                   DATEDIFF(dd, RentalDate, DATEFROMPARTS(YEAR(RentalDate), 12, 31)) AS RentalDays
              FROM Renting
             WHERE YEAR(RentalDate) + 1 = YEAR(ReturnDate)
            UNION ALL
            SELECT YEAR(ReturnDate) AS RentalYear,
                   DATEDIFF(dd, DATEFROMPARTS(YEAR(ReturnDate), 1, 1), ReturnDate) AS RentalDays
              FROM Renting
             WHERE YEAR(RentalDate) + 1 = YEAR(ReturnDate)
           ) AS Rentals
     GROUP BY RentalYear
    

    【讨论】:

    • 还没有检查完整的答案,但这一部分肯定是个问题:使用DATEFROMPARTS(YEAR(RentalDate), 12, 31)作为结束日期会导致损失一天。如果您想象一个恰好在 12 月 31 日开始的范围,则很容易看到问题:DATEDIFF(DAY, '****1231', '****1231') 将给您0,而您想要1。因此,为了获得正确的结果,应将其替换为 DATEFROMPARTS(YEAR(RentalDate) + 1, 1, 1)
    • @AndriyM:问题中显示的计算假设开始日期和结束日期不同,因为当开始日期和结束日期相同时它产生 0。 Off-by-one 是一个很容易修复的细节——也是为什么应该使用 TDQD(随手使用数据进行测试)的关键部分。关键概念是三路联合。顺便说一句,忽略年终,根据问题,从除夕到元旦的租金应该是 1 天,而不是 2 天。 (但这可能是一个错误!)我现在要睡觉了(至少比就寝时间晚了一个小时)。我将在今天晚些时候审查并修复代码。
    • 好点,一开始我没有意识到这一点。 Asked the OP to clarify.
    • 我正在检查最后一个查询,但这里的聚合查询有一些问题,它说我:kwyword GROUP 附近的语法不正确。
    • 哦,我没有给工会起个名字……加AS rentals或类似的。
    猜你喜欢
    • 2016-07-09
    • 2019-11-02
    • 2020-08-22
    • 1970-01-01
    • 1970-01-01
    • 2020-04-30
    • 1970-01-01
    • 1970-01-01
    • 2018-04-30
    相关资源
    最近更新 更多