逆透视数据,同时保留一行作为列
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
不可爱,但能完成任务。
我有以下数据,我正在尝试取消透视。我处理的列数一直到 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
不可爱,但能完成任务。