SQL Server动态透视查询?
我的任务是提出一种翻译以下数据的方法:SQL Server动态透视查询?,sql,sql-server,tsql,pivot,Sql,Sql Server,Tsql,Pivot,我的任务是提出一种翻译以下数据的方法: date category amount 1/1/2012 ABC 1000.00 2/1/2012 DEF 500.00 2/1/2012 GHI 800.00 2/10/2012 DEF 700.00 3/1/2012 ABC 1100.00 具体如下: date
date category amount
1/1/2012 ABC 1000.00
2/1/2012 DEF 500.00
2/1/2012 GHI 800.00
2/10/2012 DEF 700.00
3/1/2012 ABC 1100.00
具体如下:
date ABC DEF GHI
1/1/2012 1000.00
2/1/2012 500.00
2/1/2012 800.00
2/10/2012 700.00
3/1/2012 1100.00
空白点可以是空的,也可以是空白的,两者都可以,类别需要是动态的。另一个可能的警告是,我们将在有限的容量内运行查询,这意味着临时表已过时。我曾尝试过研究,并在
PIVOT
上登陆,但由于我以前从未使用过PIVOT,所以我真的不理解它,尽管我尽了最大的努力去弄清楚它。有人能给我指出正确的方向吗?您可以使用动态TSQL实现这一点(记住使用QUOTENAME以避免SQL注入攻击):
必须参考动态SQL PIVOT:
create table temp
(
date datetime,
category varchar(3),
amount money
)
insert into temp values ('1/1/2012', 'ABC', 1000.00)
insert into temp values ('2/1/2012', 'DEF', 500.00)
insert into temp values ('2/1/2012', 'GHI', 800.00)
insert into temp values ('2/10/2012', 'DEF', 700.00)
insert into temp values ('3/1/2012', 'ABC', 1100.00)
DECLARE @cols AS NVARCHAR(MAX),
@query AS NVARCHAR(MAX);
SET @cols = STUFF((SELECT distinct ',' + QUOTENAME(c.category)
FROM temp c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set @query = 'SELECT date, ' + @cols + ' from
(
select date
, amount
, category
from temp
) x
pivot
(
max(amount)
for category in (' + @cols + ')
) p '
execute(@query)
drop table temp
结果:
Date ABC DEF GHI
2012-01-01 00:00:00.000 1000.00 NULL NULL
2012-02-01 00:00:00.000 NULL 500.00 800.00
2012-02-10 00:00:00.000 NULL 700.00 NULL
2012-03-01 00:00:00.000 1100.00 NULL NULL
我的解决方案是清除不必要的空值
DECLARE @cols AS NVARCHAR(MAX),
@maxcols AS NVARCHAR(MAX),
@query AS NVARCHAR(MAX)
select @cols = STUFF((SELECT ',' + QUOTENAME(CodigoFormaPago)
from PO_FormasPago
order by CodigoFormaPago
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select @maxcols = STUFF((SELECT ',MAX(' + QUOTENAME(CodigoFormaPago) + ') as ' + QUOTENAME(CodigoFormaPago)
from PO_FormasPago
order by CodigoFormaPago
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set @query = 'SELECT CodigoProducto, DenominacionProducto, ' + @maxcols + '
FROM
(
SELECT
CodigoProducto, DenominacionProducto,
' + @cols + ' from
(
SELECT
p.CodigoProducto as CodigoProducto,
p.DenominacionProducto as DenominacionProducto,
fpp.CantidadCuotas as CantidadCuotas,
fpp.IdFormaPago as IdFormaPago,
fp.CodigoFormaPago as CodigoFormaPago
FROM
PR_Producto p
LEFT JOIN PR_FormasPagoProducto fpp
ON fpp.IdProducto = p.IdProducto
LEFT JOIN PO_FormasPago fp
ON fpp.IdFormaPago = fp.IdFormaPago
) xp
pivot
(
MAX(CantidadCuotas)
for CodigoFormaPago in (' + @cols + ')
) p
) xx
GROUP BY CodigoProducto, DenominacionProducto'
t @query;
execute(@query);
动态SQL数据透视
创建列字符串的不同方法
create table #temp
(
date datetime,
category varchar(3),
amount money
)
insert into #temp values ('1/1/2012', 'ABC', 1000.00)
insert into #temp values ('2/1/2012', 'DEF', 500.00)
insert into #temp values ('2/1/2012', 'GHI', 800.00)
insert into #temp values ('2/10/2012', 'DEF', 700.00)
insert into #temp values ('3/1/2012', 'ABC', 1100.00)
DECLARE @cols AS NVARCHAR(MAX)='';
DECLARE @query AS NVARCHAR(MAX)='';
SELECT @cols = @cols + QUOTENAME(category) + ',' FROM (select distinct category from #temp ) as tmp
select @cols = substring(@cols, 0, len(@cols)) --trim "," at end
set @query =
'SELECT * from
(
select date, amount, category from #temp
) src
pivot
(
max(amount) for category in (' + @cols + ')
) piv'
execute(@query)
drop table #temp
结果
date ABC DEF GHI
2012-01-01 00:00:00.000 1000.00 NULL NULL
2012-02-01 00:00:00.000 NULL 500.00 800.00
2012-02-10 00:00:00.000 NULL 700.00 NULL
2012-03-01 00:00:00.000 1100.00 NULL NULL
下面的代码提供了将输出中的NULL替换为zero的结果 表格创建和数据插入:
create table test_table
(
date nvarchar(10),
category char(3),
amount money
)
insert into test_table values ('1/1/2012','ABC',1000.00)
insert into test_table values ('2/1/2012','DEF',500.00)
insert into test_table values ('2/1/2012','GHI',800.00)
insert into test_table values ('2/10/2012','DEF',700.00)
insert into test_table values ('3/1/2012','ABC',1100.00)
查询以生成准确的结果,该结果还将NULL替换为零:
DECLARE @DynamicPivotQuery AS NVARCHAR(MAX),
@PivotColumnNames AS NVARCHAR(MAX),
@PivotSelectColumnNames AS NVARCHAR(MAX)
--Get distinct values of the PIVOT Column
SELECT @PivotColumnNames= ISNULL(@PivotColumnNames + ',','')
+ QUOTENAME(category)
FROM (SELECT DISTINCT category FROM test_table) AS cat
--Get distinct values of the PIVOT Column with isnull
SELECT @PivotSelectColumnNames
= ISNULL(@PivotSelectColumnNames + ',','')
+ 'ISNULL(' + QUOTENAME(category) + ', 0) AS '
+ QUOTENAME(category)
FROM (SELECT DISTINCT category FROM test_table) AS cat
--Prepare the PIVOT query using the dynamic
SET @DynamicPivotQuery =
N'SELECT date, ' + @PivotSelectColumnNames + '
FROM test_table
pivot(sum(amount) for category in (' + @PivotColumnNames + ')) as pvt';
--Execute the Dynamic Pivot Query
EXEC sp_executesql @DynamicPivotQuery
输出:
create table test_table
(
date nvarchar(10),
category char(3),
amount money
)
insert into test_table values ('1/1/2012','ABC',1000.00)
insert into test_table values ('2/1/2012','DEF',500.00)
insert into test_table values ('2/1/2012','GHI',800.00)
insert into test_table values ('2/10/2012','DEF',700.00)
insert into test_table values ('3/1/2012','ABC',1100.00)
我知道这个问题比较老,但我仔细查看了答案,认为我可能能够扩展问题的“动态”部分,并可能帮助他人解决问题 首先也是最重要的一点是,我构建这个解决方案是为了解决一些同事遇到的一个问题,即不稳定的大型数据集需要快速旋转 此解决方案需要创建存储过程,因此,如果您的需要无法创建存储过程,请立即停止阅读 此过程将接收pivot语句的关键变量,以便为不同的表、列名和聚合动态创建pivot语句。静态列用作pivot的group by/identity列(如果没有必要,可以从代码中删除,但这在pivot语句中非常常见,并且是解决原始问题所必需的),pivot列是生成最终结果列名的位置,值列是聚合将应用于的对象。Table参数是包含schema(schema.tablename)的表的名称。这部分代码可能需要一些爱,因为它没有我希望的那么干净。它对我有效,因为我的使用并不是公开的,sql注入也不是一个问题。Aggregate参数将接受任何标准的sql Aggregate“AVG”、“SUM”、“MAX”等。代码也默认将MAX作为聚合。这不是必需的,但最初为其构建的受众不了解数据透视,通常使用MAX作为聚合 让我们从创建存储过程的代码开始。这段代码应该适用于SSMS 2005及以上的所有版本,但我没有在2005年或2016年对其进行测试,但我不明白为什么它不起作用
create PROCEDURE [dbo].[USP_DYNAMIC_PIVOT]
(
@STATIC_COLUMN VARCHAR(255),
@PIVOT_COLUMN VARCHAR(255),
@VALUE_COLUMN VARCHAR(255),
@TABLE VARCHAR(255),
@AGGREGATE VARCHAR(20) = null
)
AS
BEGIN
SET NOCOUNT ON;
declare @AVAIABLE_TO_PIVOT NVARCHAR(MAX),
@SQLSTRING NVARCHAR(MAX),
@PIVOT_SQL_STRING NVARCHAR(MAX),
@TEMPVARCOLUMNS NVARCHAR(MAX),
@TABLESQL NVARCHAR(MAX)
if isnull(@AGGREGATE,'') = ''
begin
SET @AGGREGATE = 'MAX'
end
SET @PIVOT_SQL_STRING = 'SELECT top 1 STUFF((SELECT distinct '', '' + CAST(''[''+CONVERT(VARCHAR,'+ @PIVOT_COLUMN+')+'']'' AS VARCHAR(50)) [text()]
FROM '+@TABLE+'
WHERE ISNULL('+@PIVOT_COLUMN+','''') <> ''''
FOR XML PATH(''''), TYPE)
.value(''.'',''NVARCHAR(MAX)''),1,2,'' '') as PIVOT_VALUES
from '+@TABLE+' ma
ORDER BY ' + @PIVOT_COLUMN + ''
declare @TAB AS TABLE(COL NVARCHAR(MAX) )
INSERT INTO @TAB EXEC SP_EXECUTESQL @PIVOT_SQL_STRING, @AVAIABLE_TO_PIVOT
SET @AVAIABLE_TO_PIVOT = (SELECT * FROM @TAB)
SET @TEMPVARCOLUMNS = (SELECT replace(@AVAIABLE_TO_PIVOT,',',' nvarchar(255) null,') + ' nvarchar(255) null')
SET @SQLSTRING = 'DECLARE @RETURN_TABLE TABLE ('+@STATIC_COLUMN+' NVARCHAR(255) NULL,'+@TEMPVARCOLUMNS+')
INSERT INTO @RETURN_TABLE('+@STATIC_COLUMN+','+@AVAIABLE_TO_PIVOT+')
select * from (
SELECT ' + @STATIC_COLUMN + ' , ' + @PIVOT_COLUMN + ', ' + @VALUE_COLUMN + ' FROM '+@TABLE+' ) a
PIVOT
(
'+@AGGREGATE+'('+@VALUE_COLUMN+')
FOR '+@PIVOT_COLUMN+' IN ('+@AVAIABLE_TO_PIVOT+')
) piv
SELECT * FROM @RETURN_TABLE'
EXEC SP_EXECUTESQL @SQLSTRING
END
下面的示例显示了各种执行语句,作为一个简单的示例显示了各种聚合。我没有选择更改static、pivot和value列来保持示例的简单性。您应该能够复制并粘贴代码,然后自己开始处理它
exec [dbo].[USP_DYNAMIC_PIVOT] 'date','category','amount','dbo.temp','sum'
exec [dbo].[USP_DYNAMIC_PIVOT] 'date','category','amount','dbo.temp','max'
exec [dbo].[USP_DYNAMIC_PIVOT] 'date','category','amount','dbo.temp','avg'
exec [dbo].[USP_DYNAMIC_PIVOT] 'date','category','amount','dbo.temp','min'
此执行将分别返回以下数据集
SQL Server 2017的更新版本,使用STRING\u AGG函数构建数据透视列列表:
create table temp
(
date datetime,
category varchar(3),
amount money
);
insert into temp values ('20120101', 'ABC', 1000.00);
insert into temp values ('20120201', 'DEF', 500.00);
insert into temp values ('20120201', 'GHI', 800.00);
insert into temp values ('20120210', 'DEF', 700.00);
insert into temp values ('20120301', 'ABC', 1100.00);
DECLARE @cols AS NVARCHAR(MAX),
@query AS NVARCHAR(MAX);
SET @cols = (SELECT STRING_AGG(category,',') FROM (SELECT DISTINCT category FROM temp WHERE category IS NOT NULL)t);
set @query = 'SELECT date, ' + @cols + ' from
(
select date
, amount
, category
from temp
) x
pivot
(
max(amount)
for category in (' + @cols + ')
) p ';
execute(@query);
drop table temp;
FWIW
QUOTENAME
仅当您从用户接受@tableName作为参数,并将其附加到查询(如SET@SQL='SELECT*from'+@tableName代码>。您可以生成大量易受攻击的动态SQL字符串,QUOTENAME
对您毫无帮助。@davids请参阅。如果你删除了超链接,你的答案是不完整的。@Kermit,我同意显示代码更有用,但你是说它是答案所必需的吗?如果没有这些链接,我的回答是“您可以使用动态TSQL实现这一点”。选择的答案建议了相同的路线,如果还显示了如何做,则会带来额外的好处,这就是为什么选择它作为答案的原因。我对选择的答案投了赞成票(在选择之前),因为它有一个示例,可以更好地帮助新人。但是,我认为新用户也应该阅读我提供的链接,这就是我没有删除它们的原因。请问SQL Server的版本是什么?So\@cols的可能副本必须是字符串连接的,对吗?我们不能使用sp_executesql和参数绑定在其中插入\@cols?即使我们自己构造\@cols,但如果它包含恶意SQL呢。在连接它并执行它之前,我可以采取任何其他缓解措施吗?你会如何对这上面的行和列进行排序?@PatrickSchomburg有多种方法-如果你想对@cols
进行排序,那么你可以删除DISTINCT
,并在获得数据时使用GROUP BY
和ORDER BY
@cols
的列表。我试试看。那排呢?我也用了一个日期,但它没有按顺序出现。不管怎样,我把订单放错地方了。干得好!请选择TVF而不是存储过程。从这样的TVF中选择会很方便。不幸的是,据我所知,不是这样,因为你们不能有一个TVF的动态结构。在TVF中必须有一组静态列。不幸的是,这比@mkdave99的答案要痛苦得多。首先,如果在构建透视列列表时需要对其进行排序,则需要记住。其次,您还必须记住额外的愚蠢的MSSQL攻击,包括不必要的表别名t
。第三,它也比@mkdave99的答案稍微慢一点。酷!您知道如何在值列名中添加前缀吗?这样您就可以得到一个列结果:date、Amount\u ABC、,