提问人:Sergei 提问时间:8/3/2013 最后编辑:Roman PekarSergei 更新时间:2/26/2021 访问量:436768
SQL Server:列到行
SQL Server : Columns to Rows
问:
寻找优雅(或任何)的解决方案来将列转换为行。
下面是一个示例:我有一个具有以下架构的表:
[ID] [EntityID] [Indicator1] [Indicator2] [Indicator3] ... [Indicator150]
这是我想要得到的结果:
[ID] [EntityId] [IndicatorName] [IndicatorValue]
结果值为:
1 1 'Indicator1' 'Value of Indicator 1 for entity 1'
2 1 'Indicator2' 'Value of Indicator 2 for entity 1'
3 1 'Indicator3' 'Value of Indicator 3 for entity 1'
4 2 'Indicator1' 'Value of Indicator 1 for entity 2'
等等..
这有意义吗?对于在 T-SQL 中查找位置以及如何完成它,是否有任何建议?
答:
可以使用 UNPIVOT 函数将列转换为行:
select id, entityId,
indicatorname,
indicatorvalue
from yourtable
unpivot
(
indicatorvalue
for indicatorname in (Indicator1, Indicator2, Indicator3)
) unpiv;
请注意,要取消透视的列的数据类型必须相同,因此您可能必须在应用取消透视之前转换数据类型。
您还可以与 UNION ALL 一起使用来转换列:CROSS APPLY
select id, entityid,
indicatorname,
indicatorvalue
from yourtable
cross apply
(
select 'Indicator1', Indicator1 union all
select 'Indicator2', Indicator2 union all
select 'Indicator3', Indicator3 union all
select 'Indicator4', Indicator4
) c (indicatorname, indicatorvalue);
根据您的 SQL Server 版本,您甚至可以将 CROSS APPLY 与 VALUES 子句一起使用:
select id, entityid,
indicatorname,
indicatorvalue
from yourtable
cross apply
(
values
('Indicator1', Indicator1),
('Indicator2', Indicator2),
('Indicator3', Indicator3),
('Indicator4', Indicator4)
) c (indicatorname, indicatorvalue);
最后,如果您有 150 列要取消透视,并且不想对整个查询进行硬编码,则可以使用动态 SQL 生成 sql 语句:
DECLARE @colsUnpivot AS NVARCHAR(MAX),
@query AS NVARCHAR(MAX)
select @colsUnpivot
= stuff((select ','+quotename(C.column_name)
from information_schema.columns as C
where C.table_name = 'yourtable' and
C.column_name like 'Indicator%'
for xml path('')), 1, 1, '')
set @query
= 'select id, entityId,
indicatorname,
indicatorvalue
from yourtable
unpivot
(
indicatorvalue
for indicatorname in ('+ @colsunpivot +')
) u'
exec sp_executesql @query;
评论
好吧,如果您有 150 列,那么我认为 UNPIVOT 不是一个选项。所以你可以使用xml技巧
;with CTE1 as (
select ID, EntityID, (select t.* for xml raw('row'), type) as Data
from temp1 as t
), CTE2 as (
select
C.id, C.EntityID,
F.C.value('local-name(.)', 'nvarchar(128)') as IndicatorName,
F.C.value('.', 'nvarchar(max)') as IndicatorValue
from CTE1 as c
outer apply c.Data.nodes('row/@*') as F(C)
)
select * from CTE2 where IndicatorName like 'Indicator%'
您也可以编写动态 SQL,但我更喜欢 xml - 对于动态 SQL,您必须具有直接从表中选择数据的权限,这并不总是一个选项。
更新
:由于评论中有很大的火焰,我想我会添加一些xml/dynamic SQL的优点和缺点。我会尽量客观,不提优雅和丑陋。如果您有任何其他优点和缺点,请编辑答案或写下评论
缺点
- 它不如动态 SQL 快,粗略的测试告诉我,XML 比动态慢大约 2.5 倍(它是对 ~250000 行表的一次查询,所以这个估计并不准确)。如果你愿意,你可以自己比较,这里是 sqlfiddle 示例,在 100000 行上它是 29 秒 (xml) 与 14 秒(动态);
- 对于不熟悉 XPath 的人来说,可能更难理解;
优点
- 它与您的其他查询的范围相同,这可能非常方便。我想到了几个例子
- 您可以在触发器中查询和表(动态根本不可能);
inserted
deleted
- 用户不必具有“从表中直接选择”的权限。我的意思是,如果你有存储过程层,并且用户有运行 sp 的权限,但没有直接查询表的权限,你仍然可以在存储过程中使用此查询;
- 您可以查询已在作用域中填充的表变量(要将其传递到动态 SQL 中,您必须将其设置为临时表,或者创建类型并将其作为参数传递到动态 SQL;
- 您可以在触发器中查询和表(动态根本不可能);
- 您可以在函数(标量或表值)内执行此查询。不能在函数中使用动态 SQL;
评论
DECLARE @TableName varchar(max)=NULL
SELECT @TableName=COALESCE(@TableName+',','')+t.TABLE_CATALOG+'.'+ t.TABLE_SCHEMA+'.'+o.Name
FROM sysindexes AS i
INNER JOIN sysobjects AS o ON i.id = o.id
INNER JOIN INFORMATION_SCHEMA.TABLES T ON T.TABLE_NAME=o.name
WHERE i.indid < 2
AND OBJECTPROPERTY(o.id,'IsMSShipped') = 0
AND i.rowcnt >350
AND o.xtype !='TF'
ORDER BY o.name ASC
print @tablename
您可以获取行数为 >350 的表列表。您可以在表的解决方案列表中看到行。
我需要一个解决方案来将列转换为 Microsoft SQL Server 中的行,而无需知道列名称(在触发器中使用)并且没有动态 sql(动态 sql 太慢而无法在触发器中使用)。
我终于找到了这个解决方案,效果很好:
SELECT
insRowTbl.PK,
insRowTbl.Username,
attr.insRow.value('local-name(.)', 'nvarchar(128)') as FieldName,
attr.insRow.value('.', 'nvarchar(max)') as FieldValue
FROM ( Select
i.ID as PK,
i.LastModifiedBy as Username,
convert(xml, (select i.* for xml raw)) as insRowCol
FROM inserted as i
) as insRowTbl
CROSS APPLY insRowTbl.insRowCol.nodes('/row/@*') as attr(insRow)
如您所见,我将该行转换为 XML(Subquery select i,* for xml raw,这会将所有列转换为一个 xml 列)
然后,我将函数交叉应用于此列的每个 XML 属性,以便每个属性获得一行。
总体而言,这会将列转换为行,而无需知道列名,也无需使用动态 sql。对于我的目的来说,它足够快。
(编辑:我刚刚看到上面的罗曼·佩卡尔(Roman Pekar)回答,他也在做同样的事情。 我首先使用带有游标的动态 sql 触发器,它比这个解决方案慢 10 到 100 倍,但可能是由游标引起的,而不是由动态 sql 引起的。无论如何,这个解决方案非常简单,是一个通用的,所以它绝对是一个选择)。
我把这个评论留在这个地方,因为我想在我的帖子中引用这个关于完整审计触发器的解释,你可以在这里找到: https://stackoverflow.com/a/43800286/4160788
为了帮助新读者,我创建了一个示例来更好地理解@bluefeet对 UNPIVOT 的回答。
SELECT id
,entityId
,indicatorname
,indicatorvalue
FROM (VALUES
(1, 1, 'Value of Indicator 1 for entity 1', 'Value of Indicator 2 for entity 1', 'Value of Indicator 3 for entity 1'),
(2, 1, 'Value of Indicator 1 for entity 2', 'Value of Indicator 2 for entity 2', 'Value of Indicator 3 for entity 2'),
(3, 1, 'Value of Indicator 1 for entity 3', 'Value of Indicator 2 for entity 3', 'Value of Indicator 3 for entity 3'),
(4, 2, 'Value of Indicator 1 for entity 4', 'Value of Indicator 2 for entity 4', 'Value of Indicator 3 for entity 4')
) AS Category(ID, EntityId, Indicator1, Indicator2, Indicator3)
UNPIVOT
(
indicatorvalue
FOR indicatorname IN (Indicator1, Indicator2, Indicator3)
) UNPIV;
评论
只是因为我没有看到它被提及。
如果是 2016+,这里是另一种选择,可以在不实际使用动态 SQL 的情况下动态取消透视数据。
例
Declare @YourTable Table ([ID] varchar(50),[Col1] varchar(50),[Col2] varchar(50))
Insert Into @YourTable Values
(1,'A','B')
,(2,'R','C')
,(3,'X','D')
Select A.[ID]
,Item = B.[Key]
,Value = B.[Value]
From @YourTable A
Cross Apply ( Select *
From OpenJson((Select A.* For JSON Path,Without_Array_Wrapper ))
Where [Key] not in ('ID','Other','Columns','ToExclude')
) B
返回
ID Item Value
1 Col1 A
1 Col2 B
2 Col1 R
2 Col2 C
3 Col1 X
3 Col2 D
评论
Where [Key] like 'Indicator%'
与此相反的是将列展平为 csv,例如
SELECT STRING_AGG ([value],',') FROM STRING_SPLIT('Akio,Hiraku,Kazuo', ',')
评论