在SQL中的两个日期之间拆分数据

时间:2013-01-30 10:28:30

标签: sql sql-server-2008

我的表中有两个名为Start_date和End_date的日期列。我需要先了解这两个日期之间有多少周并分割数据。

- 例如如果数据如下,

ID  Start_date  End_date   No_Of_Weeks
1   25-Apr-11   8-May-11     2
2   23-Apr-11   27-May-11    6

- 我需要这样的结果:

ID  Start_date       End_date

1       25-Apr-2011     01-May-2011
1       02-May-2011     08-May-2011  

2       23-Apr-2011     24-Apr-2011
2       25-Apr-2011     01-Apr-2011
2       02-May-2011     08-May-2011
2       09-May-2011     15-May-2011
2       16-May-2011     22-May-2011
2       23-May-2011     27-May-2011

请帮我解决一下这个问题。我的星期开始日期是星期一。

9 个答案:

答案 0 :(得分:1)

根据最后的理解,这将有效:

with demo_cte as 
(select id,
 start_date,
 dateadd(day,6,DATEADD(wk, DATEDIFF(wk,0,start_date), 0)) end_date,
 end_date last_end_date,
 no_of_weeks no_of_weeks from demo


 union all

 select id,dateadd(day,1,end_date),
   dateadd(day,7,end_date),

 last_end_date
 ,no_of_weeks-1 from demo_cte

 where no_of_weeks-1>0)

 select id, start_date,
case
when end_date<=last_end_date then end_date
else
last_end_date
end
end_date
from demo_cte order by id,no_of_weeks desc

SQL Fiddle

如果无法使用周数,请使用:

with demo_cte as 
(select id,
 start_date,
 dateadd(day,6,DATEADD(wk, DATEDIFF(wk,0,start_date), 0)) end_date,
 end_date last_end_date
 --,no_of_weeks no_of_weeks
 from demo


 union all

 select id,dateadd(day,1,end_date),
   dateadd(day,7,end_date),

 last_end_date
 --,no_of_weeks-1 
 from demo_cte

 where --no_of_weeks-1>0
 dateadd(day,7,end_date)<=last_end_date 
)

 select id, start_date,
case
when end_date<=last_end_date then end_date
else
last_end_date
end
end_date
from demo_cte order by id,start_date
--,no_of_weeks desc

答案 1 :(得分:1)

您可以使用定义周数的日历表并将其加入您的数据。

我为以下内容创建了sql fiddle

CREATE TABLE Calendar_Weeks (
  week_start_date date,
  week_end_date date )

CREATE TABLE Sample_Data (
  id int,
  start_date date,
  end_date date )

INSERT Calendar_Weeks (week_start_date, week_end_date) VALUES ('2011-04-18','2011-04-24')
INSERT Calendar_Weeks (week_start_date, week_end_date) VALUES ('2011-04-25','2011-05-01')
INSERT Calendar_Weeks (week_start_date, week_end_date) VALUES ('2011-05-02','2011-05-08')
INSERT Calendar_Weeks (week_start_date, week_end_date) VALUES ('2011-05-09','2011-05-15')
INSERT Calendar_Weeks (week_start_date, week_end_date) VALUES ('2011-05-16','2011-05-22')
INSERT Calendar_Weeks (week_start_date, week_end_date) VALUES ('2011-05-23','2011-05-29')

INSERT Sample_Data (id, start_date, end_date) VALUES (1, '2011-04-25','2011-05-08')
INSERT Sample_Data (id, start_date, end_date) VALUES (2, '2011-04-23','2011-05-27')


SELECT id, week_start_date, week_end_date
FROM Sample_Data CROSS JOIN Calendar_Weeks
WHERE week_start_date BETWEEN start_date AND end_date
UNION
SELECT id, week_start_date, week_end_date
FROM Sample_Data CROSS JOIN Calendar_Weeks
WHERE week_end_date BETWEEN start_date AND end_date

