动态Pivot(1)

原文 http://book.51cto.com/art/200710/58874.htm

7.7  动态Pivot

作为另外一个练习,假设你要编写一个存储过程,它生成动态Pivot查询。这个存储过程接收这些参数(都是Unicode字符串类型):@query、@on_rows、@on_cols、@agg_func和 @agg_col。你要根据这些输入构造PIVOT查询串并动态执行。下面是对输入参数的描述:

 @query 提供给PIVOT 运算符的查询或表/视图的名称。
 @on_rows 用作分组列的列/表达式列表。
 @on_cols 被旋转的列或表达式;该列中的不重复值将成为目标列的名称。
 @agg_func 聚合函数 (MIN、MAX、SUM、COUNT等) 。
 @agg_col 作为聚合函数的输入的列/表达式。

如果还没搞清楚要求和每个输入的含义,可以直接跳到代码清单7-8中的解决方案,分析代码清单后面的调用示例和输出以及对该解决方案所作的解释。然后在查看这个方案之前尝试提供自己的解决方案。

重要 该解决方案的编程实践非常糟糕,而且存在安全隐患。我将用这个解决方案讨论其中存在的缺陷,然后提出更健壮更安全的解决方案。

代码清单7-8显示了该任务的建议的解决方案。

代码清单7-8  创建存储过程sp_pivot的脚本

 

USE master;GO
IF OBJECT_ID('dbo.sp_pivot') IS NOT NULLDROP PROC dbo.sp_pivot;GO
CREATE PROC dbo.sp_pivot@query    AS NVARCHAR(MAX),@on_rows  AS NVARCHAR(MAX),@on_cols  AS NVARCHAR(MAX),@agg_func AS NVARCHAR(MAX) = N'MAX',@agg_col  AS NVARCHAR(MAX)AS
DECLARE@sql     AS NVARCHAR(MAX),@cols    AS NVARCHAR(MAX),@newline AS NVARCHAR(2);
SET @newline = NCHAR(13) + NCHAR(10);
-- 如果输入是有效的表或视图,则使用它构造SELECT语句-- IF COALESCE(OBJECT_ID(@query, N'U'),OBJECT_ID(@query, N'V')) IS NOT NULLSET @query = N'SELECT * FROM ' + @query;
-- 把该查询用作派生表SET @query = N'(' + @query + @newline + N'       ) AS Query';
-- 处理@agg_col中的*IF @agg_col = N'*'SET @agg_col = N'1';
-- 构造列的列表SET @sql =N'SET @result = '                                    + @newline +N'  STUFF('                                          + @newline +N'    (SELECT N'','' + '+ N'QUOTENAME(pivot_col) AS [text()]'       + @newline +N'     FROM (SELECT DISTINCT('+ @on_cols + N') AS pivot_col'              + @newline +N'           FROM' + @query + N') AS DistinctCols'   + @newline +N'     ORDER BY pivot_col'                           + @newline +N'     FOR XML PATH('''')),'                         + @newline +N'    1, 1, N'''');'
EXEC sp_executesql@stmt   = @sql,@params = N'@result AS NVARCHAR(MAX) OUTPUT',@result = @cols OUTPUT; -- 创建 PIVOT 查询SET @sql =N'SELECT *'                                           + @newline +N'FROM'                                               + @newline +N'  ( SELECT '                                        + @newline +N'      ' + @on_rows + N','                           + @newline +N'      ' + @on_cols + N' AS pivot_col,'              + @newline +N'      ' + @agg_col + N' AS agg_col'                 + @newline +N'    FROM '                                          + @newline +N'      ' + @query                                    + @newline +N'  ) AS PivotInput'                                  + @newline +N'  PIVOT'                                            + @newline +N'    ( ' + @agg_func + N'(agg_col)'                  + @newline +N'      FOR pivot_col'                                + @newline +N'        IN(' + @cols + N')'                         + @newline +N'    ) AS PivotOutput;'
EXEC sp_executesql @sql;GO

下面我通过这个练习来解释如何实现动态Pivot并讨论不良的编程实践和安全缺陷。我先讨论代码背后的逻辑,然后描述其中的不良的编程实践和缺陷并提出一个更健壮更安全的解决方案。

