-3

帮助我更快地执行此动态语句,该语句将从表中获取每列的前 n 个值。

该表将有“n”个列,但会有一个主键。NULL 是无法避免的,因为任何其他值都被视为 VALID 并且应该进入数据库。

桌子

+-------+------+------+------+
| Depth | RPMA | ROP  | WOB  |
+-------+------+------+------+
|  6111 |   72 | 14.6 | 0    |
|  6110 |   72 | 14.1 | 1    |
|  6109 |   66 | 15.2 | NULL |
|  6108 |   68 | 14   | NULL |
|  6107 |   69 | 14   | NULL |
|  6106 |   61 | 14.8 | NULL |
|  6105 |   70 | NULL | NULL |
|  6104 |   64 | NULL | NULL |
|  6103 |   59 | NULL | NULL |
|  6102 |   49 | NULL | NULL |
+-------+------+------+------+

结果集,

+-------+------+------+------+
| Depth | RPMA | ROP  | WOB  |
+-------+------+------+------+
|  6111 | 72   | NULL | 0    |
|  6110 | 72   | NULL | 1    |
|  6109 | NULL | 15.2 | NULL |
|  6106 | NULL | 14.8 | NULL |
+-------+------+------+------+

用于获取当前结果集的动态 SQL,

DECLARE @Columns VARCHAR(MAX); -- Param1
DECLARE @IdxColumn VARCHAR(250); --Param2
DECLARE @Limit VARCHAR(11); --Param3
DECLARE @SQL NVARCHAR(MAX)=''; --Param4

DECLARE @query NVARCHAR(MAX) = ' SELECT TOP (' + @pLimit + ') ' + @IdxColumn + ', ' + @Columns + ' FROM [Table] WHERE '

SET @SQL = @query + REPLACE(@Columns,',', ' IS NOT NULL ORDER BY '+ @IdxColumn + ' ASC ' + N' UNION' + @query) + ' IS NOT NULL ORDER BY ' + @IdxColumn

SET @SQL = 'SELECT * FROM ('+@SQL+') T ORDER BY ' + @IdxColumn + ' ASC'   

EXEC (@SQL)
4

2 回答 2

2

以下查询应适用于示例数据:

WITH cte AS (
  SELECT *
       , DENSE_RANK() OVER (ORDER BY RPMA DESC) AS RPMA_RANK
       , DENSE_RANK() OVER (ORDER BY ROP DESC) AS ROP_RANK
       , DENSE_RANK() OVER (ORDER BY WOB DESC) AS WOB_RANK
  FROM t
)
SELECT Depth
     , CASE WHEN RPMA_RANK <= 2 THEN RPMA END
     , CASE WHEN ROP_RANK <= 2 THEN ROP END
     , CASE WHEN WOB_RANK <= 2 THEN WOB END
FROM cte
WHERE RPMA_RANK <= 2
OR ROP_RANK <= 2
OR WOB_RANK <= 2

请注意,它为 RPMA 列返回三行(有两个 72 和一个 70)。对于 n 列,您需要使用动态 SQL。

于 2019-11-26T13:51:17.980 回答
2

这并不能回答问题,但确实修复了上述可怕的安全漏洞。

以上存在多个问题,因此请注意,这是对您拥有的 SQL 的重大必要的更改。现在,您正在向代码中注入未经处理的参数,并且还使用了太大的数据类型。@Columnsvarchar(MAX),这意味着有人有 2GB 的字符要注入您的系统。@IdxColumn是 avarchar(250)并引用单个列;一个对象最多可以有 128 个字符,因此不需要其他 122 个字符。也是@Limit一个varchar,尽管是一个int并且应该是一个参数。

首先,我建议使用表类型对象,而不是使用varchar(MAX)for :@Columns

CREATE TYPE dbo.ObjectList (ObjectName sysname);

sysnamenvarchar(128) NOT NULL;的同义词 并且是用于 SQL Server 中对象名称的数据类型。然后,您需要将INSERT列的名称转换为声明的表类型参数;每个列名一行

然后我们可以安全地注入和参数化您的查询:

--Parameters
DECLARE @Columns dbo.ObjectList,
        @IdxColumn sysname, --sysname as well
        @Limit int; --not varchar

--Variables needed in the SQL:

DECLARE @SQL nvarchar(MAX),
        @CRLF nchar(2) = NCHAR(13) + NCHAR(10);

SET @SQL = N'SELECT TOP (@Limit)' + @CRLF + 
           N'           ' + QUOTENAME(@IdxColumn) + N',' + @CRLF +
           STUFF((SELECT N',' + @CRLF +
                         N'           ' + QUOTENAME(C.ObjectName)
                  FROM @Columns C
                  FOR XML PATH(N''),TYPE).value('.','nvarchar(MAX)'),1,3,N'') + @CRLF +
           N'FROM dbo.[Table]' + @CRLF + --Should dbo.[Table] also not be safely injected?
           N'WHERE ' +
           STUFF((SELECT @CRLF + 
                         N'   OR ' + QUOTENAME(C.ObjectName) + N' IS NOT NULL'
                  FROM @Columns C
                  FOR XML PATH(N''),TYPE).value('.','nvarchar(MAX)'),1,8,N'') + @CRLF + 
          N'ORDER BY ' + QUOTENAME(@IdxColumn) + N' ASC;'

EXEC sp_executesql @SQL, N'@Limit int', @Limit;
于 2019-11-26T15:51:33.843 回答