12.9.25

Новое в SQL Server 2025: Поиск совпадений с помощью функций регулярных выражений

Автор: Louis Davidson, Viewing Multiple Matches in SQL Server Regular Expression Functions

Цель этой статьи — показать, как можно находить несколько совпадений с использованием регулярных выражений SQL Server, чтобы примеры были более наглядными (особенно в последующих частях).

Существует несколько функций, которые позволяют в выводе результата показать несколько совпадений фрагмента строки или шаблона:

  • REGEXP_REPLACE – возвращает измененную исходную строку, замененную строкой замены, в которой найдено вхождение шаблона регулярного выражения. Если совпадения не найдены, функция возвращает исходную строку.
  • REGEXP_SUBSTR – возвращает одно вхождение подстроки в анализируемой строке, которое соответствует шаблону регулярного выражения. Если совпадение не найдено, возвращается NULL.
  • REGEXP_INSTR – возвращает начальную или конечную позицию соответствующей подстроки в зависимости от значения аргумента return_option.
  • REGEXP_COUNT – подсчитывает количество совпадений шаблона регулярного выражения в строке.
  • REGEXP_SPLIT_TO_TABLE – используется аналогично функции SPLIT_TO_TABLE, но возвращает таблицу строк, разделенную шаблоном регулярных выражений. Если шаблон не соответствует, функция возвращает строку.
  • REGEXP_MATCHES – возвращает таблицу захваченных подстрок, которые соответствуют шаблону регулярного выражения строке. Если совпадение не найдено, функция не возвращает строку.

По сути, это все функции регулярных выражений, кроме той, о которой я уже писал ранее — REGEXP_LIKE. В этой статье я хочу рассмотреть функцию REGEXP_MATCHES, чтобы продемонстрировать её возможности (я ошибочно полагал, что вместо неё стоит использовать REGEXP_SPLIT_TO_TABLE, так как её название похоже на то, что было нужно, и не смотря на то, что само название функции SPLIT_TO_TABLE должно было подсказать обратное).

В этой статье мы перестанем изучать новые способы написания шаблонов поиска с использованием регулярных выражений и добавим новый инструмент: использование REGEXP_MATCHES, чтобы увидеть, сколько раз строка совпадает с шаблоном.

Несколько совпадений

Например, рассмотрим строку 1234567890. Если в запросе использовать REGEXP_LIKE :

SELECT CASE WHEN REGEXP_LIKE('1234567890','\d') THEN 1 ELSE 0 END;

Результатом будет 1, так как эта строка содержит хотя бы одну цифру. Но на самом деле совпадению будет больше, что подтверждается другими функциями. С помощью REGEXP_MATCHES мы можем увидеть все совпадения:

SELECT *
FROM   REGEXP_MATCHES('123456789','\d');

Результат содержит несколько столбцов, которые предоставляют информацию о совпадениях:

match_id  start_position end_position match_value substring_matches
--------- -------------- ------------ ----------- -----------------------
1         1              1            1           [{"value":"1","start":1,"length":1}]
2         2              2            2           [{"value":"2","start":2,"length":1}]
3         3              3            3           [{"value":"3","start":3,"length":1}]
4         4              4            4           [{"value":"4","start":4,"length":1}]
5         5              5            5           [{"value":"5","start":5,"length":1}]
6         6              6            6           [{"value":"6","start":6,"length":1}]
7         7              7            7           [{"value":"7","start":7,"length":1}]
8         8              8            8           [{"value":"8","start":8,"length":1}]
9         9              9            9           [{"value":"9","start":9,"length":1}]

Столбцы означают:

  • match_id – уникальный идентификатор совпадения.
  • start_position – позиция первого символа совпадения.
  • end_position – позиция последнего символа совпадения.
  • match_value – значение, соответствующее шаблону.
  • substring_matches – значение, начальная позиция и длина в формате JSON (по какой-то причине, которую я надеюсь понять в будущем).

Далее я буду включать только три столбца из вывода REGEXP_MATCHES: start_position, end_position, match_value, — так как этого достаточно для понимания, и это проще форматировать в статье.

Теперь рассмотрим более интересное выражение, которое ищет любую комбинацию из 2–3 цифр:

SELECT start_position, end_position, match_value
FROM   REGEXP_MATCHES('123456789','\d{2,3}');