这个存储过程是在master中创建的特殊存储过程,这样它就可以在所有数据库中运行。之前提到过,动态执行是在当前数据库的上下文中调用的。这意味着该存储过程的代码实际上是在当前数据库的上下文中运行的,并与本地用户对象进行交互。

代码检查输入参数@query是否是有效的表或视图。如果是,代码使用它构造一个SELECT语句,并保存到@query中;如果不是,则假定它已经是一个查询。

然后通过在两边添加圆括号和派生表别名(AS Query)使该查询成为一个派生表。结果字符串保存到@query中。派生表将用于确定需要被旋转的不重复值(这些值位于输入参数@on_cols中保存的列/表达式),并作为PIVOT运算符的输入表表达式。
因为PIVOT运算符不支持*作为聚合函数的输入。例如,COUNT(*)—代码把@agg_col中的*替换为1。

然后代码串联一个动态查询字符串并保存到@sql变量中。这个字符串中的代码用于构造列列表(column list),它将用于PIVOT的IN子句。这个列列表通过FOR XML PATH查询构造。该查询串联@on_cols中的列/表达式的不重复值。

这个查询字符串(保存在@sql)被动态调用。动态代码通过一个输出参数返回一个包含列列表的字符串,并把它赋给变量@cols。

后面的代码构造真正的PIVOT查询字符串,并保存在变量@sql中。它对派生表(别名是Query)构造一个外部查询,这个派生表保存在@query中。外部查询创建另一个派生表PivotInput。外部查询中的SELECT列表包含下面的项:

 保存在@on_rows中的分组列/表达式列表,PIVOT运算符利用它执行隐式的分组操作。
 被旋转的列/表达式(保存在@on_cols),别名为pivot_col。
 用作聚合函数输入的列(保存在@agg_col中),别名为agg_col。

PIVOT运算符基于派生表PivotInput。在PIVOT后面的圆括号包含下列各项:聚合函数(@agg_func)以及作为其输入的聚合列(agg_col),IN子句的圆括号中的列列表(@cols)。最外面的查询使用SELECT * 获取PIVOT操作返回的所有列。

最后,动态调用@sql变量中的PIVOT查询。

更多信息 关于PIVOT运算符更深入的讨论,请参考Inside T-SQL Querying。
sp_pivot存储过程非常灵活,尽管这种灵活性也带来了非常高的安全成本,这一点我们将在后面讨论。为演示它的灵活性,我将提供三个示例,分别通过不同的输入调用它。你要确保学习并掌握了这些输入参数的意义。

下面的代码生成每个员工每年的订单合计,按订单月份旋转,它将生成表7-12所显示的输出。

EXEC Northwind.dbo.sp_pivot@query    = N'dbo.Orders',@on_rows  = N'EmployeeID AS empid, YEAR(OrderDate) AS order_year',@on_cols  = N'MONTH(OrderDate)',@agg_func = N'COUNT',@agg_col  = N'*';

表7-12  按订单月份旋转的每个员工每年的订单合计

 


empid


order_year


1


2


3


4


5


6


7


8


9


10


11


12


1


1996


0


0


0


0


0


0


1


5


5


2


4


9


2


1996


0


0


0


0


0


0


1


2


5


2


2


4


3


1996


0


0


0


0


0


0


4


2


1


3


4


4


empid


order_year


1


2


3


4


5


6


7


8


9


10


11


12


4


1996


0


0


0


0


0


0


7


5


3


8


5


3


5


1996


0


0


0


0


0


0


3


0


1


2


2


3


6


1996


0


0


0


0


0


0


2


4


3


0


3


3


7


1996


0


0


0


0


0


0


0


1


2


5


3


0


8


1996


0


0


0


0


0


0


2


6


3


2


2


4


9


1996


0


0


0


0


0


0


2


0


0


2


0


1


1


1997


3


2


5


1


5


4


7


3


8


7


3


7


2


1997


4


1


4


3


3


4


3


1


7


1


5


5


3


1997


7


9


3


5


5


6


2


4


4


7


8


11


4


1997


8


6


4


8


5


5


6


11


5


7


6


10


5


1997


0


0


3


0


2


2


1


3


2


3


1


1


6


1997


2


2


2


4


2


2


2


2


1


4


5


5


7


1997


3


1


2


6


5


1


5


3


5


1


1


3


8


1997


5


8


