2013-03-01 16 views
5

Ich mache einige Berichte basierend auf den Zeitblöcken, in denen Mitarbeiter arbeiten. In einigen Fällen enthalten die Daten zwei separate Datensätze für das, was wirklich ein einzelner Zeitblock ist.Zeilen in SQL zusammenführen?

Hier ist eine Basisversion der Tabelle und einige Beispielsätze:

EmployeeID 
StartTime 
EndTime 

Daten:

EmpID  Start   End 
---------------------------- 
#1001 10:00 AM 12:00 PM 
#1001 4:00 PM  5:30 PM 
#1001 5:30 PM  8:00 PM 

Im Beispiel sind die letzten beiden Sätze zusammenhängend sind in der Zeit. Ich möchte eine Abfrage schreiben, die alle benachbarten Datensätze kombiniert, so dass die Ergebnismenge ist dies:

EmpID  Start   End 
---------------------------- 
#1001 10:00 AM 12:00 PM 
#1001 4:00 PM  8:00 PM 

Idealerweise sollte es auch in der Lage sein, mehr als 2 benachbarte Aufzeichnungen zu handhaben, aber das ist nicht erforderlich.

+1

Haben Sie auch eine Spalte, die das Datum speichert? –

+0

@ Jeff Rosenberg: Ja. Das sind Datumsspalten in der realen Tabelle. Diese Beispieltabelle ist sehr vereinfacht, um die Frage zu stellen. – poke

Antwort

2

Dieser Artikel enthält einige mögliche Lösungen für Ihre Frage

http://www.sqlmag.com/blog/puzzled-by-t-sql-blog-15/tsql/solutions-to-packing-date-and-time-intervals-puzzle-136851

Dies scheint wie der direkteste:

WITH StartTimes AS 
(
    SELECT DISTINCT username, starttime 
    FROM dbo.Sessions AS S1 
    WHERE NOT EXISTS 
    (SELECT * FROM dbo.Sessions AS S2 
    WHERE S2.username = S1.username 
     AND S2.starttime < S1.starttime 
     AND S2.endtime >= S1.starttime) 
), 
EndTimes AS 
(
    SELECT DISTINCT username, endtime 
    FROM dbo.Sessions AS S1 
    WHERE NOT EXISTS 
    (SELECT * FROM dbo.Sessions AS S2 
    WHERE S2.username = S1.username 
     AND S2.endtime > S1.endtime 
     AND S2.starttime <= S1.endtime) 
) 
SELECT username, starttime, 
    (SELECT MIN(endtime) FROM EndTimes AS E 
    WHERE E.username = S.username 
    AND endtime >= starttime) AS endtime 
FROM StartTimes AS S; 
0

ich ein lil geändert haben‘biss die Namen und Typen, um das Beispiel kleiner zu machen, aber das funktioniert und sollte sehr schnell sein und es hat keine Anzahl von Datensätzen zu begrenzen:

with cte as (
    select 
    x1.id 
    ,x1.t1 
    ,x1.t2 
    ,case when x2.t1 is null then 1 else 0 end as bef 
    ,case when x3.t1 is null then 1 else 0 end as aft 
    from x x1 
    left join x x2 on x1.id=x2.id and x1.t1=x2.t2 
    left join x x3 on x1.id=x3.id and x1.t2=x3.t1 
    where x2.id is null 
    or x3.id is null 
) 

select 
    cteo.id 
    ,cteo.t1 
    ,isnull(z.t2,cteo.t2) as t2 

from cte cteo 
outer apply (select top 1 * 
      from cte ctei 
      where cteo.id=ctei.id and cteo.aft=0 and ctei.t1>cteo.t1 
      order by t1) z 
where cteo.bef=1 

und die Geige für sie: http://sqlfiddle.com/#!3/ad737/12/0

0

Option mit Inline benutzerdefinierte Funktion UND CTE

