2016-06-08 3 views
1

Ich habe daran gearbeitet, eine Abfrage zu schreiben, die unsere sehr große Datenbank trifft, um maximale Abrechnungsbeträge (A und B, in diesem Beispiel) für Kunden zurück zu ziehen. Wir möchten die maximale A/B für jeden Kunden für den letzten Monat und die maximale A/B für das vergangene Jahr zurückziehen.Oracle-Abfrage läuft sehr langsam aufgrund großer Datensatz und negative Werte

Ein Problem, das wir in unserer Abrechnungsdatenbank festgestellt haben, ist die Art und Weise, in der "stornierte" Rechnungen gespeichert werden. Dies geschieht durch Hinzufügen einer zweiten, negativen Version des ersten Rechnungsdatensatzes zur Abrechnungstabelle. Wie so:

enter image description here

In diesem Fall 41040 die falsche Rechnung war, so eine negative Version des Datensatzes wurde hinzugefügt. Wenn ich jedoch versuche, den Maximalwert für diese Spalte auszuwählen, erhalte ich immer 41040 anstelle des korrekten berechneten Werts von 50. Diese Tabelle scheint diese falschen Rechnungen in keiner Weise zu markieren, die es leicht machen würde Aussortieren.

Meine aktuelle Lösung war es, den Höchstwert der ID-Spalte als die richtige Rechnung zu nehmen. Dies macht die Annahme, dass die endgültige Rechnung, die für einen Monat eingegeben wurde, die richtige ist.

Dies scheint die richtigen Daten zurück zu bringen, aber die Abfrage läuft unglaublich langsam auf dem großen Datensatz, und ich habe keinen Schreibzugriff auf diese Tabelle zum Hinzufügen oder Anzeigen von Indizes. Es gibt 98.007.807 Zeilen insgesamt und 1.596.491 eindeutige Kunden. Gibt es trotzdem eine Optimierung der Abfrage zur Verbesserung der Performance?

select mth.KY_CUSTOMER_NO,max(QY_MTH_BILLED_A) as QY_MTH_BILLED_A, max(QY_MTH_B) as QY_MTH_BILLING_B, max.MAX_BILLING_A, max.MAX_BILLING_B 
from (
    --Get the max A/B values for the past month 
    select m.* 
    from CUSTOMER_USAGE m 
    where rev_year = to_number(to_char(sysdate,'yyyy')) 
    and rev_mth in (to_number(to_char(add_months(sysdate, -1), 'mm')),to_number(to_char(sysdate,'mm'))) 
    and ID in (select max(ID) from CUSTOMER_USAGE where KY_CUSTOMER_NO = m.KY_CUSTOMER_NO group by rev_mth, rev_year) 
) mth join 
(
    --Get the max A/B values for the past year 
    select KY_CUSTOMER_NO, max(QY_MTH_B) as MAX_BILLING_B, max(QY_MTH_BILLED_A) as MAX_BILLING_A from CUSTOMER_USAGE m 
    where DT_ADDED > current_timestamp - 365 ID in (select max(ID) from CUSTOMER_USAGE where KY_CUSTOMER_NO = m.KY_CUSTOMER_NO group by rev_mth, rev_year) 
    group by KY_CUSTOMER_NO 
) max on mth.KY_CUSTOMER_NO = max.KY_CUSTOMER_NO 
group by mth.KY_CUSTOMER_NO, max.MAX_BILLING_KVA, max.MAX_BILLING_KW 
+0

Welche Index (e) gibt es und was ist der aktuelle Abfrageplan? –

Antwort

1

Analytische Funktionen scheinen die Lösung zu sein.

Ich habe die WHERE-Klauseln weggelassen, da sie für Ihre Beispieldaten nicht benötigt werden, aber Sie sollten in der Lage sein, sie wieder zur innersten Inline-Ansicht hinzuzufügen. Sie können auch EXTRACT(YEAR FROM SYSDATE) anstelle der Konvertierung in und aus einer Zeichenfolge verwenden.

Oracle-Setup:

