1

我正在构建一个报告,该报告应该根据请求的年份(即 2000 到 2002 年)生成动态列列表。用户应该能够通过SELECT如下语句运行报告:

SELECT * FROM TABLE(myreport(2002, 3)); -- Get 3 report years starting with 2002

目前,我有一个生成此输出的表函数,但具有静态列名:

-- Row type
CREATE OR REPLACE TYPE typ_myreport AS OBJECT (
  myuser VARCHAR(260),
  yr1_p DECIMAL(5,2),
  yr1_t INTEGER,
  yr2_p DECIMAL(5,2),
  yr2_t INTEGER,
  ...
);

-- Table type
CREATE OR REPLACE TYPE tab_myreport AS TABLE OF typ_myreport;

-- Report
CREATE OR REPLACE FUNCTION myreport(pAsOfYear IN INTEGER) 
RETURN tab_myreport PIPELINED AS
BEGIN
  FOR vRec IN (
    SELECT user_name, yr1_p, yr1_t, y2_p, y2_t, ...
    FROM mytable
  ) 
  LOOP
    PIPE ROW (
      typ_myreport(vRec.user_name, vRec.yr1_p, vRec.yr1_t, vRec.yr2_p, vRec.year2_t, ...)
    );
  END LOOP;

  RETURN;
END;

它产生如下输出:

User    yr1_p   yr1_t   yr2_p   yr2_t   yr3_p   yr3_t
-----------------------------------------------------
Bobby      25       2      33       2      20       4
Barry      50       4      66       4      50      10
Big Ben    25       2       0       0      30       6

但我希望根据请求的年份动态命名列:

User    2000_p  2000_t  2001_p  2001_t  2002_p  2002_t
------------------------------------------------------
Bobby       25       2      33       2      20       4
Barry       50       4      66       4      50      10
Big Ben     25       2       0       0      30       6

输出结果集应该保持完全相同——表结构、数据类型——只是列名应该改变。我的想法是通过一个动态构建的语句来做到这一点SELECT,我可以执行它然后返回结果。我摸索着试图让它工作,但没有运气。

有没有办法在具有动态列数和列名的函数/过程中编写动态 SQL,然后将该结果集作为(而不是脚本)输出返回?

更新
这里是生成yr字段的实际查询的简化片段:

WITH rpt_years AS (SELECT * FROM TABLE(GetReportyears(pAsOfYear)))
SELECT user_name,
  SUM(year1_cnt) AS yr1_t,
  SUM(COALESCE((year1_cnt / NULLIF(year1_tot,0)),0)) AS yr1_p,
  SUM(year2_cnt) AS yr2_t, 
  SUM(COALESCE((year2_cnt / NULLIF(year2_tot,0)),0)) AS yr2_p,
  ...
FROM (
  SELECT DISTINCT 
    ui.user_name, 
    SUM(CASE WHEN s.dist_year = y.year1 THEN 1 END) AS year1_cnt,
    COUNT(CASE WHEN s.dist_year = y.year1 THEN 1 END) AS year1_tot,
    SUM(CASE WHEN s.dist_year = y.year2 THEN 1 END) AS year2_cnt,
    COUNT(CASE WHEN s.dist_year = y.year2 THEN 1 END) AS year2_tot,
    ...
  FROM rpt_years y -- Get report years as single row
  INNER JOIN src_table s
  ...
) src
ORDER BY src.user_name

4

1 回答 1

1

您可以使用开源程序Method4在 SQL 上下文中返回动态列数。

要准确地构建您想要的东西,所有内容都在一行代码中,需要创建一个新类型。由于这些类型很复杂,我建议将该任务保存以备后用。第一步,如果只使用现有的类型,只需要编写一条生成正确 SQL 语句的 SQL 语句。

例如,让我们使用此表来表示您现有的大型查询的结果:

create table MyTable as
select 'Bobby'   user_name, 25 yr1_p, 2 yr1_t, 33 yr2_p, 2 yr2_t, 20 yr3_p, 4  yr3_t from dual union all
select 'Barry'   user_name, 50 yr1_p, 4 yr1_t, 66 yr2_p, 4 yr2_t, 50 yr3_p, 10 yr3_t from dual union all
select 'Big Ben' user_name, 25 yr1_p, 2 yr1_t,  0 yr2_p, 0 yr2_t, 30 yr3_p,  6 yr3_t from dual;

下面的静态查询会将列名转换为您想要的格式:

select user_name, yr1_p "2000_P", yr1_t "2000_T", yr2_p "2001_P", yr2_t "2001_T", yr3_p "2002_P", yr3_t "2002_T"
from myTable;

您可以使用以下查询生成上述查询:

select
    'select user_name, ' ||
    listagg('yr'||level||'_p "'||(start_year + level - 1)||'_P", yr'||level||'_t "'||(start_year + level - 1)||'_T"', ', ') within group (order by start_year) || 
    ' from myTable' v_sql
from
(
    select 2000 start_year, 3 number_of_years from dual
)
connect by level <= number_of_years;

下一个查询将它们放在一起。安装 Method4 后,该DYNAMIC_QUERY函数可以运行另一个查询生成的查询,生成您想要的结果和列名。您只需更改值 2000 和 3 即可调整结果。

select * from table(method4.dynamic_query(q'[
    select
        'select user_name, ' ||
        listagg('yr'||level||'_p "'||(start_year + level - 1)||'_P", yr'||level||'_t "'||(start_year + level - 1)||'_T"', ', ') within group (order by start_year) || 
        ' from myTable' v_sql
    from
    (
        --Only change the values in here:
        select 2000 start_year, 3 number_of_years from dual
    )
    connect by level <= number_of_years
]'));

您还需要替换myTable为生成中间结果的查询。这并不是您想要的——尽管用户只需要更改两个数字,但他们必须复制并粘贴一个巨大的查询。

如果您无法忍受复制和粘贴,还有一种方法可以创建只需要一行 SQL 语句的解决方案。对于该解决方案,您需要创建自己的类型并将其添加到包规范中。大部分工作只是复制文件 method4_dynamic_ot.tpb 和 method4_dynamic_ot.tps,更改名称和参数,修改函数 re_evaluate_statement,然后将新类型添加到包规范中。我可以提供帮助,但在使用该高级解决方案之前,您应该检查以确保该DYAMIC_QUERY方法首先有效。

于 2020-12-16T00:13:18.167 回答