简体   繁体   English

每个月的第一个SQL

[英]SQL first of every month

Supposing that I wanted to write table valued function in SQL that returns a table with the first day of every month between the argument dates, what is the simplest way to do this? 假设我想在SQL中编写表值函数,返回一个表,参数日期之间每个月的第一天,这样做的最简单方法是什么?

For example fnFirstOfMonths('10/31/10', '2/17/11') would return a one-column table with 11/1/10, 12/1/10, 1/1/11 , and 2/1/11 as the elements. 例如, fnFirstOfMonths('10/31/10', '2/17/11')将返回一个包含fnFirstOfMonths('10/31/10', '2/17/11') 11/1/10, 12/1/10, 1/1/112/1/11作为要素。

My first instinct is just to use a while loop and repeatedly insert first days of months until I get to before the start date. 我的第一直觉就是使用while循环并重复插入几个月的前几天,直到我到达开始日期之前。 It seems like there should be a more elegant way to do this though. 看起来应该有更优雅的方式来做到这一点。

Thanks for any help you can provide. 感谢您的任何帮助,您可以提供。

Something like this would work without being inside a function: 这样的东西可以在不在函数内部的情况下工作:

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

It uses a thing called CTE (Common Table Expression) - available in SQL Server 2005 and up and other database systems. 它使用一种称为CTE(公用表表达式)的东西 - 可在SQL Server 2005及更高版本和其他数据库系统中使用。

In this case, I start the recursive CTE by determining the first of the month for the @LowerDate date specified, and then I iterate adding one month to the previous first of month, until the upper limit is reached. 在这种情况下,我通过确定指定的@LowerDate日期的月份的第一个开始递归CTE,然后迭代添加一个月到前一个月的第一个月,直到达到上限。

Or if you want to package it up in a stored function, you can do so, too: 或者,如果您想将其打包在存储函数中,您也可以这样做:

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

and then call it like this: 然后像这样调用它:

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

to get an output like this: 获得这样的输出:

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

PS: by using the DATE datatype - which is present in SQL Server 2008 and newer - I fixed the two "bugs" that Richard commented about. PS:通过使用DATE数据类型 - 它存在于SQL Server 2008及更新版本中 - 我修复了Richard评论的两个“错误”。 If you're on SQL Server 2005, you'll have to use DATETIME instead - and deal with the fact you're getting a time portion, too. 如果您使用的是SQL Server 2005,则必须使用DATETIME - 并处理您获得时间部分的事实。

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

Notes 笔记

  • master..spt_values is a source for general purpose sequence numbers in SQL Server master..spt_values是SQL Server中通用序列号的源
  • dateadd(m, datediff(m is a technique for working out the first day of month for any date dateadd(m,datediff(m是一种计算任何日期的第一天的技巧)
  • +v.number is used to increase it by one month each time + v.number用于每次增加一个月
  • 0 and datediff(m, @d1, @d2) this condition gives us all the number s we need to generate a first-of-month date for each month between @d1 and @d2, inclusive of both months 0和datediff(m,@ d1,@ d2)这个条件给出了我们在@ d1和@ d2之间生成first-of-monthfirst-of-month日期所需的所有number ,包括两个月
  • and dateadd(m,datediff(m,0,@d1)+v.number,0) between @d1 and @d2 the final filter to verify that the first-of-month date generated is between @d1 and @d2 和dateadd(m,datediff(m,0,@ d1)+ v.number,0)@ d1和@ d2之间的最终过滤器,用于验证生成的first-of-month日期是否在@ d1和@ d2之间


Performance comparison against marc_s's code 与marc_s代码的性能比较

Summary 摘要

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

Test 测试

 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 

It will loop just between the months involved (4 times in the example): 它将在所涉及的月份之间循环(在示例中为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

I realize this isn't a function, but I'm going to throw this into the mix anyway. 我意识到这不是一个功能,但无论如何我都会把它扔进混音中。

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

This calendar table runs on a PostgreSQL server at work. 这个日历表在PostgreSQL服务器上运行。 I'll port it to SQL Server tonight, and run some speed comparisons. 我今晚将它移植到SQL Server,并运行一些速度比较。 (Why? Because this stuff is fun, that's why.) (为什么?因为这个东西很有趣,这就是原因。)

Just in case anybody is still reading this ... I cannot imaging that any of the aforementioned functions is faster than this: 万一有人还在读这个...我无法想象任何上述功能比这更快:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM