Регулярные выражения regex в Google BigQuery

Содержание:
  1. Применение в BigQuery и Google Data Studio
  2. Тренажер
  3. Синтаксис
  4. Примеры
  5. Использование в BigQuery
  6. Примеры применения в Google BigQuery
  7. Шпаргалка
Регулярные выражения (regexp, regex) — это инструмент для поиска строк, проверки их на соответствие какому-либо шаблону в заданном тексте. Использование регулярных выражений немного различается в разных языках программирования. Довольно часто regexp используются для извлечения определенных символов/строк из набора беспорядочных данных для последующего применения.

Например следующий regexp предназначен для поиска номера телефона в тексте.

\(?([0-9]{3})\)?([ .-]?)([0-9]{3})\2([0-9]{4})
Так как телефонный номер каждый человек записывает по разному мы вынуждены искать номера в которых есть скобки, пробелы между цифрами,точки, тире или номера без каких-либо дополнительных символов. То есть это выражение нашло номер телефона записанный шестью разными способами.

Тренажер

Для проверки работы выражений или тренировок будем использовать сайт - regex101
В первой строке пишем наше выражение, а во второй текст или набор символов в котором мы будем что-то искать.

Прежде чем начать тренировку, покажу настройки поиска.
При клике откроется список настроек, которые можно включать или выключать в зависимости от типа тестируемого выражения.
Опишу основные, те которые будем использовать почти всегда.

global - нужно искать все вхождения, если его убрать то в тексте будет подсвечиваться только первый найденный вариант.
выключаем опцию
multi line - позволяет использовать ^ и $ - символы начала и конца строки.
insensitive - отвечает за регистр буквы. При включении регулярное выражение перестает быть чувствительным к регистру и будет искать как большие буквы так и маленькие.
опция включена
опция выключена
unicode - позволяет искать не только по латинице, но и по зашифрованному с помощью юникода тексту, кириллица является юникодом.
Для наших тренировок будет достаточно трех флагов:
  • global
  • multi line
  • unicode

Синтаксис регулярных выражений

Символы бывают двух видов: литералы (обычные) и метасимволы (специальные). Большинство символов в регулярном выражении представляют сами себя за исключением специальных символов:
. - соответствует любому одному символу
Как видим из набора слов нашлись первые три буквы, но если добавить ".", то добавиться любой один символ стоящий после "при"
[ ] - соответствует одному из символов содержащихся в скобках, просто перечисляется набор символов которые подходит под условие поиска
( ) – группировка символов (где начинается и заканчивается группа)
[а-я] - любая буква от а до я (в нижнем регистре)
[А-Я] - любая буква от А до Я ( в верхнем регистре)
[0-9] или \d- любой цифровой символ
\D - любой нецифровой символ ( тоже самое что и [^0-9])
[a-z] - любая латинская буква (в нижнем регистре)
[A-Z] - любая латинская буква (в верхнем регистре)
[а-яА-Я] - любая из букв в любом регистре
[a-zA-Z] - любая из букв латинского алфавита в любом регистре
[а-яА-Я0-9] или \w - любая буква или цифра
[a-zA-Z0-9] или \w - любая буква латинского алфавита или цифра
Буква «ё» не включается в общий диапазон букв, и её нужно указывать отдельно.
\W - любой символ, кроме буквенного или цифрового символа или знака подчеркивания
\s - пробел
\S - любой символ без пробела
[^ ] - соответствует символам из не содержащихся в квадратных скобках
[^0-9] - не соответствует цифре
[^а-я] - не соответствует букве от а до я в нижнем регистре
^ - начало текста или строки
$ - конец текста или строки

Квантификаторы

Они определяют сколько раз предшествующее выражение может встречаться.

? или {0,1} - ноль или одно вхождение
* или {0,} - ноль или более вхождений
+ или {1,} - одно или более вхождений
{n} - ровно n раз
{n,m} - от n до m включительно
{m,} - не меньше чем m
{,n} - не больше чем n

Разберем на примере номера телефона:

Допустим у нас есть номера такой структуры "88005456644", что их найти нужно написать - "\d+"
Все просто, но такое бывает редко, например, номер формата "8-800-432-3243" уже не найдется. если мы напишем "\d+-\d+-\d+-\d+", то номер с тире мы найдем, но перестанем находить номер без тире
Тут нам поможет "?", то есть ставим знак вопроса после каждого тире, оно может быть, а может и не быть
Дальше номера телефонов с скобками, пробелами и т.д. Для этого используем символьные классы.

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

