2016-09-14 4 views
0

Wir haben eine Oracle SQL-Abfrage, um Datensätze zu identifizieren, bei denen sich der Wert einer Tabellenspalte von einem Datensatz zu einem anderen geändert hat. Relevant Spalten (ID, SOME_COLUMN, FROM_DATE, TO_DATE) wenn die ID nicht eindeutig ist, und FROM_DATE TO_DATE und das Zeitintervall bestimmen, für die den die spezielle Zeile für diese ID wirksam war, dhOptimierung der Self-Join-Oracle-SQL-Abfrage mit LAG/LEAD-Analysefunktionen?

(ID1, VAL1, 01/01/2016, 03/01/2016) 
(ID1, VAL2, 04/01/2016, 09/01/2016) 
(ID1, VAL3, 10/01/2016, 19/01/2016) 

usw.

Wir konnten dies mit dem folgenden implementieren Selbst beitreten

SELECT N.ID 
     O.SOME_COLUMN OLD_VALUE, 
     N.SOME_COLUMN NEW_VALUE 
FROM OUR_TABLE N, OUR_TABLE O 
WHERE N.ID = O.ID 
    AND N.FROM_DATE - 1 = O.TO_DATE 
    AND N.SOME_COLUMN <> O.SOME_COLUMN 

aber da die Tabelle 100 Millionen Datensätze enthält, ist es durchaus die Leistung trifft. Gibt es einen effektiveren Weg, dies zu tun? Jemand hat analytische Funktionen (z. B. LAG) angedeutet, aber wir konnten bisher keine funktionierende Lösung finden. Alle mögliche Ideen

+0

Ihre Frage ist nicht klar. Ihre Daten überschneiden sich überhaupt nicht und es ist unklar, welche Ergebnisse Sie wünschen. –

+0

Daten überlappen sich tatsächlich nicht, sie stellen Zeitintervalle dar, in denen die zu dieser ID gehörende bestimmte Tabellenzeile wirksam war. Wie Sie im Beispiel sehen, wird das FROM_DATE eines Intervalls immer +1 zum TO_DATE des vorherigen Intervalls hinzugefügt. Wir benötigen Ergebnisse, bei denen sich der Wert von SOME_COLUMN von Intervall zu Intervall geändert hat. Wenn Sie Ihre Antwort unten überprüfen, scheinen Sie alles richtig interpretiert zu haben. – hammerfest

Antwort

2

Ja klar, du LEAD() können den letzten Wert holen:

SELECT t.id, 
     t.some_column as OLD_VALUE, 
     LEAD(t.some_column) OVER(PARTITION BY t.id ORDER BY t.from_date) as NEW_VALUE 
FROM YourTable t 

Wenn Sie nur Änderungen wollen, wickeln Sie es mit einem anderen auswählen und filtern OLD_VALUE <> NEW_VALUE

1

Wenn Sie das wollen, Altwert und der neue Wert in einer einzigen Zeile, dann verwenden lag():

select t.*, 
     lag(some_column) over (partition by id order by from_date) as prev_val 
from t; 

Wenn die Werte ändern können nicht (wie vorschlagen ed von der Beispielabfrage):

select t.* 
from (select t.*, 
      lag(some_column) over (partition by id order by from_date) as prev_val 
     from t 
    ) t 
where prev_val <> some_column; 
1

Ich denke, das ist der LAG() Ansatz, über den Sie sprachen.

SELECT * 
    FROM (
    SELECT ID 
      N.SOME_COLUMN NEW_VALUE, 
      N.FROM_DATE, 
      lag(N.SOME_COLUMN) over (partition by N.ID order by FROM_DATE) OLD_VALUE, 
      lag(N.TO_DATE) over (partition by N.ID order by FROM_DATE) OLD_TO_DATE, 
    FROM OUR_TABLE N 
) T 
WHERE FROM_DATE - 1 = OLD_TO_DATE 
    AND NEW_VALUE<> OLD_VALUE; 
+1

Danke. Die erste von drei fast identischen Antworten, ich habe hier das Akzeptierungszeichen gesetzt. – hammerfest

+0

@hammerfest. . . Eigentlich war dies die dritte Antwort. Durch Ihre Argumentation sollten Sie Sagis Antwort akzeptieren. Wenn Sie mit der Maus über die "Zeit" über dem Namen (der jetzt "vor 2 Stunden geantwortet" steht) klicken, sehen Sie die genaue Zeit der Antwort. –

+0

Es scheint, dass Sie Recht haben, ich habe einfach die zwei Antworten vertauscht, nachdem ich die Lösungen ausprobiert und auf die Fragenseite zurückgekehrt bin. Ich habe das Akzeptierungszeichen jetzt geändert. @vercelli: Es tut mir leid, deine Antwort ist genauso richtig, aber es war in der Tat nicht die erste. – hammerfest