2013-10-10 54 views
6

我使用支点在甲骨文的PL SQL开发人员的Oracle支点如下:与子查询

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

这工作得很好,但我不希望有编辑每次添加一个新的列时间或一个被改变(即组4,5,6等),所以我尝试的子查询,如下所示:

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

这将导致以下错误:ORA-00936:缺少表达。

经过一番研究,看来我可以生成XML的结果,所以我尝试了以下内容:

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

这实际上产生所需要的数据,但在XML格式。所以我的问题是,如何将XML结果转换为PL SQL Developer中的标准表格格式?或者,如果我想将生成的XML文件放入Crystal Reports这样的工具中,我需要为这些结果创建一个模式文件。那些可以很容易地在SQL中自动生成的东西?

回答

1

您会考虑使用PIPELINED功能来实现您的目标吗?

我已经写了一个这样的函数的例子。这个例子是基于表,样本数据和汤姆凯特的文章PIVOT查询,你可以找到他的网站:

Tom Kyte's article about PIVOT/UNPIVOT

Tom Kyte's article about PIPELINED functions

的例子如下工作。

我们创建两种类型:

  • t_pivot_test_obj - 型持有我们想从XML检索
  • t_pivot_test_obj_tab列 - 上述目的的嵌套表类型。

然后,我们创建一个PIPELINED函数,其中包含查询PIVOT,该函数生成XML(因此您不必硬编码想要旋转的值)。该函数从生成的XML中提取数据并将它们传递(PIPE)到生成的调用查询中(即时生成 - 它们不会一次生成,这对性能很重要)。

最后,您编写了一个查询,该查询从该函数中选择记录(最后是此类查询的一个示例)。

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

输出:

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

您可以通过重复生成你的第一个SQL语句的文本,然后分别执行该语句。

如果您不介意准动态解决方案,则可以使用动态SQL(即EXECUTE IMMEDIATE)以此方式计划创建VIEW。

(据我所知,水晶报表需要事先知道列名。)

编辑添加代码。我没有测试这个。请注意,当SQL语句超过32KB时,无论实际的多字节字符数如何,这都会中断。

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