SQL如何将具有日期范围的行转换为每个日期的多行

时间:2013-05-03 12:29:18

标签: sql sql-server-2008-r2

如果我有一个看起来像这样的表

begin date      end date        data
 2013-01-01     2013-01-04       7
 2013-01-05     2013-01-06       9

如何让它像这样返回......

    date         data
 2013-01-01       7
 2013-01-02       7
 2013-01-03       7
 2013-01-04       7
 2013-01-05       9
 2013-01-06       9

我想做的一件事就是让另一张表只包含所有日期,然后使用date>=begin datedate<=end date将表格与日期一起加入上表,但这似乎有点笨重的是必须维持那个额外的桌子,只有重复的日期。

在某些情况下,我没有数据范围,只有一个as of日期,基本上看起来像我的第一个例子,但没有end dateend date隐含在下一行'截至'日期(即结束日期应该是下一行的as of -1)。我有一个“解决方案”,它使用row_number()函数来获取下一个值,但我怀疑这种方法,我正在做的方式有一堆嵌套的自联接,有助于很长的查询时间。

2 个答案:

答案 0 :(得分:11)

使用一些样本数据......

create table data (begindate datetime, enddate datetime, data int);
insert data select 
 '20130101', '20130104', 7 union all select
 '20130105', '20130106', 9;

查询 :(注意:如果您已有数字/统计表 - 请使用它)

select dateadd(d,v.number,d.begindate) adate, data
  from data d
  join master..spt_values v on v.type='P'
       and v.number between 0 and datediff(d, begindate, enddate)
order by adate;

<强>结果:

|                       COLUMN_0 | DATA |
-----------------------------------------
| January, 01 2013 00:00:00+0000 |    7 |
| January, 02 2013 00:00:00+0000 |    7 |
| January, 03 2013 00:00:00+0000 |    7 |
| January, 04 2013 00:00:00+0000 |    7 |
| January, 05 2013 00:00:00+0000 |    9 |
| January, 06 2013 00:00:00+0000 |    9 |

或者,您可以动态生成数字表(0-99)或根据需要生成数字

;WITH Numbers(number) AS (
  select top(100) row_number() over (order by (select 0))-1
  from sys.columns a
  cross join sys.columns b
  cross join sys.columns c
  cross join sys.columns d
  )
select dateadd(d,v.number,d.begindate) adate, data
  from data d
  join Numbers v on v.number between 0 and datediff(d, begindate, enddate)
order by adate;

SQL Fiddle Demo

答案 1 :(得分:1)

您可以使用递归CTE获取两个日期之间的所有日期。另一个CTE是让ROW_NUMBERs帮助你找到那些缺少EndDates的东西。

DECLARE @startDate DATE
DECLARE @endDate DATE

SELECT @startDate = MIN(begindate) FROM Table1
SELECT @endDate = MAX(enddate) FROM Table1

;WITH CTE_Dates AS 
(
    SELECT @startDate AS DT
    UNION ALL
    SELECT DATEADD(DD, 1, DT)
    FROM CTE_Dates
    WHERE DATEADD(DD, 1, DT) <= @endDate
)
,CTE_Data AS 
(
    SELECT *, ROW_NUMBER() OVER (ORDER BY BeginDate) AS RN FROM Table1

)
SELECT DT, t1.data FROM CTE_Dates d
LEFT JOIN CTE_Data t1 on d.DT 
BETWEEN t1.[BeginDate] AND COALESCE(t1.EndDate, 
        (SELECT DATEADD(DD,-1,t2.BeginDate) FROM CTE_Data t2 WHERE t1.RN + 1 = t2.RN))

<强> SQLFiddle DEMO