在 SQL 中转置列和行的简单方法?
Posted
技术标签:
【中文标题】在 SQL 中转置列和行的简单方法?【英文标题】:Simple way to transpose columns and rows in SQL? 【发布时间】:2012-11-02 13:30:15 【问题描述】:如何在 SQL 中简单地切换列与行? 有没有简单的转置命令?
即转这个结果:
Paul | John | Tim | Eric
Red 1 5 1 3
Green 8 4 3 5
Blue 2 2 9 1
进入这个:
Red | Green | Blue
Paul 1 8 2
John 5 4 2
Tim 1 3 9
Eric 3 5 1
PIVOT
对于这种情况来说似乎太复杂了。
【问题讨论】:
***.com/questions/10699997/… 是一个有点类似的问题。 【参考方案1】:我能够使用 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 授权
每当我引用我的“tmpoutput”表时,我都会像这个例子一样指定它:
删除表 ro.tmp 输出
【讨论】:
我找到了适合我的方法。我不知道这是否是最佳答案。【参考方案2】:这种方式将所有数据从表中的文件(列)转换为记录(行)。
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
【讨论】:
【参考方案3】:添加到上面@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
【讨论】:
【参考方案4】:我先做UnPivot
并将结果存储在CTE
中,然后在Pivot
操作中使用CTE
。
Demo
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;
【讨论】:
【参考方案5】:我想指出更多在 SQL 中转置列和行的解决方案。
第一个是 - 使用 CURSOR。尽管专业社区的普遍共识是远离 SQL Server 游标,但仍有一些情况建议使用游标。无论如何,Cursors 为我们提供了另一种将行转换为列的选项。
纵向扩展
与 PIVOT 类似,游标具有动态能力,可以在您的数据集扩展以包含更多策略编号时追加更多行。
横向扩展
与 PIVOT 不同,光标在该区域表现出色,因为它能够扩展以包含新添加的文档,而无需更改脚本。
性能细分
使用 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-transposing-rows-into-columns/
【讨论】:
【参考方案6】:我喜欢分享我用来转置基于 +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 上的拆分功能 (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
【讨论】:
【参考方案7】:基于bluefeet中的solution,这里有一个使用动态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'
【讨论】:
argg。但我想要一个所有字段都是 nvarchar(max) 对于所有字段 nvarchar(max) 只需将 sum(value) 与 max(value) 更改为从末尾开始的第 6 行【参考方案8】:您可以通过多种方式转换此数据。在您的原始帖子中,您说PIVOT
对于这种情况来说似乎太复杂了,但可以使用 SQL Server 中的 UNPIVOT
and 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);
Union All、Aggregate 和 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
见SQL Fiddle with Demo
UNION ALL
通过将列Paul, John, Tim, Eric
转换为单独的行来执行数据的UNPIVOT
。然后应用聚合函数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
见SQL Fiddle with Demo
带有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)
见SQL Fiddle with Demo
动态版本同时查询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 |
【讨论】:
我应该强调我想要一种方法来转置数据库上的查询结果。一个简单的转置类型命令,可以在查询的结果集上执行,而无需了解查询本身或从中提取信息的列、表等。类似于: (TRANSPOSE (SELECT......)) 在我上面给出的示例中,假设第一个表是由涉及多个表、联合、数据透视表等的非常复杂的查询生成的结果集。但结果是简单的表。我只想在这个结果中翻转带有行的列。 我将结果传递给 c# DataTable。我可以构建一个简单的方法“Transpose(Datatable dt)”来执行此操作,但是 SQL 中有什么可以执行此操作。 @edezzie 在 SQL 中没有一种简单的方法可以做到这一点。您可能会更改动态版本以传入表名并从系统表中提取信息。 您有什么理由没有将这个出色的答案标记为答案?给@bluefeet 一枚勋章! 这工作正常 select * from Table unpivot (name for name in ([Paul],[John],[Tim],[Eric])) up pivot (max(value) for color in ([Red],[Green],[Blue])) p 但是是否可以从 Table unpivot 中编写这个 select * (([Paul],[John],[Tim],[Eric]) 中的名称值) up pivot (max(value) for color in (SELECT COLOR FROM TABLENAME)) p 而不是输入带引号的值不能直接使用选择查询检索值。【参考方案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
Results:
| NAME | RED | GREEN | BLUE |
-----------------------------
| Eric | 3 | 5 | 1 |
| John | 5 | 4 | 2 |
| Paul | 1 | 8 | 2 |
| Tim | 1 | 3 | 9 |
补充说明:
-
给定一个表名,您可以使用 local-name() 从 sys.columns 或 FOR XML 技巧中确定所有列名。
您还可以使用 FOR XML 构建不同颜色的列表(或一列的值)。
以上可以组合成一个动态sql批处理来处理任何表。
【讨论】:
以上是关于在 SQL 中转置列和行的简单方法?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 PostgreSQL 中转置列和行(即如何切换行和列)? [复制]