2013-10-10 14 views
6

I Pivot in Oracle PL SQL Developer bin mit wie folgt:Oracle Dreh mit subquery

SELECT * 
FROM population 
PIVOT (AVG(Total) for Data_Type IN ('Group1','Group2','Group3')) 

Dies funktioniert gut, aber ich möchte nicht jedes Mal, wenn eine neue Spalte zu bearbeiten haben, hinzugefügt oder ein geändert wird (dh Gruppe 4, 5, 6, etc.), so habe ich versucht, eine Sub-Abfrage wie folgt:

SELECT * 
FROM population 
PIVOT (AVG(Total) for Data_Type IN (SELECT Data_Type FROM population)) 

das in dem folgenden Fehler ergibt: ORA-00936: fehlende Expression.

Nach einigen Recherchen scheint es, dass ich die Ergebnisse mit XML erzeugen kann, so habe ich versucht, die folgenden:

SELECT * 
FROM population 
PIVOT XML(AVG(Total) for Data_Type IN (ANY)) 

Diese tatsächlich die gewünschten Daten erzeugt, sondern im XML-Format. Also meine Frage ist, wie kann ich die XML-Ergebnisse in Standard-Tabellenformat in PL SQL Developer konvertieren? Oder, wenn ich die generierte XML-Datei in ein Werkzeug wie Crystal Reports bringen möchte, muss ich eine Schemadatei für diese Ergebnisse haben. Ist das etwas, das leicht automatisch in SQL generiert werden kann?

Antwort

1

Würden Sie die PIPELINED-Funktion verwenden, um Ihr Ziel zu erreichen?

Ich habe ein Beispiel für eine solche Funktion geschrieben. Das Beispiel basiert auf dem Tisch, Beispieldaten und PIVOT Abfrage von Tom Kyte der Artikel, die Sie auf seiner Website finden:

Tom Kyte's article about PIVOT/UNPIVOT

Tom Kyte's article about PIPELINED functions

Das Beispiel funktioniert wie folgt.

Wir schaffen zwei Typen:

  • t_pivot_test_obj - Typen, welche Spalten wir von XML
  • t_pivot_test_obj_tab abrufen mögen hält - verschachtelten Tabellentyp von obigen Aufgaben.

Dann erstellen wir eine PIPELINED Funktion, die die Abfrage mit PIVOT enthält, die XML generiert (so dass Sie die Werte zu hart Code nicht haben Sie schwenken über wollen). Diese Funktion extrahiert Daten aus dem generierten XML und übergibt (PIPEs) Zeilen an die aufrufende Abfrage, während sie generiert werden (im laufenden Betrieb - sie werden nicht auf einmal generiert, was für die Performance wichtig ist).

Schließlich schreiben Sie eine Abfrage, die Datensätze aus dieser Funktion auswählt (am Ende ist ein Beispiel für eine solche Abfrage).

CREATE TABLE pivot_test (
    id   NUMBER, 
    customer_id NUMBER, 
    product_code VARCHAR2(5), 
    quantity  NUMBER 
); 

INSERT INTO pivot_test VALUES (1, 1, 'A', 10); 
INSERT INTO pivot_test VALUES (2, 1, 'B', 20); 
INSERT INTO pivot_test VALUES (3, 1, 'C', 30); 
INSERT INTO pivot_test VALUES (4, 2, 'A', 40); 
INSERT INTO pivot_test VALUES (5, 2, 'C', 50); 
INSERT INTO pivot_test VALUES (6, 3, 'A', 60); 
INSERT INTO pivot_test VALUES (7, 3, 'B', 70); 
INSERT INTO pivot_test VALUES (8, 3, 'C', 80); 
INSERT INTO pivot_test VALUES (9, 3, 'D', 90); 
INSERT INTO pivot_test VALUES (10, 4, 'A', 100); 
COMMIT; 

CREATE TYPE t_pivot_test_obj AS OBJECT (
    customer_id NUMBER, 
    product_code VARCHAR2(5), 
    sum_quantity NUMBER 
); 
/

CREATE TYPE t_pivot_test_obj_tab IS TABLE OF t_pivot_test_obj; 
/

