如何在SQL Server中生成一个date范围
这个标题并不完全代表我的意思,这可能是重复的。
这里是长版本:给定一个客人的名字,他们的注册date和结帐date,我怎样才能生成每一天,他们是一个客人一行?
例如:鲍勃在7/14进行检查,离开7/17。 我想要
('Bob', 7/14), ('Bob', 7/15), ('Bob', 7/16), ('Bob', 7/17)
作为我的结果。
谢谢!
我会争辩说,为了这个特定的目的,下面的查询与使用专用查找表一样高效。
DECLARE @start DATE, @end DATE; SELECT @start = '20110714', @end = '20110717'; ;WITH n AS ( SELECT TOP (DATEDIFF(DAY, @start, @end) + 1) n = ROW_NUMBER() OVER (ORDER BY [object_id]) FROM sys.all_objects ) SELECT 'Bob', DATEADD(DAY, n-1, @start) FROM n;
结果:
Bob 2011-07-14 Bob 2011-07-15 Bob 2011-07-16 Bob 2011-07-17
大概你需要这个作为一个集合,而不是一个成员,所以这里是一个适应这种技术的方法:
DECLARE @t TABLE ( Member NVARCHAR(32), RegistrationDate DATE, CheckoutDate DATE ); INSERT @t SELECT N'Bob', '20110714', '20110717' UNION ALL SELECT N'Sam', '20110712', '20110715' UNION ALL SELECT N'Jim', '20110716', '20110719'; ;WITH [range](d,s) AS ( SELECT DATEDIFF(DAY, MIN(RegistrationDate), MAX(CheckoutDate))+1, MIN(RegistrationDate) FROM @t -- WHERE ? ), n(d) AS ( SELECT DATEADD(DAY, n-1, (SELECT MIN(s) FROM [range])) FROM (SELECT ROW_NUMBER() OVER (ORDER BY [object_id]) FROM sys.all_objects) AS s(n) WHERE n <= (SELECT MAX(d) FROM [range]) ) SELECT t.Member, nd FROM n CROSS JOIN @t AS t WHERE nd BETWEEN t.RegistrationDate AND t.CheckoutDate; ----------^^^^^^^ not many cases where I'd advocate between!
结果:
Member d -------- ---------- Bob 2011-07-14 Bob 2011-07-15 Bob 2011-07-16 Bob 2011-07-17 Sam 2011-07-12 Sam 2011-07-13 Sam 2011-07-14 Sam 2011-07-15 Jim 2011-07-16 Jim 2011-07-17 Jim 2011-07-18 Jim 2011-07-19
正如@德姆指出的,这可以简化为:
;WITH natural AS ( SELECT ROW_NUMBER() OVER (ORDER BY [object_id]) - 1 AS val FROM sys.all_objects ) SELECT t.Member, d = DATEADD(DAY, natural.val, t.RegistrationDate) FROM @t AS t INNER JOIN natural ON natural.val <= DATEDIFF(DAY, t.RegistrationDate, t.CheckoutDate);
我通常在某个表上使用row_number()来做这个事情。 所以:
select t.name, dateadd(d, seq.seqnum, t.start_date) from t left outer join (select row_number() over (order by (select NULL)) as seqnum from t ) seq on seqnum <= datediff(d, t.start_date, t.end_date)
seq的计算非常快,因为不需要计算或sorting。 但是,您需要确保桌子足够大,适合所有时间跨度。
如果你有一个“理货”或“数字”表,生活得到真正简单的这样的事情。
SELECT Member, DatePresent = DATEADD(dd,tN,RegistrationDate) FROM @t CROSS JOIN dbo.Tally t WHERE tN BETWEEN 0 AND DATEDIFF(dd,RegistrationDate,CheckoutDate) ;
这是如何build立一个“理货”表。
--=================================================================== -- Create a Tally table from 0 to 11000 --=================================================================== --===== Create and populate the Tally table on the fly. SELECT TOP 11001 IDENTITY(INT,0,1) AS N INTO dbo.Tally FROM Master.sys.ALL_Columns ac1 CROSS JOIN Master.sys.ALL_Columns ac2 ; --===== Add a CLUSTERED Primary Key to maximize performance ALTER TABLE dbo.Tally ADD CONSTRAINT PK_Tally_N PRIMARY KEY CLUSTERED (N) WITH FILLFACTOR = 100 ; --===== Allow the general public to use it GRANT SELECT ON dbo.Tally TO PUBLIC ; GO
有关SQL中“Tally”表的详细信息,以及如何用它来代替While循环以及计数后的CTE的“隐藏RBAR”,请参阅以下文章。
这可能适用于你:
with mycte as ( select cast('2000-01-01' as datetime) DateValue, 'Bob' as Name union all select DateValue + 1 ,'Bob' as Name from mycte where DateValue + 1 < '2000-12-31' ) select * from mycte OPTION (MAXRECURSION 0)
我会创build一个触发器来创build额外的logging,并在结帐时运行它。 或者,您可以每天进行午夜工作(如果您需要数据库中的最新信息)。