DB2 中的动态数据透视 SQL 查询

dynamic pivot SQL Query in DB2

我在 DB2 中有如下 table,其中所有这 3 列都是 VARCHAR 数据类型

DATE_F    FILE_NM   ROW_ID  COLUMN_NM   COLUMN_VAL 
20200131  ABC.TXT     1      ABC_COL1     123
20200131  ABC.TXT     1      ABC_COL2     XYZ
20201231  ABC.TXT     2      ABC_COL1     456
20201231  ABC.TXT     2      ABC_COL2     XY1
20200630  XYZ.TXT     1      XYZ_COL1     PQR
20200630  XYZ.TXT     1      XYZ_COL2     567
20200630  XYZ.TXT     1      XYZ_COL3     MNO

我需要一个动态 PIVOT 查询,当 FILE_NM 过滤器用作 ABC.TXT

当 FILE_NM 过滤器用作 XYZ.TXT

当 FILE_NM=ABC.TXT

时,输出应该如下所示
DATE_F    ROW_ID   ABC_COL1   ABC_COL2    
20200131    1        123       XYZ
20201231    2        456       XY1

当 FILE_NM=XYZ.TXT

时,输出应该如下所示
 DATE_F    ROW_ID     XYZ_COL1     XYZ_COL2     XYZ_COL3
20200630    1           PQR         567           MNO

所以,一一查询就能提取数据。在这种情况下,任何函数或等效函数都可以完成任务吗?

您可以尝试使用以下通用存储过程进行数据透视。

