每个月的第一个SQL

时间:2011-02-17 21:19:04

标签: sql sql-server

假设我想在SQL中编写表值函数,该函数返回一个表,每个月的第一天在参数日期之间,最简单的方法是什么?

例如,fnFirstOfMonths('10/31/10', '2/17/11')会返回一个包含11/1/10, 12/1/10, 1/1/112/1/11作为元素的单列表格。

我的第一直觉是使用while循环并重复插入几个月的前几天,直到我到达开始日期之前。看起来应该有更优雅的方式来做到这一点。

感谢您提供的任何帮助。

5 个答案:

答案 0 :(得分:5)

这样的东西可以在不在函数内部的情况下工作:

DECLARE @LowerDate DATE 
SET @LowerDate = GETDATE()

DECLARE @UpperLimit DATE
SET @UpperLimit = '20111231'

;WITH Firsts AS
(
    SELECT
        DATEADD(DAY, -1 * DAY(@LowerDate) + 1, @LowerDate) AS 'FirstOfMonth'

    UNION ALL

    SELECT
        DATEADD(MONTH, 1, f.FirstOfMonth) AS 'FirstOfMonth'
    FROM
        Firsts f
    WHERE
        DATEADD(MONTH, 1, f.FirstOfMonth)  <= @UpperLimit
)   
SELECT * 
FROM Firsts

它使用一种称为CTE(公用表表达式)的东西 - 可在SQL Server 2005及更高版本和其他数据库系统中使用。

在这种情况下,我通过确定指定的@LowerDate日期的第一个月开始递归CTE,然后迭代添加一个月到前一个月的第一个月,直到达到上限。

或者如果你想将它打包在一个存储的函数中,你也可以这样做:

CREATE FUNCTION dbo.GetFirstOfMonth(@LowerLimit DATE, @UpperLimit DATE)
RETURNS TABLE 
AS 
   RETURN
      WITH Firsts AS
      (
          SELECT
             DATEADD(DAY, -1 * DAY(@LowerLimit) + 1, @LowerLimit) AS 'FirstOfMonth'
          UNION ALL
          SELECT
             DATEADD(MONTH, 1, f.FirstOfMonth) AS 'FirstOfMonth'
          FROM
             Firsts f
          WHERE
             DATEADD(MONTH, 1, f.FirstOfMonth)  <= @UpperLimit
       )    
       SELECT * FROM Firsts

然后像这样调用它:

SELECT * FROM dbo.GetFirstOfMonth('20100522', '20100831')

得到这样的输出:

FirstOfMonth
2010-05-01
2010-06-01
2010-07-01
2010-08-01

PS:使用DATE数据类型 - 它存在于SQL Server 2008及更新版本中 - 我修复了Richard评论过的两个“错误”。如果您使用的是SQL Server 2005,则必须使用DATETIME代替 - 并处理您获得时间部分的事实。

答案 1 :(得分:2)

create function dbo.fnFirstOfMonths(@d1 datetime, @d2 datetime)
returns table as return
select dateadd(m,datediff(m,0,@d1)+v.number,0) as FirstDay
from master..spt_values v
where v.type='P' and v.number between 0 and datediff(m, @d1, @d2)
  and dateadd(m,datediff(m,0,@d1)+v.number,0) between @d1 and @d2
GO

注释

  • master..spt_values 是SQL Server中通用序列号的来源
  • dateadd(m,datediff(m 是一种计算任何日期的第一天的技巧
  • + v.number 用于每次增加一个月
  • 0和datediff(m,@ d1,@ d2)此条件为我们提供了{@ 1}}所需的所有number日期,我们需要为@之间的每个月生成一个first-of-month日期d1和@ d2,包括两个月
  • 和dateadd(m,datediff(m,0,@ d1)+ v.number,0)@ d1和@ d2之间最终过滤器以验证first-of-month日期生成在@ d1和@ d2
  • 之间

<小时/> 与marc_s代码

的性能比较

摘要

8220 ms (CTE)
4173 ms (master..spt_values)

测试

declare @t table (dt datetime)
declare @d datetime
declare @i int
set nocount on

set @d = GETDATE()
set @i = 0
while @i < 10000
begin
insert @t select * from dbo.getfirstofmonth('20090102', '20100506')
delete @t
set @i = @i + 1
end

print datediff(ms, @d, getdate())

set @d = GETDATE()
set @i = 0
while @i < 10000
begin
insert @t select * from dbo.fnfirstofmonths('20090102', '20100506')
delete @t
set @i = @i + 1
end
print datediff(ms, @d, getdate())

Performante

答案 2 :(得分:0)

它将在所涉及的月份之间循环(在示例中为4次):

set dateformat mdy;
declare @date1 smalldatetime,@date2 smalldatetime,@i int
set @date1= '10-31-2010'
set @date2= '02-17-2011'
set @i=1

while(@i<=DATEDIFF(mm,@date1,@date2))
begin
    select  convert(smalldatetime,CONVERT(varchar(6),DATEADD(mm,@i,@date1),112)+'01',112)
    set     @i=@i+1
end

答案 3 :(得分:0)

我意识到这不是一个功能,但无论如何我都会把它扔进混音中。

select cal_date from calendar
where day_of_month = 1
and cal_date between '2011-01-01' and '2012-01-01'

此日历表在PostgreSQL服务器上运行。我今晚将它移植到SQL Server,并运行一些速度比较。 (为什么?因为这些东西很有趣,这就是原因。)

答案 4 :(得分:0)

以防有人还在读这个...... 我无法想象上述任何功能都比这更快:

declare @DatFirst date = '20101031', @DatLast date = '21110217';

declare @DatFirstOfFirstMonth date = dateadd(day,1-day(@DatFirst),@DatFirst);

select  DatFirstOfMonth = dateadd(month,n,@DatFirstOfFirstMonth)
from    (
        select  top (datediff(month,@DatFirstOfFirstMonth,@DatLast)+1)
                n=row_number() over (order by (select 1))-1
        from    (values (1),(1),(1),(1),(1),(1),(1),(1)) a (n)
        cross join (values (1),(1),(1),(1),(1),(1),(1),(1)) b (n)
        cross join (values (1),(1),(1),(1),(1),(1),(1),(1)) c (n)
        cross join (values (1),(1),(1),(1),(1),(1),(1),(1)) d (n)
        ) x