Vor längerer Zeit hatte ich hier schon mal einen Link auf die Technik Tabibitosan untergebracht, die Aketi Jyuuzou gelegentlich im OTN-Forum vorstellte. Da ich aber gestern auf die Frage nach dem Vorgehen beim Zusammenfassen wiederholter Werte in Intervallangaben auf Anhieb nur antworten konnte: es geht, aber wie, habe ich vergessen - hier noch mal eine kurze Darstellung. Zunächst erstellte ich ein paar Testdaten mit einer Spalte VKP (was Verkaufspreise sein könnten) und einer Datumsangabe:
create table test_vkp (vkp number , startdate date); insert into test_vkp values (2, '01.01.2012'); insert into test_vkp values (2, '02.01.2012'); insert into test_vkp values (2, '03.01.2012'); insert into test_vkp values (3, '04.01.2012'); insert into test_vkp values (3, '05.01.2012'); insert into test_vkp values (3, '06.01.2012'); insert into test_vkp values (3, '07.01.2012'); insert into test_vkp values (3, '08.01.2012'); insert into test_vkp values (2, '09.01.2012'); insert into test_vkp values (2, '10.01.2012'); insert into test_vkp values (3, '11.01.2012'); insert into test_vkp values (3, '12.01.2012'); insert into test_vkp values (4, '13.01.2012'); insert into test_vkp values (5, '14.01.2012'); insert into test_vkp values (2, '15.01.2012'); insert into test_vkp values (2, '16.01.2012');
Ziel ist es nun, die wiederholten Einzelangaben zusammenzufassen und Intervalle zu bilden. Eine einfache Gruppierung mit MIN und MAX wäre dazu nicht in der Lage, da sie das erneute Auftreten eines Wertes nicht berücksichtigen kann:
select vkp , min(startdate) min_date , max(startdate) max_date from test_vkp group by vkp order by vkp; VKP MIN_DATE MAX_DATE --- ---------- ---------- 2 01.01.2012 16.01.2012 3 04.01.2012 12.01.2012 4 13.01.2012 13.01.2012 5 14.01.2012 14.01.2012
Schon auf den ersten Blick wird deutlich, dass diese Query die maximalen Intervalle liefert, in denen ein VKP-Wert vorkommt. Die Intervalle sind dabei nicht überschneidungsfrei - und absolut nicht das, was ich haben möchte.
Sinnvolle Intervalle kann man in einem solchen Fall mit Analytics bilden. Dabei erzeugt man zunächst eine laufende Nummer über alle Sätze und eine laufende Nummer pro Sub-Gruppe. Wenn man den zweiten Wert vom ersten subtrahiert, erhält man ein Ergebnis, das für die gesamte Gruppe einheitlich ist, und über das man dann gruppieren kann:
select t.* , row_number() over(order by startdate) a1 , row_number() over(partition by vkp order by startdate) a2 , row_number() over(order by startdate) - row_number() over(partition by vkp order by startdate) a3 from test_vkp t; VKP STARTDATE A1 A2 A3 --- ---------- ---------- ---------- ---------- 2 01.01.2012 1 1 0 2 02.01.2012 2 2 0 2 03.01.2012 3 3 0 3 04.01.2012 4 1 3 3 05.01.2012 5 2 3 3 06.01.2012 6 3 3 3 07.01.2012 7 4 3 3 08.01.2012 8 5 3 2 09.01.2012 9 4 5 2 10.01.2012 10 5 5 3 11.01.2012 11 6 5 3 12.01.2012 12 7 5 4 13.01.2012 13 1 12 5 14.01.2012 14 1 13 2 15.01.2012 15 6 9 2 16.01.2012 16 7 9 with basedata as ( select t.* , row_number() over(order by startdate) a1 , row_number() over(partition by vkp order by startdate) a2 , row_number() over(order by startdate) - row_number() over(partition by vkp order by startdate) a3 from test_vkp t ) select vkp , min(startdate) min_date , max(startdate) max_date from basedata group by vkp , a3 order by min_date VKP MIN_DATE MAX_DATE --- ---------- ---------- 2 01.01.2012 03.01.2012 3 04.01.2012 08.01.2012 2 09.01.2012 10.01.2012 3 11.01.2012 12.01.2012 4 13.01.2012 13.01.2012 5 14.01.2012 14.01.2012 2 15.01.2012 16.01.2012
Das ist natürlich erst mal ein recht simpler Fall, aber das Verfahren lässt sich ohne Weiteres auf komplexere Fragestellungen erweitern, was im oben erwähnten OTN-Beispiel auch getan wird.
Dieser Kommentar wurde vom Autor entfernt.
AntwortenLöschenSchöne Lösung.
AntwortenLöschenOhne Analytics geht's aber auch :)
WITH
D AS (SELECT VKP, STARTDATE FROM TEST_VKP ORDER BY STARTDATE, VKP),
E AS (SELECT D.*, ROWNUM R FROM D),
F AS (SELECT DISTINCT
COALESCE((SELECT MIN(R) FROM E X WHERE E.R < X.R AND E.VKP != X.VKP)-1
,(SELECT MAX(R) FROM E X WHERE E.R <= X.R AND E.VKP = X.VKP)) MX,
COALESCE((SELECT MAX(R) FROM E X WHERE E.R > X.R AND E.VKP != X.VKP)+1
,(SELECT MIN(R) FROM E X WHERE E.R >= X.R AND E.VKP = X.VKP)) MN
FROM E
)
SELECT A.VKP, A.STARTDATE, B.STARTDATE AS ENDDATE
FROM F
JOIN E A ON F.MN = A.R
JOIN E B ON F.MX = B.R;
Hallo Jochen,
AntwortenLöschenauch hübsch - aber vielleicht noch kommentierungsbedürftiger als die Version mit den Analytics (die ihrerseits schon nach einem Kommentar schreit). Ich stelle mir gerade die Frage, ob es überhaupt einen Fall gibt, in denen analytische Funktionen etwas leisten, was man nicht auch mit einem entsprechenden SELF JOIN erreichen kann (Performance und Lesbarkeit des Codes ausgeklammert) - und vermute auf Anhieb, dass es den nicht gibt.
Gruß
Martin
Analytics stehen nicht in jeder Datenbank zur Verfügung. Wenn man sie hat kann man sich damit das Leben aber durchaus leichter machen (bzw. einfacher lesbaren Code schreiben).
AntwortenLöschenIch erklär's mal kurz:
- Der Ansatz ist ähnlich wie bei den Analytics - man sortiert die Datensätze, nummeriert sie fortlaufend und schaut dann nach dem jeweils ersten bzw. letzten Eintrag der Gruppe.
- Zuerst wird das Ergebnis in die gewünschte Reihenfolge gebracht (Tabelle D)
- Zu dem sortierten Ergebnis wird die Zeilennummer hinzugefügt (Tabelle E)
- Tabelle F ermittelt die Grenzen der einzelnen Gruppen.
- Die kleinste Zeilennummer nach der aktuellen, bei der sich das Gruppierungskriterium unterscheidet (VKP) ist der Beginn der nächsten Gruppe; subtrahieren wir davon 1, erhalten wir das Ende der aktuellen Gruppe.
- Für den Sonderfall "Letze Gruppe" funktioniert dies nicht, hier bestimmen wir die größte Zeilennummer mit gleichem Gruppierungskriterium.
- Analog dazu wird der Anfang der Gruppe ermittelt.
- Nun haben wir für jedes Gruppierungskriterium Start und Ende ermittelt. Dies wird mittels distinct von Wiederholungen befreit.
- Zuguterletzt wird die Zeilennummer wieder aufgelöst und das Start- und Enddatum ermittelt.
- Vorteil dieses Ansatzes: Man kann im Ergebnis auf komplette Zeilen zugreifen, d.h. auch auf diejenigen Teile, die sich nicht trivial per min() und max() ermitteln lassen oder die nicht zur Gruppierung beitragen.
Letzteres geht natürlich auch mit den Analytics, man muss das Beispiel dafür etwas erweitern. Dann schwindet der Lesbarkeitsvorteil etwas.
Hallo Jochen,
AntwortenLöschenvielen Dank für den ergänzenden Code-Kommentar. Beim Thema der Datenbankunabhängigkeit fallen mir immer diverse Aussagen von Tom Kyte ein, der davon wenig hält, und seine Abneigung damit gegründet, dass man die Features einer Datenbank, für die man viel Geld bezahlt, auch einsetzen sollte. Darüber hinaus ist mein Eindruck, dass Analytics auf einem recht guten Weg sind, in den allgemeinen SQL-Standard zu wandern. Aber auf jeden Fall ist es immer nützlich, möglichst viele SQL-Varianten für eine Fragestellung zur Verfügung zu haben, um etwaigen Schwächen des CBO (oder des Servers bei der Verarbeitung) aus dem Weg gehen zu können.
Gruß
Martin