2013-10-10 23 views
6

używam pivot w Oracle PL SQL Developer następująco:Oracle pivot z podzapytania

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

Działa to dobrze, ale nie chcę, aby edytować za każdym razem nowa kolumna jest dodawana lub jeden ulegnie zmianie (tj Group4, 5, 6 itp), więc spróbowałem sub-zapytanie w następujący sposób:

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

Powoduje następujący błąd: ORA-00936: brak ekspresji.

Po niektórych badań wynika, że ​​mogę generować wyniki w formacie XML, więc próbowałem następujące:

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

to faktycznie generuje żądane dane, ale w formacie XML. Moje pytanie brzmi: jak mogę przekonwertować wyniki XML na standardowy format tabeli w języku PL SQL Developer? Lub, jeśli chcę wprowadzić wygenerowany plik XML do narzędzia takiego jak Crystal Reports, potrzebuję pliku schematu dla tych wyników. Czy to coś, co można łatwo wygenerować automatycznie w SQL?

Odpowiedz

1

Czy rozważasz użycie funkcji PIPELINED, aby osiągnąć swój cel?

Napisałem przykład takiej funkcji. Przykład opiera się na stole, przykładowe dane i PIVOT zapytania z artykułów Tom Kyte, które można znaleźć na jego stronie internetowej:

Tom Kyte's article about PIVOT/UNPIVOT

Tom Kyte's article about PIPELINED functions

przykład działa w następujący sposób.

Tworzymy dwa typy:

  • t_pivot_test_obj - typ, który posiada kolumny chcemy odzyskać z XML
  • t_pivot_test_obj_tab - typ tabeli zagnieżdżonej z powyższych obiektów.

Następnie tworzymy funkcję potokowego, który zawiera kwerendę z PIVOT, który generuje XML (dzięki czemu nie muszą ciężko kodem wartości chcesz obracać się). Funkcja ta pobiera dane z wygenerowanych wierszy XML i przekazuje (PIPE) do zapytania wywołującego, ponieważ są one generowane (w locie - nie są generowane wszystkie naraz, co jest ważne dla wydajności).

Wreszcie, piszesz zapytanie, które wybiera rekordy z tej funkcji (na końcu jest przykładem takiego zapytania).

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()) 
; 

wyjściowa:

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

Można wygenerować tekst pierwszego SQL przez iteracji, a następnie osobno wykonać to oświadczenie.

Jeśli nie masz nic przeciwko quasi-dynamicznemu rozwiązaniu, możesz zaplanować tworzenie WIDO w ten sposób przy użyciu dynamicznego SQL (tj. WYKONAJ NATYCHMIAST).

(Według raportu Crystal, muszę znać nazwy kolumn z wyprzedzeniem.)

Edytowane w celu dodania kodu. Nie testowałem tego. Zwróć też uwagę, że zostanie to przerwane, gdy instrukcja SQL przekroczy 32 KB, niezależnie od faktycznej liczby znaków wielobajtowych.

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; 
/