Sql Date - 打破月末

时间:2016-08-24 22:07:15

标签: sql sql-server tsql

我有一个查询(下面)将在特定时间开始。从那时起我需要一次增加一周(每个新行将是一个新的星期 - 从星期日到星期六)我已经完成了 - 但新的皱纹是,如果它是月底它需要在那个日期停下来,并在本月的第一天开始,但仍然在周六停止。结果集好/坏列在下面,也是我的查询到目前为止。

结果不好:

2016-05-22  2016-05-28
2016-05-29  2016-06-04

我需要什么:

2016-05-22  2016-05-28
2016-05-29  2016-05-31
2016-06-01  2016-06-04

代码:

BEGIN
    DECLARE @StartDate DATE = DATEFROMPARTS(2016, 5, 22);
    DECLARE @EndDate DATE = CAST(GETDATE() AS DATE);
    DECLARE @Today DATE = @EndDate;
    DECLARE @EndOfMonth DATE = @StartDate

    ; WITH [Dates] AS (
        SELECT
            @StartDate AS [StartDate],
            DATEADD(DAY, 6, @StartDate) AS [EndDate]
        UNION ALL
        SELECT
            DATEADD(DAY, 7, [StartDate]),
            DATEADD(DAY, 7, [EndDate])
        FROM [Dates]
        WHERE   DATEADD(DAY, 7, [StartDate]) <= @EndDate
    )
    SELECT
        [tcw].[StartDate],
        [tcw].[EndDate]
    FROM [Dates] AS [tcw]
    OPTION (MAXRECURSION 0)
END
GO

4 个答案:

答案 0 :(得分:1)

鉴于您使用DATEFROMPARTS,我假设您使用的是SQL 2012或更高版本。在确定第一个和最后一个日期时,我也依赖于您现有的逻辑。在此基础上,使用以下内容替换您的查询:

; WITH [Dates] AS (
    SELECT
        @StartDate AS [StartDate],
        DATEADD(DAY, 6, @StartDate) AS [EndDate]
    UNION ALL
    SELECT
        DATEADD(DAY, 7, [StartDate]),
        DATEADD(DAY, 7, [EndDate])
    FROM [Dates]
    WHERE   DATEADD(DAY, 7, [StartDate]) <= @EndDate
)
--  All weeks where all dates are within the same month
SELECT
    StartDate
   ,EndDate
FROM [Dates]
WHERE MONTH(StartDate) = MONTH(EndDate)
--  For weeks where all dates not within the same month, the last week in the month
UNION ALL SELECT
    StartDate
   ,EOMONTH(StartDate)
FROM [Dates]
WHERE MONTH(StartDate) <> MONTH(EndDate)
--  For weeks where all dates not within the same month, the first week in the (next) month
UNION ALL SELECT
    DATEADD(dd, 1, EOMONTH(StartDate))
   ,EndDate
FROM [Dates]
WHERE MONTH(StartDate) <> MONTH(EndDate)

工会让它有点尴尬,但除非你每次通过处理几个世纪,否则它会跑得足够快。另请注意,某些“周”将包含一天,例如2016年7月31日至2016年7月31日。

- 补遗,基于评论---------------------------------

以下查询执行此操作,但有一个很大的警告......

; WITH cteDates AS (
    SELECT
        @StartDate AS StartDate,
        DATEADD(DAY, 6, @StartDate) AS EndDate,
        CASE
          WHEN DATEPART(dw, EOMONTH(@StartDate)) between 2 and 6 then 0  --  Assumes SET DATEFIRST is 1!
          ELSE 1
        END  AS MonthEndsOnWeekend
    UNION ALL
    SELECT
        DATEADD(DAY, 7, StartDate),
        DATEADD(DAY, 7, EndDate),
        CASE
          WHEN DATEPART(dw, EOMONTH(DATEADD(DAY, 7, StartDate))) between 2 and 6 then 0
          ELSE 1
        END
    FROM cteDates
    WHERE   DATEADD(DAY, 7, StartDate) <= @EndDate
)
--  All weeks where all dates are within the same month,
--  and all month-ending weeks where the last day of the month is Saturday or Sunday
SELECT
    StartDate
   ,EndDate
FROM cteDates
WHERE MONTH(StartDate) = MONTH(EndDate)
  OR MonthEndsOnWeekend = 1
--  For weeks where all dates not within the same month,
--  and the the last day of the month is NOT Saturday or Sunday,
--  the last week in the month
UNION ALL SELECT
    StartDate
   ,EOMONTH(StartDate)
FROM cteDates
WHERE MONTH(StartDate) <> MONTH(EndDate)
  AND MonthEndsOnWeekend = 0
