在 oracle 中使用 sql 动态更改列标题

Posted

技术标签:

【中文标题】在 oracle 中使用 sql 动态更改列标题【英文标题】:Use pivot for dynamically changing column headers using sql in oracle 【发布时间】:2020-11-17 07:36:17 【问题描述】:

我有一个需要对数据进行透视的要求。 然而,枢轴需要是动态的,因为列标题会根据列 app_id 不断变化。 所以如果 app_id=1。列标题将为 A、B、C、D,如果 app_id=2,列将为 CDEF 等等。 此外,每组值都有一个 id。因此对于 id、120 和 app_id=1 ,A、B、C、D 列将显示值等等。

当前示例数据只有 2 个 app_id,但可能还有更多,所以 app_id 和标签会不断变化,因此我需要编写一个动态查询。

示例表数据:

ID  label   value   app_id
--- -----   -----   ------
120 A       Alpha   1
120 B       Beta    1
120 C       Class   1
120 D       Delta   1
120 C       Alpha   2
120 D       Beta    2
120 E       Class   2
120 F       Delta   2

建设性的查询是

WITH data( ID, label, value, app_id ) AS
(
  SELECT 120, 'A', 'Alpha', 1 FROM dual UNION ALL
  SELECT 120, 'B', 'Beta' , 1 FROM dual UNION ALL
  SELECT 120, 'C', 'Class', 1 FROM dual UNION ALL
  SELECT 120, 'D', 'Delta', 1 FROM dual UNION ALL
  SELECT 120, 'C', 'Alpha', 2 FROM dual UNION ALL
  SELECT 120, 'D', 'Beta' , 2 FROM dual UNION ALL
  SELECT 120, 'E', 'Class', 2 FROM dual UNION ALL
  SELECT 120, 'F', 'Delta', 2 FROM dual  
)
SELECT * 
  FROM data

预期输出:

SELECT * FROM data WHERE ID = 120 AND app_id = 1;    
app_id  A        B      C      D      ID
------  ------   -----  -----  -----  -----
1       Alpha    Beta   Class  Delta  120

SELECT * FROM data WHERE ID = 120 AND app_id = 2;    
app_id  C        D      E      F      ID
------  ------   -----  -----  -----  -----
2       Alpha    Beta   Class  Delta  120

【问题讨论】:

【参考方案1】:

你能做什么

SELECT * 
  FROM data  
 PIVOT  
 (
  MAX(value) FOR label IN ('A' AS "A", 'B' AS "B",'C' AS "C",'D' AS "D")
 )
 WHERE ID = 120 AND app_id = 1

作为静态数据透视语句可能会转换为包含两个相应参数的函数

CREATE OR REPLACE FUNCTION Get_Pivoted_Labels( i_id data.id%type, i_app_id data.app_id%type ) 
RETURN SYS_REFCURSOR IS
  v_recordset SYS_REFCURSOR;
  v_sql       VARCHAR2(32767); 
  v_cols      VARCHAR2(32767);  
