[英]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/11
和2/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
number
s we need to generate a first-of-month
date for each month between @d1 and @d2, inclusive of both months first-of-month
的first-of-month
日期所需的所有number
,包括两个月 first-of-month
date generated is between @d1 and @d2 first-of-month
日期是否在@ d1和@ d2之间 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.