--  For weeks where all dates not within the same month,
--  and the the last day of the month is NOT Saturday or Sunday,
--  the first week in the (next) month
UNION ALL SELECT
    DATEADD(dd, 1, EOMONTH(StartDate))
   ,EndDate
FROM cteDates
WHERE MONTH(StartDate) <> MONTH(EndDate)
  AND MonthEndsOnWeekend = 0

我使用DATEPART函数来识别星期几(星期六,星期日等)。 SQL将为此函数返回一个数字,其中一周中返回的数字取决于SET DATEFIRST的设置。对于我的安装,我们使用默认值,其中1 =星期一。如果您无法完全控制SET DATEFIRST 到处的设置,您的代码可能会被运行,永远,那么您可能会遇到本讨论范围之外的问题。我使用的另一种方法是使用DATENAME,它将返回字符串,例如星期六,星期天等等......但是这与SET LANGUAGE的设置有同样的问题。

(fyi,我也取出了[]并重命名为cte,因为他们在烦我。)

答案 1 :(得分:0)

查看您的代码我假设您使用的是sql-server。 EOMONTH()在sql-server 2012 +中可用,可以更轻松地查找结束日期。基本上,您需要测试开始日期的星期几以及开始日期的月末,然后通过递归进行平衡。如果需要,您可以添加另一个LEVEL列并跟踪周数。例如。 1在UNION Level + 1中作为Level的第一个查询中的LEVEL。

DECLARE @StartDate DATE = '2016-05-22'
DECLARE @EndDate DATE = GETDATE()

--if @StartDate provided is middle of the week and you want to adjust to sunday use this
SET @StartDate = CASE
       WHEN DATEPART(WEEKDAY,@StartDate) = 1 THEN @StartDate
       ELSE DATEADD(day,-7 + DATEPART(WEEKDAY,@StartDate),@StartDate)
    END

;WITH cteRecursiveDates AS (
    SELECT
       @StartDate as Startdate
       ,CASE
          WHEN DATEDIFF(day,@StartDate,EOMONTH(@StartDate)) < (7 - DATEPART(WEEKDAY,@StartDate))
              THEN DATEADD(day,DATEDIFF(day,@StartDate,EOMONTH(@StartDate)) ,@StartDate)
          ELSE DATEADD(day,(7- DATEPART(WEEKDAY,@StartDate)),@StartDate)
       END as EndDate

    UNION ALL

    SELECT
       DATEADD(day,1,c.EndDate) as StartDate
       ,CASE
          WHEN DATEDIFF(day,DATEADD(day,1,c.EndDate),EOMONTH(DATEADD(day,1,c.EndDate))) < (7 - DATEPART(WEEKDAY,DATEADD(day,1,c.EndDate)))
             THEN DATEADD(day,DATEDIFF(day,DATEADD(day,1,c.EndDate),EOMONTH(DATEADD(day,1,c.EndDate))) ,DATEADD(day,1,c.EndDate))
          ELSE DATEADD(day,(7- DATEPART(WEEKDAY,DATEADD(day,1,c.EndDate))),DATEADD(day,1,c.EndDate))
       END as EndDate
    FROM
       cteRecursiveDates c
    WHERE
       DATEADD(day,1,c.EndDate) <= @EndDate
)

SELECT *
FROM
    cteRecursiveDates

答案 2 :(得分:0)

我添加了星期几(乞讨/结束)只是为了说明

Declare @Date1 Date='2016-05-22'
Declare @Date2 Date='2016-07-31'

;with cteBase as (
Select *
       ,Flag1=IIF(Row_Number() over (Partition By Month(RetVal) Order By RetVal)=1 or DatePart(WEEKDAY,RetVal)=1 or Day(RetVal)=1 ,1,0)
       ,Flag2=IIF(DatePart(WEEKDAY,RetVal)=7 or Lead(Day(RetVal),1) over (Order By RetVal)=1,1,0)
       ,RowNr=Row_Number() over (Order By RetVal)
    From  [dbo].[udf-Create-Range-Date](@Date1,@Date2,'DD',1)
)
Select DateR1=cast(A.RetVal as Date)
      ,B.DateR2
      ,DOW1=DateName(Weekday,A.RetVal) 
      ,DOW2=DateName(Weekday,B.DateR2) 
From cteBase A 
Cross Apply (Select DateR2=min(cast(RetVal as date)) From cteBase Where Flag2=1 and RowNr>=A.RowNr) B
Where A.Flag1=1 and B.DateR2 is not null

返回

