2012-06-08 8 views
10

In Orakel, die LISTAGG Funktion ermöglicht es mir, es analytisch mit einer OVER (PARTITION BY column..) Klausel zu verwenden. Die Verwendung von Fenstern mit den Schlüsselwörtern ROWS oder RANGE wird jedoch nicht unterstützt.LISTAGG äquivalent mit Windowing-Klausel

Ich habe einen Datensatz aus einem Geschäft registrieren (vereinfacht für die Frage). Beachten Sie, dass die Menge der Registertabelle immer 1 - ein Element, eine Transaktionszeile ist.

TranID TranLine ItemId OrderID Dollars Quantity 
------ -------- ------ ------- ------- -------- 
1  101  23845 23  2.99 1 
1  102  23845 23  2.99 1 
1  103  23845 23  2.99 1 
1  104  23845 23  2.99 1 
1  105  23845 23  2.99 1 

Ich muss diese Daten mit einer Tabelle in einem speziellen Bestellsystem "zusammenpassen", in dem Artikel nach Menge gruppiert sind. Beachten Sie, dass das System dieselbe Artikel-ID für mehrere Zeilen haben kann (die bestellten Komponenten können sich unterscheiden, auch wenn der Artikel identisch ist).

ItemId OrderID Order Line Dollars Quantity 
------ ------- ---------- ------- -------- 
23845 23  1   8.97 3 
23845 23  2   5.98 2 

Die nur Art, wie ich diese Daten übereinstimmen kann, ist durch Auftrags-ID, Artikel-ID und Dollar-Betrag.

Im Wesentlichen muss ich zu dem folgenden Ergebnis gelangen.

ItemId OrderID Order Line Dollars Quantity Tran ID Tran Lines 
------ ------- ---------- ------- -------- ------- ---------- 
23845 23  1   8.97 3  1  101;102;103 
23845 23  2   5.98 2  1  104;105 

ich nicht speziell egal, ob die tran Linien in irgendeiner Weise geordnet sind, meinetwegen ist, dass die Dollar-Beträge übereinstimmen und dass ich nicht „Wiederverwendung“ eine Zeile aus dem Register in Computing die Summe auf der Sonderbestellung. Ich brauche die tran-Zeilen nicht in eine Tabelle aufgeteilt - das ist für Reporting-Zwecke und die Granularität geht nie zurück auf die Ebene der Registertransaktionszeile.

Meine anfängliche Meinung war, dass ich dies mit analytischen Funktionen tun kann, um eine "beste Übereinstimmung" zu finden, um die erste Menge von Zeilen zu identifizieren, die dem Dollarbetrag und -menge im Bestellsystem entsprechen :

TranID TranLine ItemId OrderID Dollars Quantity CumDollar CumQty 
------ -------- ------ ------- ------- -------- -------- ------ 
1  101  23845 23  2.99 1  2.99  1 
1  102  23845 23  2.99 1  5.98  2 
1  103  23845 23  2.99 1  8.97  3 
1  104  23845 23  2.99 1  11.96  4 
1  105  23845 23  2.99 1  14.95  5 

So weit so gut. Aber ich versuche dann LISTAGG meiner Abfrage hinzuzufügen:

SELECT tranid, tranline, itemid, orderid, dollars, quantity, 
     SUM(dollars) OVER (partition by tranid, itemid, orderid order by tranline) cumdollar, 
     SUM(quantity) OVER (partition by tranid, itemid, orderid order by tranline) cumqty 
     LISTAGG (tranline) within group (order by tranid, itemid, orderid, tranline) OVER (partition by tranid, itemid, orderid) 
FROM table 

ich feststellen, dass es immer einen vollen agg kehrt anstelle eines kumulativen agg:

TranID TranLine ItemId OrderID Dollars Quantity CumDollar CumQty ListAgg 
------ -------- ------ ------- ------- -------- -------- ------ ------- 
1  101  23845 23  2.99 1  2.99  1  101;102;103;104;105 
1  102  23845 23  2.99 1  5.98  2  101;102;103;104;105 
1  103  23845 23  2.99 1  8.97  3  101;102;103;104;105 
1  104  23845 23  2.99 1  11.96  4  101;102;103;104;105 
1  105  23845 23  2.99 1  14.95  5  101;102;103;104;105 

