逆透视数据,同时保留一行作为列

Unpivot data while keeping one row as column

我有以下数据,我正在尝试取消透视。我处理的列数一直到 F600

基本上,SEQ_NUM 行数据变成名为 SEQ_NUM 的列,单元格仍然是一列,但没有 SEQ_NUM 行,新列 "NewCol" 将包含内容除 SEQ_NUM 行数据之外的数据,现在是一列。

我想要这个格式。我现在可以使用 UNION ALL 和游标循环遍历从 F2 到 F600 的所有列并交叉连接来自 SEQ_NUM 的数据,但我认为有更好的解决方案。

可以使用的数据版本如下:

create table #t (cells varchar(15), f2 int, f3 int, f4 int);

insert #t values 
    ('seq_num', 1, 2, 3), 
    ('linkA', 4290, 42521, 42551),
    ('linkB', 0, 0, 0),
    ('linkC', 1332, 0, 15);

正如 avery_larry 所提到的,这似乎是一个基本的逆轴。我认为您可能会被抛弃的地方是 'seq_num' 与列名是多余的。 'f2' 至 'f600' 列已按顺序排列。所以只是逆透视,从逆透视列标签中提取整数,然后减去一个。在执行所有这些操作时,请忽略 'seq_num' 行。

select      up.cells,
            seq_num = try_convert(int,replace(up.col, 'f', '')) - 1,
            up.val
from        #t
unpivot     (val for col in (f2, f3, f4)) up -- you write out to 'f600'
where       cells <> 'seq_num';

如果您想避免将 'f2' 写成 'f600',您可以使用动态方式。首先构建包含所有列名的字符串,然后将其插入到 'in' 语句中。

declare 
    @cols varchar(max) = '',
    @f int = 1,
    @maxF int = 4; -- you change to 600

while @f < @maxF begin
    set @f += 1;
    set @cols += 'f' + convert(varchar(3), @f) + iif(@f <> @maxF, ',', '');
end

declare @sql nvarchar(max) = '
    select      up.cells,
                seq_num = try_convert(int,replace(up.col, ''f'', '''')) - 1,
                up.val
    from        #t
    unpivot     (val for col in (' + @cols + ')) up
    where       cells <> ''seq_num''
';

 exec (@sql);


编辑:说明 'seq_num' 不是字段 headers

的简单偏移

好的,所以你说 seq_num 不仅仅是列名的偏移量。想象一下,我们用以下内容替换了 table 插入中的 seq_num 行:

insert #t values 
    ('seq_num', 2, 4, 8), 
    ...

您必须将它与其余行完全分开并独立对待。但幸运的是,你以平行的方式对它采取行动。也就是说,就像我上面的查询反透视 cells <> 'seq_num' 的数据一样,除了 cells = 'seq_num' 之外,您做的完全相同。您需要将两个结果集重新连接在一起,以便在每个结果集中输出未旋转的字段标签。

将上面的@sql 变量替换为:

declare @sql nvarchar(max) = '
    select      up.cells,
                up.col,
                up.val
    from        #t
    unpivot     (val for col in (' + @cols + ')) up
    where       cells @operator ''seq_num''
';

请注意,我输出的是up.col,而不是修改它,我还将'<>'替换为'@operator'。与 @cols 不同,'@operator' 直接是周围字符串的一部分。

现在,@sql 正在充当模板。使用它为等于 'seq_num' 的单元格创建一个实例,为不等于 'seq_num' 的单元格创建一个实例。这些实例应该在它们自己的 CTE 表达式中,您随后在最终语句中 re-join。用这条语句覆盖已有的@sql变量,然后执行,如下图:

set @sql = '

    with

        linkVals as (' + replace(@sql, '@operator', '<>') + '),
        seqVals as (' + replace(@sql, '@operator', '=') + ')

        select      l.cells, 
                    seq_num = s.val, 
                    l.val
        from        linkVals l
        join        seqVals s on l.col = s.col

';

print (@sql);
exec (@sql);

我添加了一个打印语句以防 @sql 的最终形式不清楚,尽管您可能需要重新格式化它。

最坏的情况你可以做很长的路要走:

select b.f2 as newcol, b.cells, a.f2 as seq_num from t a cross join t b where a.cells = 'SEQ_Num' and b.cells <> 'SEQ_Num'
union select b.f3, b.cells, a.f3 from t a cross join t b where a.cells = 'SEQ_Num' and b.cells <> 'SEQ_Num'
union select b.f4, b.cells, a.f4 from t a cross join t b where a.cells = 'SEQ_Num' and b.cells <> 'SEQ_Num'
union select b.f5, b.cells, a.f5 from t a cross join t b where a.cells = 'SEQ_Num' and b.cells <> 'SEQ_Num'
union select b.f6, b.cells, a.f6 from t a cross join t b where a.cells = 'SEQ_Num' and b.cells <> 'SEQ_Num'
union select b.f7, b.cells, a.f7 from t a cross join t b where a.cells = 'SEQ_Num' and b.cells <> 'SEQ_Num'
union select b.f8, b.cells, a.f8 from t a cross join t b where a.cells = 'SEQ_Num' and b.cells <> 'SEQ_Num'
union select b.f9, b.cells, a.f9 from t a cross join t b where a.cells = 'SEQ_Num' and b.cells <> 'SEQ_Num'
union select b.f10, b.cells, a.f10 from t a cross join t b where a.cells = 'SEQ_Num' and b.cells <> 'SEQ_Num'
union select b.f18, b.cells, a.f18 from t a cross join t b where a.cells = 'SEQ_Num' and b.cells <> 'SEQ_Num'
order by seq_num, cells

不可爱,但能完成任务。