DateR1      DateR2      DOW1        DOW2
2016-05-22  2016-05-28  Sunday      Saturday
2016-05-29  2016-05-31  Sunday      Tuesday
2016-06-01  2016-06-04  Wednesday   Saturday
2016-06-05  2016-06-11  Sunday      Saturday
2016-06-12  2016-06-18  Sunday      Saturday
2016-06-19  2016-06-25  Sunday      Saturday
2016-06-26  2016-06-30  Sunday      Thursday
2016-07-01  2016-07-02  Friday      Saturday
2016-07-03  2016-07-09  Sunday      Saturday
2016-07-10  2016-07-16  Sunday      Saturday
2016-07-17  2016-07-23  Sunday      Saturday
2016-07-24  2016-07-30  Sunday      Saturday

我用来创建动态日期范围的UDF

CREATE FUNCTION [dbo].[udf-Create-Range-Date] (@DateFrom datetime,@DateTo datetime,@DatePart varchar(10),@Incr int)

Returns 
@ReturnVal Table (RetVal datetime)

As
Begin
    With DateTable As (
        Select DateFrom = @DateFrom
        Union All
        Select Case @DatePart
               When 'YY' then DateAdd(YY, @Incr, df.dateFrom)
               When 'QQ' then DateAdd(QQ, @Incr, df.dateFrom)
               When 'MM' then DateAdd(MM, @Incr, df.dateFrom)
               When 'WK' then DateAdd(WK, @Incr, df.dateFrom)
               When 'DD' then DateAdd(DD, @Incr, df.dateFrom)
               When 'HH' then DateAdd(HH, @Incr, df.dateFrom)
               When 'MI' then DateAdd(MI, @Incr, df.dateFrom)
               When 'SS' then DateAdd(SS, @Incr, df.dateFrom)
               End
        From DateTable DF
        Where DF.DateFrom < @DateTo
    )

    Insert into @ReturnVal(RetVal) Select DateFrom From DateTable option (maxrecursion 32767)

    Return
End

-- Syntax Select * from [dbo].[udf-Create-Range-Date]('2016-10-01','2020-10-01','YY',1) 
-- Syntax Select * from [dbo].[udf-Create-Range-Date]('2016-10-01','2020-10-01','DD',1) 
-- Syntax Select * from [dbo].[udf-Create-Range-Date]('2016-10-01','2016-10-31','MI',15) 
-- Syntax Select * from [dbo].[udf-Create-Range-Date]('2016-10-01','2016-10-02','SS',1) 

答案 3 :(得分:0)

将任何违反月末的任何一周拆分为两个,然后联合回到那些没有超过月底的行。

    declare 
        @StartDate date = datefromparts(2016, 5, 22),
        @EndDate date = cast(getdate() as date)

    ; 
    with 
    dates as 
    (
        select
            @StartDate as StartDate,
            dateadd(day, 6, @StartDate) as EndDate,
            eomonth(@StartDate) as EndOfMonth,
            dateadd(day, 1 - day(dateadd(month, 1, @StartDate)), 
                dateadd(month, 1, @StartDate)) as FirstDayOfNextMonth

        union all

        select
            dateadd(day, 7, d.StartDate),
            dateadd(day, 7, d.EndDate),
            eomonth(dateadd(day, 7, d.StartDate)),
            dateadd(day, 1 - day(dateadd(month, 1, d.StartDate)), 
                dateadd(month, 1, d.StartDate))
        from 
            dates as d
        where   
            dateadd(day, 7, StartDate) <= @EndDate
    )
    -- week within the month
    select
        d.StartDate,
        d.EndDate
    from 
        dates as d
    where
        d.EndDate <= d.EndOfMonth

    union all

    -- week breach to next month, first part
    select
        d.StartDate,
        d.EndOfMonth as EndDate
    from 
        dates as d
    where
        d.EndDate > d.EndOfMonth

    union all

    -- week breach to next month, second part
    select
        d.FirstDayOfNextMonth as StartDate,
        d.EndDate
    from 
        dates as d
    where
        d.EndDate > d.EndOfMonth

    order by 1

    option (maxrecursion 0)

结果:

| StartDate  | EndDate    |
|------------|------------|
| 2016-05-22 | 2016-05-28 |
| 2016-05-29 | 2016-05-31 |
| 2016-06-01 | 2016-06-04 |
| 2016-06-05 | 2016-06-11 |
| 2016-06-12 | 2016-06-18 |
| 2016-06-19 | 2016-06-25 |
| 2016-06-26 | 2016-06-30 |
| 2016-07-01 | 2016-07-02 |
| 2016-07-03 | 2016-07-09 |
| 2016-07-10 | 2016-07-16 |
| 2016-07-17 | 2016-07-23 |
| 2016-07-24 | 2016-07-30 |
| 2016-07-31 | 2016-07-31 |
| 2016-08-01 | 2016-08-06 |
| 2016-08-07 | 2016-08-13 |
| 2016-08-14 | 2016-08-20 |