对具有不同类型的列使用动态反透视

Posted

技术标签:

【中文标题】对具有不同类型的列使用动态反透视【英文标题】:Using dynamic unpivot with columns with different types 【发布时间】:2018-08-27 08:14:22 【问题描述】:

我有一个包含大约 100 列的表,名为 F1、F2、... F100。 我想逐行查询数据,如下所示:

F1: someVal1
F2: someVal2
...

我在 SP 中执行所有这些操作,因此,我正在动态生成 sql。 我已经成功生成了如下sql:

select CAST(valname as nvarchar(max)), CAST(valvalue as nvarchar(max)) from tbl_name unpivot
(
    valvalue for valname in ([form_id], [F1],[F2],[F3],[F4],[F5],[F6],[F7],[F8],[F9],[F10],[F11],[F12],[F13],[F14],[F15],[F16],[F17],[F18],[F19],[F20],[F21],[F22],[F23],[F24],[F25],[F26],[F27],[F28],[F29],[F30],[F31],[F32],[F33],[F34],[F35],[F36],[F37],[F38],[F39],[F40],[F41],[F42],[F43],[F44],[F45],[F46],[F47],[F48],[F49],[F50],[F51],[F52],[F53],[F54],[F55],[F56],[F57],[F58],[F59],[F60],[F61],[F62],[F63],[F64],[F65],[F66],[F67],[F68],[F69],[F70],[F71],[F72],[F73],[F74],[F75],[F76],[F77],[F78],[F79],[F80],[F81],[F82],[F83],[F84],[F85])
) u

但是在执行这个查询时,我得到了这个异常:

“F3”列的类型与其他列的类型冲突 在 UNPIVOT 列表中指定。

我猜这是因为 F3 是 varchar(100) 而 form_id、F1 和 F2 是 varchar(50)。根据我的理解,我不应该收到此错误,因为我在 select 语句中将所有结果转换为 nvarchar(max)。

这个表有各种列,如 datetime、smallint 和 int。 此外,该表中除一列外的所有列都有SQL_Latin1_General_CP1_CI_AScollaltion

这个错误的修复方法是什么?

【问题讨论】:

【参考方案1】:

这个解决方案是你必须使用子查询让所有列的类型相同才能具有相同的长度。

尝试CAST 子查询中的值然后unpivot 而不是select

select valname, valvalue 
from (
    SELECT  
        CAST([form_id] as nvarchar(max)) form_id, 
        CAST([F1] as nvarchar(max)) F1,
        CAST([F2] as nvarchar(max)) F2,
        CAST([F3] as nvarchar(max)) F3,
        CAST([F4] as nvarchar(max)) F4,
        ....
    FROM tbl_name 
) t1 unpivot
(
    valvalue for valname in ([form_id], [F1],[F2],[F3],[F4],[F5],[F6],[F7],[F8],[F9],[F10],[F11],[F12],[F13],[F14],[F15],[F16],[F17],[F18],[F19],[F20],[F21],[F22],[F23],[F24],[F25],[F26],[F27],[F28],[F29],[F30],[F31],[F32],[F33],[F34],[F35],[F36],[F37],[F38],[F39],[F40],[F41],[F42],[F43],[F44],[F45],[F46],[F47],[F48],[F49],[F50],[F51],[F52],[F53],[F54],[F55],[F56],[F57],[F58],[F59],[F60],[F61],[F62],[F63],[F64],[F65],[F66],[F67],[F68],[F69],[F70],[F71],[F72],[F73],[F74],[F75],[F76],[F77],[F78],[F79],[F80],[F81],[F82],[F83],[F84],[F85])
) u

以最简单的方式,我将使用CROSS APPLYVALUES 来执行unpivot

SELECT * 
    FROM People CROSS APPLY (VALUES 
        (CAST([form_id] as nvarchar(max))),
        (CAST([F1] as nvarchar(max))),
        (CAST([F2] as nvarchar(max))),
        (CAST([F3] as nvarchar(max))),
        (CAST([F4] as nvarchar(max))),
        ....
    ) v (valvalue)

这里有一个关于CROSS APPLYVALUES 的示例来做unpivot

我们可以看到People表中有很多不同的类型。

我们可以尝试使用castvarchar(max),让列的类型相同。

CREATE TABLE People
(
  IntVal int, 
  StringVal varchar(50), 
  DateVal date
)

INSERT INTO People VALUES (1, 'Jim', '2017-01-01');
INSERT INTO People VALUES (2, 'Jane', '2017-01-02');
INSERT INTO People VALUES (3, 'Bob', '2017-01-03');

查询 1

SELECT * 
FROM People CROSS APPLY (VALUES 
    (CAST(IntVal AS VARCHAR(MAX))),
    (CAST(StringVal AS VARCHAR(MAX))),
    (CAST(DateVal AS VARCHAR(MAX)))
) v (valvalue)

Results

| IntVal | StringVal |    DateVal |   valvalue |
|--------|-----------|------------|------------|
|      1 |       Jim | 2017-01-01 |          1 |
|      1 |       Jim | 2017-01-01 |        Jim |
|      1 |       Jim | 2017-01-01 | 2017-01-01 |
|      2 |      Jane | 2017-01-02 |          2 |
|      2 |      Jane | 2017-01-02 |       Jane |
|      2 |      Jane | 2017-01-02 | 2017-01-02 |
|      3 |       Bob | 2017-01-03 |          3 |
|      3 |       Bob | 2017-01-03 |        Bob |
|      3 |       Bob | 2017-01-03 | 2017-01-03 |

注意

当您使用unpivot 时需要确保unpivot 列的日期类型相同。

【讨论】:

【参考方案2】:

猫可以通过多种方式剥皮,反之亦然。 开个玩笑,D-Shih 建议的是你应该从什么开始,可能会让你回家干。

在大多数情况下; 本质上,UNPIVOT 操作是连接多行的数据。从 CAST 操作开始是最好的方法,因为它使数据类型相同(最好是字符串类型,如 varchar 或 nvarchar),除了具有相同类型之外,对所有 UNPIVOTED 列使用相同长度也是一个好主意.

在其他情况下; 如果这仍然不能解决问题,那么您需要更深入地查看并检查表上所有列的 ANSI_Padding 设置是 ON 还是 OFF。在 SQL Server 的后期版本中,默认情况下这主要是 ON,但一些开发人员可能会自定义某些列以将 ANSI_PADDING 设置为 off。如果您有这样的混合设置,最好将数据移动到另一个表,并将 ANSI_PADDING 设置为 ON。尝试在该表上使用相同的 UNPIVOT 查询,它应该可以工作。 检查 ANSI_Padding 状态

    SELECT name
    ,CASE is_ansi_padded 
    WHEN 1 THEN 'ANSI_Padding_On' 
    ELSE  'ANSI_Padding_Off'
    AS [ANSI_Padding_Check]
                      FROM sys.all_columns
    WHERE object_id = object_id('yourschema.yourtable')

许多情况更适合 CROSS APPLY VALUES。这一切都取决于您,骑师为课程选择马匹。

干杯。

【讨论】:

以上是关于对具有不同类型的列使用动态反透视的主要内容,如果未能解决你的问题,请参考以下文章

是否有可能在Oracle中有条件地透视不同数据类型的列?

oracle sql中的动态数据透视

oracle sql中的动态数据透视

将数据解析/反透视到熊猫数据框中的列中

如何使用不同的列作为参考制作数据透视表?

使用数据透视表(熊猫)中的小计行时保留索引部分(不同的列)