SQL 按月拆分期间

SQL Split Period by Months

下午好, 我有一个看似简单的问题,但事实证明并不那么简单。我有 2 个约会对象。 BeginPeriod (2010-06-10) 和 EndPeriod (2011-06-11)。

我想看看是否可以将这些日期分解为各自的月度分解。对于上面的例子,类似于

我不讲究方法。 CTE 很好,但不是首选。正如他们所说,乞丐不能挑剔。

一切顺利, 乔治

它是 CTE。

DECLARE @BeginPeriod DATETIME = '2010-06-10',
        @EndPeriod DATETIME = '2011-06-11'

;WITH cte AS
(
    SELECT DATEADD(month, DATEDIFF(month, 0, @BeginPeriod), 0) AS StartOfMonth, 
           DATEADD(s, -1, DATEADD(mm, DATEDIFF(m, 0, @BeginPeriod) + 1, 0)) AS EndOfMonth
    UNION ALL
    SELECT DATEADD(month, 1, StartOfMonth) AS StartOfMonth, 
           DATEADD(s, -1, DATEADD(mm, DATEDIFF(m, 0, DATEADD(month, 1, StartOfMonth)) + 1, 0)) AS EndOfMonth  
    FROM   cte
    WHERE  DATEADD(month, 1, StartOfMonth) <= @EndPeriod
)
SELECT  
    (CASE WHEN StartOfMonth < @BeginPeriod THEN @BeginPeriod ELSE StartOfMonth END) StartOfMonth,
    (CASE WHEN EndOfMonth > @EndPeriod THEN @EndPeriod ELSE EndOfMonth END) EndOfMonth
FROM cte

最后一个 EndOfMonth 是您用作 @EndPeriod 的值,如果您想要前一天

,请将其设置为 DATEADD(day, -1, @EndPeriod)

你可以用这个来trim时间。

SELECT  
    CONVERT(VARCHAR(10), (CASE WHEN StartOfMonth < @BeginPeriod THEN @BeginPeriod ELSE StartOfMonth END), 120) StartOfMonth,
    CONVERT(VARCHAR(10), (CASE WHEN EndOfMonth > @EndPeriod THEN @EndPeriod ELSE EndOfMonth END), 120) EndOfMonth
FROM cte

"Numbers" CTE

的另一个选项
declare @df datetime, @dt datetime
set @df = '20100610'
set @dt = '20110611'

;WITH e1(n) AS
(
    SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
    SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
    SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
), -- 10
e2(n) AS (SELECT ROW_NUMBER() OVER (ORDER BY e1.n) FROM e1 CROSS JOIN e1 AS b) -- 10*10
select  
    case when e2.n = 1 
        then @df 
        else dateadd(day, -day(@df) + 1, dateadd(month, e2.n - 1, @df)) end, 
    case when e2.n = datediff(month, @df, @dt) + 1 
        then dateadd(month, e2.n -1 , @df) 
        else EOMONTH( dateadd(month, e2.n -1 , @df) ) end
from e2
where e2.n 

您可以使用其他选项代替 Numbers CTE,例如此处所述 http://sqlperformance.com/2013/01/t-sql-queries/generate-a-set-1 我的数据库中经常有 Numbers table 用于此类任务。我认为主要是因为我在 CTE 被添加到 MS 之前就开始使用这种技术 SQL 但它也减少了输入。

我知道这是一个老问题,但我最近遇到了同样的问题,并写了一个没有 CTE 的版本。

这是经过测试的code


DECLARE @FromDate DATETIME 
      , @ToDate DATETIME
      , @UpdateBeginDate BIT   
      , @UpdateEndDate BIT    

    SET @FromDate = '2019-01-15'
    SET @ToDate = '2021-08-17'
    SET @UpdateBeginDate = 1
    SET @UpdateEndDate = 1

DECLARE @Results TABLE ( 
      MonthStart DATETIME
    , MonthEnd DATETIME 
    , TheMonth VARCHAR(2) 
    , TheYear VARCHAR(4)
)


     -- Months in that period
     INSERT INTO @Results
     SELECT TOP (DATEDIFF(MONTH, @FromDate, @ToDate)+1) -- calculate how many rows needed
          DATEADD(MONTH, number, @FromDate)
        , DATEADD(MONTH, number, @FromDate)
        , MONTH(DATEADD(MONTH, number, @FromDate))
        , YEAR(DATEADD(MONTH, number, @FromDate))
       FROM [master].dbo.spt_values 
      WHERE [type] = N'P' 
      ORDER BY number

     /*
         by using [master].dbo.spt_values, the total series number are 2048, 
         for month cases, there will be 2048/12 ≈ 170 year range
     */

     -- Update first date of month and last date of month for each row
     UPDATE @Results
        SET MonthStart = DATEADD(MONTH, DATEDIFF(MONTH, 0, MonthStart), 0) 
          , MonthEnd = DATEADD(MONTH, ((YEAR(MonthEnd) - 1900) * 12) + MONTH(MonthEnd), -1)
     
     /*
         MonthStart
         Caculate the month difference from 1900-01-01 (0 represent 1900-01-01) to the current row's MonthStart record (DATEDIFF part)
         Then add the month difference from 1900-01-01 (DATEADD part)

         MonthEnd
         Caculate the year difference from 1900 to the current row's MonthEnd record then multiply 12 and plus the months of the record (The middle part)
         Then add the month difference from 1899-12-31 (DATEADD part)
         NOTE: -1 means 1899-12-31

         The MonthEnd sentence is equivalent to this, if you keeped the record of TheYear and TheMonth
         MonthEnd = DATEADD(MONTH, ((TheYear - 1900) * 12) + TheMonth, -1)
     */


     IF @UpdateBeginDate = 1
     BEGIN
         UPDATE @Results 
            SET MonthStart = @FromDate
          WHERE MonthStart = (SELECT MIN(MonthStart) FROM @Results)
     END


     IF @UpdateEndDate = 1
     BEGIN
         UPDATE @Results 
            SET MonthEnd = @ToDate
          WHERE MonthEnd = (SELECT MAX(MonthEnd) FROM @Results)
     END


    select * from @Results

我在 MSSQL 2005 和 MSSQL 2017 中测试了这段代码,我认为其他版本也可能运行良好。

希望这会帮助其他想要没有 CTE 的解决方案的人。