BEGIN
  SELECT LISTAGG( ''''||label||''' AS "'||label||'"' , ',' )
          WITHIN GROUP ( ORDER BY label ) 
    INTO v_cols
    FROM ( SELECT DISTINCT label 
             FROM data
            WHERE ID = i_id AND app_id = i_app_id );

  v_sql :=
      'SELECT * 
         FROM data
        PIVOT 
        (
          MAX(value) FOR label IN ( '|| v_cols ||' )
        )
       WHERE ID = :id AND app_id = :aid'; 

  OPEN v_recordset FOR v_sql USING i_id, i_app_id;
  RETURN v_recordset;
END;
/

其中一个辅助查询,其中明确选择了label 列,用于确定要连接到主 SQL 字符串的字符串(v_cols for 'A' AS "A", 'B' AS "B",'C' AS "C",'D' AS "D")以便在返回 SYS_REFCURSOR 类型值的游标。

并被调用

VAR rc REFCURSOR
VAR v_id NUMBER
VAR v_app_id NUMBER
EXEC :rc := Get_Pivoted_Labels(:v_id,:v_app_id);
PRINT rc

来自 SQL 开发人员的控制台。

Demonstration 带有生成的 SQL 语句

如果 SELECT 列表中的列顺序很重要,请使用下面的代码来创建函数

CREATE OR REPLACE FUNCTION Get_Pivoted_Labels( i_id data.id%type, i_app_id data.app_id%type ) 
RETURN SYS_REFCURSOR IS
  v_recordset SYS_REFCURSOR;
  v_sql       VARCHAR2(32767); 
  v_cols_1    VARCHAR2(32767);    
  v_cols_2    VARCHAR2(32767);   
BEGIN
  SELECT LISTAGG( ''''||label||''' AS "'||label||'"' , ',' )
          WITHIN GROUP ( ORDER BY label ),
         LISTAGG( label , ',' )
          WITHIN GROUP ( ORDER BY label )  
    INTO v_cols_1, v_cols_2
    FROM ( SELECT DISTINCT label, value 
             FROM data
            WHERE ID = i_id AND app_id = i_app_id );

  v_sql :=
      'SELECT ID, '|| v_cols_2 ||', app_id
         FROM data
        PIVOT 
        (
          MAX(value) FOR label IN ( '|| v_cols_1 ||' )
        )
       WHERE ID = :id AND app_id = :aid'; 

  OPEN v_recordset FOR v_sql USING i_id, i_app_id;
  RETURN v_recordset;
END;
/

【讨论】:

正如 OP 在他的 cmets 中对我的回答所说,最终的“客户端”是 Oracle APEX,它是少数不能使用 REF CURSOR 的环境之一。 感谢@StewAshton,但随后需要阅读每条评论,即使我自己的答案或问题下方不存在:) 为什么是“但是”?我从没想过你会阅读对别人问题的每一条评论。这就是我花时间通知你的原因。为什么人们会像批评一样试图告知? 好吧@StewAshton,您的通知没问题,而OP希望清楚地说明他在问题中需要什么。这样我们就不会在一些无用的问题上浪费时间。【参考方案2】:

这是一个很常见的问题,原因很简单,答案是“否”。

使用 Oracle 数据库,每个 SELECT 语句都必须具有固定且已知的“形状”(列数、名称和数据类型)。在最近的版本中,有“多态表函数”似乎打破了这个规则,但实际上它们并没有:“形状”是在解析语句时计算的,因此在执行开始之前它是固定的和已知的。

您不想要“多态”(在解析时改变形状),您想要真正的“动态”(在执行时根据数据改变形状)。 Oracle 不这样做。

您可以使用一条 SQL 语句获得的最接近的结果是准确输出包含 XML 或 JSON 的 一个 列。然后调用数据库的程序将负责将该结果转换为行和列。

另一种选择是执行一个 SELECT 来获取列名并生成第二个 SELECT 来获得您想要的结果。我写了一个函数来帮助做到这一点: https://stewashton.wordpress.com/2018/05/30/improved-pivot-function/

我不会演示这些替代方案,因为它们不会直接回答您的问题。您的问题没有直接答案。

【讨论】:

这可以使用数组/集合来完成吗?只是问,因为我对替代方案一无所知。 您问“这可以使用数组/集合来完成吗?”我不明白你的意思,因为数组/集合也有固定的形状。首先告诉我什么软件在调用数据库:它是一个应用程序吗?是 SQL*Plus 吗?它是某种报告工具吗?可能的替代方案取决于您的答案。 SQL Developer-> Oracle 12.1 不,我的意思是一旦你完成了开发,查询会发生什么?它将在哪里部署到生产环境中?那么什么程序会执行呢? 此查询将用作 oracle apex 应用程序中的数据源

以上是关于在 oracle 中使用 sql 动态更改列标题的主要内容,如果未能解决你的问题,请参考以下文章

使用 Sql Developer Oracle 的动态数据透视查询

Oracle APEX 自动在区域中添加/删除列

如何在 Oracle SQL 语句中重用动态列?

Oracle APEX:如何更改交互式网格中开关列的值(使用动态操作 - JavaScript)?

Oracle/SQL - 基于参数的动态列

在 Oracle 的动态 SQL 中使用 BLOB 类型