Answers:
您可以通过多种方式转换此数据。在您的原始文章中,您说过PIVOT
这种情况似乎太复杂了,但是可以使用SQL Server中的UNPIVOT
和PIVOT
函数很容易地应用它。
但是,如果您无权访问这些功能,则可以使用UNION ALL
来复制UNPIVOT
该功能,然后使用以下CASE
语句将其复制到以下集合功能PIVOT
:
创建表:
CREATE TABLE yourTable([color] varchar(5), [Paul] int, [John] int, [Tim] int, [Eric] int);
INSERT INTO yourTable
([color], [Paul], [John], [Tim], [Eric])
VALUES
('Red', 1, 5, 1, 3),
('Green', 8, 4, 3, 5),
('Blue', 2, 2, 9, 1);
合并所有,汇总和CASE版本:
select name,
sum(case when color = 'Red' then value else 0 end) Red,
sum(case when color = 'Green' then value else 0 end) Green,
sum(case when color = 'Blue' then value else 0 end) Blue
from
(
select color, Paul value, 'Paul' name
from yourTable
union all
select color, John value, 'John' name
from yourTable
union all
select color, Tim value, 'Tim' name
from yourTable
union all
select color, Eric value, 'Eric' name
from yourTable
) src
group by name
的UNION ALL
执行与UNPIVOT
通过变换列中的数据Paul, John, Tim, Eric
进行分离。然后,将聚合函数sum()
与该case
语句一起使用,以获取每个函数的新列color
。
Unpivot和Pivot静态版本:
SQL Server中的UNPIVOT
和PIVOT
函数都使此转换更加容易。如果知道要转换的所有值,则可以将它们硬编码为静态版本以得到结果:
select name, [Red], [Green], [Blue]
from
(
select color, name, value
from yourtable
unpivot
(
value for name in (Paul, John, Tim, Eric)
) unpiv
) src
pivot
(
sum(value)
for color in ([Red], [Green], [Blue])
) piv
具有的内部查询UNPIVOT
执行与相同的功能UNION ALL
。它获取列列表并将其转换为行,PIVOT
然后执行最终转换为列。
动态数据透视图版本:
如果您有未知数量的列(Paul, John, Tim, Eric
在您的示例中),然后要转换的颜色数量未知,则可以使用动态sql生成列表UNPIVOT
,然后PIVOT
:
DECLARE @colsUnpivot AS NVARCHAR(MAX),
@query AS NVARCHAR(MAX),
@colsPivot as NVARCHAR(MAX)
select @colsUnpivot = stuff((select ','+quotename(C.name)
from sys.columns as C
where C.object_id = object_id('yourtable') and
C.name <> 'color'
for xml path('')), 1, 1, '')
select @colsPivot = STUFF((SELECT ','
+ quotename(color)
from yourtable t
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set @query
= 'select name, '+@colsPivot+'
from
(
select color, name, value
from yourtable
unpivot
(
value for name in ('+@colsUnpivot+')
) unpiv
) src
pivot
(
sum(value)
for color in ('+@colsPivot+')
) piv'
exec(@query)
动态版两种查询yourtable
,然后将sys.columns
表来生成物品的清单UNPIVOT
和PIVOT
。然后将其添加到要执行的查询字符串中。动态版本的加号是如果您具有更改的列表colors
和/或names
它将在运行时生成列表。
所有三个查询将产生相同的结果:
| NAME | RED | GREEN | BLUE |
-----------------------------
| Eric | 3 | 5 | 1 |
| John | 5 | 4 | 2 |
| Paul | 1 | 8 | 2 |
| Tim | 1 | 3 | 9 |
这通常需要您事先了解所有列和行标签。正如您在下面的查询中看到的那样,所有标签均在UNPIVOT和(re)PIVOT操作中完全列出。
MS SQL Server 2012架构设置:
create table tbl (
color varchar(10), Paul int, John int, Tim int, Eric int);
insert tbl select
'Red' ,1 ,5 ,1 ,3 union all select
'Green' ,8 ,4 ,3 ,5 union all select
'Blue' ,2 ,2 ,9 ,1;
查询1:
select *
from tbl
unpivot (value for name in ([Paul],[John],[Tim],[Eric])) up
pivot (max(value) for color in ([Red],[Green],[Blue])) p
结果:
| NAME | RED | GREEN | BLUE |
-----------------------------
| Eric | 3 | 5 | 1 |
| John | 5 | 4 | 2 |
| Paul | 1 | 8 | 2 |
| Tim | 1 | 3 | 9 |
我想指出一些在SQL中转置列和行的解决方案。
第一个是-使用CURSOR。尽管在专业社区中,普遍的共识是远离SQL Server游标,但仍然有一些实例建议使用游标。无论如何,Cursors为我们提供了另一种选择,可以将行转置为列。
垂直扩展
与PIVOT相似,随着数据集扩展以包含更多策略编号,光标具有动态功能来追加更多行。
横向扩展
与PIVOT不同,游标在此区域表现出色,因为它可以扩展为包括新添加的文档,而无需更改脚本。
性能细目
使用CURSOR将行转换为列的主要限制是一个缺点,通常与使用游标相关联-它们的性能成本很高。这是因为Cursor为每个FETCH NEXT操作生成一个单独的查询。
将行转换为列的另一种解决方案是使用XML。
将行转换为列的XML解决方案基本上是PIVOT的最佳版本,因为它解决了动态列限制。
脚本的XML版本通过结合使用XML路径,动态T-SQL和某些内置函数(即STUFF,QUOTENAME)来解决此限制。
垂直扩展
与PIVOT和Cursor相似,可以在脚本的XML版本中检索新添加的策略,而无需更改原始脚本。
横向扩展
与PIVOT不同,可以在不更改脚本的情况下显示新添加的文档。
性能细目
在IO方面,脚本的XML版本的统计数据几乎与PIVOT相似–唯一的区别是XML对dtTranspose表进行了第二次扫描,但这次是从逻辑读取–数据高速缓存进行的。
您可以在本文中找到有关这些解决方案的更多信息(包括一些实际的T-SQL示例):https ://www.sqlshack.com/multiple-options-to-transpose-rows-into-columns/
基于bluefeet的此解决方案,这里是一个使用动态sql生成转置表的存储过程。它要求除转置列(将作为结果表中标题的列)以外的所有字段均为数字:
/****** Object: StoredProcedure [dbo].[SQLTranspose] Script Date: 11/10/2015 7:08:02 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author: Paco Zarate
-- Create date: 2015-11-10
-- Description: SQLTranspose dynamically changes a table to show rows as headers. It needs that all the values are numeric except for the field using for transposing.
-- Parameters: @TableName - Table to transpose
-- @FieldNameTranspose - Column that will be the new headers
-- Usage: exec SQLTranspose <table>, <FieldToTranspose>
-- =============================================
ALTER PROCEDURE [dbo].[SQLTranspose]
-- Add the parameters for the stored procedure here
@TableName NVarchar(MAX) = '',
@FieldNameTranspose NVarchar(MAX) = ''
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE @colsUnpivot AS NVARCHAR(MAX),
@query AS NVARCHAR(MAX),
@queryPivot AS NVARCHAR(MAX),
@colsPivot as NVARCHAR(MAX),
@columnToPivot as NVARCHAR(MAX),
@tableToPivot as NVARCHAR(MAX),
@colsResult as xml
select @tableToPivot = @TableName;
select @columnToPivot = @FieldNameTranspose
select @colsUnpivot = stuff((select ','+quotename(C.name)
from sys.columns as C
where C.object_id = object_id(@tableToPivot) and
C.name <> @columnToPivot
for xml path('')), 1, 1, '')
set @queryPivot = 'SELECT @colsResult = (SELECT '',''
+ quotename('+@columnToPivot+')
from '+@tableToPivot+' t
where '+@columnToPivot+' <> ''''
FOR XML PATH(''''), TYPE)'
exec sp_executesql @queryPivot, N'@colsResult xml out', @colsResult out
select @colsPivot = STUFF(@colsResult.value('.', 'NVARCHAR(MAX)'),1,1,'')
set @query
= 'select name, rowid, '+@colsPivot+'
from
(
select '+@columnToPivot+' , name, value, ROW_NUMBER() over (partition by '+@columnToPivot+' order by '+@columnToPivot+') as rowid
from '+@tableToPivot+'
unpivot
(
value for name in ('+@colsUnpivot+')
) unpiv
) src
pivot
(
sum(value)
for '+@columnToPivot+' in ('+@colsPivot+')
) piv
order by rowid'
exec(@query)
END
您可以使用此命令提供的表对其进行测试:
exec SQLTranspose 'yourTable', 'color'
我UnPivot
首先要做的是将结果存储在in中CTE
并使用CTE
in Pivot
操作。
with cte as
(
select 'Paul' as Name, color, Paul as Value
from yourTable
union all
select 'John' as Name, color, John as Value
from yourTable
union all
select 'Tim' as Name, color, Tim as Value
from yourTable
union all
select 'Eric' as Name, color, Eric as Value
from yourTable
)
select Name, [Red], [Green], [Blue]
from
(
select *
from cte
) as src
pivot
(
max(Value)
for color IN ([Red], [Green], [Blue])
) as Dtpivot;
添加到上面@Paco Zarate的绝妙答案中,如果您想转置具有多种列类型的表,请将其添加到第39行的末尾,因此它仅转置int
列:
and C.system_type_id = 56 --56 = type int
这是要更改的完整查询:
select @colsUnpivot = stuff((select ','+quotename(C.name)
from sys.columns as C
where C.object_id = object_id(@tableToPivot) and
C.name <> @columnToPivot and C.system_type_id = 56 --56 = type int
for xml path('')), 1, 1, '')
要查找其他system_type_id
的,请运行以下命令:
select name, system_type_id from sys.types order by name
我喜欢分享我用来基于+ bluefeet答案转置分割文本的代码。在此方法中,我已作为MS SQL 2005中的过程实现
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author: ELD.
-- Create date: May, 5 2016.
-- Description: Transpose from rows to columns the user split function.
-- =============================================
CREATE PROCEDURE TransposeSplit @InputToSplit VARCHAR(8000)
,@Delimeter VARCHAR(8000) = ','
AS
BEGIN
SET NOCOUNT ON;
DECLARE @colsUnpivot AS NVARCHAR(MAX)
,@query AS NVARCHAR(MAX)
,@queryPivot AS NVARCHAR(MAX)
,@colsPivot AS NVARCHAR(MAX)
,@columnToPivot AS NVARCHAR(MAX)
,@tableToPivot AS NVARCHAR(MAX)
,@colsResult AS XML
SELECT @tableToPivot = '#tempSplitedTable'
SELECT @columnToPivot = 'col_number'
CREATE TABLE #tempSplitedTable (
col_number INT
,col_value VARCHAR(8000)
)
INSERT INTO #tempSplitedTable (
col_number
,col_value
)
SELECT ROW_NUMBER() OVER (
ORDER BY (
SELECT 100
)
) AS RowNumber
,item
FROM [DB].[ESCHEME].[fnSplit](@InputToSplit, @Delimeter)
SELECT @colsUnpivot = STUFF((
SELECT ',' + quotename(C.NAME)
FROM [tempdb].sys.columns AS C
WHERE C.object_id = object_id('tempdb..' + @tableToPivot)
AND C.NAME <> @columnToPivot
FOR XML path('')
), 1, 1, '')
SET @queryPivot = 'SELECT @colsResult = (SELECT '',''
+ quotename(' + @columnToPivot + ')
from ' + @tableToPivot + ' t
where ' + @columnToPivot + ' <> ''''
FOR XML PATH(''''), TYPE)'
EXEC sp_executesql @queryPivot
,N'@colsResult xml out'
,@colsResult OUT
SELECT @colsPivot = STUFF(@colsResult.value('.', 'NVARCHAR(MAX)'), 1, 1, '')
SET @query = 'select name, rowid, ' + @colsPivot + '
from
(
select ' + @columnToPivot + ' , name, value, ROW_NUMBER() over (partition by ' + @columnToPivot + ' order by ' + @columnToPivot + ') as rowid
from ' + @tableToPivot + '
unpivot
(
value for name in (' + @colsUnpivot + ')
) unpiv
) src
pivot
(
MAX(value)
for ' + @columnToPivot + ' in (' + @colsPivot + ')
) piv
order by rowid'
EXEC (@query)
DROP TABLE #tempSplitedTable
END
GO
我将此解决方案与有关如何排序行而不排序的信息(SQLAuthority.com)和MSDN上的split函数(social.msdn.microsoft.com)混合在一起
执行程序时
DECLARE @RC int
DECLARE @InputToSplit varchar(MAX)
DECLARE @Delimeter varchar(1)
set @InputToSplit = 'hello|beautiful|world'
set @Delimeter = '|'
EXECUTE @RC = [TransposeSplit]
@InputToSplit
,@Delimeter
GO
您获得下一个结果
name rowid 1 2 3
col_value 1 hello beautiful world
这样,将表中Filelds(Columns)中的所有数据转换为Record(行)。
Declare @TableName [nvarchar](128)
Declare @ExecStr nvarchar(max)
Declare @Where nvarchar(max)
Set @TableName = 'myTableName'
--Enter Filtering If Exists
Set @Where = ''
--Set @ExecStr = N'Select * From '+quotename(@TableName)+@Where
--Exec(@ExecStr)
Drop Table If Exists #tmp_Col2Row
Create Table #tmp_Col2Row
(Field_Name nvarchar(128) Not Null
,Field_Value nvarchar(max) Null
)
Set @ExecStr = N' Insert Into #tmp_Col2Row (Field_Name , Field_Value) '
Select @ExecStr += (Select N'Select '''+C.name+''' ,Convert(nvarchar(max),'+quotename(C.name) + ') From ' + quotename(@TableName)+@Where+Char(10)+' Union All '
from sys.columns as C
where (C.object_id = object_id(@TableName))
for xml path(''))
Select @ExecStr = Left(@ExecStr,Len(@ExecStr)-Len(' Union All '))
--Print @ExecStr
Exec (@ExecStr)
Select * From #tmp_Col2Row
Go
我能够使用Paco Zarate的解决方案,并且效果很好。我确实必须添加一行(“ SET ANSI_WARNINGS ON”),但这可能是我使用或调用它的方式所特有的。我的用法有问题,希望有人可以帮助我:
该解决方案仅适用于实际的SQL表。我尝试了一个临时表和一个内存中(声明的)表,但不适用于那些表。因此,在调用代码中,我在SQL数据库上创建了一个表,然后调用SQLTranspose。再次,它很棒。这就是我想要的。这是我的问题:
为了使整体解决方案真正具有动态性,我需要创建该表,在其中临时存储要“即时”发送给SQLTranspose的准备好的信息,然后在调用SQLTranspose时删除该表。表删除给我的最终实现计划带来了问题。该代码需要从最终用户应用程序(Microsoft Access表单/菜单上的按钮)运行。当我使用此SQL进程(创建SQL表,调用SQLTranspose,删除SQL表)时,最终用户应用程序遇到错误,因为所使用的SQL帐户无权删除表。
因此,我认为有几种可能的解决方案:
找到一种使SQLTranspose与临时表或声明的表变量一起使用的方法。
找出另一种不需要实际SQL表的行和列转置方法。
找出允许最终用户使用的SQL帐户删除表的适当方法。这是一个编码到我的Access应用程序中的共享SQL帐户。看来,权限是无法授予的dbo型特权。
我认识到,其中一些可能需要另外一个单独的话题和问题。但是,由于存在一种解决方案可能只是换行和列换行的不同方法的可能性,因此我将在此线程中发表我的第一篇文章。
编辑:正如Paco建议的那样,我还从结尾的第6行中用max(value)替换了sum(value)。
编辑:
我想出了一些对我有用的东西。我不知道这是不是最好的答案。
我有一个只读用户帐户,该帐户用于执行受约束的过程,因此可以从数据库生成报告输出。由于我创建的SQLTranspose函数仅适用于“合法”表(不是声明的表,也不是临时表),因此我必须找出一种只读用户帐户创建(然后删除)表的方法。 。
我认为出于我的目的,可以允许用户帐户创建表。用户仍然无法删除该表。我的解决方案是创建一个授权用户帐户的架构。然后,每当我创建,使用或删除该表时,请使用指定的架构引用该表。
我首先从“ sa”或“ sysadmin”帐户发出了此命令:CREATE SCHEMA ro AUTHORIZATION
每当我引用“ tmpoutput”表时,都会像下面的示例所示进行指定:
删除表ro.tmpoutput