在SQL中实现30天的时间间隔

时间:2013-05-22 21:00:59

标签: sql tsql group-by

我正在重新发布这个问题,因为我还没有找到最佳解决方案。

我正在设计一张能够记录患者血液样本信息的表格。它有患者ID和收集日期 - 收集患者血液样本的日期。

该表还有三个附加列 - episode_number,episode_start_date和episode_end_date。一集不过是一个30天的时间窗口。 30天内收集的任何样本属于同一集。例如,患者于2013年1月1日提交了他的第一份血液样本,并于2013年1月19日提交了下一份血液样本。由于两个收集日期都在相同的“30天窗口”内,因此它们属于相同的episode_number(第1集)。这一集的开始日期将是第一个收集日期(2013年1月1日至2013年1月),结束日期将是开始日期+ 30天(2013年1月30日至1月30日)。在该日期范围内收集的任何数量的患者血液样本属于episode_number = 1。

假设同一名患者于2013年2月4日提交了另一份血液样本。由于该收集日期在episode_number = 1的30天窗口之外,因此它将属于新的episode_number(第2集)。本集的开始日期为2013年2月4日,结束日期为+ 30天,即2013年3月2日。

让我们说下面的示例表如下:

------------------------------------------------------------------------------------------
Patient ID | Collection_Date | Episode_Number     |Episode_Start_Date | Episode_End_Date |
1          | 2013-01-01      |                    |                   |                  |
1          | 2013-01-01      |                    |                   |                  |   
1          | 2013-01-05      |                    |                   |                  |
1          | 2013-02-04      |                    |                   |                  |  
1          | 2013-02-06      |                    |                   |                  |   
1          | 2013-05-01      |                    |                   |                  |
1          | 2013-08-01      |                    |                   |                  |
-------------------------------------------------------------------------------------------

我需要一个查询,根据我上面的文字中描述的逻辑填充episode_number,episode_start_Date和episode_end_date。查询结果应填充下面提到的表值:

----------------------------------------------------------------------------------------
Patient ID | Collection_Date |Episode_number     |Episode_Start_Date| Episode_End_Date |
1          | 2013-01-01      |1                  |2013-01-01        | 2013-01-30       |
1          | 2013-01-01      |1                  |2013-01-01        | 2013-01-30       |
1          | 2013-01-05      |1                  |2013-01-05        | 2013-01-30       |
1          | 2013-02-04      |2                  |2013-02-04        | 2013-03-02       |
1          | 2013-02-06      |2                  |2013-02-04        | 2013-02-04       |
1          | 2013-05-01      |3                  |2013-05-01        | 2013-05-30       |
1          | 2013-08-01      |4                  |2013-08-01        | 2013-08-30       |
----------------------------------------------------------------------------------------

要记住的事情:

  1. 剧集= 30天时间窗口
  2. 第一集开始日期=首次收集日期
  3. 第一个epsiode结束日期=第一集开始日期+30天
  4. 在同一个30天内收集的所有样本属于同一集。
  5. 如果任何样本的收集日期大于前一集合的集合结束日期,则它属于剧集开始日期=收集日期的新剧集;和剧集结束日期=开始日期+ 30天。
  6. 我希望我的问题清楚明白。我的表有超过300万条记录,所以我不仅需要一个有效的解决方案,而且需要一个具有最佳性能的解决方案。任何帮助/建议将不胜感激。

    提前致谢!

    与Ashish

2 个答案:

答案 0 :(得分:1)

(我暂时搁置一段时间,但这并没有实现collection_date必须在episode_start_date和episode_end_date之间的未表达的约束。)

让我们看一下这个表的一部分。

Patient ID | Collection_Date |Episode_number     |Episode_Start_Date| Episode_End_Date |
1          | 2013-01-01      |1                  |2013-01-01        | 2013-01-30       |
1          | 2013-01-01      |1                  |2013-01-01        | 2013-01-30       |

重复行。这张桌子没有钥匙。

不同的东西这两个相同的行应该告诉我们什么?

这种表格呼唤真正的钥匙 - 而不仅仅是另一个身份证号码。

用于存储患者事件信息的表可能需要看起来像这样。

create table patient_episodes (
  patient_id integer not null,
  episode_number integer not null
    check (episode_number > 0),
  primary key (patient_id, episode_number),
  foreign key (patient_id, episode_number) 
    references samples (patient_id, episode_number),

  episode_start_date date not null,
  episode_end_date date not null,
  check (episode_end_date = episode_start_date + interval '30 days')
);

需要最初从patient_episodes到样本声明外键引用,因为patient_episodes为空。我不清楚在两个表填充和稳定之后它是否应该保持这种状态。 (可能不是,但我不想猜。)

您的样品表仍然存在结构问题,因为它没有钥匙。如何解决该问题将对patient_episodes表的结构产生一些影响。

答案 1 :(得分:1)

如果您可以选择更改桌面设计,我建议Mike Sherill回答。

如果您没有该选项,则以下情况应该有效,但性能可能不佳:

with cte as
(select [Patient ID],
        min(Collection_Date) Collection_Date,
        1 Episode_Number,
        min(Collection_Date) Episode_Start_Date,
        Dateadd(d,29,min(Collection_Date)) Episode_End_Date
 from sampleTable
 group by [Patient ID]
 union all
 select s.[Patient ID],
        s.Collection_Date Collection_Date,
        c.Episode_Number+1 Episode_Number,
        s.Collection_Date Episode_Start_Date,
        Dateadd(d,29,s.Collection_Date) Episode_End_Date
 from cte c
 join sampleTable s 
   on c.[Patient ID] = s.[Patient ID] and 
      c.Episode_End_Date < s.Collection_Date and
      not exists (select null
                  from sampleTable i
                  where c.[Patient ID] = i.[Patient ID] and
                        c.Episode_End_Date < i.Collection_Date and
                        i.Collection_Date < s.Collection_Date)
 )
select cte.[Patient ID],
       st.Collection_Date,
       cte.Episode_Number,
       cte.Episode_Start_Date,
       cte.Episode_End_Date
from cte
join sampleTable st 
  on st.[Patient ID] = cte.[Patient ID] and 
     st.Collection_Date between cte.Episode_Start_Date and cte.Episode_End_Date 
option (maxrecursion 0)

SQLFiddle here