【问题标题】:t-sql select get all Months within a range of yearst-sql 选择获取年份范围内的所有月份
【发布时间】:2022-02-20 17:07:01
【问题描述】:

我需要一个选择来返回月份和年份在指定的日期范围内,我将在其中输入开始年份和月份,并且选择将返回从我输入的日期到今天的月份和年份。

我知道我可以循环执行此操作,但我想知道是否可以在系列选择中执行此操作?

Year  Month
----  -----
2010  1
2010  2
2010  3
2010  4
2010  5
2010  6
2010  7

等等。

【问题讨论】:

    标签: sql sql-server tsql sql-server-2008


    【解决方案1】:

    Gosh 伙计们……使用“计数递归 CTE”或“rCTE”与使用循环一样糟糕或更糟糕。请参阅下面的文章了解我为什么这么说。

    http://www.sqlservercentral.com/articles/T-SQL/74118/

    这是一种无需任何 RBAR 的方法,包括计数 rCTE 的“隐藏 RBAR”。

    --===== Declare and preset some obviously named variables
    DECLARE @StartDate DATETIME,
            @EndDate   DATETIME
    ;
     SELECT @StartDate = '2010-01-14', --We'll get the month for both of these 
            @EndDate   = '2020-12-05'  --dates and everything in between
    ;
    WITH
    cteDates AS
    (--==== Creates a "Tally Table" structure for months to add to start date
         -- calulated by the difference in months between the start and end date.
         -- Then adds those numbers to the start of the month of the start date.
     SELECT TOP (DATEDIFF(mm,@StartDate,@EndDate) + 1)
            MonthDate = DATEADD(mm,DATEDIFF(mm,0,@StartDate) 
                      + (ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) -1),0)
       FROM sys.all_columns ac1
      CROSS JOIN sys.all_columns ac2
    )
    --===== Slice each "whole month" date into the desired display values.
     SELECT [Year]  = YEAR(MonthDate),
            [Month] = MONTH(MonthDate) 
       FROM cteDates
    ;
    

    【讨论】:

    • 我喜欢你的做法,但最后在MonthDate列上调用YEAR()和MONTH()函数是不是还有RBAR?
    • 在幕后,T-SQL 中的所有内容实际上都基于一种或另一种形式的循环,因此它们都被称为“RBR”。由于我是创造这个短语的人,这就是“RBAR”的意思...... RBAR 发音为“ree-bar”,是“Row By Agonizing Row”的“Modenism”。该术语已经指代任何需要更多资源或由于其逐行性质而导致运行时间更长的代码方法,即使该代码实际上是“基于集合”的代码。由于 SQL Server 的大多数内在函数都以机器语言的速度运行,我不得不说,“不……这不是 RBAR”。 ;-)
    【解决方案2】:

    你可以这样使用:Link

    使用日期范围生成等效的数字表。

    但是您能否澄清一下您的输入和输出?

    您要输入开始日期,例如'2010-5-1' 和结束日期,例如'2010-8-1',并让它在两者之间的每个月返回吗?您要包括开始月份和结束月份,还是排除它们?

    这是我编写的一些代码,可以快速生成两个日期之间每个月的包含结果。

    --Inputs here:
    DECLARE @StartDate datetime;
    DECLARE @EndDate datetime;
    SET @StartDate = '2010-1-5 5:00PM';
    SET @EndDate = GETDATE();
    
    --Procedure here:
      WITH RecursiveRowGenerator (Row#, Iteration) AS (
           SELECT 1, 1
            UNION ALL
           SELECT Row# + Iteration, Iteration * 2
             FROM RecursiveRowGenerator
            WHERE Iteration * 2 < CEILING(SQRT(DATEDIFF(MONTH, @StartDate, @EndDate)+1))
            UNION ALL
           SELECT Row# + (Iteration * 2), Iteration * 2
             FROM RecursiveRowGenerator
            WHERE Iteration * 2 < CEILING(SQRT(DATEDIFF(MONTH, @StartDate, @EndDate)+1))
         )
         , SqrtNRows AS (
           SELECT *
             FROM RecursiveRowGenerator
            UNION ALL
           SELECT 0, 0
         )
    SELECT TOP(DATEDIFF(MONTH, @StartDate, @EndDate)+1) 
           DATEADD(month, DATEDIFF(month, 0, @StartDate) + A.Row# * POWER(2,CEILING(LOG(SQRT(DATEDIFF(MONTH, @StartDate, @EndDate)+1))/LOG(2))) + B.Row#, 0)  Row#
      FROM SqrtNRows A, SqrtNRows B
     ORDER BY A.Row#, B.Row#;
    

    【讨论】:

      【解决方案3】:

      下面的代码生成 2013 年 7 月 21 日2014 年 1 月 15 日 之间的值。 我通常在 SSRS 报告中使用它来生成 Month 参数的查找值。

      declare
          @from date = '20130721',
          @to date = '20140115';
      
      with m as (
      select * from (values ('Jan', '01'), ('Feb', '02'),('Mar', '03'),('Apr', '04'),('May', '05'),('Jun', '06'),('Jul', '07'),('Aug', '08'),('Sep', '09'),('Oct', '10'),('Nov', '11'),('Dec', '12')) as t(v, c)),
      
      y as (select cast(YEAR(getdate()) as nvarchar(4)) [v] union all select cast(YEAR(getdate())-1 as nvarchar(4)))
      
      select m.v + ' ' + y.v [value_field], y.v + m.c [label_field]
      from m
      cross join y
      where y.v + m.c between left(convert(nvarchar, @from, 112),6) and left(convert(nvarchar, @to, 112),6)
      order by y.v + m.c desc
      

      结果:

      value_field     label_field
      ---------------------------
      Jan 2014        201401
      Dec 2013        201312
      Nov 2013        201311
      Oct 2013        201310
      Sep 2013        201309
      Aug 2013        201308
      Jul 2013        201307
      

      【讨论】:

        【解决方案4】:

        我知道这是一个老问题,但我对某些答案的复杂性感到有点害怕。使用 CTE 绝对是选择这些值的最简单方法:

        WITH months(dt) AS 
           (SELECT getdate() dt 
            UNION ALL
            SELECT dateadd(month, -1, dt)
            FROM months)
        SELECT 
        top (datediff(month, '2017-07-01' /* start date */, getdate()) + 1) 
        YEAR(months.dt) yr, MONTH(months.dt) mnth
        FROM months
        OPTION (maxrecursion 0);
        

        只需选择您想要的任何开始日期来代替上面的'2017-07-01',您就可以使用高效且易于集成的解决方案。


        编辑:Jeff Moden's answer 非常有效地反对使用 rCTE。但是,在这种情况下,它似乎是过早优化的情况——我们很可能在谈论 10 条记录,即使从今天开始回溯到 1900 年,它仍然是一个微不足道的打击。如果预期结果集很小,使用 rCTE 实现代码可维护性似乎是值得的。

        【讨论】:

        • 为什么不一直练习以正确的方式进行操作,这样如果您最终出现意外增长或代码最终每小时被调用数千次,您就不必找到它并且修复它。
        • @JeffModen 虽然我普遍同意为意外增长做准备是一件好事,但有一些事情与过程方法有关,这可能使其无法启动(超出复杂性)。具体来说,如果程序员只有对数据库的查询访问权限,他们仍然需要一个解决方案,并且可以授权他们评估他们的用例是否可能在未来受到性能影响。例如,在我的例子中,这个范围每个月只会增长一个记录,所以逐渐的性能损失几乎不是问题。
        • 您说您“对复杂性感到有些恐惧”,但是,如果您从我的代码中删除所有 cmets,它会比您的代码短一行。 ;-) 我还要说,用这样的借口不以正确的方式做事,特别是因为以正确的方式做事很容易,这就是“千刀万剐”如何潜入数据库,然后每个人都指责 SQL用于慢代码的服务器。如果它完全值得做,那就值得做正确的事,而且这样做不需要更多的努力。增量 rCTE 从来都不是正确的方法。
        • 很公平,并且可以重构您的方法以避免不使用绑定而对 T-SQL 产生任何影响。我通常不喜欢使用 sys.all_columns 作为生成器,因为这感觉很老套,但我认为在普通数据库上没有用完行的风险。
        【解决方案5】:

        ---这是一个获取通常用于会计目的的月末日期的版本

        DECLARE @StartDate datetime;
         DECLARE @EndDate datetime; 
         SET @StartDate = '2010-1-1'; 
         SET @EndDate = '2020-12-31';  
         --Procedure here:   
        
        
        
        
        
         WITH RecursiveRowGenerator (Row#, Iteration)                             
         AS (        SELECT 1, 1         
         UNION ALL        
         SELECT Row# + Iteration, Iteration * 2         
          FROM RecursiveRowGenerator         
          WHERE Iteration * 2 < CEILING(SQRT(DATEDIFF(MONTH, @StartDate, @EndDate)+1)) 
          UNION ALL        SELECT Row# + (Iteration * 2), Iteration * 2 
                   FROM RecursiveRowGenerator         
                   WHERE Iteration * 2 < CEILING(SQRT(DATEDIFF(MONTH, @StartDate, @EndDate)+1))      )  
                       , SqrtNRows AS (        SELECT *          FROM RecursiveRowGenerator         
         UNION ALL        SELECT 0, 0      ) 
         SELECT TOP(DATEDIFF(MONTH, @StartDate, @EndDate)+1)         
                   DateAdd(d,-1,DateAdd(m,1, DATEADD(month, DATEDIFF(month, 0, @StartDate) + A.Row# * POWER(2,CEILING(LOG(SQRT(DATEDIFF(MONTH, @StartDate, @EndDate)+1))/LOG(2))) + B.Row#, 0)  ))
        Row#   FROM SqrtNRows A, SqrtNRows B  ORDER BY A.Row#, B.Row#; 
        

        【讨论】:

          【解决方案6】:
          DECLARE @Date1 DATE
          DECLARE @Date2 DATE
          
          SET @Date1 = '20130401'
          SET @Date2 = DATEADD(MONTH, 83, @Date1)
          
          SELECT DATENAME(MONTH, @Date1) "Month", MONTH(@Date1) "Month Number", YEAR(@Date1) "Year"
          INTO #Month
          
          WHILE (@Date1 < @Date2)
          BEGIN 
              SET @Date1 = DATEADD(MONTH, 1, @Date1)
              INSERT INTO #Month
              SELECT DATENAME(MONTH, @Date1) "Month", MONTH(@Date1) "Month Number", YEAR(@Date1) "Year"
          END
          
          SELECT * FROM #Month 
          ORDER BY [Year], [Month Number]
          
          DROP TABLE #Month
          

          【讨论】:

            【解决方案7】:
            declare @date1 datetime, 
                @date2 datetime, 
                @date  datetime, 
                @month integer, 
                @nm_bulan varchar(20) 
            
            create table #month_tmp 
                ( bulan integer null, keterangan varchar(20) null ) 
            
            select @date1 = '2000-01-01', 
                   @date2 = '2000-12-31' 
            
            select @month = month(@date1) 
            
            while (@month < 13) 
            Begin 
                IF @month = 1 
                Begin 
                   SELECT @date  = CAST( CONVERT(VARCHAR(25),DATEADD(dd,-(DAY(DATEADD(mm,0,@date1))-1),DATEADD(mm,0,@date1)),111) + ' 00:00:00' as DATETIME ) 
                End
                ELSE
                Begin
                   SELECT @date  = CAST( CONVERT(VARCHAR(25),DATEADD(dd,-(DAY(DATEADD(mm,@month -1,@date1))-1),DATEADD(mm,@month -1,@date1)),111) + ' 00:00:00' as DATETIME ) 
                End
                select @nm_bulan = DATENAME(MM, @date)
            
                insert into #month_tmp
                select @month as nilai, @nm_bulan as nama 
            
                select @month = @month + 1
            End 
            
            select * from #month_tmp 
            drop table #month_tmp 
            go
            

            【讨论】:

              【解决方案8】:

              您可以执行以下操作

              SELECT DISTINCT YEAR(myDate) as [Year], MONTH(myDate) as [Month]
              FROM myTable
              WHERE <<appropriate criteria>>
              ORDER BY [Year], [Month]
              

              【讨论】:

              • 你会如何使用“适当的标准”你能举个例子吗...就像从 2009-12 到今天的所有日期
              猜你喜欢
              • 1970-01-01
              • 2020-08-07
              • 2017-10-19
              • 1970-01-01
              • 1970-01-01
              • 2021-02-03
              • 2013-11-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多