So dies nicht sinnvoll ist.

Ich würde viel lieber dies in SQL tun, wenn überhaupt möglich. Ich bin mir bewusst, dass ich dies mit Cursors & prozedurale Logik tun kann.

Gibt es eine Möglichkeit, Windowing mit der analytischen LISTAGG-Funktion, oder vielleicht eine andere analytische Funktion, die dies unterstützen würde?

Ich bin auf 11gR2.

+2

+1 für eine sehr gut geschriebene interessante Frage. – DCookie

Antwort

7

Die einzige Art, wie ich mir vorstellen kann, dies zu erreichen, ist mit einer korrelierten Unterabfrage:

WITH CTE AS 
( SELECT TranID, 
      TranLine, 
      ItemID, 
      OrderID, 
      Dollars, 
      Quantity, 
      SUM(dollars) OVER (PARTITION BY TranID, ItemID, OrderID ORDER BY TranLine) AS CumDollar, 
      SUM(Quantity) OVER (PARTITION BY TranID, ItemID, OrderID ORDER BY TranLine) AS CumQuantity 
    FROM T 
) 
SELECT TranID, 
     TranLine, 
     ItemID, 
     OrderID, 
     Dollars, 
     Quantity, 
     CumDollar, 
     CumQuantity, 
     ( SELECT LISTAGG(Tranline, ';') WITHIN GROUP(ORDER BY CumQuantity) 
      FROM CTE T2 
      WHERE T1.CumQuantity >= T2.CumQuantity 
      AND  T1.ItemID = T2.ItemID 
      AND  T1.OrderID = T2.OrderID 
      AND  T1.TranID = T2.TranID 
      GROUP BY tranid, itemid, orderid 
     ) AS ListAgg 
FROM CTE T1; 

Ich weiß, dies nicht die genaue Ausgabe nicht geben Sie fragten, aber hoffentlich ist es genug, um das zu überwinden Problem der kumulativen LISTAGG und dich auf den Weg bringen.

Ich habe eine SQL Fiddle eingerichtet, um die Lösung zu demonstrieren.

+0

Danke. Es funktioniert für das erste Beispiel, das ich zur Verfügung gestellt habe, aber wenn ich zusätzliche Transaktionen/Elemente hinzufüge, bekomme ich einen 'ORA-01427: einreihige Unterabfrage gibt mehr als eine Zeile' zurück. Ich denke, dass ich in der Lage bin, Ihre Anfrage zu modifizieren, um das zu tun, wonach ich suche. Hier ist ein Beispiel, wo es den Fehler wirft: http://sqlfiddle.com/#!4/53b4d/1 –

+1

Auch - SQLFiddle? Fantastisch! Ich gehe sicher in die Lesezeichen. –

+0

Alles, was ich tun musste, ist hinzuzufügen 'und t1.itemid = t2.itemid und t1.orderid = t2.orderid und t1.tranid = t2.tranid 'zum Join in der Unterabfrage. Können Sie die Antwort aktualisieren, und ich werde akzeptieren. –

2

In Ihrem Beispiel enthält Ihre Geschäftsregistertabelle 5 Zeilen und Ihre spezielle Bestellsystemtabelle enthält 2 Zeilen. Ihre erwartete Ergebnismenge enthält die zwei Zeilen Ihrer speziellen Bestellsystemtabelle und alle "tranlines" Ihrer Geschäftsregistertabelle sollten in der Spalte "Tran Line" erwähnt werden.

Dies bedeutet, dass Sie diese 5 Zeilen zu 2 Zeilen aggregieren müssen. Das heißt, Sie benötigen nicht die analytische Funktion LISTAGG, sondern die Aggregatfunktion LISTAGG.

Ihre Herausforderung besteht darin, die Zeilen der Geschäftsregistertabelle mit der rechten Zeile in der speziellen Bestellsystemtabelle zu verknüpfen. Sie waren auf Ihrem Weg, indem Sie die laufende Summe von Dollars und Mengen berechneten. Der einzige Schritt, der fehlt, ist, Bereiche von Dollars und Mengen zu definieren, mit denen Sie jede Geschäftsregisterzeile jeder speziellen Auftragssystemzeile zuordnen können.

Hier ist ein Beispiel. Zunächst definieren die Tabellen:

