繁体   English   中英

SQL中的动态间隔创建

[英]dynamic interval creation in SQL

我有以下问题,我想用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语句。 但是为了创建不同的新间隔,我真的不知道该如何处理。 任何的想法?

谢谢。

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

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

而且,不,我也想不到一种解决SQL问题的方法。

这是一个相当典型的范围查找问题,并引发了串联。不确定以下内容是否完全合适,但这是一个起点。 (通常最好避免使用游标,除非在少数情况下它们比基于集合的解决方案要快,所以在讨厌游标之前,请注意我故意在这里使用游标,因为这对我来说就像游标友好问题-我通常会避免使用它们。)

因此,如果我创建这样的数据:

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

这将完全模拟并解决上述问题:


-- 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

非常感谢所有答案,目前我已经找到了一种解决方法。 因为我正在处理一个数据仓库,并且有一个“时间”维度,所以我可以使用“在f.start_date和end_date之间的t.date上的内部联接DimTime t”样式对Time维度进行一些联接。

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

我将尝试onupdatecascade实施,以了解哪种方法更适合我。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM