动态Pivot(1)

简介: 原文 http://book.51cto.com/art/200710/58874.htm 7.7  动态Pivot 作为另外一个练习,假设你要编写一个存储过程,它生成动态Pivot查询。这个存储过程接收这些参数(都是Unicode字符串类型):@query、@on_rows、@on_cols、@agg_func和 @agg_col。

原文 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 NULL
DROP 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 NULL
SET @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, UnitPrice
FROM dbo.Orders AS O
JOIN dbo.[Order Details] AS OD
ON 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, qty
FROM 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

目录
相关文章
|
Android开发
【RecyclerView】 十四、GridLayoutManager 网格布局管理器 ( GridLayoutManager.SpanSizeLookup 指定 item 元素占用网格个数 )
【RecyclerView】 十四、GridLayoutManager 网格布局管理器 ( GridLayoutManager.SpanSizeLookup 指定 item 元素占用网格个数 )
1344 0
【RecyclerView】 十四、GridLayoutManager 网格布局管理器 ( GridLayoutManager.SpanSizeLookup 指定 item 元素占用网格个数 )
|
6月前
|
搜索推荐
实现bubble_sort冒泡排序函数,排序任意类型数据
实现bubble_sort冒泡排序函数,排序任意类型数据
50 0
|
6月前
|
小程序
排序sort()排序用法
排序sort()排序用法
|
6月前
|
C++ 容器
【C++STL基础入门】list交换、翻转,排序、合并和拼接操作
【C++STL基础入门】list交换、翻转,排序、合并和拼接操作
605 0
|
Python
python之sort,reverse的定义和使用,列表元素最大最小值的求解,列表嵌套的基本使用。
python之sort,reverse的定义和使用,列表元素最大最小值的求解,列表嵌套的基本使用。
|
C++ 容器
sort函数对结构体|pair对组|vector容器|map排序|二维数组的第x列 的排序
sort函数对结构体|pair对组|vector容器|map排序|二维数组的第x列 的排序
153 0
|
算法 前端开发
前端算法-查找旋排序数组中最小值
前端算法-查找旋转排序数组中最小值
|
索引
labview数组数据一维数组二维数组索引行列元素替换子数组排序
labview数组数据一维数组二维数组索引行列元素替换子数组排序
241 0
|
C++ 容器
C++ vector 删除和排序的相关函数
C++ vector 删除和排序的相关函数
88 0
|
Web App开发 算法 搜索推荐
数组的 sort() 方法详解
纸上得来终觉浅,绝知此事要躬行!
216 0
数组的 sort() 方法详解