您的位置:首页 > 数据库

巧用SQL PIVOT实现环境监测数据的“竖横”转换

2010-05-04 14:11 721 查看
水环境监测数据存储结构中有一种模式叫做"竖表模式",即在监测数据表中,某个点位在某个时间点上各监测项目的浓度测定值在物理表中存储在多条记录中,呈竖状分布。监测数据表中包含测点代码、监测时间、监测项目代码、浓度测定值等,所有监测项目的浓度测定值都存储在同一个字段中,加以监测项目代码作为区分字段。

"竖表模式"的最大特点是灵活、具有较好的扩展性。针对新增监测因子,只需要在监测因子编码表进行维护,增加新的监测因子,就能满足数据存储的要求。这种设计模式非常适合自动监测站以及污染源监测数据的存储。但是"竖表模式"也有一些不足之处,比如数据可读性不好,难以表达某个点位某个时间点上的整体数据情况,不符合传统习惯的"横表"数据阅读方式。如何让"竖表"转换成"横表"是很多应用系统中都要遇到的一个问题。

现假设有一张名称为"自动站监测数据日均值表" 的数据表,详细数据如下:

测点代码







监测项目

污染物浓度

P001

2008
2
1
生化需氧量

298
P001

2008
2
1
总磷

0.526
P001

2008
2
1
化学需氧量

414
P001

2008
2
1
氨氮

1.09
P002

2008
2
3
生化需氧量

198
P002

2008
2
3
总磷

0.426
P002

2008
2
3
化学需氧量

314
P002

2008
2
3
总氮

3.71
   
为了能够将数据转换为横表形式,首先,在SQL Server 2005(或2008)数据库中建立存储过程usp_pivot。SQL创建语句如下:

   

CREATE PROC [dbo].[usp_pivot]

   @schema_name AS sysname    = N'dbo',-- 表/视图的架构

   @object_name AS sysname    = NULL, -- 表/视图的名称

   @on_rows   AS sysname    = NULL, -- 分组列--可以是以,相隔的多个列

   @on_cols   AS sysname    = NULL, -- 旋转列

   @agg_func   AS NVARCHAR(12) = N'MAX',-- 聚集函数

   @agg_col   AS sysname    = NULL -- 统计列

