2009-08-13 7 views
2

T-SQL DateTime Frage.Auswahl überlappender Zeitbereiche

Ich habe eine Reihe von Zeitbereichen. Während dieser Zeitbereiche könnte es eine Reihe von überlappenden Zeitbereichen geben, die ich als "blockierte" Zeit bezeichne. Die blockierte Zeit würde nicht länger als einen Tag dauern. Was ich tun möchte, ist die Zeit zu teilen, um die blockierte Zeit auszuschließen, im Grunde geben mir die Zeitbereiche, die nicht "blockiert" sind. Es ist sicher anzunehmen, dass blockierte Zeiten nicht außerhalb der Zeitbereiche liegen können.

Beispiel: Ich arbeite 9 bis 17 Uhr mit einer 30-minütigen Mittagspause um 13 Uhr. Ich möchte das Ergebnis von 2 Zeilen: 9 bis 13 und 13:30 bis 17 Uhr.

Wie bereits erwähnt, habe ich eine Reihe von Zeitbereichen. Im obigen Beispiel können die Arbeitszeiten daher täglich abweichen und die Anzahl der Pausen sowie deren Dauer variieren.

Ich denke, in Bezug auf die SQL die Eingabeparameter wie folgt aussehen würde:

declare @timeranges table (StartDateTime datetime, EndDateTime datetime) 
declare @blockedtimes table (StartDateTime datetime, EndDateTime datetime) 

insert into @timeranges 
select '01 Jan 2009 09:00:00', '01 Jan 2009 17:00:00' 
union select '02 Feb 2009 10:00:00', '02 Feb 2009 13:00:00' 

insert into @blockedtimes 
select '01 Jan 2009 13:00:00', '01 Jan 2009 13:30:00' 
union select '02 Feb 2009 10:30:00', '02 Feb 2009 11:00:00' 
union select '02 Feb 2009 12:00:00', '02 Feb 2009 12:30:00' 

Die Ergebnismenge wird wie folgt aussehen würde.

Start     End 
--------------------- --------------------- 
'01 Jan 2009 09:00:00' '01 Jan 2009 13:00:00' 
'01 Jan 2009 13:30:00' '01 Jan 2009 17:00:00' 
'02 Feb 2009 10:00:00' '02 Feb 2009 10:30:00' 
'02 Feb 2009 11:00:00' '02 Feb 2009 12:00:00' 
'02 Feb 2009 12:30:00' '02 Feb 2009 13:00:00' 

Ich konnte dies mit einem Cursor oder while-Schleife, aber wenn jemand könnte vorschlagen, wie dies ohne Iteration zu tun, das wäre toll - danke.

+0

Dies hat komm vorher. –

+0

@Daniel A. White: Ich kann es nicht finden, kümmern sich um einen Link zu teilen? –

+0

Ich konnte keine Antwort finden, weshalb ich die Frage gestellt habe. –

Antwort

1

Ich dachte, ich die Lösung würde Aktie Ich entschied mich schließlich für:

Leichte Anpassung an die temp-Tabelle, dass ich habe ein Startdate Feld zu beiden @timeranges hinzugefügt und @blockedtimes

declare @timeranges table (StartDate datetime, StartDateTime datetime, EndDateTime datetime) 
declare @blockedtimes table (StartDate datetime, StartDateTime datetime, EndDateTime datetime) 

Anyways einfacher scheint, als einige der anderen Antwort geschrieben - Jubel für everyones Hilfe :)

select 
    * 
from 
(
    -- first SELECT get start boundry 
    select t.StartDateTime s, b.StartDateTime e 
    from @timeranges t, @blockedtimes b 
    where 
     -- same day and blocks overlaps timerange 
     t.StartDate = b.StartDate and (t.StartDateTime <= b.EndDateTime and b.StartDateTime <= t.EndDateTime) 
    and 
     -- the following is the important bit for this SELECT 
     not exists (select 1 from @blockedtimes b2 where b2.StartDate = b.StartDate and b2.StartDateTime < b.StartDateTime) 
union 
    -- second SELECT get spikes ie middle 
    select b1.EndDateTime s, b2.StartDateTime e 
    from @timeranges t, @blockedtimes b1, @blockedtimes b2 
    where 
     -- same day and blocks overlaps timerange 
     t.StartDate = b1.StartDate and (t.StartDateTime <= b1.EndDateTime and b1.StartDateTime <= t.EndDateTime) 
    and 
     -- same day and blocks overlaps timerange 
     t.StartDate = b2.StartDate and (t.StartDateTime <= b2.EndDateTime and b2.StartDateTime <= t.EndDateTime) 
    and 
     -- the following is the important bit for this SELECT 
     b1.EndDateTime < b2.StartDateTime 
union 
    -- third SELECT get end boundry 
    select b.EndDateTime s, t.EndDateTime e 
    from @timeranges t, @blockedtimes b 
    where 
     -- same day and blocks overlaps timerange 
     t.StartDate = b.StartDate and (t.StartDateTime <= b.EndDateTime and b.StartDateTime <= t.EndDateTime) 
    and 
     -- the following is the important bit for this SELECT 
     not exists (select 1 from @blockedtimes b2 where b2.StartDate = b.StartDate and b2.StartDateTime > b.StartDateTime) 
) t1 
2

