SQL Server PATINDEX иногда возвращает неверный найденный индекс - PullRequest
4 голосов
/ 17 апреля 2019

Я заметил, что PATINDEX на SQL Server (я использую 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
  • Появляется дефис в шаблонеlast.

Я видел другие подобные вопросы, но они не решают совершенно одинаковую ситуацию, особенно почему шаблон работает с одним сопоставлением, а не с другим, и ОК с не-юникодной строкой, а не сСтрока Юникода.Я видел patindex t-sql специальные символы , которые говорят, что символ «-» в любой позиции, кроме первой, является спецификацией диапазона для LIKE и PATINDEX (хотя я не вижу его в SQL Server PATINDEX или Подстановочный знак документы).Все еще не объясняет, почему он работает в некоторых конфигурациях, а не в других, как показано.

Почему такой разный PATINDEX и явно неправильный результат?

Ответы [ 2 ]

3 голосов
/ 17 апреля 2019

Вы уже упоминали, что символ '-' в любой позиции, кроме первой, является (или, скорее, может быть ) спецификацией диапазона.Проблема с ' -' заключается в том, что конец диапазона не указан.Итак, давайте выясним, каков конец диапазона:

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]%'.В двоичном коде конец диапазона (если он не указан) всегда равен ], независимо от того, каким является начало диапазона.

Теперь давайте посмотрим, что делают не-юникодные значения:

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).

Другой способ: если мы посмотрим на решение Ларну:

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 | -        |
+--------+----------+

Таким образом, вы все еще оцениваете диапазон.Не уверен, что это то, что вы хотите, но что-то нужно знать.

0 голосов
/ 17 апреля 2019

Удвойте дефис, так как кажется, что он иногда используется как оператор между ними.

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.
...