CREATE TABLE customer_usage (id, ky_customer_no, rev_mth, rev_year, qy_mth_billed_a, qy_mth_billed_b) AS 
SELECT 1, 1, 1, 2016, 41040, 0 FROM DUAL UNION ALL 
SELECT 2, 1, 1, 2016, -41040, 0 FROM DUAL UNION ALL 
SELECT 3, 1, 1, 2016,  50, 0 FROM DUAL UNION ALL 
SELECT 4, 1, 1, 2016,  0, 0 FROM DUAL; 

Abfrage:

SELECT id, 
     ky_customer_no, 
     rev_mth, 
     rev_year, 
     qy_mth_billed_a, 
     qy_mth_billed_b 
FROM (
    SELECT c.*, 
     ROW_NUMBER() 
      OVER (PARTITION BY ky_customer_no, rev_year, rev_mth 
        ORDER BY total_mth_billed_a DESC) AS rn 
    FROM (
    SELECT c.*, 
      SUM(qy_mth_billed_a) 
      OVER (PARTITION BY ky_customer_no, rev_year, rev_mth, ABS(qy_mth_billed_a) 
        ORDER BY id DESC) AS total_mth_billed_a    
    FROM customer_usage c 
) c 
) 
WHERE rn = 1; 

Ausgang:

 ID KY_CUSTOMER_NO REV_MTH REV_YEAR QY_MTH_BILLED_A QY_MTH_BILLED_B 
---------- -------------- ---------- ---------- --------------- --------------- 
     3    1   1  2016    50    0 
0

Ich habe versucht ano Der nächste Ansatz ist die Verwendung der meisten @ MT0-Einstellungen.

CREATE TABLE customer_usage (id, ky_customer_no, rev_mth, rev_year, qy_mth_billed_a, qy_mth_billed_b) AS 
SELECT 1, 1, 1, 2016, 41040, 0 FROM DUAL UNION ALL 
SELECT 2, 1, 1, 2016, -41040, 0 FROM DUAL UNION ALL 
SELECT 3, 1, 1, 2016,  50, 0 FROM DUAL UNION ALL 
SELECT 4, 1, 1, 2016,  0, 0 FROM DUAL; 

Da wir wollen von diesen Werten, um loszuwerden, deren ABS() gleich, aber die unterschiedliche Vorzeichen ich das versucht:

SELECT c.KY_CUSTOMER_NO, c.REV_MTH, c.REV_YEAR, max(qy_mth_billed_a) as qy_mth_billed_a , max(QY_MTH_BILLED_B) as qy_mth_billed_b 
    FROM (
    SELECT c.*, 
      max(qy_mth_billed_a) 
      OVER (PARTITION BY ky_customer_no, rev_year, rev_mth,ABS(qy_mth_billed_a)) AS max_mth_billed_a, 
      min(qy_mth_billed_a) 
      OVER (PARTITION BY ky_customer_no, rev_year, rev_mth,ABS(qy_mth_billed_a)) AS min_mth_billed_a  
    FROM customer_usage c 
) c where max_mth_billed_a+min_mth_billed_a!=0 
group by c.KY_CUSTOMER_NO, c.REV_MTH, c.REV_YEAR; 

Der Ausgang es gleich und ist, da man einige konfrontiert sind Performance-Probleme, würde ich beide Ansätze versuchen:

KY_CUSTOMER_NO REV_MTH REV_YEAR qy_mth_billed_a qy_mth_billed_b 
1 1 1 2016 50 0 

EDIT Eigentlich, wenn Sie die verschiedenen si zählen gns von jedem abs (Wert) und es ist eine ungerade Zahl Ich denke, es wird noch schneller funktionieren (nur eine Fensterfunktion benötigt)

SELECT c.KY_CUSTOMER_NO, c.REV_MTH, c.REV_YEAR, max(qy_mth_billed_a) as qy_mth_billed_a , max(QY_MTH_BILLED_B) as qy_mth_billed_b 
    FROM (
    SELECT c.*, 
      count(sign(qy_mth_billed_a)) 
      OVER (PARTITION BY ky_customer_no, rev_year, rev_mth,ABS(qy_mth_billed_a)) AS signo  
    FROM customer_usage c 
) c where mod(signo,2) =1 
group by c.KY_CUSTOMER_NO, c.REV_MTH, c.REV_YEAR 
Verwandte Themen