【问题标题】:SQL how to convert row with date range to many rows with each dateSQL如何将具有日期范围的行转换为每个日期的多行
【发布时间】:2013-04-27 20:38:19
【问题描述】:

如果我有一张像这样的表

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() 函数来获取下一个值,但我怀疑这种方法,我这样做的方式有一堆嵌套的自连接,会导致查询时间很长。

【问题讨论】:

  • 您正在重新解释数据,您不应该使用 SQL。在业务逻辑端,这是额外的 2~3 行代码,而不会使数据库负担一些它从来没有想过能够轻松完成的事情。
  • @Niels 你说哪一部分不应该发生在 SQL 服务器上?你是说我应该以我希望它显示的格式存储我的数据,或者我应该使用 SQL 服务器之外的另一个进程以我想要的格式生成输出?
  • 不,您应该像现在一样以标准化的正确可索引方式将数据存储在数据库中。演示不是数据库的任务,所以在调用代码中这样做。

标签: sql sql-server-2008-r2


【解决方案1】:

使用一些样本数据...

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

【讨论】:

  • 尽管spt_values 表是SQL Server 的一个未记录的特性,尽管公认是解决问题的一个相当出色的解决方案,因此不能保证继续工作或永远不会改变。参考this topic
  • 我不相信这张桌子会永远消失,但我添加了一个替代方案。感谢您的反馈
  • 很高兴我找到了这个问题和答案。这是工作面试中测试的一部分。这个目标对我来说很有意义,但解决方案很棘手。很高兴我找到了您简单而完美的解决方案!
  • 小提琴失败了,这在 2019 年仍然有效吗?
  • @Notflip sqlfiddle 没有工作的 sql2014 主机,切换链接以使用 sql2017
【解决方案2】:

您可以使用递归 CTE 来获取两个日期之间的所有日期。另一个 CTE 是获取 ROW_NUMBER 来帮助您解决缺少的 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

【讨论】:

    猜你喜欢
    • 2021-07-20
    • 2020-12-08
    • 2021-04-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-11
    • 2014-07-29
    • 1970-01-01
    相关资源
    最近更新 更多