【问题标题】:SQL Server PATINDEX sometimes returns false found indexSQL Server PATINDEX 有时会返回错误的找到索引
【发布时间】:2019-09-07 09:26:23
【问题描述】:

我注意到 SQL Server 上的 PATINDEX(我使用的是 2016)给出了奇怪的结果。 我怀疑它与排序规则和字符集有关。

我正在尝试使用 PATINDEX 获取第一个空格或连字符的索引。

在具有默认排序规则 SQL_Latin1_General_CP1_CI_AS 的数据库上,我得到预期结果 0(未找到):

select PATINDEX('%[ -]%',  'ABC') -- returns 0
select PATINDEX('%[ -]%', N'ABC') -- returns 0
select PATINDEX('%[- ]%', N'ABC') -- returns 0

但是,在排序为 Latin1_General_100_BIN 的数据库中,我得到了一个意外的错误结果,表明在索引 1 处发现了空格或连字符:

select PATINDEX('%[ -]%',  'ABC') -- returns 0
select PATINDEX('%[ -]%', N'ABC') -- returns 1 (WRONG!)
select PATINDEX('%[- ]%', N'ABC') -- returns 0

总之,我注意到这个明显错误的结果是:

  • 排序Latin1_General_100_BIN
  • 搜索 unicode 字符串
  • 模式中的连字符出现在最后。

我见过其他类似的问题,但它们并没有解决完全相同的情况,尤其是为什么模式适用于一种排序规则而不适用于其他排序规则,并且适用于非 unicode 字符串而不适用于 unicode 字符串。 我见过patindex t-sql special characters,它表示除第一个位置之外的任何位置的“-”字符都是 LIKE 和 PATINDEX 的范围规范(尽管我在SQL Server PATINDEXWildcard 文档中没有看到它)。仍然没有解释为什么它在某些配置中有效,而在其他配置中无效。

为什么会有如此不同的 PATINDEX 和明显错误的结果?

【问题讨论】:

  • 我确实在 2012 年将其报告为错误。它被关闭为“无法修复”。原链接connect.microsoft.com/SQLServer/feedback/details/742841/…但貌似在返程机中不存在这个
  • 响应是 来自 SQL Server 团队的问候。感谢您写信给微软。我们非常重视您的反馈。我们能够重现您提到的错误。但是,repro 中提到的所有模式都是无效的,因为 SQL Server 将“-”视为特殊字符并需要一个开始和结束范围。理想情况下,这些无效模式应返回 NULL,但当前行为不会这样做。我们将考虑修改行为以供将来发布。再次感谢您提供反馈并使 SQL Server 成为最出色的数据库服务器。问候阿蒂什·阿格拉瓦尔
  • 我认为行为实际上在未来版本中被修改的可能性非常接近于零(尤其是它永远返回NULL!),因为它可能会破坏现有的系统,这些系统正是指望这种不直观的行为发生,而它的解决方法相当简单(如果不一定明显要完全正确)。以防万一自 2012 年以来没有发生任何事情的事实已经不是一个很好的指标,可以让你屏住呼吸。
  • 是的 - 可以将适当的正则表达式支持作为本机函数实现,这样我们就可以忘记PATINDEX
  • 感谢大家的回复,非常感谢。在所有这些中,我认为 Martin Smith 的 cmets 是最准确的,因为它们表明 Microsoft 认识到这是一个错误。即连字符是一个特殊字符,不作为单独的字符进行搜索。因此,在 PATINDEX 中所有这些连字符的使用都应该返回一个错误,而不是它们经常出现的巧合结果。

标签: sql-server tsql patindex


【解决方案1】:

您已经提到自己,除了第一个位置之外的任何位置的“-”字符都是(或者更确切地说 可以是)一个范围规范。 ' -' 的问题是没有给出范围的结尾。因此,让我们找出范围的结尾是什么:

SELECT  SV.number, NCHAR(SV.number) TestChar
FROM    master..spt_values AS SV
WHERE   SV.type = 'p'
    AND NCHAR(SV.number) LIKE N'%[ -]%'

这会返回:

+--------+----------+
| number | TestChar |
+--------+----------+
|     32 |          |
|     45 |        - |
+--------+----------+

因此,在非二进制排序规则(我使用的是 Latin1_General_CI_AS)中,- 根本不被解释为范围说明符,而是作为文字字符。否则,也会返回 32 到 45 之间的字符。所以只有空格和破折号会返回一个 patindex 0。让我们尝试一个二进制排序规则:

SELECT  SV.number, NCHAR(SV.number) TestChar 
FROM    master..spt_values AS SV
WHERE   SV.type = 'p'
    AND NCHAR(SV.number) LIKE N'%[ -]%' COLLATE Latin1_General_100_BIN2

这会返回:

