使用 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 子句的不同方法。我确信还有其他方法也能达到同样的效果。但是本文展示的是我觉得有用的方法。请随时在下面分享您的反馈。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,809评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,189评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,290评论 0 359
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,399评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,425评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,116评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,710评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,629评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,155评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,261评论 3 339
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,399评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,068评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,758评论 3 332
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,252评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,381评论 1 271
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,747评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,402评论 2 358