regresion raiz multiplicaciones lineal funcion elementos cuadrada c# sql-server linq sql-server-2012 intervals

c# - raiz - ¿Cómo hacer cálculos en intervalos de tiempo?



multiplicaciones en numbers (5)

Tengo un problema, lo resuelvo, pero he escrito un largo procedimiento y no puedo estar seguro de que cubra todos los casos posibles.

El problema:

Si tengo un intervalo de tiempo principal (de From A to B ) y tiempos de intervalo secundarios (muchos o no)

(`From X to Y AND From X` to Y` AND X`` to Y`` AND ....`)

Quiero sumar todas las partes de Mi intervalo de tiempo principal (AB) fuera de los intervalos secundarios en minutos en forma eficiente y con el menor número de condiciones (procedimiento del servidor SQL y método C #).

Por ejemplo: si mi intervalo principal es de 02:00 to 10:30 y diga un intervalo secundario de 04:00 to 08:00

Ahora quiero este resultado: ((04:00 - 02:00) + (10:30 -08:00))* 60

Ejemplo con gráfica:

En el primer caso el resultado será:

((X-A) + (B-Y)) * 60

y será más complicado cuando tenga muchos periodos secundarios.

NOTA:

Puede ser que la superposición entre los intervalos secundarios ocurra solo cuando tengo que comparar el período principal [A, B] con la UNIÓN de a lo sumo dos conjuntos paralelos de intervalos secundarios . El primer conjunto debe contener solo un intervalo secundario y el segundo el conjunto contiene (muchos o no) intervalos secundarios. Por ejemplo, en el gráfico que compara [A,B] con (conjuntos de 2,5 ), el primer conjunto (2) consta de un intervalo secundario y el segundo conjunto (5) consta de Tres intervalos secundarios. y este es el peor de los casos, necesito manejarlo.

Por ejemplo :

SI mi intervalo principal es [15:00,19:40] y tengo dos conjuntos de intervalos secundarios. De acuerdo con mi regla, al menos uno de estos conjuntos debe consistir en un intervalo secundario. digamos que el primer conjunto es [11:00 ,16:00] y el segundo conjunto consiste en, por ejemplo, dos intervalos secundarios [10:00,15:00],[16:30,17:45] Ahora quiero el resultado (16:30 -16:00) +(19:40 -17:45)

Según los comentarios:

Mi mesa es así:

La primera tabla contiene períodos secundarios, como máximo dos conjuntos de períodos secundarios en la misma fecha para un empleado específico. el primer conjunto contiene solo un período secundario en el día laboral (W) [work_st,work_end] , y este conjunto estará vacío si el día es fin de semana [E] y, en este caso, no se superponen entre los períodos secundarios. y el segundo conjunto puede contener muchos períodos secundarios en la misma fecha [check_in,check_out] , porque el empleado puede check_in_out muchas veces en el mismo día.

emp_num day_date work_st work_end check_in check_out day_state 547 2015-4-1 08:00 16:00 07:45 12:10 W 547 2015-4-1 08:00 16:00 12:45 17:24 W 547 2015-4-2 00:00 00:00 07:11 13:11 E

La segunda tabla contiene el período principal [A,B] y es un período para este empleado en ese día (un registro)

emp_num day_date mission_in mission_out 547 2015-4-1 15:00 21:30 547 2015-4-2 8:00 14:00

En el ejemplo anterior, si tengo un procedimiento o método según sea necesario, este procedimiento debería tomar dos parámetros:

  • La fecha
  • El emp_num

en el ejemplo anterior debería ser así (''2015-4-1'' ,547)

Según mi explicación:

  • El período principal (período de la misión) [A,B] de la segunda tabla: debe ser solo un período en esta fecha para ese empleado

    [15:00,21:30]

  • El período secundario para la fecha aprobada (''2015-4-1'') para ese empleado fue de dos grupos de períodos secundarios (el peor de los casos) de la primera tabla

    El primer conjunto debe contener solo un período secundario (o períodos cero) [08:00,16:00] el segundo conjunto podría contener muchos períodos secundarios (o períodos cero)

    [07:45,12:10] , [12:45,17:24]

La salida debe ser [17: 24,21: 30] convertida a minutos

Nota

all day_date,mission_in,mission_out,work_st,work_end,check_in,check_out son campos de datetime y datetime pero pongo la hora en el ejemplo de simplificación, quiero ignorar la parte de la fecha, excepto la day_date porque es la fecha en la que calculo más. al emp_num .


Aquí está SQLFiddle con consulta completa.

emp_num, day_date cómo construí una consulta que devuelve la cantidad de minutos para cada emp_num, day_date . Si resulta que no quedan minutos para un emp_num, day_date particular emp_num, day_date , el resultado no tendrá una fila con 0 , no habrá tal fila en absoluto.

Idea general

Usaré una tabla de números . Solo necesitaremos 24*60=1440 números, pero es una buena idea tener dicha tabla en su base de datos para otros informes. Yo personalmente lo tengo con 100,000 filas. Aquí hay un muy buen artículo que compara diferentes métodos para generar dicha tabla.

Para cada intervalo, generaré un conjunto de filas utilizando la tabla de números: una fila por cada minuto en el intervalo. Supongo que los intervalos son [start; end) [start; end) , es decir, el minuto de inicio es inclusivo, el minuto de finalización es exclusivo. Por ejemplo, el intervalo de 07:00 a 08:00 es de 60 minutos, no de 61 .

Generar una tabla de números.

DECLARE @Numbers TABLE (N int); INSERT INTO @Numbers(N) SELECT TOP(24*60) ROW_NUMBER() OVER(ORDER BY S.object_id) - 1 AS N FROM sys.all_objects AS S ORDER BY N ;

Para esta tarea, es mejor tener números que comiencen desde 0. Normalmente lo tendría como una tabla permanente con clave principal en N

Data de muestra

DECLARE @Missions TABLE (emp_num int, day_date datetime, mission_in datetime, mission_out datetime); DECLARE @Periods TABLE (emp_num int, day_date datetime, work_st datetime, work_end datetime, check_in datetime, check_out datetime, day_state char(1)); INSERT INTO @Missions (emp_num, day_date, mission_in, mission_out) VALUES (547, ''2015-04-01'', ''2015-04-01 15:00:00'', ''2015-04-01 21:30:00''), (547, ''2015-04-02'', ''2015-04-02 08:00:00'', ''2015-04-02 14:00:00''); INSERT INTO @Periods (emp_num, day_date, work_st, work_end, check_in, check_out, day_state) VALUES (547, ''2015-04-01'', ''2015-04-01 08:00:00'', ''2015-04-01 16:00:00'', ''2015-04-01 07:45:00'', ''2015-04-01 12:10:00'', ''W''), (547, ''2015-04-01'', ''2015-04-01 08:00:00'', ''2015-04-01 16:00:00'', ''2015-04-01 12:45:00'', ''2015-04-01 17:24:00'', ''W''), (547, ''2015-04-02'', ''2015-04-02 00:00:00'', ''2015-04-02 00:00:00'', ''2015-04-02 07:11:00'', ''2015-04-02 13:11:00'', ''E'');

Mi solución no utilizará la columna day_state . Espero que tenga 00:00:00 para work_st y work_end . La solución espera que el componente de fecha dentro de la misma fila sea el mismo y que day_date no tenga componente de tiempo.

Si diseñara el esquema para esta tarea, tendría tres tablas en lugar de dos: Missions , WorkPeriods y CheckPeriods . Dividiría los Periods su tabla en dos para evitar repetir work_st y work_end en varias filas. Pero esta solución se ocuparía de su esquema actual y generará esencialmente esta tercera tabla sobre la marcha. En la práctica significa que el rendimiento puede ser mejorado.

Minutos de misión

WITH CTE_MissionMinutes AS ( SELECT emp_num, day_date, N.N FROM @Missions AS M CROSS JOIN @Numbers AS N WHERE N.N >= DATEDIFF(minute, M.day_date, M.mission_in) AND N.N < DATEDIFF(minute, M.day_date, M.mission_out) )

Cada fila original de @Missions convierte en un conjunto de filas, una para cada minuto del intervalo (mission_in, mission_out) .

Periodos de trabajo

,CTE_WorkPeriods AS ( SELECT P.emp_num, P.day_date, P.work_st, P.work_end FROM @Periods AS P GROUP BY P.emp_num, P.day_date, P.work_st, P.work_end )

Genere una tercera tabla auxiliar: una fila para cada emp_num, day_date, work_st, work_end - todos los intervalos para (work_st, work_end) .

Minutos de trabajo y control

,CTE_WorkMinutes AS ( SELECT emp_num, day_date, N.N FROM CTE_WorkPeriods CROSS JOIN @Numbers AS N WHERE N.N >= DATEDIFF(minute, CTE_WorkPeriods.day_date, CTE_WorkPeriods.work_st) AND N.N < DATEDIFF(minute, CTE_WorkPeriods.day_date, CTE_WorkPeriods.work_end) ) ,CTE_CheckMinutes AS ( SELECT emp_num, day_date, N.N FROM @Periods AS P CROSS JOIN @Numbers AS N WHERE N.N >= DATEDIFF(minute, P.day_date, P.check_in) AND N.N < DATEDIFF(minute, P.day_date, P.check_out) )

Exactamente lo mismo que para Missions .

Unión "intervalos secundarios"

,CTE_UnionPeriodMinutes AS ( SELECT emp_num, day_date, N FROM CTE_WorkMinutes UNION ALL -- can be not ALL here, but ALL is usually faster SELECT emp_num, day_date, N FROM CTE_CheckMinutes )

Restar intervalos secundarios de primaria

,CTE_FinalMinutes AS ( SELECT emp_num, day_date, N FROM CTE_MissionMinutes EXCEPT SELECT emp_num, day_date, N FROM CTE_UnionPeriodMinutes )

Suma la cantidad de minutos

SELECT emp_num ,day_date ,COUNT(*) AS FinalMinutes FROM CTE_FinalMinutes GROUP BY emp_num, day_date ORDER BY emp_num, day_date;

Para hacer la consulta final solo tiene que poner todos los CTE juntos.

Conjunto resultante

emp_num day_date FinalMinutes 547 2015-04-01 00:00:00.000 246 547 2015-04-02 00:00:00.000 49 There are 246 minutes between 17:24 and 21:30. There are 49 minutes between 13:11 and 14:00.

Aquí está SQLFiddle con consulta completa.

Es bastante fácil mostrar los intervalos reales que llevan a esta SUM de minutos, pero usted dijo que solo necesita la SUM .


Descubrí probablemente la solución más simple.

.netFiddle

  1. Ordenar "Intervalos secundarios" por fecha de inicio.
  2. Busque espacios en "intervalos secundarios" (iteración simple)
  3. Comparar espacios con "intervalo principal".

    //declare intervals var secondryIntervals = new List<Tuple<DateTime, DateTime>> { new Tuple<DateTime, DateTime>( new DateTime(2015, 03, 15, 4, 0, 0), new DateTime(2015, 03, 15, 5, 0, 0)), new Tuple<DateTime, DateTime>( new DateTime(2015, 03, 15, 4, 10, 0), new DateTime(2015, 03, 15, 4, 40, 0)), new Tuple<DateTime, DateTime>( new DateTime(2015, 03, 15, 4, 40, 0), new DateTime(2015, 03, 15, 5, 20, 0))}; var mainInterval = new Tuple<DateTime, DateTime>(new DateTime(2015, 03, 15, 3, 0, 0), new DateTime(2015, 03, 15, 7, 0, 0)); // add two empty intervals before and after main interval secondryIntervals.Add(new Tuple<DateTime, DateTime>(mainInterval.Item1.AddMinutes(-1), mainInterval.Item1.AddMinutes(-1))); secondryIntervals.Add(new Tuple<DateTime, DateTime>(mainInterval.Item2.AddMinutes(1), mainInterval.Item2.AddMinutes(1))); secondryIntervals = secondryIntervals.OrderBy(s => s.Item1).ToList(); // endDate will rember ''biggest'' end date var endDate = secondryIntervals.First().Item1; var result = secondryIntervals.Select(s => { var temp = endDate; endDate = endDate < s.Item2 ? s.Item2 : endDate; if (s.Item1 > temp) { return new Tuple<DateTime, DateTime>(temp < mainInterval.Item1 ? mainInterval.Item1 : temp, mainInterval.Item2 < s.Item1 ? mainInterval.Item2 : s.Item1); } return null; }) // remove empty records .Where(s => s != null && s.Item2 > s.Item1).ToList(); var minutes = result.Sum(s => (s.Item2 - s.Item1).TotalMinutes);

El algoritmo requiere tiempo O (n log n) (para clasificar) sin almacenamiento y suposiciones adicionales.


He actualizado mi respuesta con su ejemplo de datos y estoy agregando otro ejemplo para un empleado 248 que usa los casos 2 y 5 de su gráfica.

--load example data for emply 547 select CONVERT(int, 547) emp_num, Convert(datetime, ''2015-4-1'') day_date, Convert(datetime, ''2015-4-1 08:00'') work_st, Convert(datetime, ''2015-4-1 16:00'') work_end, Convert(datetime, ''2015-4-1 07:45'') check_in, Convert(datetime, ''2015-4-1 12:10'') check_out, ''W'' day_state into #SecondaryIntervals insert into #SecondaryIntervals select 547, ''2015-4-1'', ''2015-4-1 08:00'', ''2015-4-1 16:00'', ''2015-4-1 12:45'', ''2015-4-1 17:24'', ''W'' insert into #SecondaryIntervals select 547, ''2015-4-2'', ''2015-4-2 00:00'', ''2015-4-2 00:00'', ''2015-4-2 07:11'', ''2015-4-2 13:11'', ''E'' select CONVERT(int, 547) emp_num, Convert(datetime, ''2015-4-1'') day_date, Convert(datetime, ''2015-4-1 15:00'') mission_in, Convert(datetime, ''2015-4-1 21:30'') mission_out into #MainIntervals insert into #MainIntervals select 547, ''2015-4-2'', ''2015-4-2 8:00'', ''2015-4-2 14:00'' --load more example data for an employee 548 with overlapping secondary intervals insert into #SecondaryIntervals select 548, ''2015-4-1'', ''2015-4-1 06:00'', ''2015-4-1 11:00'', ''2015-4-1 9:00'', ''2015-4-1 10:00'', ''W'' insert into #SecondaryIntervals select 548, ''2015-4-1'', ''2015-4-1 06:00'', ''2015-4-1 11:00'', ''2015-4-1 10:30'', ''2015-4-1 12:30'', ''W'' insert into #SecondaryIntervals select 548, ''2015-4-1'', ''2015-4-1 06:00'', ''2015-4-1 11:00'', ''2015-4-1 13:15'', ''2015-4-1 16:00'', ''W'' insert into #MainIntervals select 548, ''2015-4-1'', ''2015-4-1 8:00'', ''2015-4-1 14:00'' --Populate your Offline table with the intervals in #SecondaryIntervals select ROW_NUMBER() over (Order by emp_num, day_date, StartDateTime, EndDateTime) Rownum, emp_num, day_date, StartDateTime, EndDateTime into #Offline from (select emp_num, day_date, work_st StartDateTime, work_end EndDateTime from #SecondaryIntervals where day_state = ''W'' Group by emp_num, day_date, work_st, work_end union select emp_num, day_date, check_in StartDateTime, check_out EndDateTime from #SecondaryIntervals Group by emp_num, day_date, check_in, check_out ) SecondaryIntervals --Populate your Online table select ROW_NUMBER() over (Order by emp_num, day_date, mission_in, mission_out) Rownum, emp_num, day_date, mission_in StartDateTime, mission_out EndDateTime into #Online from #MainIntervals group by emp_num, day_date, mission_in, mission_out ------------------------------- --find overlaping offline times ------------------------------- declare @Finished as tinyint set @Finished = 0 while @Finished = 0 Begin update #Offline set #Offline.EndDateTime = OverlapEndDates.EndDateTime from #Offline join ( select #Offline.Rownum, MAX(Overlap.EndDateTime) EndDateTime from #Offline join #Offline Overlap on #Offline.emp_num = Overlap.emp_num and #Offline.day_date = Overlap.day_date and Overlap.StartDateTime between #Offline.StartDateTime and #Offline.EndDateTime and #Offline.Rownum <= Overlap.Rownum group by #Offline.Rownum ) OverlapEndDates on #Offline.Rownum = OverlapEndDates.Rownum --Remove Online times completely inside of online times delete #Offline from #Offline join #Offline Overlap on #Offline.emp_num = Overlap.emp_num and #Offline.day_date = Overlap.day_date and #Offline.StartDateTime between Overlap.StartDateTime and Overlap.EndDateTime and #Offline.EndDateTime between Overlap.StartDateTime and Overlap.EndDateTime and #Offline.Rownum > Overlap.Rownum --LOOK IF THERE ARE ANY MORE CHAINS LEFT IF NOT EXISTS( select #Offline.Rownum, MAX(Overlap.EndDateTime) EndDateTime from #Offline join #Offline Overlap on #Offline.emp_num = Overlap.emp_num and #Offline.day_date = Overlap.day_date and Overlap.StartDateTime between #Offline.StartDateTime and #Offline.EndDateTime and #Offline.Rownum < Overlap.Rownum group by #Offline.Rownum ) SET @Finished = 1 END ------------------------------- --Modify Online times with offline ranges ------------------------------- --delete any Online times completely inside offline range delete #Online from #Online join #Offline on #Online.emp_num = #Offline.emp_num and #Online.day_date = #Offline.day_date and #Online.StartDateTime between #Offline.StartDateTime and #Offline.EndDateTime and #Online.EndDateTime between #Offline.StartDateTime and #Offline.EndDateTime --Find Online Times with offline range at the beginning update #Online set #Online.StartDateTime = #Offline.EndDateTime from #Online join #Offline on #Online.emp_num = #Offline.emp_num and #Online.day_date = #Offline.day_date and #Online.StartDateTime between #Offline.StartDateTime and #Offline.EndDateTime and #Online.EndDateTime >= #Offline.EndDateTime --Find Online Times with offline range at the end update #Online set #Online.EndDateTime = #Offline.StartDateTime from #Online join #Offline on #Online.emp_num = #Offline.emp_num and #Online.day_date = #Offline.day_date and #Online.StartDateTime <= #Offline.StartDateTime and #Online.EndDateTime between #Offline.StartDateTime and #Offline.EndDateTime --Find Online Times with offline range punched in the middle select #Online.Rownum, #Offline.Rownum OfflineRow, #Offline.StartDateTime, #Offline.EndDateTime, ROW_NUMBER() over (Partition by #Online.Rownum order by #Offline.Rownum Desc) OfflineHoleNumber into #OfflineHoles from #Online join #Offline on #Online.emp_num = #Offline.emp_num and #Online.day_date = #Offline.day_date and #Offline.StartDateTime between #Online.StartDateTime and #Online.EndDateTime and #Offline.EndDateTime between #Online.StartDateTime and #Online.EndDateTime declare @HoleNumber as integer select @HoleNumber = isnull(MAX(OfflineHoleNumber),0) from #OfflineHoles --Punch the holes out of the online times While @HoleNumber > 0 Begin insert into #Online select -1 Rownum, #Online.emp_num, #Online.day_date, #OfflineHoles.EndDateTime StartDateTime, #Online.EndDateTime EndDateTime from #Online join #OfflineHoles on #Online.Rownum = #OfflineHoles.Rownum where OfflineHoleNumber = @HoleNumber update #Online set #Online.EndDateTime = #OfflineHoles.StartDateTime from #Online join #OfflineHoles on #Online.Rownum = #OfflineHoles.Rownum where OfflineHoleNumber = @HoleNumber set @HoleNumber=@HoleNumber-1 end --Output total hours select emp_num, day_date, SUM(datediff(second,StartDateTime, EndDateTime)) / 3600.0 TotalHr, SUM(datediff(second,StartDateTime, EndDateTime)) / 60.0 TotalMin from #Online group by emp_num, day_date order by 1, 2 --see how it split up the online intervals select emp_num, day_date, StartDateTime, EndDateTime from #Online order by 1, 2, 3, 4

La salida es:

emp_num day_date TotalHr TotalMin ----------- ----------------------- --------------------------------------- --------------------------------------- 547 2015-04-01 00:00:00.000 4.100000 246.000000 547 2015-04-02 00:00:00.000 0.816666 49.000000 548 2015-04-01 00:00:00.000 0.750000 45.000000 (3 row(s) affected) emp_num day_date StartDateTime EndDateTime ----------- ----------------------- ----------------------- ----------------------- 547 2015-04-01 00:00:00.000 2015-04-01 17:24:00.000 2015-04-01 21:30:00.000 547 2015-04-02 00:00:00.000 2015-04-02 13:11:00.000 2015-04-02 14:00:00.000 548 2015-04-01 00:00:00.000 2015-04-01 12:30:00.000 2015-04-01 13:15:00.000 (3 row(s) affected)

Dejé mi otra respuesta publicada porque es más genérica en caso de que alguien más quiera engancharla. Veo que has añadido una recompensa a esta pregunta. Avísame si hay algo específico en mi respuesta que no te satisface e intentaré ayudarte. Procesé miles de intervalos con este método y regresa en solo unos segundos.


Mi solución es bastante similar a Vladimir Baranov.

Enlace a .NetFiddle

Idea general

Mi algoritmo se basa en una modificación del árbol de intervalos . Se supone que la unidad de tiempo más pequeña es de 1 minuto (fácil de modificar).

Cada nodo de árbol está en 1 de 3 estados: no visitado, visitado y utilizado. El algoritmo se basa en la función de búsqueda recursiva que se puede describir mediante los siguientes pasos:

  1. Si se utiliza el nodo o el intervalo de búsqueda está vacío, devuelva el intervalo vacío.
  2. Si el nodo no está visitado y el intervalo de nodos es igual al intervalo de búsqueda, marque el nodo actual como utilizado y devuelva el intervalo de nodo.
  3. Marque el nodo como visitado , divida el intervalo de búsqueda y devuelva la suma de Búsqueda de elementos secundarios izquierdo y derecho.

Solución en pasos

  1. Calcula el mayor intervalo.
  2. Añadir al árbol "intervalos secundarios".
  3. Añadir al árbol "intervalo principal".
  4. Calcular suma de intervalos.

    Tenga en cuenta que supongo que los intervalos son [inicio; fin], es decir, ambos intervalos son inclusivos, lo que es fácil de cambiar.

Requerimientos

Asumiendo

n - número de "intervalos secundarios"

m - tiempo máximo en unidad base

La construcción requiere O (2n) espacio de almacenamiento y trabajo en tiempo O (n log n + m).

Aquí está mi código

public class Interval { public int Start { get; set; } public int End { get; set; } }; enum Node { Unvisited = 0, Visited = 1, Used = 2 }; Node[] tree; public void Calculate() { var secondryIntervalsAsDates = new List<Tuple<DateTime,DateTime>> { new Tuple<DateTime, DateTime>( new DateTime(2015, 03, 15, 4, 0, 0), new DateTime(2015, 03, 15, 5, 0, 0))}; var mainInvtervalAsDate = new Tuple<DateTime, DateTime>(new DateTime(2015, 03, 15, 3, 0, 0), new DateTime(2015, 03, 15, 7, 0, 0)); // calculate biggest interval var startDate = secondryIntervalsAsDates.Union( new List<Tuple<DateTime,DateTime>>{mainInvtervalAsDate}).Min(s => s.Item1).AddMinutes(-1); var endDate = secondryIntervalsAsDates.Union(new List<Tuple<DateTime, DateTime>> { mainInvtervalAsDate }).Max(s => s.Item2); var mainInvterval = new Interval { Start = (int)(mainInvtervalAsDate.Item1 - startDate).TotalMinutes, End = (int)(mainInvtervalAsDate.Item2 - startDate).TotalMinutes }; var wholeInterval = new Interval { Start = 1, End = (int)(endDate - startDate).TotalMinutes}; //convert intervals to minutes var secondaryIntervals = secondryIntervalsAsDates.Select(s => new Interval { Start = (int)(s.Item1 - startDate).TotalMinutes, End = (int)(s.Item2 - startDate).TotalMinutes}).ToList(); tree = new Node[wholeInterval.End * 2 + 1]; //insert secondary intervals secondaryIntervals.ForEach(s => Search(wholeInterval, s, 1)); //insert main interval var result = Search(wholeInterval, mainInvterval, 1); //calculate result var minutes = result.Sum(r => r.End - r.Start) + result.Count(); } public IEnumerable<Interval> Search(Interval current, Interval searching, int index) { if (tree[index] == Node.Used || searching.End < searching.Start) { return new List<Interval>(); } if (tree[index] == Node.Unvisited && current.Start == searching.Start && current.End == searching.End) { tree[index] = Node.Used; return new List<Interval> { current }; } tree[index] = Node.Visited; return Search(new Interval { Start = current.Start, End = current.Start + (current.End - current.Start) / 2 }, new Interval { Start = searching.Start, End = Math.Min(searching.End, current.Start + (current.End - current.Start) / 2) }, index * 2).Union( Search(new Interval { Start = current.Start + (current.End - current.Start) / 2 + 1 , End = current.End}, new Interval { Start = Math.Max(searching.Start, current.Start + (current.End - current.Start) / 2 + 1), End = searching.End }, index * 2 + 1)); }


Tuve que resolver este problema para digerir algunos datos de programación. Esto permite múltiples tiempos en línea, pero supone que no se superponen.

select convert(datetime,''1/1/2015 5:00 AM'') StartDateTime, convert(datetime,''1/1/2015 5:00 PM'') EndDateTime, convert(varchar(20),''Online'') IntervalType into #CapacityIntervals insert into #CapacityIntervals select ''1/1/2015 4:00 AM'' StartDateTime, ''1/1/2015 6:00 AM'' EndDateTime, ''Offline'' IntervalType insert into #CapacityIntervals select ''1/1/2015 5:00 AM'' StartDateTime, ''1/1/2015 6:00 AM'' EndDateTime, ''Offline'' IntervalType insert into #CapacityIntervals select ''1/1/2015 10:00 AM'' StartDateTime, ''1/1/2015 12:00 PM'' EndDateTime, ''Offline'' IntervalType insert into #CapacityIntervals select ''1/1/2015 11:00 AM'' StartDateTime, ''1/1/2015 1:00 PM'' EndDateTime, ''Offline'' IntervalType insert into #CapacityIntervals select ''1/1/2015 4:00 PM'' StartDateTime, ''1/1/2015 6:00 PM'' EndDateTime, ''Offline'' IntervalType insert into #CapacityIntervals select ''1/1/2015 1:30 PM'' StartDateTime, ''1/1/2015 2:00 PM'' EndDateTime, ''Offline'' IntervalType --Populate your Offline table select ROW_NUMBER() over (Order by StartDateTime, EndDateTime) Rownum, StartDateTime, EndDateTime into #Offline from #CapacityIntervals where IntervalType in (''Offline'',''Cleanout'') group by StartDateTime, EndDateTime --Populate your Online table select ROW_NUMBER() over (Order by StartDateTime, EndDateTime) Rownum, StartDateTime, EndDateTime into #Online from #CapacityIntervals where IntervalType not in (''Offline'',''Cleanout'') --If you have overlapping online intervals... check for those here and consolidate. ------------------------------- --find overlaping offline times ------------------------------- declare @Finished as tinyint set @Finished = 0 while @Finished = 0 Begin update #Offline set #Offline.EndDateTime = OverlapEndDates.EndDateTime from #Offline join ( select #Offline.Rownum, MAX(Overlap.EndDateTime) EndDateTime from #Offline join #Offline Overlap on Overlap.StartDateTime between #Offline.StartDateTime and #Offline.EndDateTime and #Offline.Rownum <= Overlap.Rownum group by #Offline.Rownum ) OverlapEndDates on #Offline.Rownum = OverlapEndDates.Rownum --Remove Online times completely inside of online times delete #Offline from #Offline join #Offline Overlap on #Offline.StartDateTime between Overlap.StartDateTime and Overlap.EndDateTime and #Offline.EndDateTime between Overlap.StartDateTime and Overlap.EndDateTime and #Offline.Rownum > Overlap.Rownum --LOOK IF THERE ARE ANY MORE CHAINS LEFT IF NOT EXISTS( select #Offline.Rownum, MAX(Overlap.EndDateTime) EndDateTime from #Offline join #Offline Overlap on Overlap.StartDateTime between #Offline.StartDateTime and #Offline.EndDateTime and #Offline.Rownum < Overlap.Rownum group by #Offline.Rownum ) SET @Finished = 1 END ------------------------------- --Modify Online times with offline ranges ------------------------------- --delete any Online times completely inside offline range delete #Online from #Online join #Offline on #Online.StartDateTime between #Offline.StartDateTime and #Offline.EndDateTime and #Online.EndDateTime between #Offline.StartDateTime and #Offline.EndDateTime --Find Online Times with offline range at the beginning update #Online set #Online.StartDateTime = #Offline.EndDateTime from #Online join #Offline on #Online.StartDateTime between #Offline.StartDateTime and #Offline.EndDateTime and #Online.EndDateTime >= #Offline.EndDateTime --Find Online Times with offline range at the end update #Online set #Online.EndDateTime = #Offline.StartDateTime from #Online join #Offline on #Online.StartDateTime <= #Offline.StartDateTime and #Online.EndDateTime between #Offline.StartDateTime and #Offline.EndDateTime --Find Online Times with offline range punched in the middle select #Online.Rownum, #Offline.Rownum OfflineRow, #Offline.StartDateTime, #Offline.EndDateTime, ROW_NUMBER() over (Partition by #Online.Rownum order by #Offline.Rownum Desc) OfflineHoleNumber into #OfflineHoles from #Online join #Offline on #Offline.StartDateTime between #Online.StartDateTime and #Online.EndDateTime and #Offline.EndDateTime between #Online.StartDateTime and #Online.EndDateTime declare @HoleNumber as integer select @HoleNumber = isnull(MAX(OfflineHoleNumber),0) from #OfflineHoles --Punch the holes out of the online times While @HoleNumber > 0 Begin insert into #Online select -1 Rownum, #OfflineHoles.EndDateTime StartDateTime, #Online.EndDateTime EndDateTime from #Online join #OfflineHoles on #Online.Rownum = #OfflineHoles.Rownum where OfflineHoleNumber = @HoleNumber update #Online set #Online.EndDateTime = #OfflineHoles.StartDateTime from #Online join #OfflineHoles on #Online.Rownum = #OfflineHoles.Rownum where OfflineHoleNumber = @HoleNumber set @HoleNumber=@HoleNumber-1 end --Output total hours select SUM(datediff(second,StartDateTime, EndDateTime)) / 3600.0 TotalHr from #Online --see how it split up the online intervals select * from #Online order by StartDateTime, EndDateTime