2011-04-15 15 views
9

Sin bucles o cursores, ¿Cómo se toma una lista de intervalos de fecha y convertirlos en una cadena de 1s y 0s tal que:rompecabezas SQL Interesante

  • cada bit representa cada día de minutos (todas las fechas) a un máximo (todas las fechas)
  • el bit es 1 si ese día cae dentro de cualquiera de los intervalos de fechas
  • el bit es 0 si ese día no cae en cualquiera de los intervalos

So por ejemplo, si los intervalos son e:

  • 1/1/2011 a 1/2/2011
  • 1/4/2011 a 1/5/2011

A continuación, el SQL se escribe debe ser la salida 11011. Aquí está una secuencia de comandos de configuración que puede usar:

declare @TimeSpan table 
(
    start datetime 
    ,finish datetime 
) 

-- this is a good data set, with overlapping and non-overlapping time spans 
insert into @TimeSpan values ('02/02/2010', '02/02/2010') 
insert into @TimeSpan values ('02/03/2010', '02/03/2010') 
insert into @TimeSpan values ('02/04/2010', '02/05/2010') 
insert into @TimeSpan values ('02/05/2010', '02/06/2010') 
insert into @TimeSpan values ('02/07/2010', '02/09/2010') 
insert into @TimeSpan values ('02/08/2010', '02/08/2010') 
insert into @TimeSpan values ('02/08/2010', '02/10/2010') 
insert into @TimeSpan values ('02/14/2010', '02/16/2010') 

-- for this set of data, the output string would be 111111111000111 
+5

¿Tarea o entrevista? – JNK

+0

¿Cuenta un CTE recursivo como un bucle? –

+0

Suena como tarea –

Respuesta

7
DECLARE @Result VARCHAR(MAX), @start DATETIME 

SELECT @start= MIN(start) , 
     @Result =REPLICATE('0',1+DATEDIFF(DAY,MIN(start),MAX(finish))) 
FROM @TimeSpan 

SELECT @Result = STUFF(@Result, 
         DATEDIFF(DAY,@start,start)+1, 
         DATEDIFF(DAY,start,finish)+1, 
         REPLICATE('1',1+DATEDIFF(DAY,start,finish))) 
FROM @TimeSpan 

SELECT @Result  
+0

+1 para corregir, pero el tuyo fue realmente más lento. Eso fue extraño para mí también ... – Milimetric

+0

@Milimetric - No es particularmente sorprendente para mí. Una mesa de conteo permanente le ganará a la cruz unida al CTE, especialmente cuando comienzas a unirte a ella. Al ser un "acertijo", no estaba preocupado por el rendimiento. Actualizado con una versión mucho más simple de todos modos. –

+0

¡Tipo, impresionante! Este es definitivamente el camino a seguir. – Milimetric

2

estoy tenido que usar un CTE recursiva ;-)

DECLARE @BitString varchar(100); 
Declare @minStart datetime 
DECLARE @MaxEnd datetime 
declare @RangeDates table 
(
    start datetime 
    ,finish datetime 
) 

-- this is a good data set, with overlapping and non-overlapping time spans 
insert into @RangeDates values ('02/02/2010', '02/02/2010') 
insert into @RangeDates values ('02/03/2010', '02/03/2010') 
insert into @RangeDates values ('02/04/2010', '02/05/2010') 
insert into @RangeDates values ('02/05/2010', '02/06/2010') 
insert into @RangeDates values ('02/07/2010', '02/09/2010') 
insert into @RangeDates values ('02/08/2010', '02/08/2010') 
insert into @RangeDates values ('02/08/2010', '02/10/2010') 
insert into @RangeDates values ('02/14/2010', '02/16/2010') 

SELECT @minStart = MIN(start) FROM @RangeDates 
SELECT @MaxEnd = MAX(finish) FROM @RangeDates 

;WITH Dates AS (

     SELECT myDate = CONVERT(DateTime, @minStart), 
     CASE 
      WHEN exists (SELECT * FROM @RangeDates where @minStart between start and finish) then '1' 
      else '0' 
     END as myBit 
     UNION ALL 
     SELECT myDate = DATEADD(DAY,1,myDate), 
     CASE 
      WHEN exists (SELECT * FROM @RangeDates where myDate between start and finish) then '1' 
      else '0' 
     END 
     FROM Dates 
     where myDate <= @MaxEnd 
) 

SELECT @BitString = COALESCE(@BitString,'') + myBit FROM Dates 
SELECT @BitString 
+1

Genial, esa es una solución tan +1. Pero esperaré para ver si alguien lo hace sin tantas consultas secundarias. – Milimetric

1

Ok, esta es mi solución. Un poco más rápido que la otra solución de tabla de recuento, pero no es genial. Además, está limitado por la conversión a numérico para permitir solo intervalos de fechas min - max más pequeños. Es extraño que los CTE recursivos sean más rápidos que las tablas de conteo. ¿Las tablas de conteo escalan mejor?

declare @Tally table 
(
    N int identity(1,1), 
    T bit 
) 

insert into @Tally 
select TOP 11000 0 as T 
    from master.dbo.SysColumns sc1, master.dbo.SysColumns sc2 


declare @begin datetime = (select MIN(start) from @TimeSpan); 
declare @end datetime = (select MAX(finish) from @TimeSpan); 

with strings as 
(
select S.* 
     , 
     '1'+ 
     REPLICATE('0', DATEDIFF(DAY, @begin, DATEADD(DAY,N-1,S.start)))+ 
     '1'+ 
     REPLICATE('0', DATEDIFF(DAY, DATEADD(DAY,N-1,S.start), @end)) task 

    from @TimeSpan S 
      inner join 
     @Tally T     ON DateAdd(DAY,T.N-1,S.start) <= S.finish 
) 

select SUM(DISTINCT convert(numeric(38,0),task)) 
     - COUNT(DISTINCT task)*(convert(numeric(38,0), '1' + REPLICATE('0',DATEDIFF(d,@begin,@end)+1))) 
    from strings 
+0

RE: las tablas de Tally intentan agregar una clave principal. Vea esta respuesta para algunas comparaciones de rendimiento. http://stackoverflow.com/questions/10819/sql-auxiliary-table-of-numbers/2663232#2663232 He cambiado mi respuesta para que no sea necesario ahora. –