6


2


4


3


6


5


3


7


2


3


9


1997


1


0


1


2


1


3


1


1


2


1


3


3


1


1998


9


9


11


8


5


0


0


0


0


0


0


0


2


1998


7


3


9


18


2


0


0


0


0


0


0


0


3


1998


10


6


12


10


0


0


0


0


0


0


0


0


4


1998


6


14


12


10


2


0


0


0


0


0


0


0


5


1998


4


6


2


1


0


0


0


0


0


0


0


0


6


1998


3


4


7


5


0


0


0


0


0


0


0


0


7


1998


4


6


4


9


2


0


0


0


0


0


0


0


8


1998


7


2


10


9


3


0


0


0


0


0


0


0


9


1998


5


4


6


4


0


0


0


0


0


0


0


0

下面的代码生成按订单年份旋转的每个员工的订单金额合计(数量*单价),生成的输入如表7-13所示。

EXEC Northwind.dbo.sp_pivot@query    = N'SELECT O.OrderID, EmployeeID, OrderDate, Quantity, UnitPriceFROM dbo.Orders AS OJOIN dbo.[Order Details] AS ODON OD.OrderID = O.OrderID',@on_rows  = N'EmployeeID AS empid',@on_cols  = N'YEAR(OrderDate)',@agg_func = N'SUM',@agg_col  = N'Quantity*UnitPrice';

表7-13  按订单年份旋转的每个员工的订单金额合计 


empid


1996


1997


1998


3


19231.80


111788.61


82030.89


6


17731.10


45992.00


14475.00


9


11365.70


29577.55


42020.75


7


18104.80


66689.14


56502.05


1


38789.00


97533.58


65821.13


4


53114.80


139477.70


57594.95


2


22834.70


74958.60


79955.96


5


21965.20


32595.05


21007.50


8


23161.40


59776.52


50363.11

下面的代码生成按订单年份和月份旋转的每个商店的销量数量合计,生成的输出如表7-14所示。

EXEC pubs.dbo.sp_pivot@query    = N'SELECT stor_id, YEAR(ord_date) AS oy, MONTH(ord_date) AS om, qtyFROM dbo.sales',@on_rows  = N'stor_id',@on_cols  = N'CAST(oy AS VARCHAR(4)) + ''_''+ RIGHT(''0'' + CAST(om AS VARCHAR(2)), 2)',@agg_func = N'SUM',@agg_col  = N'qty';

表7-14  按订单年份和月份旋转的每个商店的销量数量合计 


stor_id


1992_06


1993_02


1993_03


1993_05


1993_10


1993_12


1994_09


6380


NULL


NULL


NULL


NULL


NULL


NULL


8


7066


NULL


NULL


NULL


50


NULL


NULL


75


7067


80


NULL


NULL


NULL


NULL


NULL


10


7131


NULL


NULL


NULL


85


NULL


NULL


45


7896


NULL


35


NULL


NULL


15


10


NULL


8042


NULL


NULL


25


30


NULL


NULL


25

时间: 2024-09-20 05:34:03

动态Pivot(1)的相关文章

动态Pivot(2)

原文  http://book.51cto.com/art/200710/58875.htm 存储过程sp_pivot的实现包含糟糕的编程习惯和安全隐患.就像我在本章的前面提到的,微软强烈建议不要在用户定义存储过程的名称中使用sp_前缀.一方面,把存储过程创建为特殊存储过程的会带来灵活性:但另一方面,你所依赖的行为得不到任何支持.所以最好放弃这种通过创建以sp_为前缀的存储过程获取的灵活性,在用户数据库中使用其他前缀创建用户定义存储过程.代码定义的所有输入参数都未限制大小(使用MAX说明符),而

SQL Server中如何动态行转列

