2011-01-10 2 views
1

ich die unten haben.(Set-basiert) in T-SQL

Name Date 
A 2011-01-01 01:00:00.000 
A 2011-02-01 02:00:00.000 
A 2011-03-01 03:00:00.000 
B 2011-04-01 04:00:00.000 
A 2011-05-01 07:00:00.000 

Die gewünschte Ausgabe ist

Name  StartDate      EndDate 
------------------------------------------------------------------- 
A   2011-01-01 01:00:00.000   2011-04-01 04:00:00.000  
B   2011-04-01 04:00:00.000   2011-05-01 07:00:00.000  
A   2011-05-01 07:00:00.000   NULL 

Wie das gleiche mit TSQL in einem Satz basierten Ansatz zu erreichen.

DDL wie unter

DECLARE @t TABLE(PersonName VARCHAR(32), [Date] DATETIME) 
INSERT INTO @t VALUES('A', '2011-01-01 01:00:00') 
INSERT INTO @t VALUES('A', '2011-01-02 02:00:00') 
INSERT INTO @t VALUES('A', '2011-01-03 03:00:00') 
INSERT INTO @t VALUES('B', '2011-01-04 04:00:00') 
INSERT INTO @t VALUES('A', '2011-01-05 07:00:00') 

Select * from @t 
+0

Ich verstehe nicht, wie Sie die gewünschte Ausgabe sind herauszufinden. Wie entscheiden Sie, wie das Enddatum ist? Zum Beispiel in der gewünschten Ausgabe haben Sie einen Datensatz: Name Startdatum Enddatum A 2011-01-01 01: 00: 00.000 2011-04-01 04: 00: 00.000 Aber die Eingabe hatte dieses Datum 2011- 04-01 04: 00: 00.000 mit Name B verbunden. Wie bestimmen wir, wie das Enddatum für einen Datensatz sein soll. Was ist die Definition eines Datensatzes? – richard

+0

Das Enddatum eines Datensatzes ist das Startdatum eines anderen Datensatzes. So A die Startd Datum ist 2011-01-01 01: 00: 00.000 aber B ist 2011-04-01 04: 00: 00.000. Das Enddatum von A ist 2011-04-01 04: 00: 00.000. Ebenso hat das A, das B folgt, ein Startdatum als 2011-05-01 07: 00: 00.000, was das Enddatum für B. –

+0

ist. Aber woher wissen Sie, welcher Datensatz als Enddatum eines bestimmten Datensatzes ausgewählt werden soll ? – richard

Antwort

0

Die andere Antwort mit dem CTE gut ist. Eine andere Möglichkeit wäre, die Sammlung auf jeden Fall zu durchlaufen. Es ist nicht festgelegt, aber es ist eine andere Möglichkeit, es zu tun.

Sie müssen entweder A. zuweisen eine eindeutige ID zu jedem Datensatz wiederholen, die zu seiner Transaktion entspricht, oder B. Ihre Ausgabe erhalten tatsächlich.

TSQL ist nicht ideal zum Iterieren von Datensätzen, besonders wenn Sie viel haben, und daher würde ich eine andere Methode empfehlen, ein kleines .net-Programm oder etwas, das besser iterieren kann.

7
;WITH cte1 
    AS (SELECT *, 
       ROW_NUMBER() OVER (ORDER BY Date) - 
       ROW_NUMBER() OVER (PARTITION BY PersonName 
       ORDER BY Date) AS G 
     FROM @t), 
    cte2 
    AS (SELECT PersonName, 
       MIN([Date]) StartDate, 
       ROW_NUMBER() OVER (ORDER BY MIN([Date])) AS rn 
     FROM cte1 
     GROUP BY PersonName, 
        G) 
SELECT a.PersonName, 
     a.StartDate, 
     b.StartDate AS EndDate 
FROM cte2 a 
     LEFT JOIN cte2 b 
     ON a.rn + 1 = b.rn 

Da das Ergebnis von CTEs sind jedoch im Allgemeinen nicht materialisiert Sie können auch feststellen, Ihnen eine bessere Leistung, wenn man das Zwischenergebnis selbst, wie unten materialisieren.

DECLARE @t2 TABLE (
    rn   INT IDENTITY(1, 1) PRIMARY KEY, 
    PersonName VARCHAR(32), 
    StartDate DATETIME); 

INSERT INTO @t2 
SELECT PersonName, 
     MIN([Date]) StartDate 
FROM (SELECT *, 
       ROW_NUMBER() OVER (ORDER BY Date) - 
       ROW_NUMBER() OVER (PARTITION BY PersonName 
       ORDER BY Date) AS G 
     FROM @t) t 
GROUP BY PersonName, 
      G 
ORDER BY StartDate 

SELECT a.PersonName, 
     a.StartDate, 
     b.StartDate AS EndDate 
FROM @t2 a 
     LEFT JOIN @t2 b 
     ON a.rn + 1 = b.rn 
0

Holen Sie sich eine Zeilennummer, damit Sie wissen, wo der vorherige Datensatz ist. Dann nehmen Sie eine Aufzeichnung und die nächste Aufzeichnung danach. Wenn sich der Zustand ändert, haben wir eine Kandidatenreihe.

select 
    state, 
    min(start_timestamp), 
    max(end_timestamp) 

from 
(
    select 
     first.state, 
     first.timestamp_ as start_timestamp, 
     second.timestamp_ as end_timestamp 

     from 
     (
      select 
       *, row_number() over (order by timestamp_) as id 
      from test 
     ) as first 

     left outer join 
     (
      select 
       *, row_number() over (order by timestamp_) as id 
      from test 
     ) as second 
     on 
      first.id = second.id - 1 
      and first.state != second.state 
) as agg 
group by state 
    having max(end_timestamp) is not null 

union 

-- last row wont have a ending row 
--(select state, timestamp_, null from test order by timestamp_ desc limit 1) 
    -- I think it something like this for sql server 
    (select top state, timestamp_, null from test order by timestamp_ desc) 

order by 2 
; 

mit PostgreSQL getestet, sollte aber auch

0
SELECT 
    PersonName, 
    StartDate = MIN(Date), 
    EndDate 
FROM (
    SELECT 
    PersonName, 
    Date, 
    EndDate = (
     /* get the earliest date after current date 
     associated with a different person */ 
     SELECT MIN(t1.Date) 
     FROM @t AS t1 
     WHERE t1.Date > t.Date 
     AND t1.PersonName <> t.PersonName 
    ) 
    FROM @t AS t 
) s 
GROUP BY PersonName, EndDate 
ORDER BY 2 

Grundsätzlich mit SQL Server arbeiten, für jeden Date finden wir den nächsten Tag, nachdem es so, dass mit einem anderen PersonName zugeordnet ist. Das gibt uns EndDate, die jetzt für uns aufeinander folgende Gruppen von Terminen für die gleiche Person unterscheidet.

Jetzt brauchen wir nur zu einer Gruppe, die Daten von PersonName & EndDate und die minimalen Date in jeder Gruppe als StartDate bekommen. Und ja, sortieren Sie die Daten natürlich nach StartDate.