2010-11-25 11 views
5

Ich habe eine Tabelle, die Startzeiten enthält (mit der Nummer im Beispiel, um es einfach zu halten) und die Dauer von Ereignissen.Gruppierung der Zeilen unter Berücksichtigung der "Differenz" zwischen den Zeilen

Ich möchte "Blöcke" und ihre Start- und Endzeit identifizieren.
Immer wenn der Unterschied zwischen der Endzeit (Startzeit + Dauer) der vorherigen Zeile (sortiert nach Startzeit) und der Startzeit der aktuellen Zeile >=5 ist, sollte ein neuer "Block" beginnen.

Dies ist mein Test-Daten, einschließlich dem Versuch einer graphischen Erklärung in den Kommentaren:

WITH test_data AS (
    SELECT 0 s, 2 dur FROM dual UNION ALL --# ■■ 
    SELECT 2 , 2  FROM dual UNION ALL --# ■■ 
    SELECT 10 , 1  FROM dual UNION ALL --#   ■ 
    SELECT 13 , 4  FROM dual UNION ALL --#    ■■■■ 
    SELECT 15 , 4  FROM dual    --#    ■■■■ 
) 
--# Should return 
--# 0 .. 4        --# ■■■■ 
--# 10 .. 19        --#   ■■■■■■■■■ 

Der erste Block an 0 beginnt und endet bei 4. Da der Unterschied zur nächsten Zeile >=5 ist, starten Sie einen weiteren Block unter 10, der auf 19 endet.


kann ich die erste Zeile eines Blocks identifizieren, mit LAG, aber ich habe noch nicht herausgefunden, wie es weitergeht.

Und ich könnte das Problem in einer PL/SQL-Schleife lösen, aber ich versuche, das aus Leistungsgründen zu vermeiden.


Haben Sie Vorschläge zum Schreiben dieser Abfrage?

Vielen Dank im Voraus, Peter

+1

+1 für ziemlich coole Grafiken – thomaspaulb

Antwort

3

Ich benutze Unterabfragen mit Analytics zusammenhängende Bereiche zu identifizieren und Gruppe:

SQL> WITH test_data AS (
    2 SELECT 0 s, 2 dur FROM dual UNION ALL --# ■■ 
    3 SELECT 2 , 2  FROM dual UNION ALL --# ■■ 
    4 SELECT 10 , 1  FROM dual UNION ALL --#   ■ 
    5 SELECT 13 , 4  FROM dual UNION ALL --#    ■■■■ 
    6 SELECT 15 , 4  FROM dual    --#    ■■■■ 
    7 ) 
    8 SELECT MIN(s) "begin", MAX(s + dur) "end" 
    9 FROM (SELECT s, dur, SUM(gap) over(ORDER BY s) my_group 
10    FROM (SELECT s, dur, 
11       CASE 
12        WHEN lag(s + dur) over(ORDER BY s) >= s - 5 THEN 
13        0 
14        ELSE 
15        1 
16       END gap 
17      FROM test_data 
18      ORDER BY s)) 
19 GROUP BY my_group; 

    begin  end 
---------- ---------- 
     0   4 
     10   19 
+0

Danke Vincent! Genau das habe ich gebraucht, mir fehlte der 'SUM (lücke) OVER ...' Teil. Minor note: Ich denke, dass 'ORDER BY s' aus der inneren Unterauswahl entfernt werden könnte und bei Bedarf zur Hauptabfrage verschoben werden könnte. –

1

In MS-SQL würde ich ROW_NUMBER() OVER(ORDER BY starttime) AS Rank verwenden, um die Zeilen auf Startzeit Rang.

Dann würde ich eine Abfrage schreiben, um jede Zeile mit der Zeile mit vorherigem Rank zu verbinden und ein Flag setzen, wenn der Unterschied größer als fünf oder NULL (erste Zeile) ist.

Dann würde ich alle Zeilen aktivieren Sie dieses Flag, die Reihen beginnen sind, und für diese Teilmenge wiederholen Sie den Vorgang Reihen der Nummerierung und in die nächste Zeile Beitritt zur Zeit zu erhalten umspannt:

blockstarttime1 nextstarttime1 (=starttime2) 
blockstarttime2 nextstarttime2 (=starttime3) 
blockstarttime3 NULL 

Schließlich Dieser Datensatz kann mit den ursprünglichen Daten mit einer WHERE starttime BETWEEN blockstarttime and nextstarttime verbunden werden, um die Ergebnisse zu partitionieren.

Bis zu Ihnen dies zu Oracle zu übersetzen ...

+0

Dank! Das sollte funktionieren, aber es würde eine weitere Abfrage der Originaldaten erfordern, die die von Vincent Malgrat und MikeyByCrikey bereitgestellte Lösung vermeidet. –

1

Es gibt ein fantastisches Buch von Richard Snodgrass ist, die helfen können: Developing Time-Oriented Database Applications in SQL (gratis zum Download), die ich von unschätzbarem Wert gefunden haben, wenn sie mit der Zeit in Datenbanken handelt.

Schauen Sie auf Richards page für Links zu einigen Buchkorrekturen und der zugehörigen CD-ROM im Zip-Format.

+0

Danke für den Link, ich werde diese 528 Seiten später lesen :) Hast du überprüft, ob es schon in der [Liste frei verfügbarer Programmierbücher] ist (http://stackoverflow.com/questions/194812/list- frei verfügbare Programmierbücher)? –

+0

@Peter: Gerade mal gucken, das Buch ist unter "SQL (Implementation agnostic)" aufgelistet. – Tony

2

Der Code wird ein bisschen kompliziert mit einer Reihe von Unterabfragen, etc. Die Daten können Fälle sein, in denen dies nicht funktioniert, aber ich kann mir nichts von meinem Kopf vorstellen.

Arbeiten mit temporalen Daten ist immer ein Schmerz!

WITH test_data AS (
    SELECT 0 s, 2 dur FROM dual UNION ALL --# ■■ 
    SELECT 2 , 2  FROM dual UNION ALL --# ■■ 
    SELECT 10 , 1  FROM dual UNION ALL --#   ■ 
    SELECT 13 , 4  FROM dual UNION ALL --#    ■■■■ 
    SELECT 15 , 4  FROM dual    --#    ■■■■ 
) 
select 
-- Group on each block 
    min(start_time) as s, 
    max(end_time) - min(start_time) as dur 
from (
    select 
    start_time, 
    duration, 
    end_time, 
-- number the blocks sequentially 
    sum(is_block_start) over (order by start_time) as block_num 
    from (
    select 
     start_time, 
     duration, 
     end_time, 
-- Mark the start of each block 
     case 
     when nvl2(prev_end_time, start_time - prev_end_time,5) >= 5 
     then 1 else 0 end as is_block_start 
    from (
     select 
     s as start_time, 
     dur as duration, 
     s+dur as end_time, 
     lag(s+dur) over (order by s) prev_end_time 
     from test_data 
    ) 
) 
) 
group by block_num 
+0

Danke! Ich habe den "sum (is_block_start) over" -Teil vermisst. Accepting Vincents antwort, das ist mehr oder weniger die gleiche Frage, die Sie zur Verfügung gestellt, aber ich finde es einfacher zu lesen. –

Verwandte Themen