2012-02-04 44 views
2

我有一個包含以下數據時段之間獲取日期

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] 

回答

1

我的第一個伊茨克風格的交叉聯接

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

什麼是真正了不起這個壞男孩是產生ZERO 的讀取。絕對沒有,納達,零。

請約翰發佈性能測試!!!

+0

事件探查器問題的性能,我會在解決問題後立即發佈結果。 – mko 2012-02-04 23:32:31

+0

這基本上是從我提供的文章中提取的,但是既然您表現出了幫助的努力,我會將其標記爲解決方案。 – mko 2012-02-05 11:03:15

1

不能完全確定我理解,但我想你想使用BETWEEN操作。像

SELECT PeriodID, @Date FROM Periods WHERE @Date BETWEEN periodstart AND periodend 

的東西,或者如果你是通過連接到包含日期的表中獲取的日,從使用的加入

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 
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子句解決了這個問題。我認爲這是一個很好的問題。

EDITED由於OP評論

我已經發布了這種做法,因爲OP要求沒有循環,也沒有光標的解決方案。我不知道任何其他方式來寫一個沒有循環或遊標的SQL句子,這一個。

此外,我認爲生成日期的正確方法是在內部循環的週期表上使用遊標或使用Itzik式交叉連接

+0

週期跨度較大的問題。 「聲明終止了。在聲明完成之前,最大遞歸100已經耗盡。」 ?? – mko 2012-02-04 21:35:39

+0

追加這個選項來查詢:'SELECT * FROM CTE OPTION(MAXRECURSION 150);'按最大日期變化150。這裏有一個例子:[與SQL玩雜耍](http://www.sqlservercentral.com/blogs/juggling_with_sql/2011/06/04/maximum-recursion-possible-with-cte-in-sql-server-2005-2008/ ) – danihp 2012-02-04 21:41:01

+0

感謝您的參與,但我發現這篇文章解釋了爲什麼CTE不是計數的最佳解決方案。 http://www.sqlservercentral.com/articles/T-SQL/74118/ – mko 2012-02-04 21:43:57