CREATE FUNCTION dbo.Overlap 
(
    @availStart datetime, 
    @availEnd datetime, 
    @availStart2 datetime, 
    @availEnd2 datetime 
) 
RETURNS TABLE 
RETURN 
    SELECT CASE WHEN @availStart > @availEnd2 OR @availEnd < @availStart2 
       THEN @availStart ELSE 
           CASE WHEN @availStart > @availStart2 THEN @availStart2 ELSE @availStart END 
           END AS availStart, 
     CASE WHEN @availStart > @availEnd2 OR @availEnd < @availStart2 
       THEN @availEnd ELSE 
          CASE WHEN @availEnd > @availEnd2 THEN @availEnd ELSE @availEnd2 END 
          END AS availEnd 

;WITH cte AS 
(
    SELECT EmpID, Start, [End], ROW_NUMBER() OVER (PARTITION BY EmpID ORDER BY Start) AS Id 
    FROM dbo.TableName 
), cte2 AS 
(
    SELECT Id, EmpID, Start, [End] 
    FROM cte 
    WHERE Id = 1 
    UNION ALL 
    SELECT c.Id, c.EmpID, o.availStart, o.availEnd 
    FROM cte c JOIN cte2 ct ON c.Id = ct.Id + 1 
      CROSS APPLY dbo.Overlap(c.Start, c.[End], ct.Start, ct.[End]) AS o 
) 
    SELECT EmpID, Start, MAX([End]) 
    FROM cte2 
    GROUP BY EmpID, Start 

Demo auf SQLFiddle

1

Wenn dies streng zu benachbarten Reihen (nicht überlappende sind) , könnten Sie die folgende Methode versuchen:

  1. Deaktivieren Sie die Zeitstempel.

  2. Lassen Sie nur diejenigen, die keine Duplikate haben.

  3. Drehen Sie die übrigen zurück, koppeln Sie alle Start mit den direkt folgenden End.

Oder in Transact-SQL, etwa so:

WITH unpivoted AS (
    SELECT 
    EmpID, 
    event, 
    dtime, 
    count = COUNT(*) OVER (PARTITION BY EmpID, dtime) 
    FROM atable 
    UNPIVOT (
    dtime FOR event IN (StartTime, EndTime) 
) u 
) 
, filtered AS (
    SELECT 
    EmpID, 
    event, 
    dtime, 
    rowno = ROW_NUMBER() OVER (PARTITION BY EmpID, event ORDER BY dtime) 
    FROM unpivoted 
    WHERE count = 1 
) 
, pivoted AS (
    SELECT 
    EmpID, 
    StartTime, 
    EndTime 
    FROM filtered 
    PIVOT (
    MAX(dtime) FOR event IN (StartTime, EndTime) 
) p 
) 
SELECT * 
FROM pivoted 
; 

Es gibt eine Demo für diese Abfrage at SQL Fiddle.

0

CTE mit kumulativer Summe:

DECLARE @t TABLE(EmpId INT, Start TIME, Finish TIME) 
INSERT INTO @t (EmpId, Start, Finish) 
VALUES 
    (1001, '10:00 AM', '12:00 PM'), 
    (1001, '4:00 PM', '5:30 PM'), 
    (1001, '5:30 PM', '8:00 PM') 

;WITH rowind AS (
    SELECT EmpId, Start, Finish, 
     -- IIF returns 1 for each row that should generate a new row in the final result 
     IIF(Start = LAG(Finish, 1) OVER(PARTITION BY EmpId ORDER BY Start), 0, 1) newrow 
    FROM @t), 
    groups AS (
    SELECT EmpId, Start, Finish, 
     -- Cumulative sum 
     SUM(newrow) OVER(PARTITION BY EmpId ORDER BY Start) csum 
    FROM rowind) 

SELECT 
    EmpId, 
    MIN(Start) Start, 
    MAX(Finish) Finish 
FROM groups 
GROUP BY EmpId, csum 
Verwandte Themen