Еще рассмотрим пример номера "+7 800 432 4324" или похожие. Проблема в том что символ "+" один из квантификаторов (означает одно или более вхождений). Для того чтобы регулярное выражение приняло "+" просто как "плюс" его нужно экранировать "\" . То есть "\+?" - означает плюс может быть, а может и не быть перед цифрой
НО, проблема такого запроса заключается в том, что мы не ограничили количество символов в выражении. Если, например, в тексте есть номер счета или другая числовая последовательность, то наше выражение найдет и их тоже.
Чтобы этого избежать давайте рассмотрим наши номера телефонов, из чего они состоят:
  • плюс
  • цифра
  • пробел, дефис, открывающая скобка
  • трехсимвольный код региона в скобках
  • пробел, дефис, закрывающая скобка
  • три цифры
  • может быть тире
  • четыре цифры
По порядку строим выражение
Как видим все равно цифры которые не являются номером телефона тоже выделились, так как, обычно номера телефонов в тексте, заканчиваются знаками препинания - точкой, запятой, переносом строки "\n", пробелом, давайте добавим в условие.
Понятно, что есть еще варианты написания номера телефона, но смысл остается один и тот же.
Постепенно, шаг за шагом пишем наше регулярное выражение. Чтобы прочитать чужое выражение или свое если забыли, можно просто разбить его на части.
Перечисление

Символ "|" в регулярном выражении соответствует логическому ИЛИ (OR). Т. е. могут совпадать либо предыдущее, либо последующее выражения.

Например, cpc|cpm|direct ➜ найдет или cpc или cpm или direct. Если внутри слова, то используем еще и круглые скобки () go(o|a)gle ➜ найдет google или goagle, но не найдет goegle

  • для поиска одного символа — используем []
  • для нескольких символов или целого слова — используем |
Мета-символы

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

\ (обратный слеш) - экранирование служебных символов.

[ ] \ / ^ $ . | ? * + ( ) { } 
Например,

a\.? ➜ a. или a
a\\\\b ➜ a\\b
a\[F\] ➜ a[F]

Можно экранировать целую последовательность символов, заключив её между \Q и \E (но не во всех системах)
Например, \Q{google / cpc}\E ➜ найдет {google / cpc}

Примеры

Пример с почтой
Как найти номер телефона в тексте мы уже рассмотрели. Давайте еще найдем email в тексте.
Надо понять, что вообще из себя представляет адрес электронной почты. Что там может быть:
  • буквы и/или цифры
  • точка/тире/нижнее подчеркивание
  • @
  • буквы/цифры
  • точка
  • буквы
test@mail.ru
test123@mail.ru
test-123@mail.info
test_fd2@mail.su
3213@432.рф
test.test2@gmail.com
test23.try.re2@12gmail.com
test23.try.re2@12-gmail.com

Пишем выражение постепенно:
  1. может быть как буквы так и цифры, а также нижнее подчеркивание или тире или точка ➜ [\w\.-]+
  2. дальше идет собака ➜ @
  3. доменной имя может содержать буквы и цифры и иногда тире ➜ [\w-]+
  4. обязательно идет точка ➜ \. (точку экранируем)
  5. после точки будут только буквы латинского или кириллического алфавита не менее двух и не более десяти ➜ [a-zа-я]{2,10}
Целиком выражение выглядит так:

[\w\.-]+@[\w-]+\.[a-zа-я]{2,10}
Мы нашли все возможные варианты написания почты и отсеяли явно ошибочные или просто обозначающие совсем иное.
Конечно, могут быть еще какие-то символы, но в основном в ходу такие варианты.
Пример с ссылками

Проблема с url состоит в том что мы не можем это сделать все одним выражением, приходится разбивать на этапы. Например, если мы хотим извлечь ссылку из текста, а так как она может иметь множество форматов сделать это за раз практически не возможно.

Допустим в тексте есть такие варианты написания url
При написании regexp мы будем находить то одно, то другое, то все вместе.

http[s]?:\/\/[^\/]+\/
Вроде получилось найти протокол и домен, но не везде и это вообще только часть ссылки.
Можно написать выражения типа:

