MS SQL 转置类似于 Excel 转置(动态 SQL)
Posted
技术标签:
【中文标题】MS SQL 转置类似于 Excel 转置(动态 SQL)【英文标题】:MS SQL Transpose Like Excel Transpose (Dynamic SQL) 【发布时间】:2021-09-01 10:29:27 【问题描述】:我需要一个类似于excel转置操作的动态SQL代码。
我试过用动态 SQL 转置一个表。
我尝试过 Pivot/XML、动态 SQL。整理。我失败了。
我尝试了CAST(column collate database_default AS NVARCHAR(MAX))
转换。
我哪里做错了?如何编写代码?
我的最终目标是创建一个作为以下查询结果获得的复合表。如您所见,此表中的示例列是空的。我想做一个“while 循环”查询来填充这些值。在创建循环之前,我需要一个类似于 excel 转置操作的代码。通过这个操作,我可以将结果复制到复合表中。
SELECT A.TABLE_CATALOG, A. TABLE_SCHEMA, B.COLUMN_CNT,B.DUM_TABLE_POSITION,
A.ORDINAL_POSITION AS DEF_COLUMN_POSITION, A.TABLE_NAME, A.COLUMN_NAME,
SAMPLE_1=NULL,SAMPLE_2=NULL,SAMPLE_3=NULL,SAMPLE_4=NULL,SAMPLE_5=NULL,
SAMPLE_6=NULL,SAMPLE_7=NULL,SAMPLE_8=NULL,SAMPLE_9=NULL,SAMPLE_10=NULL
FROM INFORMATION_SCHEMA.COLUMNS A,
(SELECT TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME,COUNT(*) AS COLUMN_CNT,
ROW_NUMBER() OVER (ORDER BY TABLE_NAME) AS DUM_TABLE_POSITION
FROM INFORMATION_SCHEMA.COLUMNS
GROUP BY TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME
HAVING COUNT(*)>0 ) B
WHERE A.TABLE_CATALOG=B.TABLE_CATALOG
AND A.TABLE_SCHEMA=B.TABLE_SCHEMA
AND A.TABLE_NAME=B.TABLE_NAME
AND A.TABLE_CATALOG='DWH_PROD'
AND A.TABLE_SCHEMA='dbo'
AND A.TABLE_NAME IN (N'DWH_PROD.dbo.MY_TABLE_1', N'DWH_PROD.dbo.MY_TABLE_2')
enter image description here
..................
样本数据
CREATE TABLE #temporary_table
(CUS_ID INT,
TITLE NVARCHAR (50),
PROMOTER NVARCHAR (50),
CUS_STATUS NVARCHAR (50))
;
INSERT INTO #temporary_table
VALUES
(11,'A',NULL,'PASSIVE'),
(22,'B',NULL,'ACTIVE'),
(33,'D',NULL,'ACTIVE'),
(44,'B',NULL,'ACTIVE'),
(55,'B',NULL,'ACTIVE'),
(66,'C',NULL,'ACTIVE'),
(77,'D',NULL,'ACTIVE'),
(88,'D',NULL,'ACTIVE'),
(101,'D',NULL,'ACTIVE'),
(123,'D',NULL,'ACTIVE'),
(200,'D',NULL,'ACTIVE'),
(300,'A',NULL,'PASSIVE')
;
SELECT TOP 10 CONCAT('SAMPLE_', ROW_NUMBER() OVER( ORDER BY (SELECT 1) ) ) AS AAA,*
FROM #temporary_table
我可以使用下面的单个代码单独获得结果。但是这个结果不符合我的要求。
DECLARE @sql nvarchar(max) = N'';
SELECT @sql += N'
SELECT TOP (10) [table] = N''' + REPLACE(name, '''','') + ''', *
FROM ' + QUOTENAME(SCHEMA_NAME([schema_id]))
+ '.' + QUOTENAME(name) + ';'
FROM sys.tables AS t
WHERE name IN (N'DWH_PROD.dbo.MY_TABLE_1', N'DWH_PROD.dbo.MY_TABLE_2')
EXEC sys.sp_executesql @sql;
编辑:::::::::::::::::::::::::::
非常感谢@John Cappelletti,我几乎完成了查询。该代码适用于 Server 2014。我没有测试所有其他场景。如果一列的前 10 行只有 NULL 值,则第二个表名将被视为 NULL。
-- DROP TABLE #HamdullahUstadKacincidir2
-- DROP TABLE #HamdullahUstadKacincidir
CREATE TABLE #HamdullahUstadKacincidir2
(COLUMN_NAME NVARCHAR (MAX),
SAMPLE_1 NVARCHAR (MAX),
SAMPLE_2 NVARCHAR (MAX),
SAMPLE_3 NVARCHAR (MAX),
SAMPLE_4 NVARCHAR (MAX),
SAMPLE_5 NVARCHAR (MAX),
SAMPLE_6 NVARCHAR (MAX),
SAMPLE_7 NVARCHAR (MAX),
SAMPLE_8 NVARCHAR (MAX),
SAMPLE_9 NVARCHAR (MAX),
SAMPLE_10 NVARCHAR (MAX))
CREATE TABLE #HamdullahUstadKacincidir
(TABLE_NAME NVARCHAR (MAX),
COLUMN_NAME NVARCHAR (MAX),
SAMPLE_1 NVARCHAR (MAX),
SAMPLE_2 NVARCHAR (MAX),
SAMPLE_3 NVARCHAR (MAX),
SAMPLE_4 NVARCHAR (MAX),
SAMPLE_5 NVARCHAR (MAX),
SAMPLE_6 NVARCHAR (MAX),
SAMPLE_7 NVARCHAR (MAX),
SAMPLE_8 NVARCHAR (MAX),
SAMPLE_9 NVARCHAR (MAX),
SAMPLE_10 NVARCHAR (MAX))
DECLARE @SourceTableName AS NVARCHAR (MAX)
DECLARE @SourceTableSql AS NVARCHAR (MAX)
DECLARE @SourceTableSql22 AS NVARCHAR (MAX)
DECLARE @SourceTableSql333 AS NVARCHAR (MAX)
DECLARE @TableI INT
DECLARE @TableIN NVARCHAR(MAX)
DECLARE @SampleSize INT
DECLARE @TableCount INT
SET @TableI=1
SET @SampleSize= 10
SET @TableCount= (SELECT COUNT(*) AS MAX_TABLE_CNT
FROM
(SELECT TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME,COUNT(*) AS COLUMN_CNT
FROM INFORMATION_SCHEMA.COLUMNS
WHERE CONCAT(TABLE_CATALOG,'.', TABLE_SCHEMA,'.', TABLE_NAME) NOT IN --Has at Least One Geography Data_Type Column
(SELECT CONCAT(C.TABLE_CATALOG,'.', C.TABLE_SCHEMA,'.', C.TABLE_NAME) AS TABLE_HAS_GEOGRAPHY_DATA_TYPE
FROM INFORMATION_SCHEMA.COLUMNS C
WHERE DATA_TYPE='Geography')
AND CONCAT(TABLE_CATALOG,'.', TABLE_SCHEMA,'.', TABLE_NAME) NOT IN --Eliminate Empty Tables
(SELECT CONCAT(C.TABLE_CATALOG,'.', C.TABLE_SCHEMA,'.', C.TABLE_NAME) AS EMPTY_TABLE
FROM sys.tables t
join sys.schemas s on (t.schema_id = s.schema_id)
join sys.partitions p on (t.object_id = p.object_id)
join INFORMATION_SCHEMA.COLUMNS c on (s.name=c.TABLE_SCHEMA and t.name=c.TABLE_NAME)
WHERE p.index_id in (0,1)
GROUP BY C.TABLE_CATALOG, C.TABLE_SCHEMA, C.TABLE_NAME
HAVING SUM(p.rows) = 0)
AND CONCAT(TABLE_CATALOG,'.', TABLE_SCHEMA,'.', TABLE_NAME) NOT IN --Black List Tables
('DWH_PROD.dbo._tmp_retro')
GROUP BY TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME) T )
WHILE @TableI<=@TableCount
BEGIN
SET @TableIN=CAST(@TableI AS NVARCHAR(MAX))
SET @SourceTableSql=
'SELECT @SourceTableTemp=CONCAT(TABLE_CATALOG,''.'',TABLE_SCHEMA,''.'',TABLE_NAME)
FROM
(SELECT TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME,COUNT(*) AS COLUMN_CNT,
ROW_NUMBER() OVER (ORDER BY TABLE_NAME) AS DUM_TABLE_POSITION
FROM INFORMATION_SCHEMA.COLUMNS
WHERE CONCAT(TABLE_CATALOG,''.'', TABLE_SCHEMA,''.'', TABLE_NAME) NOT IN --Has at Least One Geography Data_Type Column
(SELECT CONCAT(C.TABLE_CATALOG,''.'', C.TABLE_SCHEMA,''.'', C.TABLE_NAME) AS TABLE_HAS_GEOGRAPHY_DATA_TYPE
FROM INFORMATION_SCHEMA.COLUMNS C
WHERE DATA_TYPE=''Geography'')
AND CONCAT(TABLE_CATALOG,''.'', TABLE_SCHEMA,''.'', TABLE_NAME) NOT IN --Eliminate Empty Tables
(SELECT CONCAT(C.TABLE_CATALOG,''.'', C.TABLE_SCHEMA,''.'', C.TABLE_NAME) AS EMPTY_TABLE
FROM sys.tables t
join sys.schemas s on (t.schema_id = s.schema_id)
join sys.partitions p on (t.object_id = p.object_id)
join INFORMATION_SCHEMA.COLUMNS c on (s.name=c.TABLE_SCHEMA and t.name=c.TABLE_NAME)
WHERE p.index_id in (0,1)
GROUP BY C.TABLE_CATALOG, C.TABLE_SCHEMA, C.TABLE_NAME
HAVING SUM(p.rows) = 0)
AND CONCAT(TABLE_CATALOG,''.'', TABLE_SCHEMA,''.'', TABLE_NAME) NOT IN --Black List Tables
(''DWH_PROD.dbo._tmp_retro'')
GROUP BY TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME) K
WHERE DUM_TABLE_POSITION=@TableIN'
EXECUTE sp_executesql @SourceTableSql, N'@TableIN NVARCHAR(MAX),@SourceTableTemp NVARCHAR (MAX) OUTPUT',
@TableIN=@TableIN, @SourceTableTemp = @SourceTableName OUTPUT
DELETE #HamdullahUstadKacincidir2
SET @SourceTableSql22 ='INSERT INTO #HamdullahUstadKacincidir2
SELECT *
FROM ( SELECT A.AAA,C.*
FROM ( SELECT TOP '+CONCAT('',@SampleSize)+' AAA = CONCAT(''SAMPLE_'', ROW_NUMBER() OVER( ORDER BY (SELECT 1) ) ),*
FROM '+@SourceTableName+' SRC ) A
CROSS APPLY ( VALUES ((Select A.* FOR XML RAW,Type)) )B(XMLData)
CROSS APPLY ( SELECT COLUMN_NAME = xAttr.value(''local-name(.)'', ''VARCHAR(MAX)''),VALUE = xAttr.value(''.'',''VARCHAR(MAX)'')
FROM XMLData.nodes(''//@*'') xNode(xAttr)
WHERE xAttr.value(''local-name(.)'', ''varchar(100)'') not in (''AAA'')) C ) SRC
PIVOT (MAX(VALUE) FOR [AAA] IN (' + STUFF((SELECT TOP (@SampleSize) ','+CONCAT('SAMPLE_', ROW_NUMBER() OVER( ORDER BY (SELECT 1) ))
FROM master..spt_values FOR XML Path('')),1,1,'') + ') ) P'
EXECUTE(@SourceTableSql22)
INSERT INTO #HamdullahUstadKacincidir
SELECT TABLE_NAME=@SourceTableName,* FROM #HamdullahUstadKacincidir2
SET @TableI=@TableI+1
END
SELECT E.*,K.*,H.*
FROM
(SELECT CONCAT(TABLE_CATALOG,'.',TABLE_SCHEMA,'.',TABLE_NAME) AS TABLE_NAME,COLUMN_NAME,DATA_TYPE,
ROW_NUMBER() OVER (PARTITION BY TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME ORDER BY ORDINAL_POSITION ) AS COLUMN_POSITION
FROM INFORMATION_SCHEMA.COLUMNS
) K
LEFT JOIN
#HamdullahUstadKacincidir H
ON K.TABLE_NAME COLLATE DATABASE_DEFAULT=H.TABLE_NAME COLLATE DATABASE_DEFAULT
AND K.COLUMN_NAME COLLATE DATABASE_DEFAULT=H.COLUMN_NAME COLLATE DATABASE_DEFAULT
LEFT JOIN
(SELECT CONCAT(C.TABLE_CATALOG,'.', C.TABLE_SCHEMA,'.', C.TABLE_NAME) AS EMPTY_TABLE
FROM sys.tables t
join sys.schemas s on (t.schema_id = s.schema_id)
join sys.partitions p on (t.object_id = p.object_id)
join INFORMATION_SCHEMA.COLUMNS c on (s.name=c.TABLE_SCHEMA and t.name=c.TABLE_NAME)
WHERE p.index_id in (0,1)
GROUP BY C.TABLE_CATALOG, C.TABLE_SCHEMA, C.TABLE_NAME
HAVING SUM(p.rows) = 0) E
ON K.TABLE_NAME=EMPTY_TABLE
ORDER BY K.TABLE_NAME,K.COLUMN_POSITION
【问题讨论】:
这个表达式的目的是什么?CONCAT('SAMPLE_', ROW_NUMBER() OVER( ORDER BY (SELECT 1) ) ) AS AAA,
- 它没有做任何有用的事情。
为了向用户显示数据而将数据从行转移到列通常不应该在 SQL 中完成,因为它是表示层问题,而不是数据访问忧虑。请告诉我们您为什么要在 SQL 中使用PIVOT
。这样做本质上会使输出数据对其他程序无用。如果您想在 Excel 中使用转置的 SQL 数据,那么您应该在 Excel 中使用宏在加载后对其进行转置。
您“必须”在 SQL Server 中执行此操作吗?这确实是您的表示层的东西,不是 RDBMS。
我尝试使用 AAA 作为转置表的列名。它不是“必须拥有”的专栏。我尝试将其用作虚拟列名。
那么可消耗的样本数据,不是图像,将帮助我们帮助您。花时间为您的表和数据发布 DDL 和 DML 语句。
【参考方案1】:
更新 - 更动态的版本。只需提供表名(或查询)和 SampleSize
这将动态地 UNPIVOT 您的数据,然后 PIVOT 结果
示例或dbFiddle
Declare @Source varchar(500) = '#temporary_table'
Declare @SampleSize int = 10
Declare @SQL varchar(max) = '
Select *
From (
Select A.AAA
,B.*
From (
SELECT TOP '+concat('',@SampleSize)+'
AAA = CONCAT(''SAMPLE_'', ROW_NUMBER() OVER( ORDER BY (SELECT 1) ) )
,*
FROM '+ @Source +' src
) A
Cross Apply (
Select [Key]
,Value
From OpenJson((Select A.* For JSON Path,Without_Array_Wrapper,INCLUDE_NULL_VALUES ))
Where [Key] not in (''AAA'')
) B
) src
Pivot (max(Value) For [AAA] in (' + Stuff((Select top (@SampleSize) ','+CONCAT('SAMPLE_', ROW_NUMBER() OVER( ORDER BY (SELECT 1) )) From master..spt_values For XML Path('')),1,1,'') + ') ) p
'
Exec(@SQL)
结果
只是为了好玩
您还可以提供一个查询作为@Source
Set @Source = '( Select * from [dbo].[ZIPCodes] where StateCode=''RI'') '
【讨论】:
非常感谢。这就是我需要的。但我有一个错误。 'Without_Array_Wrapper' 附近的语法不正确。 @Emre 你不是 2016+ 吗? 嗨,是的,它是 SQL Server 2016(版本:13.0.16106.4) 再看一下 dbFiddle dbfiddle.uk/… 这是 2016 年,没有错误 我只复制粘贴。我连一个“.”都没改。我的消息是:(12 行受影响)(12 行受影响)消息 102,级别 15,状态 1,第 15 行“Without_Array_Wrapper”附近的语法不正确。以上是关于MS SQL 转置类似于 Excel 转置(动态 SQL)的主要内容,如果未能解决你的问题,请参考以下文章