SQL> create table store_register_table (tranid,tranline,itemid,orderid,dollars,quantity) 
    2 as 
    3 select 1, 101, 23845, 23, 2.99, 1 from dual union all 
    4 select 1, 102, 23845, 23, 2.99, 1 from dual union all 
    5 select 1, 103, 23845, 23, 2.99, 1 from dual union all 
    6 select 1, 104, 23845, 23, 2.99, 1 from dual union all 
    7 select 1, 105, 23845, 23, 2.99, 1 from dual 
    8/

Table created. 

SQL> create table special_order_system_table (itemid,orderid,order_line,dollars,quantity) 
    2 as 
    3 select 23845, 23, 1, 8.97, 3 from dual union all 
    4 select 23845, 23, 2, 5.98, 2 from dual 
    5/

Table created. 

Und die Abfrage:

SQL> with t as 
    2 (select tranid 
    3   , tranline 
    4   , itemid 
    5   , orderid 
    6   , sum(dollars) over (partition by itemid,orderid order by tranline) running_sum_dollars 
    7   , sum(quantity) over (partition by itemid,orderid order by tranline) running_sum_quantity 
    8  from store_register_table srt 
    9 ) 
10 , t2 as 
11 (select itemid 
12   , orderid 
13   , order_line 
14   , dollars 
15   , quantity 
16   , sum(dollars) over (partition by itemid,orderid order by order_line) running_sum_dollars 
17   , sum(quantity) over (partition by itemid,orderid order by order_line) running_sum_quantity 
18  from special_order_system_table 
19 ) 
20 , t3 as 
21 (select itemid 
22   , orderid 
23   , order_line 
24   , dollars 
25   , quantity 
26   , 1 + lag(running_sum_dollars,1,0) over (partition by itemid,orderid order by order_line) begin_sum_dollars 
27   , running_sum_dollars end_sum_dollars 
28   , 1 + lag(running_sum_quantity,1,0) over (partition by itemid,orderid order by order_line) begin_sum_quantity 
29   , running_sum_quantity end_sum_quantity 
30  from t2 
31 ) 
32 select t3.itemid "ItemID" 
33  , t3.orderid "OrderID" 
34  , t3.order_line "Order Line" 
35  , t3.dollars "Dollars" 
36  , t3.quantity "Quantity" 
37  , t.tranid "Tran ID" 
38  , listagg(t.tranline,';') within group (order by t3.itemid,t3.orderid) "Tran Lines" 
39 from t3 
40   inner join t 
41   on ( t.itemid = t3.itemid 
42    and t.orderid = t3.orderid 
43    and t.running_sum_dollars between t3.begin_sum_dollars and t3.end_sum_dollars 
44    and t.running_sum_quantity between t3.begin_sum_quantity and t3.end_sum_quantity 
45    ) 
46 group by t3.itemid 
47  , t3.orderid 
48  , t3.order_line 
49  , t3.dollars 
50  , t3.quantity 
51  , t.tranid 
52/

    ItemID OrderID Order Line Dollars Quantity Tran ID Tran Lines 
---------- ---------- ---------- ---------- ---------- ---------- -------------------- 
    23845   23   1  8.97   3   1 101;102;103 
    23845   23   2  5.98   2   1 104;105 

2 rows selected. 

Grüße,
Rob.

+0

Danke! Dies ist ein interessanter Weg, dies zu tun. Es funktioniert gut für die Beispieldaten. Ein Problem tritt auf, wenn die Auftragstabelle zwei Zeilen mit den gleichen $ Beträgen hat. Schau mal hier: http://sqlfiddle.com/#!4/06e72/1. Die Auftragszeile 2 zeigt nur eine übereinstimmende Tran-Zeile, obwohl es fünf geben sollte. Ich werde mit der Abfrage herumspielen, um zu sehen, ob ich es zum Laufen bringen kann. –

+0

Entschuldigung - Ich hatte einen Tippfehler auf meinen Beilagen, die Artikel-IDs waren anders. Es ist jedoch immer noch ein Problem, wenn es den "verbleibenden" 2.99 Artikel mit dem 14.95 Artikel in der Auftragstabelle übereinstimmt. –

+0

Ich werde heute Abend hineinschauen. –

Verwandte Themen