我必须承认{{1>}的查询感觉有点黑客在项目的开头或结尾包含行,所以您可能更喜欢使用Ravi Singh的解决方案

如果您愿意,也可以使用UNION

INNER JOIN

答案 2 :(得分:1)

设置环境测试

    declare @dt table (ID int,Start_date datetime,
                       End_date datetime,No_Of_Weeks int)

    insert into @dt (ID,Start_date,End_date,No_Of_Weeks)
    select 1,   '25-Apr-11',   '8-May-11',     2
    union all
    select 2,  '23-Apr-11' ,  '27-MAy-11' ,   6;

试试这个......

    with cte as (select d.ID
                       ,d.Start_date
                       ,(select MIN([end]) from (values(d.End_date),(DATEADD(day,-1,DATEADD(week,DATEDIFF(week,0,d.Start_date)+1,0))))V([end])) as End_date
                       ,d.End_date as end_of_period
                 from @dt d
                 union all select d.ID
                      ,DATEADD(day,1,d.End_date) as Start_date
                       , case when d.end_of_period < DATEADD(week,1,d.End_date) then d.end_of_period else DATEADD(week,1,d.End_date) end as End_date
                       ,d.end_of_period as end_of_period
                 from cte d
                 where end_of_period <> End_date
                )
    select ID
          ,cast(Start_date as DATE) Start_date
          ,cast(End_date as date) End_date 
    from cte
    order by cte.ID,cte.Start_date
    option(maxrecursion 0)

结果集实现了......

    ID  Start_date  End_date
    1   2011-04-25  2011-05-01
    1   2011-05-02  2011-05-08
    2   2011-04-23  2011-04-24
    2   2011-04-30  2011-05-01
    2   2011-05-07  2011-05-08
    2   2011-05-14  2011-05-15
    2   2011-05-21  2011-05-22
    2   2011-05-28  2011-05-27

答案 3 :(得分:1)

尝试此查询,希望它能正常工作

如果您的星期在星期日开始,请使用以下

set datefirst 7

declare @FromDate datetime = '20130110'
declare @ToDate datetime = '20130206'

select datepart(week, @ToDate) - datepart(week, @FromDate) + 1

如果您的一周从星期一开始,请使用以下

set datefirst 1

declare @FromDate datetime = '20100201'
declare @ToDate datetime = '20100228'

select datepart(week, @ToDate) - datepart(week, @FromDate) + 1

注意:两个查询都会产生不同的结果,因为它们的开始日期不同。

答案 4 :(得分:0)

您应该查看使用DATEDIFF功能。

我不确定你在问题的第二部分要求的是什么,但是一旦你得到了日期之间的差异,你可能想看看你的DATEDIFF结果使用CASE

答案 5 :(得分:0)

这是一个使用 datepart 功能帐户的解决方案,因为周从星期一开始

with demo_normalized as 
(
 select id,
  start_date,
  (datepart(dw,start_date) + 5) % 7 as test,
  dateadd(d,
       0 - ((datepart(dw,start_date) + 5) % 7),
       start_date
  ) as start_date_firstofweek,
  dateadd(d,
       6 - ((datepart(dw,start_date) + 5) % 7),
       start_date
  ) as start_date_lastofweek,
  end_date,
  dateadd(d,
       0 - ((datepart(dw,end_date) + 5) % 7),
       end_date
  ) as end_date_firstofweek,
  dateadd(d,
       6 - ((datepart(dw,end_date) + 5) % 7),
       end_date
  ) as end_date_lastofweek,
  datediff(week,
      dateadd(d,
          0 - ((datepart(dw,start_date) + 5) % 7),
          start_date
      ),
      dateadd(d,
          6 - ((datepart(dw,end_date) + 5) % 7),
          end_date
      )
  ) as no_of_weeks
 from demo
),
demo_cte as
(
  select
    id,
    dateadd(day,7,start_date_firstofweek) as start_date,
    dateadd(day,7,start_date_lastofweek) as end_date,
    end_date_firstofweek,
    no_of_weeks   
  from demo_normalized
  where no_of_weeks >= 3
  UNION ALL select
    id,
    dateadd(day,7,start_date) as start_date,
    dateadd(day,7,end_date) as end_date,
    end_date_firstofweek,
    no_of_weeks   
  from demo_cte
  where
    (dateadd(day,8,start_date) < end_date_firstofweek)
),
demo_union as
(
  select id, start_date, end_date, no_of_weeks from demo_normalized where no_of_weeks = 1
  union all
  select id, start_date, start_date_lastofweek as end_date, no_of_weeks
  from demo_normalized where no_of_weeks >= 2  
  union all
  select id, start_date, end_date, no_of_weeks from demo_cte
  union all
  select id, end_date_firstofweek as start_date, end_date, no_of_weeks
  from demo_normalized where no_of_weeks >= 2  
)
select
  d0.id,
 d0.no_of_weeks,
 convert(varchar, d0.start_date, 106) as start_date,
 convert(varchar, d0.end_date, 106) as end_date
from demo_union d0
order by d0.id, d0.start_date

编辑(两周之间加入CTE): 这是link to sqlfiddle

注意:此解决方案不需要额外的DDL - 不必创建和维护其他实体。简而言之,它不会重新发明日历。 所有日历逻辑都包含在查询中。

答案 6 :(得分:0)

您应该使用类似于Jeff Moden在The "Numbers" or "Tally" Table: What it is and how it replaces a loop中建议的类型的日期统计表(必需登录)。

  

Tally表只不过是一个表,其中包含一列非常好的索引序列号,从0或1开始(我的开始时为1)并且上升到某个数字。 Tally表中最大的数字不应该只是一些任意选择。它应该基于您认为您将使用它的内容。我将VARCHAR(8000)与我分开,所以它必须至少有8000个数字。由于我偶尔需要生成30年的日期,因此我将大部分生产的Tally表保持在11,000或更多,超过365.25天30年。

我从Tony的SQL Fiddler开始,但实现了一个DateInformation表更加通用。这可能是你可以重用的东西。

--Build Test Data, For production set the date 
--range large enough to handle all cases.
CREATE TABLE DateInformation (
    [Date] date,
    WeekDayNumber int,
)

--From Tony
CREATE TABLE Sample_Data (
id int,
start_date date,
end_date date )

DECLARE @CurrentDate Date = '2010-12-27'

While @CurrentDate < '2014-12-31'
BEGIN
    INSERT DateInformation VALUES (@CurrentDate,DatePart(dw,@CurrentDate))
    SET @CurrentDate = DATEADD(DAY,1,@CurrentDate)
END

--From Tony
INSERT Sample_Data VALUES (1, '2011-04-25','2011-05-08')
INSERT Sample_Data VALUES (2, '2011-04-23','2011-05-27')

以下是使用CTE将样本数据连接到DateInformation表的解决方案。

--Solution using CTE
with Week (WeekStart,WeekEnd) as
(
  select d.Date 
        ,dateadd(day,6,d.date) as WeekEnd
  from DateInformation d
  where d.WeekDayNumber = 2
)

select 
  s.ID
  ,case when s.Start_date > w.WeekStart then s.Start_Date 
    else w.WeekStart end as Start_Date
  ,case when s.End_Date < w.WeekEnd then s.End_Date
    else w.WeekEnd end as End_Date
from Sample_Data s
join Week w on w.WeekStart > dateadd(day,-6,s.start_date)
            and w.WeekEnd <= dateadd(day,6,s.end_date);

请参阅solution in SQL Fiddle

答案 7 :(得分:0)

set datefirst 1
GO

with cte as (
    select ID, Start_date, End_date, Start_date as Week_Start_Date, (case when datepart(weekday, Start_date) = 7 then Start_Date else cast(null as datetime) end) as Week_End_Date, datepart(weekday, Start_date) as start_weekday, cast(0 as int) as week_id
    from (
        values (1, cast('25-Apr-2011' as datetime), cast('8-May-2011' as datetime)),
                (2, cast('23-Apr-2011' as datetime), cast('27-May-2011' as datetime))
    ) t(ID, Start_date, End_date)

    union all

    select ID, Start_date, End_date, dateadd(day, 1, Week_Start_date) as Week_Start_Date, (case when start_weekday + 1 = 7 then dateadd(day, 1, Week_Start_date) else null end) as Week_End_date, (case when start_weekday = 7 then 1 else start_weekday + 1 end) as start_weekday, (case when start_weekday = 7 then week_id + 1 else week_id end) as week_id
    from cte
    where Week_Start_Date != End_date
)
select ID, min(Week_Start_Date), isnull(max(Week_End_Date), max(End_Date))
from cte
group by ID, Week_id
order by ID, 2
option (maxrecursion 0)

如果您想获得周数,可以在cte之后更改选择:

select ID, Start_date, End_date, count(distinct week_id) as Number_Of_Weeks
from cte
group by ID, Start_date, End_date
option (maxrecursion 0)

显然要更改使用的数据,将更改使用values()的cte的锚点(第一部分)。

这使用星期一作为一周的第一天。在另一天给我们,请更改顶部的set datefirst语句 - http://msdn.microsoft.com/en-gb/library/ms181598.aspx

答案 8 :(得分:0)

享受!

WITH D AS (
SELECT id
     , start_date
     , end_date
     , start_date AS WEEK_START
     , start_date + 7 - DATEPART(weekday,start_date) + 1
       AS week_end
 FROM DATA
), W AS (
SELECT id
     , start_date
     , end_date
     , WEEK_START
     , WEEK_END
 FROM D
UNION ALL
SELECT id
     , start_date
     , end_date
     , WEEK_END + 1 AS WEEK_START
     , WEEK_END + 7 AS WEEK_END
 FROM W
WHERE WEEK_END < END_DATE
)
SELECT ID
     , WEEK_START AS START_DATE
     , WEEK_END AS END_DATE
 FROM W
 ORDER BY 1, 2;