Zuerst schneiden, kann einige Probleme haben, aber ich werde weiter daran arbeiten.
Arbeiten für die gegebenen Daten müssen nur zusätzliche Szenarien

declare @timeranges table (StartDateTime datetime, EndDateTime datetime) 
declare @blockedtimes table (StartDateTime datetime, EndDateTime datetime) 

insert into @timeranges 
select '01 Jan 2009 09:00:00', '01 Jan 2009 17:00:00' 
union select '02 Feb 2009 10:00:00', '02 Feb 2009 13:00:00' 
--union select '03 Feb 2009 10:00:00', '03 Feb 2009 15:00:00' 


insert into @blockedtimes 
select '01 Jan 2009 13:00:00', '01 Jan 2009 13:30:00' 
union select '02 Feb 2009 10:30:00', '02 Feb 2009 11:00:00' 
union select '02 Feb 2009 12:00:00', '02 Feb 2009 12:30:00' 

--build an ordered, time range table with an indicator 
--to determine which ranges are timeranges 'tr' 
--and which are blockedtimes 'bt' 
-- 
declare @alltimes table (row int, rangetype varchar(10), StartDateTime datetime, EndDateTime datetime) 
insert into @alltimes 
select 
    row_number() over (order by a.startdatetime), * 
from 
    (
    select 'tr' as rangetype ,startdatetime, enddatetime from @timeranges 
    union 
    select 'bt' as rangetype ,startdatetime, enddatetime from @blockedtimes 
    )a 

--what does the data look like 
-- 
select * from @alltimes 


-- 
-- build up the results 
select 
    --start time is either the start time of a timerange, or the end of a blockedtime 
    case 
     when at1.rangetype = 'tr' then at1.startdatetime 
     when at1.rangetype = 'bt' then at1.enddatetime 
    end as [Start], 
    case 
     --a time range followed by another time range : end time from the current time range 
     when at1.rangetype = 'tr' and (select at2.rangetype from @alltimes at2 where at2.row = at1.row+1) = 'tr' 
      then at1.enddatetime 

     --a time range followed by nothing (last record) : end time from the currenttime range 
     when at1.rangetype = 'tr' and (select at2.rangetype from @alltimes at2 where at2.row = at1.row+1) is null 
      then at1.enddatetime 

     --a time range followed by a blockedtime : end time is start time of blocked time 
     when at1.rangetype = 'tr' and (select at2.rangetype from @alltimes at2 where at2.row = at1.row+1) = 'bt' 
      then (select top 1 at2.startdatetime from @alltimes at2 where at2.row > at1.row and at2.rangetype = 'bt' order by row) 

     --a blocked time followed by a blockedtime : end time is start time of next blocked time  
     when at1.rangetype = 'bt' and (select at2.rangetype from @alltimes at2 where at2.row = at1.row+1) = 'bt' 
      then (select top 1 at2.startdatetime from @alltimes at2 where at2.row > at1.row and at2.rangetype = 'bt' order by row) 

     --a blocked time followed by a time range : end time is end time of previous time range  
     when at1.rangetype = 'bt' and (select at2.rangetype from @alltimes at2 where at2.row = at1.row+1) = 'tr' 
      then (select top 1 at2.enddatetime from @alltimes at2 where at2.row < at1.row and at2.rangetype = 'tr' order by row desc) 

     --a blocked time followed by nothing (last record) : end time is end time of previous time range  
     when at1.rangetype = 'bt' and (select at2.rangetype from @alltimes at2 where at2.row = at1.row+1) is null 
      then (select top 1 at2.enddatetime from @alltimes at2 where at2.row < at1.row and at2.rangetype = 'tr' order by row desc) 

    end as [End] 