SQL Server 动态行转列(参数化表名.分组列.行转列字段.字段值) 一.本文所涉及的内容(Contents) 本文所涉及的内容(Contents) 背景(Contexts) 实现代码(SQL Codes) 方法一:使用拼接SQL,静态列字段: 方法二:使用拼接SQL,动态列字段: 方法三:使用PIVOT关系运算符,静态列字段: 方法四:使用PIVOT关系运算符,动态列字段: 扩展阅读一:参数化表名.分组列.行转列字段.字段值: 扩展阅读二:在前面的基础上加入条件过滤: 参考文献(Refe

SQL Server 动态行转列

  一.本文所涉及的内容(Contents) 本文所涉及的内容(Contents) 背景(Contexts) 实现代码(SQL Codes) 方法一:使用拼接SQL,静态列字段; 方法二:使用拼接SQL,动态列字段; 方法三:使用PIVOT关系运算符,静态列字段; 方法四:使用PIVOT关系运算符,动态列字段; 二.背景(Contexts) 其实行转列并不是一个什么新鲜的话题了,甚至已经被大家说到烂了,网上的很多例子多多少少都有些问题,所以我希望能让大家快速的看到执行的效果,所以在动态列的基础上

SQL server 2005 PIVOT运算符的使用

原文:SQL server 2005 PIVOT运算符的使用         PIVOT,UNPIVOT运算符是SQL server 2005支持的新功能之一,主要用来实现行到列的转换.本文主要介绍PIVOT运算符的操作,以及如何实现动态PIVOT的行列转换.        关于UNPIVOT及SQL server 2000下的行列转换请参照本人的其它文章. 一.PIVOT的语法 SELECT [non-pivoted column], -- optional [additional non-p

SQL Server 动态行转列(参数化表名、分组列、行转列字段、字段值)

原文:SQL Server 动态行转列(参数化表名.分组列.行转列字段.字段值) 一.本文所涉及的内容(Contents) 本文所涉及的内容(Contents) 背景(Contexts) 实现代码(SQL Codes) 方法一:使用拼接SQL,静态列字段: 方法二:使用拼接SQL,动态列字段: 方法三:使用PIVOT关系运算符,静态列字段: 方法四:使用PIVOT关系运算符,动态列字段: 扩展阅读一:参数化表名.分组列.行转列字段.字段值: 扩展阅读二:在前面的基础上加入条件过滤: 参考文献(R

Android 下拉刷新框架实现、仿新浪微博、QQ好友动态滑到底部自动加载

苦苦找寻的2个版本,经过测试好用.再次感谢原作者! 1.第一个版本 Android 下拉刷新框架实现 http://blog.csdn.net/leehong2005/article/details/12567757 前段时间项目中用到了下拉刷新功能,之前在网上也找到过类似的demo,但这些demo的质量参差不齐,用户体验也不好,接口设计也不行.最张没办法,终于忍不了了,自己就写了一个下拉刷新的框架,这个框架是一个通用的框架,效果和设计感觉都还不错,现在分享给各位看官. 1. 关于下拉刷新 下拉

C#实现Excel动态生成PivotTable_C#教程

Excel 中的透视表对于数据分析来说,非常的方便,而且很多业务人员对于Excel的操作也是非常熟悉的,因此用Excel作为分析数据的界面,不失为一种很好的选择.那么如何用C#从数据库中抓取数据,并在Excel 动态生成PivotTable呢?下面结合实例来说明. 一般来说,数据库的设计都遵循规范化的原则,从而减少数据的冗余,但是对于数据分析来说,数据冗余能够提高数据加载的速度,因此为了演示透视表,这里现在数据库中建立一个视图,将需要分析的数据整合到一个视图中.如下图所示: 数据源准备好后,我们

SQL Server中行列转换 Pivot UnPivot

原文:SQL Server中行列转换 Pivot UnPivot PIVOT用于将列值旋转为列名(即行转列),在SQL Server 2000可以用聚合函数配合CASE语句实现 PIVOT的一般语法是:PIVOT(聚合函数(列) FOR 列 in (-) )AS P 完整语法: table_source PIVOT( 聚合函数(value_column) FOR pivot_column IN(<column_list>) )   UNPIVOT用于将列明转为列值(即列转行),在SQL Ser

oracle-Oracle动态行转列问题

问题描述 Oracle动态行转列问题 有什么好的动态行转列方法?(动态噢!) 看过pivot xml函数但结果是CLOB类型,Java读取没写出来, 请问decode函数能实现动态的行转列吗? 解决方案 http://www.cnblogs.com/QQParadise/articles/1712093.html 解决方案二: /*物料 需要数量 需要仓库 现存量仓库 现存量仓库数量 批次 A1 2 C1 C1 20 123 A1 2 C1 C2 30 111 ......答案就在这里:ORAC