--#SET TERMINATOR @
create or replace procedure pivot
(
  in  sel_stmt varchar(4000)
, in  row_cols varchar(200)
, in  col_col  varchar(128)
, in  agg_col  varchar(128)
, in  agg_fn   varchar(10)
, in  tmp_tbl  varchar(128)
, in  null_ind varchar(10)
, out rc       int
, out msg      varchar(128)
, out stmt     varchar(4000)
)
LANGUAGE SQL
DYNAMIC RESULT sets 1
BEGIN
 declare QUOT1        char(1) default '''';
 declare QUOT2        char(1) default '"';
 declare SQLCODE      int default 0;
 declare SQLTYPE_ID   int;
 declare SQLTYPE      varchar(128);
 declare SQLLENGTH    int;
 declare SQLSCALE     int;
 declare SQLNAME_DATA varchar(128);
 declare SQLTYPEF     varchar(128);
 declare col_val      varchar(4000);
 declare apo          varchar(1);
 declare l1 RESULT_set_LOCATOR VARYING;

 declare c2 cursor for s2;
 declare c_out cursor with return for s_out;

 declare EXIT HANDLER FOR SQLEXCEPTION
 BEGIN
   GET DIAGNOSTICS EXCEPTION 1 MSG = MESSAGE_TEXT;
   set RC = SQLCODE;
 END;

 set col_col=upper(col_col);
 set agg_col=upper(agg_col);
 -- insert result of select statement into temp table
 set stmt = 'describe '||sel_stmt;
 call SYSPROC.ADMIN_CMD(stmt);
 set stmt = '';
 associate result set locator (l1)
 with procedure SYSPROC.ADMIN_CMD;
 allocate c1 cursor for result set l1;
 --open c1;
 fetch c1 into SQLTYPE_ID, SQLTYPE, SQLLENGTH, SQLSCALE, SQLNAME_DATA;
 while (SQLCODE!=100) do
   set SQLTYPEF = SQLTYPE
   ||case 
      when SQLTYPE IN ('DECIMAL', 'DECFLOAT', 'CHARACTER', 'VARCHAR') then 
         '('||RTRIM(CHAR(SQLLENGTH))
       ||case when SQLTYPE='DECIMAL' then ','||RTRIM(CHAR(SQLSCALE)) else '' end
       ||')'                                
      else ''
     end;
   if (col_col=SQLNAME_DATA) then
     set apo = 
      case 
       when SQLTYPE in ('DECIMAL', 'DECFLOAT', 'INTEGER', 'SMALLINT', 'BIGINT', 'REAL', 'DOUBLE') then '' 
       else QUOT1 
      end;
   end if;
   set stmt = stmt||', '||SQLNAME_DATA||' '||SQLTYPEF;
   fetch c1 into SQLTYPE_ID, SQLTYPE, SQLLENGTH, SQLSCALE, SQLNAME_DATA;
 end while;
 close c1;
 set stmt = 
   'declare global temporary table '||tmp_tbl||'('||substr(stmt, 3)
 ||') with replace on commit preserve rows not logged';
 execute immediate stmt;
 set stmt = 'insert into '||tmp_tbl||' '||sel_stmt;
 execute immediate stmt;

 -- construct select statement
 set stmt = 'select distinct rtrim(char('||col_col||')) from '||tmp_tbl||' order by 1';
 prepare s2 from stmt;
 set stmt='';
 open c2;
 fetch c2 into col_val;
 while (SQLCODE!=100) do
   set stmt = 
     stmt||', '||agg_fn||'('
   ||'case when '||col_col||' '
   ||case when col_val is null then 'IS NULL' else ('='||apo||replace(col_val, QUOT1, QUOT1||QUOT1)||apo) end
   ||' then '||agg_col||' end) as '||QUOT2||coalesce(replace(col_val, QUOT2, QUOT2||QUOT2), null_ind)||QUOT2;
   fetch c2 into col_val;
 end while;
 close c2;
 -- add to the select statement groups
 set row_cols = nullif(row_cols, '');
 set stmt = 
   'select '||case when row_cols is not null then row_cols||',' else coalesce(row_cols, '') end
 ||substr(stmt, 2)||' from '||tmp_tbl||' '
 ||case when row_cols is not null then ('group by '||row_cols||' order by '||row_cols) else '' end;
 -- execute this statement
 prepare s_out from stmt;
 open c_out;
END@

参数说明:

PARM DESC
sel_stmt Any valid SELECT statement for source data generation
row_cols Comma separated list of column names used in the GROUP BY of final statement
col_col A column name to pivot
agg_col A column name to aggregate
agg_fn Any valid Db2 aggregation function for the column name in "agg_col" parameter
tmp_tbl DGTT name for intermediate result
null_ind Null indicator
rc Return code (OUT)
msg Message text (OUT)
stmt The final SELECT generated (OUT)

简要说明:
tmp_tbl 中传递的 DGTT 是用 sel_stmt 中传递的 SELECT 语句的结果创建和填充的。 row_colscol_colagg_col 中指定的所有列名必须在此语句的 SELECT 列表中。
此 DGTT 的最终 SELECT 语句是使用以下规则动态生成的:

  • 对于在 col_col 中传递的列名称中的每个不同值 Vx,将生成一个额外的列表达式,例如:, <agg_fn> (case when <col_col> = Vx then <agg_col> end) as "Vx", <agg_fn> (case when <col_col> IS NULL then <agg_col> end) as "<null_ind>" 用于 Vx IS NULL),其中 <parameter> 表示使用此参数传递的字符串值。
  • row_cols 列列表用于 GROUP BY,如果它不是 NULL。

用于您的情况:

DECLARE GLOBAL TEMPORARY TABLE SESSION.MYTAB 
(DATE_F, FILE_NM, ROW_ID, COLUMN_NM, COLUMN_VAL)
AS
(
VALUES
  ('20200131', 'ABC.TXT', 1, 'ABC_COL1', '123')
, ('20200131', 'ABC.TXT', 1, 'ABC_COL2', 'XYZ')
, ('20201231', 'ABC.TXT', 2, 'ABC_COL1', '456')
, ('20201231', 'ABC.TXT', 2, 'ABC_COL2', 'XY1')
, ('20200630', 'XYZ.TXT', 1, 'XYZ_COL1', 'PQR')
, ('20200630', 'XYZ.TXT', 1, 'XYZ_COL2', '567')
, ('20200630', 'XYZ.TXT', 1, 'XYZ_COL3', 'MNO')
) WITH DATA WITH REPLACE ON COMMIT PRESERVE ROWS NOT LOGGED;

第 1 次调用:

call pivot
(
  'select * from session.mytab where file_nm = ''ABC.TXT'''
, 'DATE_F, ROW_ID'
, 'COLUMN_NM'
, 'COLUMN_VAL'
, 'max'
, 'session.tab1'
, '-'
, ?, ?, ?
);
DATE_F ROW_ID ABC_COL1 ABC_COL2
20200131 1 123 XYZ
20201231 2 456 XY1

第二次调用:

call pivot
(
  'select * from session.mytab where file_nm = ''XYZ.TXT'''
, 'DATE_F, ROW_ID'
, 'COLUMN_NM'
, 'COLUMN_VAL'
, 'max'
, 'session.tab1'
, '-'
, ?, ?, ?
);
DATE_F ROW_ID XYZ_COL1 XYZ_COL2 XYZ_COL3
20200630 1 PQR 567 MNO