+--------+----------+
| number | TestChar |
+--------+----------+
|     32 |          |
|     33 | !        |
|     34 | "        |
|     35 | #        |
|     36 | $        |
|     37 | %        |
|     38 | &        |
|     39 | '        |
|     40 | (        |
|     41 | )        |
|     42 | *        |
|     43 | +        |
|     44 | ,        |
|     45 | -        |
|     46 | .        |
|     47 | /        |
|     48 | 0        |
|     49 | 1        |
|     50 | 2        |
|     51 | 3        |
|     52 | 4        |
|     53 | 5        |
|     54 | 6        |
|     55 | 7        |
|     56 | 8        |
|     57 | 9        |
|     58 | :        |
|     59 | ;        |
|     60 | <        |
|     61 | =        |
|     62 | >        |
|     63 | ?        |
|     64 | @        |
|     65 | A        |
|     66 | B        |
|     67 | C        |
|     68 | D        |
|     69 | E        |
|     70 | F        |
|     71 | G        |
|     72 | H        |
|     73 | I        |
|     74 | J        |
|     75 | K        |
|     76 | L        |
|     77 | M        |
|     78 | N        |
|     79 | O        |
|     80 | P        |
|     81 | Q        |
|     82 | R        |
|     83 | S        |
|     84 | T        |
|     85 | U        |
|     86 | V        |
|     87 | W        |
|     88 | X        |
|     89 | Y        |
|     90 | Z        |
|     91 | [        |
|     92 | \        |
|     93 | ]        |
+--------+----------+

所以现在它解释为一个范围,并且该范围包括A-Z。注意它包含a-z!当我们使用LIKE N'%[ -z]%' 时,将包含小写字母。在二进制中,范围的结束(当没有指定时)总是],不管范围的开始是什么。

现在,让我们看看非 unicode 值的作用:

SELECT  SV.number, CHAR(SV.number) TestChar
FROM    master..spt_values AS SV
WHERE   SV.type = 'p'
    AND CHAR(SV.number) LIKE '%[ -]%' COLLATE Latin1_General_100_BIN2

这会返回:

+--------+----------+
| number | TestChar |
+--------+----------+
|     32 |          |
|     45 |        - |
+--------+----------+

因此,作为 ASCII,破折号再次不被解释为范围运算符。很奇怪吧?

顺便说一句,如果你真的要搜索space dash,你也可以使用PATINDEX(N'% [-]%', N'ABC' COLLATE Latin1_General_BIN2)

顺便说一句:如果我们检查一下 Larnu 的解决方案:

SELECT  SV.number, NCHAR(SV.number) TestChar
FROM    master..spt_values AS SV
WHERE   SV.type = 'p'
    AND CHAR(SV.number) LIKE '%[ --]%' COLLATE Latin1_General_100_BIN2

你会得到:

+--------+----------+
| number | TestChar |
+--------+----------+
|     32 |          |
|     33 | !        |
|     34 | "        |
|     35 | #        |
|     36 | $        |
|     37 | %        |
|     38 | &        |
|     39 | '        |
|     40 | (        |
|     41 | )        |
|     42 | *        |
|     43 | +        |
|     44 | ,        |
|     45 | -        |
+--------+----------+

所以你仍在评估一个范围。不确定这是否是您想要的,但要注意一些事情。

【讨论】:

    【解决方案2】:

    将连字符加倍,因为它有时被用作介于运算符。

    SELECT PATINDEX(N'%[ --]%', 'ABC' COLLATE Latin1_General_100_BIN); --Returns 0
    SELECT PATINDEX(N'%[ --]%', N'ABC' COLLATE Latin1_General_100_BIN); --Returns 0
    SELECT PATINDEX(N'%[-- ]%', N'ABC' COLLATE Latin1_General_100_BIN); --Returns 0
    
    SELECT PATINDEX(N'%[ --]%', '-ABC' COLLATE Latin1_General_100_BIN); --Returns 1
    SELECT PATINDEX(N'%[ --]%', N'ABC-' COLLATE Latin1_General_100_BIN); --Returns 4
    SELECT PATINDEX(N'%[-- ]%', N'-ABC' COLLATE Latin1_General_100_BIN); --Returns 0, as the hyphen is at the start, so doesn't need escaping.
    

    【讨论】:

    • 感谢您的观察和可能的解决方法。它仍然没有解释为什么 PATINDEX 没有表现出应有的行为。至于连字符是范围运算符,作为上下文中显示的正则表达式模式,不应以这种方式解释。 regex101.com 是尝试这些的好地方。
    • @John PATINDEX 不支持 REGEX,因此 REGEX 标准不适用。 SQL Server 使用自己的模式匹配公式,虽然格式与 REGEX 相似,但事实并非如此。不应期望两者之间的行为相同。
    • 感谢您指出这一点。不过,我需要知道为什么 PATINDEX 中的不同行为会根据不同的配置而有所不同。我为一家软件供应商编写代码,该供应商的客户使用各种配置。我不想在晚上醒来想知道这样的事情是否/何时会破裂。目前正在考虑 PATINDEX 的老式替代品。
    • @John: FWIW,LIKE 承认有一个明确的转义字符来规避这个问题——WHERE ... LIKE '%[ \-]%' ESCAPE '\' 具有预期的语义,无论排序规则如何。不幸的是,PATINDEX 没有那么幸运,但是如果不包含该模式的行最终不会在结果中使用,您可以将它们组合起来。
    • 这不只是找到第一个空格或连字符,它还会找到其他 12 个字符(我在回答中提到了它们)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-05-01
    • 2021-08-08
    • 2023-03-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多