создание динамического интервала в SQL - PullRequest
4 голосов
/ 01 августа 2009

У меня есть следующая проблема, которую я хотел бы решить с помощью 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 

Для объединения элементов и комбинаций я уже думал об использовании оператора FOR XML. Но для того, чтобы создать разные новые интервалы ... Я действительно не знаю, как подойти к этому. Есть идеи?

Спасибо.

Ответы [ 4 ]

1 голос
/ 02 августа 2009

Это довольно типичная проблема определения дальности с добавленной конкатенацией. Не уверен, что следующее точно соответствует, но это отправная точка. (Курсоров обычно лучше избегать, за исключением небольшого набора случаев, когда они быстрее, чем решения на основе множеств, поэтому, прежде чем ненавистники курсора попадут на меня, обратите внимание, что я специально использую курсор здесь, потому что это пахнет для меня как дружественный к курсору проблема - я обычно их избегаю.)

Итак, если я создам данные, подобные этим:

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

Использование курсора для этого особого случая позволяет всего один «проход» через данные, как проблема с промежуточными итогами. Ицик Бен-Ган привел несколько замечательных примеров в своих книгах по SQL 2005.

1 голос
/ 01 августа 2009

У меня была очень похожая проблема с некоторыми данными об использовании компьютера. У меня были данные сеанса, указывающие время входа / выхода. Я хотел найти время (часы дня в день недели), которое было наиболее востребованным, то есть часы, в которые было зарегистрировано большинство пользователей. Я решил проблему на стороне клиента, используя хеш-таблицы. Для каждого сеанса я увеличивал интервал для определенного местоположения, соответствующего дню недели и часу дня для каждого дня / часа, для которого сеанс был активным. После проверки всех сеансов значения хеш-таблицы показывают количество входов в систему в течение каждого часа для каждого дня недели.

Я думаю, вы могли бы сделать нечто подобное, отслеживая каждый элемент, видимый для каждого начального / конечного значения. Затем можно восстановить таблицу, свернув смежные записи, которые имеют одинаковую комбинацию элементов.

И, нет, я тоже не мог придумать, как решить мою проблему с SQL.

0 голосов
/ 03 августа 2009

Большое спасибо за все ответы, на данный момент я нашел способ сделать это. Так как я имею дело с хранилищем данных, и у меня есть измерение Time, я мог бы сделать несколько объединений с измерением Time в стиле «внутреннее соединение DimTime t на t.date между f.start_date и end_date».

Это не очень хорошо с точки зрения производительности, но, похоже, работает на меня.

Я попробую обновить каскадную реализацию, чтобы узнать, какая из них лучше для меня.

0 голосов
/ 02 августа 2009

Это точно подражает и решает упомянутую проблему:


-- 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...