使用 SQL 创建动态 where 子句

本文翻译自 Creating Dynamic Where Clauses with SQL
译者:耐心的农夫2020
时间:2020-04-12

使用用户界面数据库查询时,一种非常常见的场景是“动态搜索查询”,即在 where 子句中包含可选条件的 select 语句。尽管有很多方法可以实现这种效果,但是不同的方法复杂程度不一样。总的来说,不同方法在实现、可读性、性能和可读性方面做了取舍。本文介绍了实现这个效果的不同方法。

配置介绍

在我讨论不同方法之前,我想要先展示一下创建表和插入记录的脚本,以便说明接下来所有的概念。下面的清单创建了一张具有两个 VARCHAR 字段的表,并填充了一些数据,包括空值。

CREATE TABLE TestData
(
    ColumnA VARCHAR(20),
    ColumnB VARCHAR(25)
)

-- Insert test data
INSERT INTO TestData
    (ColumnA)
VALUES
    ('Me')

INSERT INTO TestData
    (ColumnA, ColumnB)
VALUES
    ('Me', 'You')

INSERT INTO TestData
    (ColumnA, ColumnB)
VALUES
    ('You', 'Me')

运行上面的脚本后,我们就有了下面这样一张表。

ColumnA ColumnB
Me null
Me You
You Me

现在我们有了数据,我们可以讨论一个搜索过程的实现,该过程允许同时搜索 ColumnA 和 ColumnB,但是两个搜索都可以省略。如果未选择任何内容,则应该返回所有条目,而不仅仅是“空”条目。

动态 SQL

通常,想要构建动态 SQL 语句以满足这些需求的人第一个想法就是使用动态 SQL 进行构建。我们大多数人已经了解使用动态 SQL 相关的问题和风险。通常这种办法应该是最后一个选项,因为这样做不安全。下面是一个如何通过动态 SQL 进行操作的快速示例。

DECLARE @ColumnA VARCHAR(20)
DECLARE @ColumnB VARCHAR(25)
DECLARE @SQL NVARCHAR(1000)

SET @SQL = 'SELECT
    ColumnA,
    ColumnB
FROM TestData
WHERE 1=1'

IF(@ColumnA IS NULL)
BEGIN
    SET @SQL = @SQL + '
               AND ColumnA = ''' + @ColumnA + ''''
END

IF(@ColumnB IS NULL)
BEGIN
    SET @SQL = @SQL + ' 
                AND ColumnB = ''' + @ColumnB + ''''
END

EXEC(@SQL)

总的来说,这是可行的。但是在 @ColumnA 和 @ColumnB 数据中进行级联存在风险。一个单引号 ' 或者其他字符会破坏脚本的执行。不推荐使用这种方法查询系统。个人认为这种方法是“最后一搏”的努力。

使用 Coalesce

第二种方法 (我过去用过)使用 SQL Server 中提供的 Coalesce 函数。这个函数的作用是接受多个参数,并返回第一个非空的参数。所以如果你执行 COALESCE(@ColumnA, ColumnA) ,如果参数值为空,那就用这一列的值替换,使得该条件总是为 true。下面是使用这种方法的一个例子。

DECLARE @ColumnA VARCHAR(20)
DECLARE @ColumnB VARCHAR(25)

SELECT
    ColumnA,
    ColumnB
FROM TestData
WHERE ColumnA = COALESCE(@ColumnA, ColumnA)
    AND ColumnB = COALESCE(@ColumnB, ColumnB)

现在这是更加简洁的一种方法,而且格式相当容易阅读。但是,当对可能为空的数据进行过滤时,可能会碰到问题。我们的测试数据得出的结果集实际上类似于以下内容。

ColumnA ColumnB
Me You
You Me

结果不包含 ColumnB 为空的第一行数据,这是因为 COALESCE 没有给该字段提供空值并且没有使条件为 true。所以这种方法在处理非空数据列的时候非常有效,但是如果有空的列,就不是一种好方法。

使用 CASE

我要提供的第三种方法解决了 COALESCE 方法的空列问题和动态 SQL 方法的安全问题。虽然这种方法最好,但是有一个缺点,可读性差。这种方法在每个列的两边使用 CASE 子句,如果参数为空,就使用值 1,从而生成了 1 = 1 子句;如果参数不为空,就构建了一个标准格式的 where 子句。下面是一个简单实现。

DECLARE @ColumnA VARCHAR(20)
DECLARE @ColumnB VARCHAR(25)

SELECT 
    ColumnA,
    ColumnB
FROM TestData
WHERE CASE WHEN @ColumnA IS NULL
            THEN 1
            ELSE ColumnA
      END = CASE WHEN @ColumnA IS NULL
                    THEN 1
                    ELSE @ColumnA
      END
    AND CASE WHEN @ColumnB IS NULL
            THEN 1
            ELSE ColumnB
      END = CASE WHEN @ColumnB IS NULL
                    THEN 1
                    ELSE @ColumnB
      END

这种方法可读性很差,但是可行的,并且不会使用动态 SQL 创建查询。与其他方法相比,查看执行计划发现不会对性能产生影响。最好的部分是,它可以显示所有记录,并且是真正动态的,并且满足我们的特定需求。

总结

我希望本文能够作为一个很好的概述,展示一些在 SQL Server 语句中创建动态 where 子句的不同方法。我确信还有其他方法也能达到同样的效果。但是本文展示的是我觉得有用的方法。请随时在下面分享您的反馈。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。