SQL Server:列到行
寻找优雅的(或任何)解决方案将列转换为行。
这里是一个例子:我有一个表格,具有以下模式:
[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;
请注意,您不透明的列的数据类型必须相同,因此您可能必须在应用unpivot之前转换数据类型。
您也可以使用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列到untivot,你不想硬编码整个查询,那么你可以使用动态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, FCvalue('local-name(.)', 'nvarchar(128)') as IndicatorName, FCvalue('.', '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小提琴演示
您也可以编写动态SQL,但我更喜欢xml – 对于动态SQL,您必须具有从表中直接选择数据的权限,但这并不总是一个选项。
UPDATE
因为在评论中有一个大的火焰,我想我会添加一些优点和缺点的XML /动态SQL。 我会尽量客观,不要提高雅和丑陋。 如果你有任何其他的利弊,编辑答案或写评论
缺点
- 它不如动态SQL 快 ,粗略的测试给了我xml大约是动态的2.5倍(这是一个约250000行表上的查询,所以这个估计是不正确的)。 你可以自己比较一下,如果你想,这是sqlfiddle的例子,在100000行是29s(xml)vs 14s(dynamic);
- 对于不熟悉xpath的人来说可能会更难理解 ;
利弊
- 这与其他查询的范围相同 ,这可能非常方便。 想到几个例子
- 你可以在你的触发器中查询
inserted
和deleted
表格(根本不可能); - 用户不必具有直接从表中选择的权限 。 我的意思是如果你有存储过程层和用户有权限运行SP,但没有权限直接查询表,你仍然可以在存储过程中使用此查询;
- 你可以查询你已经在你的作用域中填充的表变量 (为了在动态SQL中传递它,你必须使它成为临时表,或者创建类型并将其作为参数传递给动态SQL;
- 你可以在你的触发器中查询
- 你可以在函数内部进行这个查询 (标量或表值)。 在函数内部使用动态SQL是不可能的;
DECLARE @TableName nvarchar(50) DECLARE column_to_row CURSOR FOR --List of tables that we want to unpivot columns as row SELECT DISTINCT t.name FROM sys.tables t JOIN sys.schemas s ON t.schema_id=t.schema_id WHERE t.name like '%_CT%' AND s.name='cdc' OPEN column_to_row FETCH NEXT FROM column_to_row INTO @TableName WHILE @@FETCH_STATUS = 0 BEGIN DECLARE @script nvarchar(max) = null DECLARE @columns nvarchar(2000) = null -- keep the table's column list select @columns = COALESCE(@columns + ',','') + c.name from sys.tables t join sys.columns c on t.object_id = c.object_id where t.name = @TableName set @script = 'SELECT '+@columns+' FROM [cdc].['+@TableName+'] (nolock)' --print (@script) exec (@script) FETCH NEXT FROM column_to_row INTO @TableName END CLOSE column_to_row DEALLOCATE column_to_row
这里是列到行的另一种方法,你有多少表和多少列是不重要的。 只需设置参数,并获得结果。 我写这个,因为有时我需要一个表A(这是列结果集)的结果,作为另一个表B(它必须是行字段)的字段。 在这种情况下,我不知道我为表B设置了多少个字段
为了帮助新读者,我创建了一个例子来更好地理解@ 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;
我需要一个解决方案,将列转换为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(子查询select i,* for xml raw,这将所有列转换为一个xml列)
然后,我对这个列的每个XML属性交叉应用一个函数,以便每个属性获得一行。
总的来说,这将列转换为行,而不知道列名,也不使用动态SQL。 这对我的目的来说足够快。
(编辑:我刚刚在上面看到了Roman Pekar的答案,谁也是这样做的,我首先使用了带有游标的动态sql触发器,比这个解决方案慢了10到100倍,但也许是由光标引起的,而不是动态sql。无论如何,这个解决方案是非常简单的一个通用的,所以它明确的一个选项)。
我在这个地方留下了这个评论,因为我想在我的文章中引用这个解释关于完整的审计触发器,你可以在这里找到: https : //stackoverflow.com/a/43800286/4160788