AS

   DECLARE

   @object AS NVARCHAR(600),

   @sql   AS NVARCHAR(MAX),

   @cols   AS NVARCHAR(MAX),

   @newline AS NVARCHAR(2),

   @msg   AS NVARCHAR(500);

   

   SET @newline = NCHAR(13) + NCHAR(10);

   SET @object = QUOTENAME(@schema_name) + N'.' + QUOTENAME(@object_name);

   

   -- 检查是否缺少输入

   IF @schema_name IS NULL

   OR @object_name IS NULL

   OR @on_rows   IS NULL

   OR @on_cols   IS NULL

   OR @agg_func   IS NULL

   OR @agg_col   IS NULL

   BEGIN

    SET @msg = N'Missing input parameters: '

    + CASE WHEN @schema_name IS NULL

    THEN N'@schema_name;' ELSE N'' END

    + CASE WHEN @object_name IS NULL

    THEN N'@object_name;' ELSE N'' END

    + CASE WHEN @on_rows   IS NULL

    THEN N'@on_rows;'   ELSE N'' END

    + CASE WHEN @on_cols   IS NULL

    THEN N'@on_cols;'   ELSE N'' END

    + CASE WHEN @agg_func   IS NULL

    THEN N'@agg_func;'   ELSE N'' END

    + CASE WHEN @agg_col   IS NULL

    THEN N'@agg_col;'   ELSE N'' END

    RAISERROR(@msg, 16, 1);

    RETURN;

   END

    

   --只允许已存在的表或视图作为输入对象

   IF COALESCE(OBJECT_ID(@object, N'U'),

   OBJECT_ID(@object, N'V')) IS NULL

   BEGIN

    SET @msg = N'%s is not an existing table or view in the database.';

    RAISERROR(@msg, 16, 1, @object);

    RETURN;

   END

   

   -- 检查@on_rows, @on_cols, @agg_col 中的列名称是否存在

   IF --COLUMNPROPERTY(OBJECT_ID(@object), @on_rows, 'ColumnId') IS NULL OR --为允许多个分组列,注释

   COLUMNPROPERTY(OBJECT_ID(@object), @on_cols, 'ColumnId') IS NULL

   OR COLUMNPROPERTY(OBJECT_ID(@object), @agg_col, 'ColumnId') IS NULL

   BEGIN

    SET @msg = N'%s, %s and %s must'

    + N' be existing column names in %s.';

    RAISERROR(@msg, 16, 1, @on_rows, @on_cols, @agg_col, @object);

    RETURN;

   END

   -- 检查@agg_func是否是已知的函数

   -- 根据需要增加该清单并相应调整@agg_func的大小 

   IF @agg_func NOT IN

   (N'AVG', N'COUNT', N'COUNT_BIG', N'SUM', N'MIN', N'MAX',

   N'STDEV', N'STDEVP', N'VAR', N'VARP')

   BEGIN

    SET @msg = N'%s is an unsupported aggregate function.';

    RAISERROR(@msg, 16, 1, @agg_func);

    RETURN;

   END

   

   --构造列列表

   SET @sql =

   N'SET @result = '                   + @newline +

   N' STUFF('                      + @newline +

   N'   (SELECT N'','' + '

   + N'QUOTENAME(pivot_col) AS [text()]'    + @newline +

   N'   FROM (SELECT DISTINCT('

   + QUOTENAME(@on_cols) + N') AS pivot_col'  + @newline +

   N'      FROM ' + @object + 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;

   

   --检查@cols 是否存在SQL 注入尝试

   IF UPPER(@cols) LIKE UPPER(N'%0x%')

   OR UPPER(@cols) LIKE UPPER(N'%;%')

   OR UPPER(@cols) LIKE UPPER(N'%''%')

   OR UPPER(@cols) LIKE UPPER(N'%--%')

   OR UPPER(@cols) LIKE UPPER(N'%/*%*/%')

   OR UPPER(@cols) LIKE UPPER(N'%EXEC%')

   OR UPPER(@cols) LIKE UPPER(N'%xp[_]%')

   OR UPPER(@cols) LIKE UPPER(N'%sp[_]%')

   OR UPPER(@cols) LIKE UPPER(N'%SELECT%')

   OR UPPER(@cols) LIKE UPPER(N'%INSERT%')

   OR UPPER(@cols) LIKE UPPER(N'%UPDATE%')

   OR UPPER(@cols) LIKE UPPER(N'%DELETE%')

   OR UPPER(@cols) LIKE UPPER(N'%TRUNCATE%')

   OR UPPER(@cols) LIKE UPPER(N'%CREATE%')

   OR UPPER(@cols) LIKE UPPER(N'%ALTER%')

   OR UPPER(@cols) LIKE UPPER(N'%DROP%')

   --其他一些可能用于SQL注入的字符串

   BEGIN

    SET @msg = N'Possible SQL injection attempt.';

    RAISERROR(@msg, 16, 1);

    RETURN;

   END

   

   --创建PIVOT查询

   SET @sql =

   N'SELECT *'                      + @newline +

   N'FROM'                        + @newline +

   N' ( SELECT '                    + @newline +

   N'    ' + @on_rows + N','        + @newline +--QUOTENAME(@on_rows) + N','--为允许多个分组列,注释

   N'    ' + QUOTENAME(@on_cols) + N' AS pivot_col,' + @newline +

   N'    ' + QUOTENAME(@agg_col) + N' AS agg_col'   + @newline +

   N'   FROM ' + @object                + @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;

   
存储过程建立之后,在SQL Server Management Studio中使用以下语句调用查询:
EXEC dbo.usp_pivot @object_name = N'自动站监测数据日均值表',@on_rows = N'测点代码,年,月,日',@on_cols = N'监测项目',@agg_func = N'AVG',@agg_col = N'污染物浓度';

   

通过执行,顺利地实现了"竖表"向"横表"的转换,查询结果如下:

测点代码







生化需氧量

总磷

化学需氧量

氨氮

总氮

P001

2008
2
1
298
0.526
414
1.09
NULL
P002

2008
2
3
198
0.426
314
NULL
3.71
   

SQL Server2005新增的T-SQL语句PIVOT能够很容易地将"竖表"处理为"横表",通过建立公用的存储过程usp_pivot,参数传递表名、分组列、旋转列、聚合函数、统计列,巧妙地将数据变成"横表"形式,让数据展现符合阅读习惯。除了自动站监测数据,该存储过程还可用以转换显示类似于企业污染源监测等"竖表"数据。

    备注:原始源码出自《Microsoft SQL Server 2005技术内幕:T-SQL程序设计》。

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息