【问题标题】:How to Duplicate Records According to Start and End Dates如何根据开始和结束日期复制记录
【发布时间】:2018-05-16 22:40:35
【问题描述】:

我在 SQL 数据库中有需要扩展的 startDateendDate 记录。

| userName    | startDate  | endDate    | weekDay |  
| :---------: | :--------: | :--------: | :-----: |  
| Test User 1 | 2011-03-30 | 2011-04-05 | 1       |  
| Test User 2 | 2016-10-05 | 2016-10-07 | 5       |  
| Test User 3 | 2018-05-22 | 2018-05-26 | 4       |  

在上表中,每条记录都包含涵盖多个日期的信息。我需要的是每个用户每个一个日期一个记录。我正在寻找的一个例子:

| userName    | startDate  | weekDay    |
| :---------: | :--------: | :--------: |
| Test User 1 | 2011-03-30 | 1          |
| Test User 1 | 2011-03-31 | 1          |
| Test User 1 | 2011-04-01 | 1          |
| Test User 1 | 2011-04-02 | 1          |
| Test User 1 | 2011-04-03 | 1          |
| Test User 1 | 2011-04-04 | 1          |
| Test User 1 | 2011-04-05 | 1          |
| Test User 2 | 2016-10-05 | 5          |
| Test User 2 | 2016-10-06 | 5          |
| Test User 2 | 2016-10-07 | 5          |
| Test User 3 | 2018-05-22 | 4          |
| Test User 3 | 2018-05-23 | 4          |
| Test User 3 | 2018-05-24 | 4          |
| Test User 3 | 2018-05-25 | 4          |
| Test User 3 | 2018-05-26 | 4          |

This answer 让我更进一步,指定如何在 SQL 中生成日期序列。如何在 SQL 中根据开始和结束日期复制表格记录?

请注意,我需要这个解决方案同时在 MSSQL 和 PostgreSQL 中工作。

【问题讨论】:

  • 您可能想看看 MS SQL 的这个问题:Custom SQL Calendar
  • 谢谢,这些很有帮助。您链接的第二个问题中的cross join 很有趣,它可以在这里工作,但是每一行都需要与一组不同的日期交叉连接。所以我想我理解它的逻辑,但我很难过的是每一行都需要重复不同的次数,并且在不同的日期......我能想到的唯一方法是循环遍历每条记录,定义一个startDateendDate,然后加入一个日期序列表,但我也认为必须有更好/更有效的方法来做到这一点..
  • 假设你有一个列出所有相关日期的日历表——无论如何你可能应该这样做——这是一个简单的PERIODS_TABLE p JOIN CALENDAR c ON c.date BETWEEN p.startDate AND p.endDate
  • @tsouchlarakis:是的,交叉连接不会根据您的需要为您提供准确的重复数。它将为每个提供相同的内容,并且在每种情况下,重复的行都比您需要的要多得多。但这不是问题,因为您只需通过添加一个条件过滤掉不需要的重复项,该条件将测试日历表日期是否在您的开始日期和结束日期之间(如 Nockolay 所示)。在某些情况下,引入循环可能更有效,但通常比使用日历表更糟糕。这就是日历表如此普遍的原因。

标签: sql sql-server postgresql date-range


【解决方案1】:

您可以在 SQL Server 和 Postgres 中使用递归 CTE,但语法略有不同。而且,在 Postgres 中有一个更简单的方法。因此,在 SQL Server 中,您可以这样做:

with cte as (
      select username, startdate, weekday, enddate
      from t
      union all
      select username, dateadd(day, 1, startdate) weekday, enddate
      from cte
      where startdate < enddate
     )
select username, startdate, weekday
from cte
order by username, startdate;

您可以调整日期算术并为 Postgres 添加 recursive 关键字。

Postgres 中更简单的方法是横向连接:

select t.username, g.startdate, t.weekday
from t, lateral
     generate_series(start_date, end_date, interval '1 day') g(startdate);

如果您需要相同的代码在两者中工作,则需要生成一个数字表。这是一种(不愉快的)方法:

with digits as (
      select v.n
      from (values (0), (1), (2), (3), (4), (5), (6), (7), (8), (9)) v(n)
     ),
     n as (
      select d1.n * 100 + d2.n * 10 + d3.n as n
      from digits d1 cross join digits d2 cross join digits d3
     )
select t.username, t.startdate + n.n, t.weekday
from t join
     n 
     on t.startdate + n.n <= t.enddate;

请注意,要使其正常工作,startdate 在 SQL Server 中必须是 datetime,而在 Postgres 中必须是 date

【讨论】:

  • 感谢您的回答。但是我不能让它在 MSSQL 中工作。我必须更改一些东西才能让它运行,当它运行时,它看起来就像给我的输出与SELECT username, startdate, weekday FROM t 给我的输出一样。我必须在WITH 内的两个子查询中将startdate 转换为datetime,就像你说的那样避免invalid type 错误。我还不得不将dateadd(day, 1, startdate) weekday 更改为dateadd(day, 1, startdate) as startdate, weekday,因为UNION 给我带来了双方没有相同列的问题。不确定我哪里出错了
  • 作为后续,我刚刚让它为 MSSQL 工作!谢谢!只需要做出我上面提到的那些改变。
【解决方案2】:

试试下面的代码。我使用了递归公用表表达式。

;with cte
AS
(
  SELECT userName,startDate,startDate AS endDate,weekDay FROM tab1
    Union all
  SELECT t1.userName,DATEADD(d,1,t1.startdate) AS startDate,
  DATEADD(d,1,t1.startdate) AS startDate,t1.weekDay
  FROM cte t1
 JOIN tab1 t2 on t1.userName=t2.userName
 WHERE t2.endDate>t1.endDate
) 

 Select userName,startDate,weekDay from cte order by userName

SQL 小提琴:http://sqlfiddle.com/#!18/fa22a/3

【讨论】: