Sql 将一行表展平为键值对表
获取表示行中列值的键值对结果集的最佳方法是什么 给定下表A,其中只有一行Sql 将一行表展平为键值对表,sql,tsql,sql-server-2008,pivot,Sql,Tsql,Sql Server 2008,Pivot,获取表示行中列值的键值对结果集的最佳方法是什么 给定下表A,其中只有一行 Column1 Column2 Column3 ... Value1 Value2 Value3 也许你让事情变得比实际需要的更复杂了。部分原因是我无法将我的小脑袋围绕在PIVOT/UNPIVOT/的数量上,任何组合和动态SQL“红色海洋”都是实现这一点所必需的。因为您知道该表只有一行,所以提取每列的值可以只是一个子查询,作为一组UNIONed查询的一部分 DECLARE @sql NVARCHAR(MAX) =
Column1 Column2 Column3 ...
Value1 Value2 Value3
也许你让事情变得比实际需要的更复杂了。部分原因是我无法将我的小脑袋围绕在
PIVOT
/UNPIVOT
/的数量上,任何组合和动态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列,但这适用于任何数量的列):
世界上最有效率的事情?可能不会。但是,对于一行表,并且希望是一次性任务,我认为它可以很好地工作。只要留意包含撇号的列名,如果你允许这些东西出现在你的商店里
编辑很抱歉,不能这样处理。现在,它将处理列名中的撇号和其他次优命名选择。我认为您已经完成了一半。只需按照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
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)
当然,这里有一个警告。要使上述代码正常工作,所有列都必须是相同的数据类型。如果不是,则会出现以下错误:
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列的数据类型
对于多个列类型:
Key Val
------------ ------------
Code Foo
Name Bar
Details Baz
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)
不涉及动态的版本。如果列名在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
更新
对于具有多个表的查询,可以使用forxml 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)
这是一个
UNPIVOT
而不是PIVOT
,但是您仍然需要动态SQL.+1作为Martin的注释。由于您不知道哪些列是未填充的,所以需要动态SQL。@Aaron-只是一个打字错误,现在必须承认,这是一个有趣的方法。我刚刚添加了元素xsini
forforfor XML PATH(“”),元素xsini,TYPE)作为T1(X)
以包含带有NULL的列。thanksMy dataset使用相同的列名联接多个表。是否有一种简单的方法可以获取包含表名的列名(而不必手动为所有内容添加别名)?
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)