我有一个包含以下数据的表
periodID periostart periodend
1 01-01-2012 10-01-2012
2 11-01-2012 01-04-2012
我想查询返回这样的内容。 periodID日期
1 01-01-2012
1 02-01-2012
1 03-01-2012
etc.
1 09-01-2012
2 11-01-2012
2 12-01-2012
etc.
2 31-03-2012
由于我有大约100,000行包含句点,我想研究一种不会影响性能的解决方案(游标,循环)。是否有可能在不使用游标或循环的情况下获得我想要的结果?
感谢参与者。
到目前为止,我得到了这个解决方案
create table #p (id int, periodstart smalldatetime, periodend smalldatetime );
insert into #p values
(1, '2012-01-01', '2015-01-10')
insert into #p values
(2, '2012-04-10', '2015-11-20');
SELECT TOP 366 --aprox one year
IDENTITY(INT,0,1) AS N
INTO #Tally
FROM Master.dbo.SysColumns sc1,
Master.dbo.SysColumns sc2
SELECT DATEADD(day, T.N, periodstart) AS [Date]
FROM #p
cross join #tally T
WHERE (T.N >= 0 AND T.N < DATEDIFF(day, periodstart, periodend))
ORDER BY [Date]
答案 0 :(得分:4)
这是:
create table #p (id int, periostart date, periodend date );
insert into #p values
(1, '20120101', '20120110'),
(2, '20120110', '20120120');
with cte as (
select
id, periostart as aDay
from
#p
union all
select
cte.id, dateadd( day, 1, cte.aDay) as aDay
from
#p
inner join
cte on #p.id = cte.id
where
cte.aDay < #p.periodend
)
select * from cte
结果:
id aDay
-- -------------
1 2012-01-01 00:00:00
2 2012-01-10 00:00:00
...
2 2012-01-17 00:00:00
2 2012-01-18 00:00:00
2 2012-01-19 00:00:00
2 2012-01-20 00:00:00
1 2012-01-02 00:00:00
1 2012-01-03 00:00:00
...
1 2012-01-08 00:00:00
1 2012-01-09 00:00:00
1 2012-01-10 00:00:00
解释:我使用CTE递归将新日期添加到当前日期限制为结束时段的1天。难以在递归连接中获取最后生成的日期,我使用OVER子句解决此问题。我认为这是一个很好的查询。
已审核OP评论
我发布了这种方法,因为OP要求一个没有循环且没有游标的解决方案。我不知道在没有循环或游标的情况下编写sql语句的任何其他方法。
另外,我认为生成日期的正确方法是使用内部循环或使用Itzik样式交叉连接的句点表上的光标。
答案 1 :(得分:1)
我不完全确定,但我认为你想使用BETWEEN运算符。像
这样的东西SELECT PeriodID, @Date FROM Periods WHERE @Date BETWEEN periodstart AND periodend
或者如果您通过加入包含日期的表来获取日期,请在加入中使用BETWEEN
SELECT periodTable.PeriodID, dateTable.Date
FROM periodTable
INNER JOIN dateTable ON dateTable.Date BETWEEN periodTable.periodstart AND periodTable.periodEnd
在这种情况下,您将拥有一张表,其中包含从最早到最晚可能的每个日期,并加入如上所示的表格。像这样的表可以这样创建:
CREATE TABLE dateTable(ID int identity(1,1), Date datetime NOT NULL)
Declare @d datetime
set @d=CONVERT(datetime, '1/1/1990')--start date
While @d<=CONVERT(datetime, '1/1/2020')--enddate
Begin
Insert into dateTable values (@d)
set @d=DATEADD(dd, 1, @d)
End
答案 2 :(得分:1)
我的第一个 Itzik风格交叉加入:
declare @longestPeriod int
set @longestPeriod = 1000 --you should calculate it with a single query
create table #p (id int, periostart date, periodend date );
insert into #p values
(1, '20120101', '20120110'),
(2, '20120110', '20120120');
WITH
E1(N) AS (
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
), -- 1*10^1 or 10 rows
E2(N) AS (SELECT 1 FROM E1 a, E1 b), -- 1*10^2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), -- 1*10^4 or 10,000 rows
E8(N) AS (SELECT 1 FROM E4 a, E4 b), -- 1*10^8 or 100,000,000 rows
NN as (SELECT top(@longestPeriod) ROW_NUMBER()
OVER (ORDER BY (SELECT NULL)) as N FROM E8 )
select
id,dateadd( dd, NN.N , periostart) as aDay
from
#p
cross join
NN
where NN.N between 0 and datediff( dd, periostart, periodend )
每天我都会学到一些东西。
<强> Itzik-Style Cross-Join 强>:
这个坏孩子的真正惊奇之处在于产生了零 读取数据。绝对没有,纳达,没有。
请约翰,发布性能测试!!!