2009-08-01 198 views
4

我有以下問題,我想用transact-sql解決。 我有這樣的事情在SQL中動態創建間隔

Start | End | Item 
    1 | 5 | A 
    3 | 8 | B 

,我想創造這樣

Start | End | Item-Combination 
    1 | 2 | A 
    3 | 5 | A-B 
    6 | 8 | B 

因爲我已經想到了使用FOR XML語句的項目,組合串聯。但爲了創造不同的新時間間隔......我真的不知道如何處理它。任何想法?

謝謝。

+0

您運行的是哪個版本的SQL Server? – Sung 2009-08-01 16:27:11

+0

有多少物品可能重疊? (也就是說它總是2,a和b,或者它可能是任何數字?) – onupdatecascade 2009-08-01 18:44:16

回答

1

我的計算機使用數據有一個非常類似的問題。我有會話數據指示登錄/註銷時間。我想找到最需要的時間(即每週每天的小時),也就是大多數用戶登錄的時間。我最終使用散列表解決了客戶端問題。對於每個會話,我會針對特定位置增加一個特定位置,該位置對應於會話處於活動狀態的每天/每小時的星期幾和小時。在檢查所有會話之後,哈希表值顯示一週中每一天每個小時的登錄次數。

我認爲你可以做類似的事情,跟蹤每個開始/結束值的每個項目。然後,您可以通過摺疊具有相同項目組合的相鄰條目來重建表格。

而且,不,我想不出一種解決SQL問題的方法。

1

這是一個相當典型的範圍尋找問題,引入了級聯。不確定以下幾點是否完全吻合,但這是一個起點。 (遊標通常是最好的避免,除了小於一組的情況下,它們比基於集合的解決方案更快,所以在遊標討厭之前請注意我在這裏使用遊標,因爲這對我來說像一個友好的遊標。問題 - 我通常避免他們)

所以,如果我創建的數據是這樣的:

 
CREATE TABLE [dbo].[sourceValues](
    [Start] [int] NOT NULL, 
    [End] [int] NOT NULL, 
    [Item] [varchar](100) NOT NULL 
) ON [PRIMARY] 
GO 

ALTER TABLE [dbo].[sourceValues] WITH CHECK ADD CONSTRAINT [End_after_Start] CHECK (([End]>[Start])) 
GO 

ALTER TABLE [dbo].[sourceValues] CHECK CONSTRAINT [End_after_Start] 
GO 

declare @i int; set @i = 0; 
declare @start int; 
declare @end int; 
declare @item varchar(100); 
while @i < 1000 
begin 
    set @start = ABS(CHECKSUM(newid()) % 100) + 1 ; -- "random" int 
    set @end = @start + (ABS(CHECKSUM(newid()) % 10)) + 2; -- bigger random int 
    set @item = char((ABS(CHECKSUM(newid())) % 5) + 65); -- random letter A-E 
    print @start; print @end; print @item; 
    insert into sourceValues(Start, [End], Item) values (@start , @end, @item); 
    set @i += 1; 
end 

然後我可以把這樣的問題:每一個「開始」,每個「結束」值表示的改變在當前Items的集合中,在某個時間添加一個或刪除一個。在下面的代碼中,我把這個概念與「事件」混爲一談,意思是添加或刪除。每個開始或結束都像是一個時間,所以我使用術語「打勾」。如果我按照事件時間(開始和結束)排序的所有事件的集合,我可以遍歷所有事件,同時保持所有正在運行的項目在內存表中的運行計數。每次刻度值的變化,我拿這一紀錄的快照:

 
declare @tick int; 
declare @lastTick int; 
declare @event varchar(100); 
declare @item varchar(100); 
declare @concatList varchar(max); 
declare @currentItemsList table (Item varchar(100)); 

create table #result (Start int, [End] int, Items varchar(max)); 

declare eventsCursor CURSOR FAST_FORWARD for 
    select tick, [event], item from (
     select start as tick, 'Add' as [event], item from sourceValues as adds 
     union all 
     select [end] as tick, 'Remove' as [event], item from sourceValues as removes 
    ) as [events] 
    order by tick 

set @lastTick = 1 
open eventsCursor 
fetch next from eventsCursor into @tick, @event, @item 
while @@FETCH_STATUS = 0 
BEGIN 
    if @tick != @lastTick 
    begin 
     set @concatList = '' 
     select @concatList = @concatlist + case when len(@concatlist) > 0 then '-' else '' end + Item 
     from @currentItemsList 
     insert into #result (Start, [End], Items) values (@lastTick, @tick, @concatList) 
    end 

    if @event = 'Add' insert into @currentItemsList (Item) values (@item); 
    else if @event = 'Remove' delete top (1) from @currentItemsList where Item = @item; 

    set @lastTick = @tick; 
    fetch next from eventsCursor into @tick, @event, @item; 
END 

close eventsCursor 
deallocate eventsCursor 

select * from #result order by start 
drop table #result 

使用光標這種特殊情況下允許只有一個「通」,通過數據,如運行總計問題。 Itzik Ben-Gan在他的SQL 2005書籍中有一些很好的例子。

0

這將準確模擬和解決了上述問題:


-- prepare problem, it can have many rows with overlapping ranges 
declare @range table 
(
    Item char(1) primary key, 
    [Start] int, 
    [End] int 
) 
insert @range select 'A', 1, 5 
insert @range select 'B', 3, 8 

-- unroll the ranges into helper table 
declare @usage table 
(
    Item char(1), 
    Number int 
) 

declare 
    @Start int, 
    @End int, 
    @Item char(1) 

declare table_cur cursor local forward_only read_only for 
    select [Start], [End], Item from @range 
open table_cur 
fetch next from table_cur into @Start, @End, @Item 
while @@fetch_status = 0 
begin 
    with 
    Num(Pos) as -- generate numbers used 
    (
     select cast(@Start as int) 
     union all 
     select cast(Pos + 1 as int) from Num where Pos < @End 
    ) 
    insert 
     @usage 
    select 
     @Item, 
     Pos 
    from 
     Num 
    option (maxrecursion 0) -- just in case more than 100 

    fetch next from table_cur into @Start, @End, @Item 
end 
close table_cur 
deallocate table_cur 

-- compile overlaps 
; 
with 
overlaps as 
(
    select 
     Number, 
     (
      select 
       Item + '-' 
      from 
       @usage as i 
      where 
       o.Number = i.Number 
      for xml path('') 
     ) 
     as Items 
    from 
     @usage as o 
    group by 
     Number 
) 
select 
    min(Number) as [Start], 
    max(Number) as [End], 
    left(Items, len(Items) - 1) as Items -- beautify 
from 
    overlaps 
group by 
    Items 
0

非常感謝所有的答案,目前我發現做這件事的方式。即使我正在處理一個數據倉庫,並且我有一個時間維度,我可以使用時間維度進行一些連接,樣式爲「在f.start_date和end_date之間的t.date內部連接DimTime t」。

從性能的角度來看這不太好,但它似乎對我有用。

我會嘗試onupdatecascade實現,看看哪些更適合我。