Заметьте, что функция не останавливается на совпадении из двух символов, а продолжает до трёх, прежде чем перейти к следующему результату:

start_position end_position match_value
-------------- ------------ -----------
1              3            123
4              6            456
7              9            789

А если использовать \d+, то будет совпадение с числом от одной до бесконечности цифр:

SELECT start_position, end_position, match_value
FROM   REGEXP_MATCHES('123456789','\d+');
-- что то же самое, что '\d{1,N}' для любого N >= 9 в данном случае:

Вывод — одна строка со всеми символами, потому что каждый следующий символ продолжает удовлетворять шаблону:

start_position end_position match_value
-------------- ------------ -----------
1              9            123456789

Несколько совпадений в таблице

Теперь вместо анализа одной строки результата мы посмотрим на несколько строк в таблице. Вернёмся к таблице для примеров, её скрипт в Приложении ниже, и загрузим несколько строк:

TRUNCATE TABLE RegExTest;

INSERT INTO RegExTest (Value)
VALUES ('aa'),('aaa'),('aaaa'),('aaaaa'),
('baaaaaa'),('aaaaab'),('baaaaab'),
('bababab'),('bababac'),('cbababac'),
('b1ab2ab3ab4a');

Используем такой запрос:

SELECT Value
FROM RegExTest
WHERE REGEXP_LIKE(Value,'a{3,4}');

Он находит следующие строки:

Value
-----------
aaa
aaaa
aaaaa
aaaaab
baaaaaa
baaaaab

Если мы хотим посмотреть, как шаблон сопоставляется с набором строк, можно использовать CROSS APPLY, с помощью которого будем передавать значения в функцию REGEXP_MATCHES:

SELECT CAST(Value as nvarchar(15)) as Value,
       REGEXOUT.start_position, REGEXOUT.end_position,
       REGEXOUT.match_value
FROM RegExTest
        CROSS APPLY REGEXP_MATCHES(Value,'a{3,4}') as REGEXOUT;

Результаты будут такими же, как и раньше, но теперь мы видим, что совпало, и его длину:

Value           start_position end_position match_value
-------------- -------------- ------------ ---------------
aaa             1              3            aaa
aaaa            1              4            aaaa
aaaaa           1              4            aaaa
aaaaab          1              4            aaaa
baaaaaa         2              5            aaaa
baaaaab         2              5            aaaa

Изменим выражение на a{2}:

SELECT cast(Value as nvarchar(15)) as Value,
       REGEXOUT.start_position, REGEXOUT.end_position,
       REGEXOUT.match_value
FROM RegExTest
       CROSS APPLY REGEXP_MATCHES(Value,'a{2}') as REGEXOUT;

Теперь результаты для таблицы RegExTest те же, но совпадения теперь выглядят иначе (и строки из таблицы повторяются):

Value           start_position end_position match_value
-------------- -------------- ------------ -----------
aa              1              2            aa
aaa             1              2            aa
aaaa            1              2            aa
aaaa            3              4            aa
aaaaa           1              2            aa
aaaaa           3              4            aa
aaaaab          1              2            aa
aaaaab          3              4            aa
baaaaaa         2              3            aa
baaaaaa         4              5            aa
baaaaaa         6              7            aa
baaaaab         2              3            aa
baaaaab         4              5            aa

Заключение

В этой статье мы добавили новый инструмент, который позволяет видеть несколько совпадений, выводя их в виде таблицы. Этот инструмент я буду использовать чаще в будущих публикациях (особенно когда перейду от шаблонов к функциям, с которых начинал эту серию статей). Теперь у нас есть возможность показать все возможные совпадения регулярного выражения с литералом, переменной или даже значениями в таблице.

Приложение

USE TempDB;
GO

SET NOCOUNT ON;

-- Удаляем таблицу, если она существует
IF OBJECT_ID('dbo.RegExTest', 'U') IS NOT NULL
DROP TABLE dbo.RegExTest;

-- Создаём таблицу
CREATE TABLE dbo.RegExTest (
RegExTestId INT IDENTITY(1,1) CONSTRAINT PK_RegExTest PRIMARY KEY,
Value NVARCHAR(100) CONSTRAINT AK_RegExTest UNIQUE
);

Комментариев нет:

Отправить комментарий