CREATE OR REPLACE FUNCTION extract_from_xml RETURN t_pivot_test_obj_tab PIPELINED 
AS 
    v_xml XMLTYPE; 
    v_item_xml XMLTYPE; 
    v_index NUMBER; 
    v_sum_quantity NUMBER; 

    CURSOR c_customer_items IS 
    SELECT customer_id, product_code_xml 
     FROM (SELECT customer_id, product_code, quantity 
       FROM pivot_test) 
     PIVOT XML (SUM(quantity) AS sum_quantity FOR (product_code) IN (SELECT DISTINCT product_code 
                     FROM pivot_test)); 
BEGIN 
    -- loop through all records returned by query with PIVOT 
    FOR v_rec IN c_customer_items 
    LOOP 
    v_xml := v_rec.product_code_xml; 
    v_index := 1; 

    -- loop through all ITEM elements for each customer 
    LOOP 
     v_item_xml := v_xml.EXTRACT('/PivotSet/item[' || v_index || ']'); 

     EXIT WHEN v_item_xml IS NULL; 

     v_index := v_index + 1; 

     IF v_item_xml.EXTRACT('/item/column[@name="SUM_QUANTITY"]/text()') IS NOT NULL THEN 
     v_sum_quantity := v_item_xml.EXTRACT('/item/column[@name="SUM_QUANTITY"]/text()').getNumberVal(); 
     ELSE 
     v_sum_quantity := 0; 
     END IF; 

     -- finally, for each customer and item - PIPE the row to the calling query 
     PIPE ROW(t_pivot_test_obj(v_rec.customer_id, 
           v_item_xml.EXTRACT('/item/column[@name="PRODUCT_CODE"]/text()').getStringVal(), 
           v_sum_quantity)); 
    END LOOP; 
    END LOOP; 
END; 
/

SELECT customer_id, product_code, sum_quantity 
    FROM TABLE(extract_from_xml()) 
; 

Ausgang:

CUSTOMER_ID   PRODUCT_CODE SUM_QUANTITY   
---------------------- ------------ ---------------------- 
1      A   10      
1      B   20      
1      C   30      
1      D   0      
2      A   40      
2      B   0      
2      C   50      
2      D   0      
3      A   60      
3      B   70      
3      C   80      
3      D   90      
4      A   100      
4      B   0      
4      C   0      
4      D   0      

16 rows selected 
0

Sie können den Text Ihrer ersten SQL-Anweisung erzeugen durch Iteration, dann getrennt, dass die Anweisung auszuführen.

Wenn Ihnen eine quasi-dynamische Lösung nichts ausmacht, können Sie die Erstellung einer VIEW auf diese Weise mithilfe von dynamischem SQL planen (d. H. EXECUTE IMMEDIATE).

(Ein Crystal Report müsste nach meinem Wissen die Spaltennamen im Voraus wissen.)

Bearbeitet, um Code hinzuzufügen. Ich habe das nicht getestet. Beachten Sie auch, dass dies bricht, wenn die SQL-Anweisung 32 KB überschreitet, unabhängig von der tatsächlichen Anzahl der Multibyte-Zeichen.

DECLARE 
    sql_statement_ VARCHAR2(32767); 
BEGIN 
    sql_statement_ := 'CREATE OR REPLACE VIEW population_view AS ' || 
        'SELECT * FROM population ' || 
        'PIVOT (AVG(total) FOR data_type IN ('; 
    FOR rec_ IN (SELECT DISTINCT data_type FROM population) LOOP 
     sql_statement_ := sql_statement_ || 
         '''' || REPLACE(rec_.data_type, '''', '''''') || ''', '; 
    END LOOP; 
    /* trim last comma and space */ 
    sql_statement_ = SUBSTR(1, sql_statement_, LENGTH(sql_statement_) - 2); 
    /* close statement */ 
    sql_statement_ = sql_statement_ || ')) WITH READ ONLY'; 
    /* Rub your rabbit's foot, scatter garlic, and grab your four leaf clover. 
     This could hurt if we didn't properly handle injection above. */ 
    EXECUTE IMMEDIATE sql_statement_; 
END; 
/