将 1 行表展平为键值对表
Posted
技术标签:
【中文标题】将 1 行表展平为键值对表【英文标题】:Flattening of a 1 row table into a key-value pair table 【发布时间】:2011-11-12 13:23:57 【问题描述】:获取表示一行中的列值的键值对结果集的最佳方法是什么?
鉴于下表 A 只有 1 行
Column1 Column2 Column3 ...
Value1 Value2 Value3
我想查询它并插入另一个表B:
Key Value
Column1 Value1
Column2 Value2
Column3 Value3
事先不知道表 A 中的一组列。
注意:我正在查看 FOR XML 和 PIVOT 功能以及动态 SQL 来执行以下操作:
DECLARE @sql nvarchar(max)
SET @sql = (SELECT STUFF((SELECT ',' + column_name
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name='TableA'
ORDER BY column_name FOR XML PATH('')), 1, 1, ''))
SET @sql = 'SELECT ' + @sql + ' FROM TableA'
EXEC(@sql)
【问题讨论】:
这是UNPIVOT
不是 PIVOT
,但您仍然需要动态 SQL。
马丁的评论+1。由于您不知道未透视的列,因此您需要动态 SQL。
@Aaron - 只是一个错字,现已修正
【参考方案1】:
不涉及动态的版本。如果您有无效的列名用作 XML 中的元素名,这将失败。
select T2.N.value('local-name(.)', 'nvarchar(128)') as [Key],
T2.N.value('text()[1]', 'nvarchar(max)') as Value
from (select *
from TableA
for xml path(''), type) as T1(X)
cross apply T1.X.nodes('/*') as T2(N)
一个工作样本:
declare @T table
(
Column1 varchar(10),
Column2 varchar(10),
Column3 varchar(10)
)
insert into @T values('V1','V2','V3')
select T2.N.value('local-name(.)', 'nvarchar(128)') as [Key],
T2.N.value('text()[1]', 'nvarchar(max)') as Value
from (select *
from @T
for xml path(''), type) as T1(X)
cross apply T1.X.nodes('/*') as T2(N)
结果:
Key Value
-------------------- -----
Column1 V1
Column2 V2
Column3 V3
更新
对于包含多个表的查询,您可以使用for xml auto
来获取 XML 中的表名。请注意,如果您在查询中使用别名作为表名,您将获得别名。
select X2.N.value('local-name(..)', 'nvarchar(128)') as TableName,
X2.N.value('local-name(.)', 'nvarchar(128)') as [Key],
X2.N.value('text()[1]', 'nvarchar(max)') as Value
from (
-- Your query starts here
select T1.T1ID,
T1.T1Col,
T2.T2ID,
T2.T2Col
from T1
inner join T2
on T1.T1ID = T2.T1ID
-- Your query ends here
for xml auto, elements, type
) as X1(X)
cross apply X1.X.nodes('//*[text()]') as X2(N)
SQL Fiddle
【讨论】:
不得不承认,这是一个有趣的方法。 我刚刚为FOR XML PATH(''), ELEMENTS XSINIL, TYPE) AS T1(X)
添加了 ELEMENTS XSINIL
以包含具有 NULL 的列。谢谢
我的数据集连接了多个具有相同列名的表。有没有一种简单的方法来获取包含表名的列名(无需手动为所有内容命名)?【参考方案2】:
我想你已经成功了一半。只需按照 Martin 的建议使用 UNPIVOT
和 dynamic SQL
:
CREATE TABLE TableA (
Code VARCHAR(10),
Name VARCHAR(10),
Details VARCHAR(10)
)
INSERT TableA VALUES ('Foo', 'Bar', 'Baz')
GO
DECLARE @sql nvarchar(max)
SET @sql = (SELECT STUFF((SELECT ',' + column_name
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name='TableA'
ORDER BY ordinal_position FOR XML PATH('')), 1, 1, ''))
SET @sql = N'SELECT [Key], Val FROM (SELECT ' + @sql + ' FROM TableA) x '
+ 'UNPIVOT ( Val FOR [Key] IN (' + @sql + ')) AS unpiv'
EXEC (@sql)
结果:
Key Val
------------ ------------
Code Foo
Name Bar
Details Baz
当然,有一个警告。您的所有列都需要具有相同的数据类型才能使上述代码正常工作。如果不是,您将收到此错误:
Msg 8167, Level 16, State 1, Line 1
The type of column "Col" conflicts with the type of
other columns specified in the UNPIVOT list.
为了解决这个问题,您需要创建两个列字符串语句。一个获取列,一个将它们全部转换为 Val 列的数据类型。
对于多种列类型:
CREATE TABLE TableA (
Code INT,
Name VARCHAR(10),
Details VARCHAR(10)
)
INSERT TableA VALUES (1, 'Foo', 'Baf')
GO
DECLARE
@sql nvarchar(max),
@cols nvarchar(max),
@conv nvarchar(max)
SET @cols = (SELECT STUFF((SELECT ',' + column_name
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name='TableA'
ORDER BY ordinal_position FOR XML PATH('')), 1, 1, ''))
SET @conv = (SELECT STUFF((SELECT ', CONVERT(VARCHAR(50), '
+ column_name + ') AS ' + column_name
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name='TableA'
ORDER BY ordinal_position FOR XML PATH('')), 1, 1, ''))
SET @sql = N'SELECT [Key], Val FROM (SELECT ' + @conv + ' FROM TableA) x '
+ 'UNPIVOT ( Val FOR [Key] IN (' + @cols + ')) AS unpiv'
EXEC (@sql)
【讨论】:
【参考方案3】:也许你让这变得比它需要的更复杂。部分原因是我无法用我的小脑袋来处理PIVOT
/UNPIVOT
/whatever 组合的数量,并且需要动态 SQL“红海”来实现这一目标。由于您知道该表只有一行,因此提取每一列的值可以只是作为一组 UNION
ed 查询的一部分的子查询。
DECLARE @sql NVARCHAR(MAX) = N'INSERT dbo.B([Key], Value) '
SELECT @sql += CHAR(13) + CHAR(10)
+ ' SELECT [Key] = ''' + REPLACE(name, '''', '''''') + ''',
Value = (SELECT ' + QUOTENAME(name) + ' FROM dbo.A) UNION ALL'
FROM sys.columns
WHERE [object_id] = OBJECT_ID('dbo.A');
SET @sql = LEFT(@sql, LEN(@sql)-9) + ';';
PRINT @sql;
-- EXEC sp_executesql @sql;
结果(我只创建了 4 列,但这适用于任何数字):
INSERT dbo.B([Key], Value)
SELECT [Key] = 'Column1',
Value = (SELECT [Column1] FROM dbo.A) UNION ALL
SELECT [Key] = 'Column2',
Value = (SELECT [Column2] FROM dbo.A) UNION ALL
SELECT [Key] = 'Column3',
Value = (SELECT [Column3] FROM dbo.A) UNION ALL
SELECT [Key] = 'Column4',
Value = (SELECT [Column4] FROM dbo.A);
世界上最高效的东西?可能不会。但同样,对于单排表,希望是一次性任务,我认为它会工作得很好。如果您允许在您的商店中使用这些东西,请注意包含撇号的列名...
编辑抱歉,不能这样。现在它将处理列名中的撇号和其他次优命名选择。
【讨论】:
以上是关于将 1 行表展平为键值对表的主要内容,如果未能解决你的问题,请参考以下文章
将 json 字典列转换为键值对行(Redshift+Postgresql)