T-SQL 模式匹配异常

T-SQL pattern matching with exceptions

这是我在玩Stack Exchange Data Explorer时反复遇到的问题,它是基于T-SQL:

如何搜索字符串 除了 当它作为其他字符串的子字符串出现时?

例如,我如何 select table MyTable 中的所有记录,其中列 MyCol 包含字符串 foo,但忽略任何foo 是字符串 foobar?

的一部分

快速而肮脏的尝试类似于:

SELECT * 
FROM MyTable 
WHERE MyCol LIKE '%foo%' 
  AND MyCol NOT LIKE '%foobar%'

但显然这将无法匹配,例如MyCol = 'not all foos are foobars',我确实想要匹配。

我想出的一个解决方案是用一些虚拟标记(不是 foo 的子串)替换所有出现的 foobar,然后检查任何剩余的 foos,如:

SELECT * 
FROM MyTable 
WHERE REPLACE(MyCol, 'foobar', 'X') LIKE '%foo%'

这行得通,但我怀疑它不是很有效,因为它必须 运行 REPLACE() table 中的每条记录。 (对于 SEDE,这通常是 Posts table,目前有大约 3000 万行。)有没有更好的方法来做到这一点?

(FWIW,提示此问题的 the real use case 正在搜索带有使用 http:// 方案前缀但不指向主机 i.stack.imgur.com 的图像 URL 的 SO 帖子。)

三级过滤器应该可以工作:

  1. 收集所有匹配“%foo%”的行;

  2. 将 'foobar' 的所有实例替换为未出现的字符串(例如 '' 可能);

  3. 再次检查匹配“%foo%”

这里您只对可能匹配的行执行 REPLACE,而不是对所有行。如果您只期望一小部分匹配,这应该更有效。

SQL 看起来像这样:

;with data as (
    select * 
    from MyTable 
    where MyCol like '%foo%'      
)
select *
from data
where replace(MyCol, 'foobar', 'X') like '%foo%'

请注意,子查询是必需的,因为 SQL 中没有表达式快捷方式;引擎可以根据需要自由地重新排序布尔项,以便在单个查询级别内进行高效处理。

假设您只对查找 foo 周围有空格的实例感兴趣

 SELECT * 
 FROM MyTable 
 WHERE MyCol LIKE 'foo %' OR MyCol LIKE '% foo %' OR MyCol LIKE '% foo'

这将比您当前的查询更快:

SELECT * 
FROM MyTable 
WHERE 
  MyCol like '%foo%' AND
  REPLACE(MyCol, 'foobar', 'X') LIKE '%foo%'

REPLACE 是在应用 MyCol 之后计算的,因此这比仅仅:

REPLACE(MyCol, 'foobar', 'X') LIKE '%foo%'

到目前为止给出的方法都不能保证像宣传的那样工作,并且只能对行的子集执行 REPLACE

SQL 服务器 does not guarantee short circuiting of predicates and can move compute scalars up into the underlying query for derived tables and CTEs.

唯一 (mostly) 保证有效的是 CASE 语句。下面我使用 IIF 的语法糖变体扩展为 CASE

SELECT *
FROM   MyTable
WHERE  1 = IIF(MyCol LIKE '%foo%', 
               IIF(REPLACE(MyCol, 'foobar', 'X') LIKE '%foo%', 1, 0), 
               0);