from @alltimes at1 
0

Hier ist eine Lösung, um zu versuchen, die funktionieren sollte, wenn zwei Bedingungen für die Eingangsdaten halten: A) Jedes Intervall blockiert Zeit innerhalb eines Intervalls Einzelzeitbereich. (Sie sagten, dies könnte angenommen werden.), Und B) Blockierte Zeitintervalle überschneiden sich nicht - das heißt, keine Zeit wird "doppelt blockiert", wenn sie in mehr als ein blockiertes Intervall fällt.

Wie funktioniert das?

Zuerst werden alle datetime-Werte mit einer 0 oder 1 gekennzeichnet, um anzuzeigen, ob die Verfügbarkeit endet (0 für EndDateTime-Werte in @termitages und für StartDateTime-Werte in @blockedtimes) oder beginnt (1 für die anderen beiden Möglichkeiten) die bestimmte Zeit. Dann werden die Zeiten und Tags nach der Zeit geordnet, markiert und mit einer Spalte rk nummeriert, die die row_number-Funktion verwendet. Das Tagging könnte mit einem CASE-Ausdruck lesbarer gemacht werden, aber CHARINDEX war weniger zu tippen ...

Aufgrund der Annahmen wird die Tag-Sequenz zwischen 0 und 1 wechseln: 0,1,0,1,0, 1 ..., wobei jedes aufeinanderfolgende (0,1) Paar den Beginn und das Ende eines Verfügbarkeitsintervalls angibt. Diese Intervalle können mit (rk-1)/2 nummeriert werden.

Die Zeilen sind in jedem Verfügbarkeitsintervall gruppiert. Die minimale Datetime in der Gruppe ist die Startzeit, und das Maximum ist die Endzeit, und wenn diese unterschiedlich sind, repräsentiert die Gruppe ein nicht leeres Intervall, das in die Ergebnismenge gehört. Beachten Sie, dass es für Ihre Daten keine leeren Intervalle gibt, sondern wenn zwei blockierte Zeiten aneinanderstoßen oder wenn eine blockierte Zeit gleichzeitig mit einem Zeitbereich endet.

Schließlich werden die Ergebnisse für das gewünschte Anzeigeformat gedreht.

Es ist nicht der einfachste Code zu lesen, aber es könnte wert sein, rätseln.Lösungen wie diese, die row_number und Gruppierung verwenden, sind manchmal praktisch, um knifflige Fragen zu lösen.

0
SELECT COALESCE(bt.StartDateTime, tr.StartDateTime), 
     bt.EndDateTime 
FROM @timeranges tr 
CROSS APPLY 
     (
     SELECT bp.StartDateTime, bt.StartDateTime AS EndDateTime 
     FROM (
       SELECT StartDateTime 
       FROM @blockedtimes bt 
       WHERE bt.EndDateTime >= tr.StartDateTime 
         AND bt.StartDateTime <= tr.EndDateTime 
       UNION ALL 
       SELECT tr.EndDateTime 
       ) bt 
     OUTER APPLY 
       (
       SELECT TOP 1 EndDateTime AS StartDateTime 
       FROM @blockedtimes bti 
       WHERE bti.EndDateTime >= tr.StartDateTime 
         AND bti.StartDateTime <= tr.EndDateTime 
         AND bti.StartDateTime < bt.StartDateTime 
       ORDER BY 
         bti.StartDateTime DESC 
       ) AS bp 
     ) bt 

Diese Lösung beruht auf den folgenden Annahmen:

  • Zeitbereiche überlappen nie andere Zeitbereiche
  • Blockierte Zeiten nie überlappen anderen blockierten Zeiten
+0

Dies sah wie eine elegante Lösung aus, also habe ich versucht zu verstehen, wie es funktioniert, aber ich habe festgestellt, dass es nicht das richtige Ergebnis - 3. und 4. Ergebnisse sind falsch :( –

+0

'@ Kev': richtig, ich habe es korrigiert. – Quassnoi

+0

OK, bevor ich anfange, es zu betrachten - WIE bist du darauf gekommen? Kannst du mir einen Hinweis geben, wie du zu dieser Lösung gelangt bist? Mein Kopf dreht sich! –

Verwandte Themen