【问题标题】:Interesting SQL Join on dates between dates有趣的 SQL 加入日期之间的日期
【发布时间】:2011-10-30 00:40:36
【问题描述】:

首先,感谢任何帮助我解决此问题的人。我使用的是SQL 2005,但是如果05没有可用的解决方案,可以使用2008。

我有一行看起来像这样的数据:

select * from mySPtable

| myPK | Area | RequestType |  StartDate  |  EndDate  |
   1      SB        ADD        8/14/2011    8/18/2011
   2      NB        RMV        8/16/2011    8/16/2011

所以我想做的是按天计算每个区域的总请求数。结果应该是:

|  myDate  | RequestType |  Area  | myCount |
  8/14/2011      ADD         SB        1
  8/15/2011      ADD         SB        1
  8/16/2011      ADD         SB        1
  8/16/2011      RMV         NB        1
  8/17/2011      ADD         SB        1
  8/18/2011      ADD         SB        1

我该怎么做呢?我被难住了,再多的谷歌搜索也无济于事。

【问题讨论】:

  • 第一行 (myPK == 1) 是否意味着总共有五个请求,每天一个?

标签: sql sql-server-2005 sql-server-2008 datetime join


【解决方案1】:

您需要一个日历表,或者您可以使用 CTE 生成一个。一旦你有了它,剩下的查询应该是相当微不足道的。由于递归问题并且不允许使用聚合,CTE 方法可能有点复杂,所以下面我使用了一个表变量。您也可以将其设为永久表并保存在数据库中。

SET NOCOUNT ON

DECLARE @Calendar TABLE (my_date DATETIME NOT NULL)
DECLARE @date DATETIME, @max_date DATETIME

SELECT @date = MIN(StartDate), @max_date = MAX(EndDate) FROM My_Table

WHILE (@date <= @max_date)
BEGIN
    INSERT INTO @Calendar (my_date) VALUES (@date)
    SELECT @date = DATEADD(dy, 1, @date)
END

SELECT
    C.myDate,
    M.RequestType,
    M.Area,
    COUNT(*) AS myCount
FROM
    @Calendar C
INNER JOIN My_Table M ON
    M.StartDate <= C.myDate AND
    M.EndDate >= C.myDate
GROUP BY
    C.myDate,
    M.RequestType,
    M.Area
ORDER BY
    C.myDate,
    M.RequestType,
    M.Area

根据您的潜在日期范围有多大,填写表格变量可能需要一段时间。例如,如果范围跨越一两年。

【讨论】:

  • 添加一个包含所有日期的表格不是更容易、更快捷吗?而不是在运行时创建它?顺便说一句,很好的解决方案...
  • 这就是我通常会这样做的方式,我确实提到你可以让它成为一个永久的桌子。我的数据库中通常有一个日历,我还可以在其中包含 is_holiday、is_weekday、financial_quarter 等内容,以便更轻松地进行大量查询。
【解决方案2】:

听起来您可能想要'Calendar' file。特别是作为更大的商业组织的一部分,这将变得非常有用。

生成日历后,您可以使用以下内容获取表格:

SELECT a.isoDate, b.RequestType, b.Area, count(*)
FROM calendar as a
JOIN mySPTable as b
ON a.isoDate between b.StartDate and b.EndDate
WHERE a.isoDate >= [input_start_date] 
      AND a.isoDate < [input_end_date]
GROUP BY a.isoDate, b.RequestType, b.Area

这将为日历文件中至少一行 mySPTable 的开始日期和结束日期之间的每个日期生成一行。

附带说明,也可以使用递归 CTE 生成日期范围,但特别是从长远来看,我建议生成和使用日历文件。
快速 CTE:

WITH DateRange (thisDate) as (SELECT [input_start_date]
                              UNION ALL
                              SELECT DATEADD(dy, 1, thisDate)
                              FROM DateRange
                              WHERE thisDate < [input_end_date])

【讨论】:

  • 我是 CTE 的粉丝,但请注意递归次数是有限制的。绝对限制为 32,767,默认限制为 100。请参阅msdn.microsoft.com/en-us/library/ms175972.aspx 上的 MAXRECURSION。
  • 我一直忘记 SQL Server 具有该默认值 - DB2 没有,而且我认为限制是出现内存不足错误。当然,如果您的日期超过了大约一年,那么您可能无论如何都需要一个实际的日历文件......
  • 更正:将 MAXRECURSION 设置为零会禁用任何限制。我的错。
【解决方案3】:

您可以使用数字表(从 0 开始)来执行此操作。在这里,我使用 master..spt_values 代替。 SQL, Auxiliary table of numbers

select dateadd(day, N.Number, M.StartDate) as myDate,
       RequestType,
       Area, 
       count(*) as myCount
from mySPtable as M
  inner join master..spt_values as N
    on N.Number <= datediff(day, M.StartDate, M.EndDate)
where N.type = 'P'
group by dateadd(day, N.Number, M.StartDate),
         RequestType,
         Area
order by dateadd(day, N.Number, M.StartDate)

【讨论】:

  • 我发现这对我遇到的相关问题非常有帮助!
猜你喜欢
  • 2017-11-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-17
  • 2017-06-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多