SQL中的动态区间创建

时间:2009-08-01 14:02:18

标签: sql sql-server tsql

我有以下问题,我想用transact-sql解决。 我有类似的东西

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

我想创建像

这样的东西
Start | End | Item-Combination 
  1   |  2  |    A 
  3   |  5  |    A-B 
  6   |  8  |    B 

对于Item-Combination连接,我已经考虑过使用FOR XML语句。但是为了创造不同的新间隔...我真的不知道如何处理它。有什么想法吗?

感谢。

4 个答案:

答案 0 :(得分:1)

我的一些计算机使用数据问题非常类似。我有会话数据表明登录/注销时间。我想找到最需要的时间(一周中每天的每小时),即大多数用户登录的小时数。我最终使用哈希表解决了客户端问题。对于每个会话,我会针对与会话处于活动状态的每天/每小时的星期几和小时相对应的特定位置递增桶。检查所有会话后,哈希表值显示每周每天的登录次数。

我认为你可以做类似的事情,跟踪每个开始/结束值的每个项目。然后,您可以通过折叠具有相同项目组合的相邻条目来重建表格。

而且,不,我想不出用SQL解决我的问题的方法。

答案 1 :(得分: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

然后我可以像这样处理问题:每个“开始”和每个“结束”值表示当前项目集合的变化,在某个时间添加一个或删除一个。在下面的代码中,我把这个概念称为“事件”,意思是添加或删除。每个开始或结束都像一个时间,所以我使用术语“刻度”。如果我按事件时间(开始和结束)排序所有事件的集合,我可以迭代它,同时在正在播放的所有项目的内存表中保持运行计数。每次刻度值发生变化时,我都会对该记录进行快照:

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书籍中有一些很好的例子。

答案 2 :(得分: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

答案 3 :(得分:0)

非常感谢所有答案,目前我找到了一种方法。我正在处理一个数据仓库,我有一个时间维度,我可以使用“内部联接DimTime t on t.date f.start_date和end_date”之类的时间维度进行一些连接。

从性能的角度来看,这不是很好,但它似乎对我有用。

我将尝试onupdatecascade实现,看看哪种更适合我。