带动态WHERE列的参数化SQL

use*_*415 3 sql t-sql sql-server-2012

我正在尝试编写简单的过滤,用户可以在其中输入要过滤的列和值.棘手的部分是动态选择要过滤的列.

我在网上找到了几个解决方案,我不确定要实施哪些解决方案.我倾向于倾向于性能而不是可维护性.任何意见将不胜感激.

假设我有一个表"t",它有5个VARCHAR列:"c1","c2","c3","c4"和"c5".

解决方案1 ​​ - 简单方法

我可以使用动态SQL.有点像:

DECLARE @sql VARCHAR(MAX) = 'SELECT * FROM t WHERE ' + @columnName + ' = ''' + @columnValue + ''';'
EXEC (@sql);
Run Code Online (Sandbox Code Playgroud)

会出现类似的结果:

SELECT *
FROM t
WHERE c1 = 'asdf'
;
Run Code Online (Sandbox Code Playgroud)

出于以下两个原因,我不想使用此解决方案.在走下兔子洞之前,我主要将此作为一个简单的参考点.

  1. 它不防止SQL注入.
  2. 即使我要参数化columnValue,我也会为5列中的每一列缓存5个不同的执行计划,因为你无法参数化@columnName.

解决方案2 - 或者是

可以使用只有两个参数的一系列OR.所以我们说:

@columnName = 'c1'
@columnValue = 'asdf'
Run Code Online (Sandbox Code Playgroud)

然后SQL将成为:

SELECT *
FROM t
WHERE (@columnName = 'c1' AND c1 = @columnValue)
  OR (@columnName = 'c2' AND c2 = @columnValue)
  OR (@columnName = 'c3' AND c3 = @columnValue)
  OR (@columnName = 'c4' AND c4 = @columnValue)
  OR (@columnName = 'c5' AND c5 = @columnValue)
  OR (@columnName IS NULL AND 0 = 0)
;
Run Code Online (Sandbox Code Playgroud)

我通常尽可能使用OR来避免离开.我记得在某个地方阅读它会遇到性能问题,但我不是DBA而且不能支持它.思考?

解决方案3 - COALESCE

此解决方案依赖于为每列提供参数.所以参数将是:

@c1 = 'asdf';
@c2 = NULL;
@c3 = NULL;
@c4 = NULL;
@c5 = NULL;
Run Code Online (Sandbox Code Playgroud)

SQL出来:

SELECT *
FROM t
WHERE c1 = COALESCE(@c1, c1)
  AND c2 = COALESCE(@c2, c2)
  AND c3 = COALESCE(@c3, c3)
  AND c4 = COALESCE(@c4, c4)
  AND c5 = COALESCE(@c5, c5)
;
Run Code Online (Sandbox Code Playgroud)

有没有人对实施什么方法有意见?我倾向于COALESCE,但我没有硬数据或经验.也许有更好的做事方式?

Aar*_*and 10

最安全的方式:

DECLARE @sql NVARCHAR(MAX) = N'SELECT * FROM dbo.t WHERE ' 
 + QUOTENAME(@columnName) + ' = @ColumnValue;';

EXEC sp_executesql @sql, N'@ColumnValue VARCHAR(255)', @ColumnValue;
Run Code Online (Sandbox Code Playgroud)

为了进一步防止SQL注入,您可以先检查:

IF @columnName NOT IN (N'c1',N'c2',N'c3',N'c4',N'c5')
BEGIN
  RAISERROR('Nice try! %s is not valid.', 11, 1, @columnName);
  RETURN;
END
Run Code Online (Sandbox Code Playgroud)

或者@HABO建议,针对sys.columns目录视图:

IF NOT EXISTS 
(
   SELECT 1 FROM sys.columns WHERE name = @ColumnName
     AND [object_id] = OBJECT_ID('dbo.t')
)
BEGIN
  RAISERROR('Nice try! %s is not valid.', 11, 1, @columnName);
  RETURN;
END
Run Code Online (Sandbox Code Playgroud)

特别是当与之结合使用时Optimize for ad hoc workloads,可能有5种不同的执行计划 - 因为它们毕竟是5种不同的查询,可以根据不同列上的索引,这些列中的数据分布等进行不同的优化.

ORCOALESCE版本 - 除非您每次都支付一次编译命中 - 无论提供哪一列,都会使用相同的计划,因此它可能适用于某些情况,但对其他情况则不太好.每个人获得的计划都不会基于什么是最好的,而是首先发送哪个参数.

此外,如果您担心性能,可能不要使用SELECT *- 特别是如果您不需要所有列.即使你这样做,你也不知道有人在表中添加blob或几何或XML或其他昂贵的列,并且即使它不关心它,你的代码也会检索它.