^(https?:\/\/)?([\w\.]+)\.([a-z]{2,6}\.?)(\/[\w\.]*)*\/?$/
^((http|https|ftp):\/\/)?(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)
(?http://[-A-Za-z0-9+&@#/%?=~_()|!:,.;]*[-A-Za-z0-9+&@#/%=~_()|]
Но и они не смогут поймать все что нам нужно.
Поэтому для извлечения ссылок нужно работать поэтапно.
Использовать для этого можно много вариантов, но я покажу самый простой и доступный всем.
  • копируем наш текст
  • вставляем его в google docs
  • Правка ➜ Найти и заменить или просто нажать Ctrl+H
  • в первое поле "Найти" вставляем наше регулярное выражение
  • поле "заменить на" оставляем пустым
  • нажимаем на галочку - "Учитывать регулярные выражения." . В тексте уже будет подсвечено что вы хотели получить.
  • кнопка "Заменить все"

  • Готово
1. уберем из ссылок https и http с точками и слешами, они не несут никакой важной для нас информации

http[s]?:\/\/ 
2. избавимся от "www." это тоже не несет никакой важной информации.

w{3}\.
3. теперь избавимся от "index.php", ".jpg". Может конечно понадобится формат файла при скачивании, но обычно используют название файла для понимания какой именно был скачан файл.

index\.php|\.php|\.jpg
4. Теперь можем достать нужную нам ссылку.

[a-z0-9]+\.\w+\.?\w+\/(\w+)?[\?\/]?(\w+=?\w)?\/?(\w+)?\/?(\w)?
Как это выглядит в документе, на примере

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

BigQuery — одно из самых популярных облачных хранилищ данных, используемое миллионами людей по всему миру для хранения и анализа данных. В BigQuery очень часто требуется извлечь из строки определенные ее части, проверить формат строки или заменить/удалить определенные символы.

В BigQuery есть 6 функций для работы с регулярными выражениями:
  1. REGEXP_CONTAINS - дословно "текст содержит". Возвращает строку (TRUE) если она частично или полностью совпадает с регулярным выражением. Обычно используется как условие в WHEN
Синтаксис:
REGEXP_CONTAINS (название столбца, 'условие поиска')
Например, у нас есть столбец с названиями:

select distinct title from `business-metric.project.example_final_bitrix24`
И мы хотим найти только те поля в которых есть слово "заявка"

select distinct title from `business-metric.project.example_final_bitrix24`
where REGEXP_CONTAINS (title,'Заявка')
Конечно, можно использовать LIKE, но это что касается данного примера, потенциал regexp практически не ограничен.

select distinct title from `business-metric.project.example_final_bitrix24`
where title like ('Заявка%')
Результатом будет те же строки.
2. REGEXP_REPLACE - ищет конкретную структуру из выбранного столбца и заменяет его тем, что мы указали.
Синтаксис:
REGEXP_REPLACE (название столбца, 'искомое рег. выражение', 'на что меняем найденное значение '
Хотим заменить "Заявка от" на "Клиент"

select distinct title,
REGEXP_REPLACE (title,'Заявка от','Клиент') as new_title
from `business-metric.project.example_final_bitrix24`
where title like ('Заявка%')
3. REGEXP_EXTRACT - извлекает значение из указанной строки.
Синтаксис:
REGEXP_EXTRACT (название столбца, 'регулярное выражение')
Для примера извлечем из ссылки значение "utm_source"

select distinct source,
REGEXP_EXTRACT(source, 'utm_source=([\\w\\d-_%.]+)') as new_source
from `business-metric.project.example_final_bitrix24`
То есть из полноценной ссылки мы изъяли только Источник
4. REGEXP_EXTRACT_ALL - извлекает группу значений из указанной строки с помощью регулярного выражения. Под группами подразумевается совпадающие шаблоны которое присутствуют в тексте. Чаще всего используется для извлечения каждого слова из строки или для извлечения всех чисел из строки.
Синтаксис:
REGEXP_EXTRACT_ALL (название столбца, 'регулярное выражение')

Основное отличие извлечения групп от простого REGEXP_EXTRACT заключается в том, что просто EXTRACT извлечет первое слово из строки, в то время как ALL извлечет каждое слово.

select title,
REGEXP_EXTRACT (title, r'[А-Яа-я]+') as new_title
from `business-metric.project.example_final_bitrix24`
where title like 'Заявка%' 
А вот как будет выглядеть извлечение групп
Это может быть полезно при работе с ключевыми словами.
5. REGEXP_INSTR - извлекает номер позиции, с которой начинается первый подходящий символ из регулярного выражения. Используется редко. Подробнее можно почитать в справке от google
6. REGEXP_SUBSTR - синоним функции REGEXP_EXTRACT. Также извлекает нужное значение из строки, но может быть уточнено дополнительными параметрами типа позиции символа в самом запросе, подробнее в статье от google. Используется редко.

Примеры использования регулярных выражений в Google BigQuery

Возьмем часто встречающуюся задачу, привести ссылки к единому формату, разбить на составляющие и т.д.
Допустим есть список ссылок:
Много лишнего, отвлекает. Нужно избавиться от протокола https и от www. Для этого будем использовать регулярные выражения и функцию CASE WHEN

SELECT
  source,
  --- Определяем источник
  CASE
  --- Если у нас есть utm метка, то извлекаем из нее название источника
    WHEN REGEXP_CONTAINS(source, 'utm_source.*') THEN REGEXP_EXTRACT(source, 'utm_source=([\\w\\d-_%.]+)(?:&|$)')
  --- Если у нас есть просто переход из поисковых систем или социальных сетей, то достаем просто название 
    WHEN REGEXP_CONTAINS(source, '^https?://(?:www\\.)?(yandex|google|facebook|vk|instagram).*') THEN REGEXP_EXTRACT(source, '^https?://(?:www\\.)?(yandex|google|facebook|vk|instagram).*')
  --- Если данные отсутствуют, возвращаем direct
    WHEN source = '' THEN '(direct)'
  ELSE
  --- Иначе берем просто домен без лишнего
  REGEXP_EXTRACT(source, '^https?://((?:www\\.)?.*)')
END
  AS new_source,
  --- Определяем канал
  CASE
    WHEN REGEXP_CONTAINS(source, 'utm_medium.*') THEN REGEXP_EXTRACT(source, 'utm_medium=([\\w\\d-_%.]+)(?:&|$)')
    WHEN REGEXP_CONTAINS(source, '^https?://(?:www\\.)?(yandex|google).*') THEN 'organic'
    WHEN REGEXP_CONTAINS(source, '^https?://(?:www\\.)?(facebook|vk|instagram).*') THEN 'social'
    WHEN source = '' THEN '(none)'
  ELSE
  REGEXP_EXTRACT(source, '^https?://((?:www\\.)?.*)')
END
  AS medium,
FROM

В результате запроса мы получим разбивку на два столбца и читаемый вид, также можно делать и с кампанией, ключевым словом и т.д.
Полный запрос при помощи которого вы можете попрактиковаться именно в BigQuery на своих примерах. Просто замените примеры внизу запроса своими.

SELECT
  source,
  --- Определяем источник
  CASE
  --- Если у нас есть utm метка, то извлекаем из нее название источника
    WHEN REGEXP_CONTAINS(source, 'utm_source.*') THEN REGEXP_EXTRACT(source, 'utm_source=([\\w\\d-_%.]+)(?:&|$)')
  --- Если у нас есть просто переход из поисковых систем или социальных сетей, то достаем просто название 
    WHEN REGEXP_CONTAINS(source, '^https?://(?:www\\.)?(yandex|google|facebook|vk|instagram).*') THEN REGEXP_EXTRACT(source, '^https?://(?:www\\.)?(yandex|google|facebook|vk|instagram).*')
  --- Если данные отсутствуют, возвращаем direct
    WHEN source = '' THEN '(direct)'
  ELSE
  --- Иначе берем просто домен без лишнего
  REGEXP_EXTRACT(source, '^https?://((?:www\\.)?.*)')
END
  AS new_source,
  --- Определяем канал
  CASE
    WHEN REGEXP_CONTAINS(source, 'utm_medium.*') THEN REGEXP_EXTRACT(source, 'utm_medium=([\\w\\d-_%.]+)(?:&|$)')
    WHEN REGEXP_CONTAINS(source, '^https?://(?:www\\.)?(yandex|google).*') THEN 'organic'
    WHEN REGEXP_CONTAINS(source, '^https?://(?:www\\.)?(facebook|vk|instagram).*') THEN 'social'
    WHEN source = '' THEN '(none)'
  ELSE
  REGEXP_EXTRACT(source, '^https?://((?:www\\.)?.*)')
END
  AS medium,
FROM (
  SELECT
    'https://facebook.com' AS source
  UNION ALL (
    SELECT
      'https://test.me/credo-vs-tredo?utm_source=ya_poisk_piter_bfl&utm_medium=lidgen&utm_campaign=text2&utm_content=image2' AS source )
  UNION ALL (
    SELECT
      'https://test.me/credo-vs-tredo?utm_source=google&utm_medium=cpc&utm_campaign=text3&utm_content=image0' AS source)
  UNION ALL (
    SELECT
      'https://test.me/credo-vs-tredo?utm_source=yandex&utm_medium=cpm&utm_campaign=text2&utm_content=image1'AS source)
  UNION ALL (
    SELECT
      'https://google.com' AS source)
  UNION ALL (
    SELECT
      'https://direct.yandex.ru' AS source)
  UNION ALL (
    SELECT
      'https://www.yandex.ru' AS source )
  UNION ALL (
    SELECT
      'https://link.2gis.ru'AS source)
  UNION ALL (
    SELECT
      'https://vk.com' AS source)
  UNION ALL (
    SELECT
      '' AS source)
  UNION ALL (
    SELECT
      'https://instagram.com' AS source )) AS table_1

Шпаргалка основных выражений

Символьные классы + Скобочные выражения
Якоря
Квантификаторы
Made on
Tilda