Регулярные выражения в SQL

Привет, Хабр!
Представьте, что вам нужно найти иголку в стоге сена, но стог — это ваша БД, а иголка — данные со сложным шаблоном. Дефолтные операторы LIKE и IN тут не помогут — слишком уж они прямолинейны. Но зато здесь отлично зайдут регулярные выражения, которые позволяют выполнять сложные поиски и преобразования строк.
Основные функции для работы с регулярками
Основные функции для работы с регулярными выражениями в SQL:
REGEXP_LIKE— проверяет, соответствует ли строка заданному шаблону.REGEXP_REPLACE— заменяет часть строки, соответствующую шаблону.REGEXP_SUBSTR— извлекает подстроку по шаблону.REGEXP_INSTR— находит позицию вхождения шаблона.REGEXP_COUNT— считает количество вхождений шаблона.
Начнем сразу с практического применения этих замечательных функций.
Практические сценарии
Валидация электронных адресов
Допустим, есть таблица users с полем email, и есть подозрение, что не все адреса введены корректно. Нужно выбрать все записи, где email не соответствует стандартному формату.
Используем REGEXP_LIKE для проверки формата email:
SELECT user_id, email
FROM users
WHERE NOT REGEXP_LIKE(email, '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$');
Разбор:
^[A-Za-z0-9._%+-]+— начало строки, допустимые символы перед@.@[A-Za-z0-9.-]+— символ@и доменная часть.\.[A-Za-z]{2,}$— точка и домен верхнего уровня из минимум 2 букв.NOT REGEXP_LIKE— выбираем записи, которые не соответствуют шаблону.
В итоге мы получим список пользователей с некорректными email, которым можно отправить уведомление о необходимости обновить контактную информацию.
Стандартизация номеров телефонов
В таблице contacts номера телефонов хранятся в разных форматах: с пробелами, скобками, тире и даже без кода страны. Необходимо преобразовать все номера к единому формату +7XXXXXXXXXX.
Используем REGEXP_REPLACE для удаления лишних символов и добавления кода страны:
UPDATE contacts
SET phone_number = '+7' || REGEXP_REPLACE(phone_number, '\D', '')
WHERE REGEXP_LIKE(phone_number, '^\+?7?\d{10}$');
Разбор:
REGEXP_REPLACE(phone_number, '\D', '')— удаляем все нецифровые символы.'+7' ||— добавляем код страны в начале.^\+?7?\d{10}$— выбираем номера, которые уже могут начинаться с+7или7, чтобы избежать дублей кода страны.
На выходе получим единообразные номера телефонов, с которыми легче работать и которые можно использовать для автоматического набора или отправки SMS.
Поиск специфичных паттернов в логах
Есть таблица system_logs с полем log_entry, и нужно найти все записи, где произошла ошибка доступа для пользователей с определенным шаблоном имени, например, начинающихся с admin_ и заканчивающихся цифрами.
Используем REGEXP_LIKE для поиска соответствующих записей:
SELECT log_id, log_entry
FROM system_logs
WHERE REGEXP_LIKE(log_entry, 'Access denied for user \'admin_\w*\d+\'', 'i');
Разбор:
Access denied for user \'admin_\w*\d+\'— ищем строки с сообщением об отказе в доступе для пользователя, имя которого соответствует шаблону.\'— экранируем одинарные кавычки.\w*— любое количество буквенных символов.\d+— один или более цифр.'i'— флаг нечувствительности к регистру.
На выходе получаем список логов с попытками доступа подозрительных пользователей.
Маскировка персональных данных
В целях безопасности нужно замаскировать номера кредитных карт в таблице payments, оставив видимыми только последние 4 цифры.
Используем функцию REGEXP_REPLACE для замены части строки на символы *:
SELECT
payment_id,
REGEXP_REPLACE(card_number, '\d{12}(\d{4})', '************\1') AS masked_card_number
FROM payments;
Разбор:
\d{12}(\d{4})— ищем первые 12 цифр и захватываем последние 4 цифры.'************\1'— заменяем первые 12 цифр на*, а последние 4 цифры оставляем (ссылка на первую захватывающую группу\1).
Получим полеmasked_card_number, где номера карт выглядят как ************1234. Это важно для соответствия требованиям безопасности и сохранения конфиденциальности клиентов.
Извлечение хэштегов из сообщений
В таблице social_posts есть поле content, содержащее текст сообщений с хэштегами. Нужно извлечь все уникальные хэштеги из этих сообщений.
Используем комбинацию функций REGEXP_SUBSTR и рекурсивного запроса:
WITH hashtags AS (
SELECT
post_id,
REGEXP_SUBSTR(content, '#\w+', 1, LEVEL) AS hashtag
FROM social_posts
CONNECT BY REGEXP_SUBSTR(content, '#\w+', 1, LEVEL) IS NOT NULL
AND PRIOR post_id = post_id
AND PRIOR SYS_GUID() IS NOT NULL
)
SELECT DISTINCT hashtag
FROM hashtags
WHERE hashtag IS NOT NULL;
Разбор:
#\w+— ищем слова, начинающиеся с#.LEVEL— используем для извлечения каждого последующего вхождения.CONNECT BY— рекурсивно проходим по каждому сообщению и извлекаем все хэштеги.DISTINCT— оставляем только уникальные хэштеги.
После выполнения получим список всех уникальных хэштегов, использованных в сообщениях. Это может быть полезно для аналитики, трендовых тем или рекомендаций.
Разбиение CSV-строки на элементы
В таблице orders есть поле product_ids, содержащее идентификаторы продуктов в формате CSV (например, 1,2,3,4). Нужно создать запись для каждого продукта в заказе.
Используем REGEXP_SUBSTR вместе с рекурсией:
SELECT
order_id,
TO_NUMBER(REGEXP_SUBSTR(product_ids, '[^,]+', 1, LEVEL)) AS product_id
FROM orders
CONNECT BY REGEXP_SUBSTR(product_ids, '[^,]+', 1, LEVEL) IS NOT NULL
AND PRIOR order_id = order_id
AND PRIOR SYS_GUID() IS NOT NULL;
Разбор:
[^,]+— захватываем последовательности символов, не являющихся запятой.LEVEL— используем для прохода по каждому элементу CSV.TO_NUMBER— преобразуем строку в число, еслиproduct_idчисловой.
На выходе получаем список заказов, где каждая строка соответствует одному продукту из заказа.
Продолжаем наш увлекательный поход в мир регулярных выражений в SQL! Если вы всё ещё со мной, значит, вас не испугали ни загадочные символы, ни хитрые функции. Что ж, самое время погрузиться глубже и разобрать нюансы, о которых часто умалчивают учебники.
Нюансы использования регулярных выражений в разных СУБД
Oracle, MySQL, PostgreSQL
Разные системы управления базами данных могут по-разному реализовывать поддержку регулярных выражений. Пройдёмся по основным отличиям.
Oracle
Oracle имеет полный набор функций для работы с регулярками:
REGEXP_LIKEREGEXP_REPLACEREGEXP_SUBSTRREGEXP_INSTRREGEXP_COUNT
MySQL
В MySQL поддержка регулярных выражений зависит от версии:
До версии 8.0 использовался оператор
REGEXPилиRLIKEдля проверки соответствия.В версии 8.0 и выше добавлены функции
REGEXP_LIKE,REGEXP_REPLACE,REGEXP_INSTR,REGEXP_SUBSTR.
PostgreSQL
PostgreSQL славится своей поддержкой регулярных выражений:
Оператор
~и~*для проверки соответствия (чувствительный и нечувствительный к регистру).Функции
regexp_replace,regexp_matches,regexp_split_to_table,regexp_split_to_array.
Синтаксис основан на расширениях POSIX (как и mysql), но с доп. возможностями.
Нюансы синтаксиса
Обратите внимание на различия в экранировании символов и использовании спец. последовательностей:
В Oracle нужно удваивать обратные слэши
\\в некоторых случаях.В MySQL и PostgreSQL достаточно одного обратного слэша
\.
Пример:
В Oracle:
SELECT REGEXP_REPLACE('ABC123', '[A-Z]+', '') FROM dual;
-- Результат: 123
В PostgreSQL:
SELECT regexp_replace('ABC123', '[A-Z]+', '');
-- Результат: 123
Различные техники
Обратные ссылки позволяют ссылаться на захваченные группы внутри регулярного выражения. Допустим, нужно найти строки, где есть повторяющиеся последовательности символов:
SELECT column_name
FROM table_name
WHERE REGEXP_LIKE(column_name, '(.*)\1');
(.*)\1 — захватываем любую последовательность символов и ищем её повторение сразу после.
Некоторые СУБД позволяют использовать условные конструкции внутри регулярных выражений.
Пример (Oracle):
SELECT REGEXP_SUBSTR('abc123xyz', '(abc|123|xyz)', 1, 2) FROM dual;
-- Результат: 123
(abc|123|xyz) — используем оператор | для указания альтернатив. Параметр 2 в конце функции указывает, что мы хотим получить второе совпадение.
По дефлоту квантификаторы в регулярных выражениях «жадные», т.е они захватывают максимально возможное количество символов.
Пример:
SELECT REGEXP_SUBSTR('abcccccd', 'abc*') FROM dual;
-- Результат: abccccc
Если мы хотим сделать квантификатор «ленивым», то добавляем ? после него:
SELECT REGEXP_SUBSTR('abcccccd', 'abc*?') FROM dual;
-- Результат: abc
Разбор:
c*— жадный квантификатор, захватывает всеc.c*?— ленивый квантификатор, захватывает минимальное количествоc.
Производительность
Регулярные выражения — инструмент мощный, но ресурсоёмкий.
Советы:
Индексы не работают с регулярками. Если возможно, используйте дополнительные условия в
WHERE, которые могут задействовать индексы.Ограничивайте выборку. Используйте доп. фильтры, чтобы сократить количество обрабатываемых строк.
Кэшируйте результаты. Если регулярное выражение используется часто и результаты редко меняются, рассмотрите возможность кэширования.
Заключение
Регулярные выражения позволяют выполнять сложные операции, которые иначе потребовали бы дополнительных шагов или иногда скриптов.
В завершение хочу порекомендовать вебинар, на котором вы узнаете, как специалисты с разными ролями используют базы данных, как их подходы к SQL-запросам отличаются и пересекаются. На практике попробуете написать запросы с разных позиций и разберетесь, как эффективно работать вместе, чтобы достигать общих целей. Урок поможет вам понять, как создать эффективное взаимодействие между аналитиками и разработчиками. Зарегистрироваться.
