2009-08-01 11 views
4

J'ai le problème suivant, que je voudrais résoudre avec transact-sql. J'ai quelque chose comme çacréation d'intervalle dynamique dans SQL

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

et je veux créer quelque chose comme

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

Pour la concaténation Point-Combinaison je l'ai déjà pensé à utiliser l'instruction FOR XML. Mais afin de créer les différents nouveaux intervalles ... Je ne sais vraiment pas comment l'aborder. Une idée?

Merci.

+0

Quelle version de SQL Server utilisez-vous? – Sung

+0

Combien d'éléments peuvent se chevaucher? (c'est à dire toujours 2, a et b, ou pourrait-il être un nombre quelconque?) – onupdatecascade

+0

SQL Server 2008, et le nombre d'éléments superposés peut être n'importe qui – river0

Répondre

1

J'ai eu un problème très similaire avec certaines données d'utilisation de l'ordinateur. J'avais des données de session indiquant les heures de connexion/déconnexion. Je voulais trouver les heures (heure du jour par jour de la semaine) les plus demandées, c'est-à-dire les heures où le plus d'utilisateurs étaient connectés. J'ai fini par résoudre le problème côté client en utilisant des tables de hachage. Pour chaque session, j'augmenterais le seau pour un emplacement particulier correspondant au jour de la semaine et à l'heure du jour pour chaque jour/heure pour lequel la session était active. Après avoir examiné toutes les sessions, les valeurs de table de hachage indiquent le nombre de connexions pendant chaque heure pour chaque jour de la semaine.

Je pense que vous pourriez faire quelque chose de similaire, en gardant une trace de chaque élément vu pour chaque valeur de début/fin. Vous pouvez ensuite reconstruire la table en réduisant les entrées adjacentes qui ont la même combinaison d'éléments.

Et, non, je ne pouvais pas non plus trouver un moyen de résoudre mon problème avec SQL.

1

Il s'agit d'un problème de télémétrie assez typique, avec la concaténation lancée. Vous ne savez pas si ce qui suit correspond exactement, mais c'est un point de départ. (Les curseurs sont généralement mieux évités sauf dans le petit ensemble de cas où ils sont plus rapides que les solutions basées sur les ensembles, donc avant que les ennemis du curseur ne m'atteignent, veuillez noter que j'utilise un curseur ici parce que cela me semble . problème - je les évite généralement)

donc, si je crée des données comme ceci:

 
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 

Ensuite, je peux traiter le problème comme ceci: chaque « Démarrer » et chaque valeur « End » représente un changement dans la collection d'éléments actuels, en ajoutant ou en supprimant un, à un moment donné. Dans le code ci-dessous, j'alias cette notion comme "événement", ce qui signifie un ajout ou un retrait. Chaque début ou fin est comme un temps, donc j'utilise le terme «tick». Si je fais une collection de tous les événements, classés par événement (Début ET Fin), je peux parcourir la liste tout en gardant un pointage dans une table en mémoire de tous les éléments en jeu. Chaque fois que la valeur tiques changements, je prends un aperçu de ce décompte:

 
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 

l'aide d'un curseur pour ce cas particulier ne permet qu'un seul « passer » à travers les données, comme un problème de cumuls. Itzik Ben-Gan en a d'excellents exemples dans ses livres SQL 2005.

0

Ce sera émule exactement et résout le problème mentionné:


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

Merci beaucoup pour toutes les réponses, pour le moment, je l'ai trouvé un moyen de le faire. Puisqu'il s'agit d'un datawarehouse, et que j'ai une dimension Time, je pourrais faire quelques jointures avec la dimension Time dans le style "jointure interne DimTime t sur t.date entre f.start_date et end_date".

Ce n'est pas très bon du point de vue des performances, mais il semble que cela fonctionne pour moi.

Je vais essayer de mettre en œuvre onupdatecascade, pour voir ce qui convient le mieux pour moi.

+0

Ehm, un petit cran serait le plus apprécié, merci! :) –