如何使用 DB2 sql 代码动态地将列转置为表的行,其中列可能会随着时间的推移而增加且无需更改代码?

Posted

技术标签:

【中文标题】如何使用 DB2 sql 代码动态地将列转置为表的行,其中列可能会随着时间的推移而增加且无需更改代码?【英文标题】:How to transpose columns to rows of a table dynamically using a DB2 sql code where columns may increase over time and no need to change the code? 【发布时间】:2021-06-14 15:46:32 【问题描述】:

我有一个表格,其中列最终会随着时间的推移而增加。我想编写一个查询来转置表,即使后来的列增加,也不需要添加额外的代码行来实现它。我需要转置值为'Y'的那些列

Eg: Source data on day 1
| Emp_ID | DOC 1 | DOC 2 |
| ------ |-------|-------|
| 001    | Y     |Y      |
| 002    | N     |Y      |

Day 1 output
| Emp_ID | Transposed | 
| ------ |-------|
| 001    | DOC 1 |
| 001    | DOC 2 |
| 002    | DOC 2 |

现在列最终可能会增加,并且希望在不更改代码的情况下使用相同的查询块来处理它,可以吗?

Source data on day 2
| Emp_ID | DOC 1 | DOC 2 | DOC 3|
| ------ |-------|-------|------|
| 001    | Y     |Y      |N     |
| 002    | N     |Y      |Y     |
| 003    | N     |N      |N     |

Day 2 output
| Emp_ID | Transposed | 
| ------ |-------|
| 001    | DOC 1 |
| 001    | DOC 2 |
| 002    | DOC 2 |
| 002    | DOC 3 |

**Note have considered only docs having Y as a value. Thanks in advance**

【问题讨论】:

您可以使用表函数或返回结果集的存储过程动态生成查询。这两种方法都将使用 syscat.columns 系统视图来获取给定表的列列表。你更喜欢哪种方法? 感谢@MarkBarinstein,我可能更喜欢表格函数。 但是你能帮忙吗,我正在努力构建逻辑。 你有一些命名规则来决定如何根据它们的名字来选择需要转置的列吗?例如,colname like 'DOC%'colname <> 'EMP_ID'。所有名称满足此条件的列都将被考虑转置。 @MarkBarinstein,不需要命名规则,唯一的要求是转置除 EMP_ID 之外的列,并将列名作为该 EMP_ID 的转置值,其中 DOC 为“Y”。 【参考方案1】:

您需要为给定的基表动态构造以下语句MYSCHEMA.MYTAB (EMP_ID INT, DOC1 CHAR, ..., DOCn CHAR):

SELECT T.EMP_ID, V.COLNAME
FROM 
  MYSCHEMA.MYTAB T
, (
SELECT COLNAME
FROM SYSCAT.COLUMNS
WHERE TABSCHEMA = 'MYSCHEMA' AND TABNAME = 'MYTAB' AND COLNAME <> 'EMP_ID' AND TYPENAME LIKE '%CHAR%'
  ) V
WHERE
   V.COLNAME = 'DOC1' AND T.DOC1 = 'Y'
...
OR V.COLNAME = 'DOCn' AND T.DOCn = 'Y'

语句只有动态WHERE 部分。 SYSCAT.COLUMNS 上的子选择返回要转置的所有表列(除EMP_ID 之外的所有表列)。 表函数中的以下SELECT INTO 语句生成任何数量的此类列所需的最终语句。

CREATE OR REPLACE FUNCTION MYFUNC ()
RETURNS TABLE (EMP_ID INT, TRANSPOSED VARCHAR (128))
BEGIN
  DECLARE V_SQL VARCHAR (4000);
  DECLARE V_EMP_ID INT;
  DECLARE V_TRANSPOSED VARCHAR (128);
  DECLARE SQLSTATE CHAR(5);
  DECLARE C1 CURSOR FOR S1;
  
    SELECT 
     'SELECT T.EMP_ID, V.COLNAME '
  || 'FROM ' 
  || '  MYSCHEMA.MYTAB T '
  || ', ( '
  || 'SELECT COLNAME '
  || 'FROM SYSCAT.COLUMNS '
  || 'WHERE TABSCHEMA = ''MYSCHEMA'' AND TABNAME = ''MYTAB'' AND COLNAME <> ''EMP_ID'' AND TYPENAME LIKE ''%CHAR%'''
  || '  ) V '
  || 'WHERE '
  || LISTAGG ('V.COLNAME = ''' || COLNAME || ''' AND T.' || COLNAME || ' = ''Y''', ' OR ')
  INTO V_SQL
  FROM SYSCAT.COLUMNS
  WHERE TABSCHEMA = 'MYSCHEMA' AND TABNAME = 'MYTAB' AND COLNAME <> 'EMP_ID' AND TYPENAME LIKE '%CHAR%';

  PREPARE S1 FROM V_SQL;
  OPEN C1;
  L1: LOOP 
    FETCH C1 INTO V_EMP_ID, V_TRANSPOSED;
    IF SQLSTATE = '02000' THEN LEAVE L1; END IF;
    PIPE (V_EMP_ID, V_TRANSPOSED);
  END LOOP L1;
  CLOSE C1;
  RETURN;
END@

【讨论】:

所以一旦创建了函数,我应该将表名添加到函数中以获取输出。比如select * from myfunc(mytable)?? 目前表名是硬编码在函数体中的。所以,你需要SELECT * FROM TABLE (MYFUNC ()) 我应该传递什么 LOOP 参数?在我的 DB2 中运行时的函数查询给出错误 没有任何参数需要传递到任何地方。如果您有一些错误,请使用以下信息编辑您的问题:确切的 CREATE TABLECREATE FUNCTION 语句、完整的错误代码和您收到的消息文本。

以上是关于如何使用 DB2 sql 代码动态地将列转置为表的行,其中列可能会随着时间的推移而增加且无需更改代码?的主要内容,如果未能解决你的问题,请参考以下文章

oracle sql 11G中如何将列转置为行

t-sql 将列转置为行

Postgres 表选择多列并将结果(列)动态转换为行 - 将列转置为行

使用交叉应用将列转置为行

oracle如何在没有UNION的情况下将列转置为行

使用 Spark 将列转置为行