Ten artykuł jest obecnie po rosyjsku — trwa tłumaczenie na angielski.
В SQL часто приходится работать со строками, внутри которых уже есть понятная структура.
Например:
anna@gmail.com
В email есть две части:
anna
gmail.com
И они разделены символом @.
Или есть путь:
eng/backend/payments
В нём несколько частей:
eng
backend
payments
И они разделены слешем /.
Или есть составной статус заказа:
paid:card:eur
Он может означать:
статус оплаты: способ оплаты: валюта
В таких случаях не всегда нужны сложные регулярные выражения. Если строка устроена просто и разделитель известен заранее, в PostgreSQL удобно использовать функцию SPLIT_PART.
Она делает ровно то, что нужно: разбивает строку по разделителю и возвращает нужную часть.
Что делает SPLIT_PART простыми словами
SPLIT_PART берёт строку, разрезает её по указанному разделителю и возвращает одну конкретную часть.
Синтаксис:
SPLIT_PART(string, delimiter, field_number)
Где:
string — строка, которую нужно разделить;
delimiter — разделитель;
field_number — номер части, которую нужно вернуть.
Простой пример:
SELECT SPLIT_PART('anna@gmail.com', '@', 1) AS local_part;
Результат:
local_part
----------
anna
Мы сказали базе:
Раздели строку anna@gmail.com по символу @ и верни первую часть.
Если нужна вторая часть:
SELECT SPLIT_PART('anna@gmail.com', '@', 2) AS domain;
Результат:
domain
---------
gmail.com
То есть SPLIT_PART идеально подходит для ситуаций, где строка похожа на набор частей, склеенных через один и тот же разделитель.
Нумерация начинается с 1
Очень важный момент: части строки в SPLIT_PART считаются с единицы.
Не с нуля, как во многих языках программирования, а именно с 1.
Например:
SELECT
SPLIT_PART('a.b.c', '.', 1) AS part_1,
SPLIT_PART('a.b.c', '.', 2) AS part_2,
SPLIT_PART('a.b.c', '.', 3) AS part_3;
Результат:
part_1 | part_2 | part_3
-------+--------+-------
a | b | c
Можно представить это так:
'a.b.c'
1-я часть: a
2-я часть: b
3-я часть: c
Поэтому, если вы хотите взять первую часть строки, пишите 1, а не 0.
Пример с email: получаем домен
Один из самых понятных примеров — получение домена из email.
Допустим, есть таблица users:
id | email
---+------------------
1 | anna@gmail.com
2 | bob@mail.ru
3 | kate@yahoo.com
4 | tom@gmail.com
Чтобы получить домен, нужно взять вторую часть email после символа @.
SELECT
id,
email,
SPLIT_PART(email, '@', 2) AS domain
FROM users;
Результат:
id | email | domain
---+-----------------+----------
1 | anna@gmail.com | gmail.com
2 | bob@mail.ru | mail.ru
3 | kate@yahoo.com | yahoo.com
4 | tom@gmail.com | gmail.com
Теперь можно, например, посчитать пользователей по почтовым доменам.
SELECT
SPLIT_PART(email, '@', 2) AS domain,
COUNT(*) AS users_count
FROM users
GROUP BY SPLIT_PART(email, '@', 2)
ORDER BY users_count DESC;
Результат может быть таким:
domain | users_count
----------+------------
gmail.com | 2
mail.ru | 1
yahoo.com | 1
Такой запрос помогает быстро понять, какими почтовыми сервисами пользуются ваши пользователи.
Получаем часть email до @
Если вторая часть email — это домен, то первая часть — это всё, что идёт до @.
Например:
anna@gmail.com
Первая часть:
anna
Запрос:
SELECT
id,
email,
SPLIT_PART(email, '@', 1) AS local_part
FROM users;
Результат:
id | email | local_part
---+-----------------+-----------
1 | anna@gmail.com | anna
2 | bob@mail.ru | bob
3 | kate@yahoo.com | kate
Это может пригодиться, например, чтобы быстро найти тестовые аккаунты, внутренние маски или пользователей с определённым шаблоном в email.
SELECT
id,
email
FROM users
WHERE SPLIT_PART(email, '@', 1) LIKE 'test%';
Такой запрос найдёт email, где часть до @ начинается с test.
Что будет, если разделителя нет
Это важная особенность SPLIT_PART.
Если разделитель не найден, вся строка считается первой частью.
Например:
SELECT
SPLIT_PART('hello', '@', 1) AS part_1,
SPLIT_PART('hello', '@', 2) AS part_2;
Результат:
part_1 | part_2
-------+-------
hello |
Первая часть — это вся строка hello.
А второй части нет, поэтому PostgreSQL вернул пустую строку.
Важно:
Если нужной части нет, SPLIT_PART возвращает пустую строку '', а не NULL.
Это может влиять на фильтры и отчёты.
Например, если в таблице есть невалидные email без @, такой запрос:
SELECT
SPLIT_PART(email, '@', 2) AS domain,
COUNT(*) AS users_count
FROM users
GROUP BY SPLIT_PART(email, '@', 2);
может дать отдельную группу с пустым доменом.
domain | users_count
----------+------------
gmail.com | 10
mail.ru | 5
| 2
Пустое значение здесь может означать, что у части пользователей email записан некорректно.
Как отфильтровать строки без нужной части
Если вы хотите работать только с email, где есть символ @, можно добавить фильтр:
SELECT
SPLIT_PART(email, '@', 2) AS domain,
COUNT(*) AS users_count
FROM users
WHERE email LIKE '%@%'
GROUP BY SPLIT_PART(email, '@', 2)
ORDER BY users_count DESC;
Так мы заранее убираем строки, где нет @.
Можно также отфильтровать пустой результат:
SELECT
SPLIT_PART(email, '@', 2) AS domain,
COUNT(*) AS users_count
FROM users
WHERE SPLIT_PART(email, '@', 2) <> ''
GROUP BY SPLIT_PART(email, '@', 2)
ORDER BY users_count DESC;
Оба варианта рабочие, но смысл немного разный.
Первый проверяет, что в email есть разделитель @.
Второй проверяет, что после разделения вторая часть не пустая.
Для простых учебных запросов обычно достаточно email LIKE '%@%'.
Для настоящей валидации email этого мало, но для демонстрации SPLIT_PART подходит хорошо.
Разделитель — это обычный текст, а не регулярное выражение
SPLIT_PART делит строку по конкретному разделителю.
Например:
SELECT SPLIT_PART('a.b.c', '.', 2) AS result;
Результат:
result
------
b
Здесь точка . означает именно точку.
Это не регулярное выражение.
То есть SPLIT_PART не думает, что точка — это «любой символ». Она ищет в строке обычный символ ..
То же самое с другими символами:
SELECT SPLIT_PART('paid:card:eur', ':', 2);
Результат:
card
Разделителем может быть не только один символ, но и строка из нескольких символов.
Например:
SELECT SPLIT_PART('2026--06--17', '--', 2) AS month;
Результат:
month
-----
06
Здесь разделитель — это строка --.
Пример: разбираем составной статус заказа
Допустим, в таблице orders статус хранится в таком виде:
id | status
---+---------------
1 | paid:card:eur
2 | paid:cash:usd
3 | failed:card:eur
4 | pending:bank:usd
Это составная строка. В ней зашито сразу несколько смыслов:
payment_state : method : currency
То есть:
- первая часть — состояние платежа;
- вторая часть — способ оплаты;
- третья часть — валюта.
Можно разложить это по отдельным колонкам прямо в запросе:
SELECT
id,
SPLIT_PART(status, ':', 1) AS payment_state,
SPLIT_PART(status, ':', 2) AS payment_method,
SPLIT_PART(status, ':', 3) AS currency
FROM orders;
Результат:
id | payment_state | payment_method | currency
---+---------------+----------------+---------
1 | paid | card | eur
2 | paid | cash | usd
3 | failed | card | eur
4 | pending | bank | usd
Теперь с этими частями можно работать отдельно.
Например, посчитать заказы по способу оплаты:
SELECT
SPLIT_PART(status, ':', 2) AS payment_method,
COUNT(*) AS orders_count
FROM orders
GROUP BY SPLIT_PART(status, ':', 2)
ORDER BY orders_count DESC;
Или выбрать только платежи в евро:
SELECT
id,
status
FROM orders
WHERE SPLIT_PART(status, ':', 3) = 'eur';
Пример: верхний уровень отдела
Допустим, в таблице employees отдел хранится как путь:
id | name | dept
---+------------+----------------------
1 | Anna | eng/backend/payments
2 | Bob | eng/frontend/web
3 | Kate | marketing/content
4 | Tom | sales/b2b
Первая часть до / — это верхний уровень отдела:
eng
marketing
sales
Запрос:
SELECT
id,
name,
SPLIT_PART(dept, '/', 1) AS top_level_dept
FROM employees;
Результат:
id | name | top_level_dept
---+------+---------------
1 | Anna | eng
2 | Bob | eng
3 | Kate | marketing
4 | Tom | sales
Теперь можно посчитать сотрудников по верхнему уровню отдела:
SELECT
SPLIT_PART(dept, '/', 1) AS top_level_dept,
COUNT(*) AS employees_count
FROM employees
GROUP BY SPLIT_PART(dept, '/', 1)
ORDER BY employees_count DESC;
Результат:
top_level_dept | employees_count
---------------+----------------
eng | 2
marketing | 1
sales | 1
Такой подход удобен, когда в одном поле хранится иерархия.
Отрицательный номер части в PostgreSQL 14+
В PostgreSQL 14 и новее SPLIT_PART умеет брать части с конца строки.
Для этого третий аргумент можно сделать отрицательным.
Например:
SELECT SPLIT_PART('eng/backend/payments', '/', -1) AS last_part;
Результат:
last_part
---------
payments
-1 означает «первая часть с конца», то есть последняя часть строки.
Можно взять предпоследнюю часть:
SELECT SPLIT_PART('eng/backend/payments', '/', -2) AS second_from_end;
Результат:
second_from_end
---------------
backend
Это удобно, когда длина строки заранее неизвестна.
Например, путь может быть таким:
eng/payments
А может быть таким:
eng/backend/payments
А может быть таким:
company/eng/backend/payments
Если вам всегда нужна последняя часть, отрицательный индекс сильно упрощает запрос.
SELECT SPLIT_PART(dept, '/', -1) AS final_dept
FROM employees;
Но есть важный нюанс: если ваш проект работает на PostgreSQL 13 или старше, отрицательный индекс в SPLIT_PART не поддерживается. Такой запрос может завершиться ошибкой.
Поэтому перед использованием отрицательных индексов лучше проверить версию PostgreSQL.
SELECT version();
Если версия старая, последний сегмент можно получить другими способами, например через регулярные выражения.
SELECT regexp_replace('eng/backend/payments', '^.*/', '') AS last_part;
Результат:
last_part
---------
payments
Но для новичка главное запомнить простую мысль:
В новых версиях PostgreSQL можно брать части с конца через отрицательные номера.
В старых версиях так делать нельзя.
SPLIT_PART и пустые части
Иногда в строке могут быть два разделителя подряд.
Например:
a..c
Если разделитель — точка, то между двумя точками находится пустая часть.
Проверим:
SELECT
SPLIT_PART('a..c', '.', 1) AS part_1,
SPLIT_PART('a..c', '.', 2) AS part_2,
SPLIT_PART('a..c', '.', 3) AS part_3;
Результат:
part_1 | part_2 | part_3
-------+--------+-------
a | | c
Вторая часть пустая.
Это не ошибка функции.
Это значит, что в исходной строке действительно есть пустой сегмент между двумя разделителями.
Такое часто встречается в грязных данных:
paid::eur
eng//payments
anna@@gmail.com
Поэтому, если формат строки важен, иногда стоит дополнительно проверять, что нужные части не пустые.
Например:
SELECT
id,
status
FROM orders
WHERE SPLIT_PART(status, ':', 1) <> ''
AND SPLIT_PART(status, ':', 2) <> ''
AND SPLIT_PART(status, ':', 3) <> '';
Такой запрос оставит только строки, где все три части заполнены.
SPLIT_PART не заменяет нормальную структуру данных
SPLIT_PART очень удобен, но им не стоит злоупотреблять.
Если в поле status хранится строка:
paid:card:eur
это может быть нормально для логов, временного импорта или технических событий.
Но если это важные бизнес-данные, часто лучше хранить их в отдельных колонках:
payment_state | payment_method | currency
--------------+----------------+---------
paid | card | eur
Тогда запросы будут проще:
SELECT *
FROM orders
WHERE currency = 'eur';
Вместо:
SELECT *
FROM orders
WHERE SPLIT_PART(status, ':', 3) = 'eur';
Почему это важно?
Потому что отдельные колонки:
- проще читать;
- проще валидировать;
- проще индексировать;
- проще использовать в отчётах;
- меньше зависят от формата строки.
SPLIT_PART отлично подходит для разбора строк, но если вы постоянно достаёте из одной строки одни и те же части, возможно, данные стоит хранить иначе.
SPLIT_PART в WHERE
SPLIT_PART можно использовать не только в SELECT, но и в WHERE.
Например, выбрать пользователей с доменом gmail.com:
SELECT
id,
email
FROM users
WHERE SPLIT_PART(email, '@', 2) = 'gmail.com';
Это удобно и читаемо.
Но на больших таблицах стоит помнить о производительности. Если вы применяете функцию к колонке в условии, обычный индекс по email может не помочь так эффективно, как простой поиск по готовому значению.
Для маленьких таблиц или разовой аналитики это обычно не проблема.
Но если такой фильтр используется часто, можно подумать о более правильном решении:
- хранить домен в отдельной колонке;
- создавать вычисляемую колонку;
- использовать функциональный индекс;
- нормализовать данные при загрузке.
Для обучения достаточно понимать идею:
SPLIT_PART в WHERE работает, но для больших рабочих таблиц лучше заранее подумать о структуре данных и индексах.
SPLIT_PART в GROUP BY
Если вы выводите результат SPLIT_PART и хотите по нему группировать, нужно использовать то же выражение в GROUP BY.
Например:
SELECT
SPLIT_PART(email, '@', 2) AS domain,
COUNT(*) AS users_count
FROM users
GROUP BY SPLIT_PART(email, '@', 2)
ORDER BY users_count DESC;
В PostgreSQL также можно написать короче через номер колонки:
SELECT
SPLIT_PART(email, '@', 2) AS domain,
COUNT(*) AS users_count
FROM users
GROUP BY 1
ORDER BY users_count DESC;
GROUP BY 1 означает:
группировать по первой колонке из SELECT.
В нашем случае первая колонка — это:
SPLIT_PART(email, '@', 2)
Для учебных статей лучше сначала писать полное выражение. Так понятнее, что именно происходит.
А когда вы уже уверенно читаете SQL, можно использовать короткую запись.
Когда SPLIT_PART подходит хорошо
SPLIT_PART хорошо подходит, когда:
- разделитель известен заранее;
- формат строки простой;
- нужно взять конкретную часть;
- не требуется сложная проверка;
- строка не содержит вложенной логики.
Хорошие примеры:
anna@gmail.com
paid:card:eur
eng/backend/payments
RU-MOW-001
2026-06-17
Например, из кода:
RU-MOW-001
можно достать страну, город и номер:
SELECT
SPLIT_PART('RU-MOW-001', '-', 1) AS country_code,
SPLIT_PART('RU-MOW-001', '-', 2) AS city_code,
SPLIT_PART('RU-MOW-001', '-', 3) AS item_number;
Результат:
country_code | city_code | item_number
-------------+-----------+------------
RU | MOW | 001
Это простой, понятный и читаемый сценарий для SPLIT_PART.
Когда SPLIT_PART уже недостаточно
SPLIT_PART становится неудобным, когда формат строки сложный.
Например:
- разделителей может быть разное количество;
- часть строки может отсутствовать;
- внутри значения могут быть вложенные разделители;
- нужно проверять шаблон;
- нужно извлечь значение по сложному правилу;
- строка похожа на JSON, URL или произвольный лог.
Например:
payment[state=paid;method=card;currency=eur]
Здесь простой разрез по одному символу уже не такой удобный.
В таких случаях лучше использовать другие инструменты:
regexp_match;
regexp_replace;
regexp_split_to_array;
- функции для JSON, если строка на самом деле JSON;
- нормализацию данных в отдельные колонки.
Главная идея:
Если строка простая — берите SPLIT_PART.
Если формат сложный — не прячьте бизнес-логику в цепочке разрезаний.
Аналог в MySQL: SUBSTRING_INDEX
В MySQL функции SPLIT_PART нет.
Похожую задачу часто решают через SUBSTRING_INDEX.
Синтаксис:
SUBSTRING_INDEX(string, delimiter, count)
Но работает она немного иначе.
Если count положительный, MySQL берёт всё до указанного количества разделителей слева.
Пример:
SELECT SUBSTRING_INDEX('a.b.c.d', '.', 2) AS result;
Результат:
result
------
a.b
То есть это не ровно вторая часть, а первые две части вместе.
Если count отрицательный, MySQL берёт части справа.
SELECT SUBSTRING_INDEX('a.b.c.d', '.', -1) AS result;
Результат:
result
------
d
Для email это удобно. Чтобы получить домен:
SELECT SUBSTRING_INDEX(email, '@', -1) AS domain
FROM users;
Из строки:
anna@gmail.com
получится:
gmail.com
А вот чтобы получить ровно вторую часть строки a.b.c.d, нужен вложенный вызов:
SELECT SUBSTRING_INDEX(
SUBSTRING_INDEX('a.b.c.d', '.', 2),
'.',
-1
) AS second_part;
Результат:
second_part
-----------
b
Логика такая:
SUBSTRING_INDEX('a.b.c.d', '.', 2) берёт a.b;
- внешний
SUBSTRING_INDEX(..., '.', -1) берёт последнюю часть из a.b;
- получается
b.
То есть в MySQL можно сделать то же самое, но синтаксис менее прямой, чем в PostgreSQL.
Аналог в ClickHouse: splitByChar и splitByString
В ClickHouse задача обычно решается через массивы.
Функция splitByChar разбивает строку по символу и возвращает массив частей.
Например:
SELECT splitByChar('@', 'anna@gmail.com');
Результат будет похож на массив:
['anna', 'gmail.com']
Чтобы взять нужный элемент массива, используют индекс.
В ClickHouse индексация массивов начинается с 1.
Домен email:
SELECT splitByChar('@', email)[2] AS domain
FROM users;
Если разделитель состоит из нескольких символов, используют splitByString.
Например:
SELECT splitByString('--', '2026--06--17')[2] AS month;
Результат:
06
Чтобы взять последний элемент массива, можно использовать arrayElement с отрицательным индексом:
SELECT arrayElement(splitByChar('/', 'eng/backend/payments'), -1) AS last_part;
Результат:
payments
В ClickHouse подход немного другой:
сначала разбиваем строку в массив, потом берём элемент массива.
Краткое сравнение PostgreSQL, MySQL и ClickHouse
В PostgreSQL:
SELECT SPLIT_PART('a.b.c', '.', 2);
Результат:
b
В MySQL:
SELECT SUBSTRING_INDEX(SUBSTRING_INDEX('a.b.c', '.', 2), '.', -1);
Результат:
b
В ClickHouse:
SELECT splitByChar('.', 'a.b.c')[2];
Результат:
b
Идея везде одна:
разбить строку по разделителю и взять нужную часть.
Но синтаксис отличается.
PostgreSQL в этом случае выглядит особенно удобно, потому что SPLIT_PART сразу возвращает нужное поле.
Практические шаблоны
Домен из email
SELECT SPLIT_PART(email, '@', 2) AS domain
FROM users;
Локальная часть email до @
SELECT SPLIT_PART(email, '@', 1) AS local_part
FROM users;
Первая часть статуса
SELECT SPLIT_PART(status, ':', 1) AS payment_state
FROM orders;
Вторая часть статуса
SELECT SPLIT_PART(status, ':', 2) AS payment_method
FROM orders;
Третья часть статуса
SELECT SPLIT_PART(status, ':', 3) AS currency
FROM orders;
Верхний уровень отдела
SELECT SPLIT_PART(dept, '/', 1) AS top_level_dept
FROM employees;
Последняя часть пути в PostgreSQL 14+
SELECT SPLIT_PART(dept, '/', -1) AS last_dept
FROM employees;
Группировка пользователей по доменам
SELECT
SPLIT_PART(email, '@', 2) AS domain,
COUNT(*) AS users_count
FROM users
GROUP BY SPLIT_PART(email, '@', 2)
ORDER BY users_count DESC;
Фильтр по домену
SELECT
id,
email
FROM users
WHERE SPLIT_PART(email, '@', 2) = 'gmail.com';
Что важно запомнить
SPLIT_PART — это функция PostgreSQL для простого разделения строки.
Она используется так:
SPLIT_PART(string, delimiter, field_number)
Например:
SELECT SPLIT_PART('anna@gmail.com', '@', 2);
Результат:
gmail.com
Главные правила:
- части считаются с
1, а не с 0;
- если нужной части нет, возвращается пустая строка
'';
- если разделитель не найден, вся строка считается первой частью;
- разделитель — это обычный текст, а не регулярное выражение;
- в PostgreSQL 14+ можно использовать отрицательные номера частей для отсчёта с конца.
SPLIT_PART хорошо подходит для простых форматов:
email@domain.com
paid:card:eur
eng/backend/payments
RU-MOW-001
Но если формат строки сложный, лучше использовать регулярные выражения, массивы, JSON-функции или хранить данные в отдельных колонках.
Короткий вывод
SPLIT_PART помогает быстро достать нужную часть строки по разделителю.
Например, домен из email:
SELECT SPLIT_PART(email, '@', 2) AS domain
FROM users;
Или валюту из составного статуса:
SELECT SPLIT_PART(status, ':', 3) AS currency
FROM orders;
Это простая и читаемая функция, которая отлично подходит для учебных задач, аналитических запросов и быстрой обработки строк.
Главное — помнить, что SPLIT_PART не проверяет качество данных за вас. Если строка пришла в неожиданном формате, функция не упадёт, а может вернуть пустую строку. Поэтому в важных отчётах лучше дополнительно проверять, что нужная часть действительно существует и не пустая.
В SQL часто приходится работать со строками, внутри которых уже есть понятная структура.
Например:
В email есть две части:
И они разделены символом
@.Или есть путь:
В нём несколько частей:
И они разделены слешем
/.Или есть составной статус заказа:
Он может означать:
В таких случаях не всегда нужны сложные регулярные выражения. Если строка устроена просто и разделитель известен заранее, в PostgreSQL удобно использовать функцию
SPLIT_PART.Она делает ровно то, что нужно: разбивает строку по разделителю и возвращает нужную часть.
Что делает SPLIT_PART простыми словами
SPLIT_PARTберёт строку, разрезает её по указанному разделителю и возвращает одну конкретную часть.Синтаксис:
Где:
string— строка, которую нужно разделить;delimiter— разделитель;field_number— номер части, которую нужно вернуть.Простой пример:
SELECT SPLIT_PART('anna@gmail.com', '@', 1) AS local_part;Результат:
Мы сказали базе:
Если нужна вторая часть:
SELECT SPLIT_PART('anna@gmail.com', '@', 2) AS domain;Результат:
То есть
SPLIT_PARTидеально подходит для ситуаций, где строка похожа на набор частей, склеенных через один и тот же разделитель.Нумерация начинается с 1
Очень важный момент: части строки в
SPLIT_PARTсчитаются с единицы.Не с нуля, как во многих языках программирования, а именно с
1.Например:
SELECT SPLIT_PART('a.b.c', '.', 1) AS part_1, SPLIT_PART('a.b.c', '.', 2) AS part_2, SPLIT_PART('a.b.c', '.', 3) AS part_3;Результат:
Можно представить это так:
Поэтому, если вы хотите взять первую часть строки, пишите
1, а не0.Пример с email: получаем домен
Один из самых понятных примеров — получение домена из email.
Допустим, есть таблица
users:Чтобы получить домен, нужно взять вторую часть email после символа
@.SELECT id, email, SPLIT_PART(email, '@', 2) AS domain FROM users;Результат:
Теперь можно, например, посчитать пользователей по почтовым доменам.
SELECT SPLIT_PART(email, '@', 2) AS domain, COUNT(*) AS users_count FROM users GROUP BY SPLIT_PART(email, '@', 2) ORDER BY users_count DESC;Результат может быть таким:
Такой запрос помогает быстро понять, какими почтовыми сервисами пользуются ваши пользователи.
Получаем часть email до @
Если вторая часть email — это домен, то первая часть — это всё, что идёт до
@.Например:
Первая часть:
Запрос:
SELECT id, email, SPLIT_PART(email, '@', 1) AS local_part FROM users;Результат:
Это может пригодиться, например, чтобы быстро найти тестовые аккаунты, внутренние маски или пользователей с определённым шаблоном в email.
SELECT id, email FROM users WHERE SPLIT_PART(email, '@', 1) LIKE 'test%';Такой запрос найдёт email, где часть до
@начинается сtest.Что будет, если разделителя нет
Это важная особенность
SPLIT_PART.Если разделитель не найден, вся строка считается первой частью.
Например:
SELECT SPLIT_PART('hello', '@', 1) AS part_1, SPLIT_PART('hello', '@', 2) AS part_2;Результат:
Первая часть — это вся строка
hello.А второй части нет, поэтому PostgreSQL вернул пустую строку.
Важно:
Это может влиять на фильтры и отчёты.
Например, если в таблице есть невалидные email без
@, такой запрос:SELECT SPLIT_PART(email, '@', 2) AS domain, COUNT(*) AS users_count FROM users GROUP BY SPLIT_PART(email, '@', 2);может дать отдельную группу с пустым доменом.
Пустое значение здесь может означать, что у части пользователей email записан некорректно.
Как отфильтровать строки без нужной части
Если вы хотите работать только с email, где есть символ
@, можно добавить фильтр:SELECT SPLIT_PART(email, '@', 2) AS domain, COUNT(*) AS users_count FROM users WHERE email LIKE '%@%' GROUP BY SPLIT_PART(email, '@', 2) ORDER BY users_count DESC;Так мы заранее убираем строки, где нет
@.Можно также отфильтровать пустой результат:
SELECT SPLIT_PART(email, '@', 2) AS domain, COUNT(*) AS users_count FROM users WHERE SPLIT_PART(email, '@', 2) <> '' GROUP BY SPLIT_PART(email, '@', 2) ORDER BY users_count DESC;Оба варианта рабочие, но смысл немного разный.
Первый проверяет, что в email есть разделитель
@.Второй проверяет, что после разделения вторая часть не пустая.
Для простых учебных запросов обычно достаточно
email LIKE '%@%'. Для настоящей валидации email этого мало, но для демонстрацииSPLIT_PARTподходит хорошо.Разделитель — это обычный текст, а не регулярное выражение
SPLIT_PARTделит строку по конкретному разделителю.Например:
SELECT SPLIT_PART('a.b.c', '.', 2) AS result;Результат:
Здесь точка
.означает именно точку.Это не регулярное выражение.
То есть
SPLIT_PARTне думает, что точка — это «любой символ». Она ищет в строке обычный символ..То же самое с другими символами:
SELECT SPLIT_PART('paid:card:eur', ':', 2);Результат:
Разделителем может быть не только один символ, но и строка из нескольких символов.
Например:
SELECT SPLIT_PART('2026--06--17', '--', 2) AS month;Результат:
Здесь разделитель — это строка
--.Пример: разбираем составной статус заказа
Допустим, в таблице
ordersстатус хранится в таком виде:Это составная строка. В ней зашито сразу несколько смыслов:
То есть:
Можно разложить это по отдельным колонкам прямо в запросе:
SELECT id, SPLIT_PART(status, ':', 1) AS payment_state, SPLIT_PART(status, ':', 2) AS payment_method, SPLIT_PART(status, ':', 3) AS currency FROM orders;Результат:
Теперь с этими частями можно работать отдельно.
Например, посчитать заказы по способу оплаты:
SELECT SPLIT_PART(status, ':', 2) AS payment_method, COUNT(*) AS orders_count FROM orders GROUP BY SPLIT_PART(status, ':', 2) ORDER BY orders_count DESC;Или выбрать только платежи в евро:
SELECT id, status FROM orders WHERE SPLIT_PART(status, ':', 3) = 'eur';Пример: верхний уровень отдела
Допустим, в таблице
employeesотдел хранится как путь:Первая часть до
/— это верхний уровень отдела:Запрос:
SELECT id, name, SPLIT_PART(dept, '/', 1) AS top_level_dept FROM employees;Результат:
Теперь можно посчитать сотрудников по верхнему уровню отдела:
SELECT SPLIT_PART(dept, '/', 1) AS top_level_dept, COUNT(*) AS employees_count FROM employees GROUP BY SPLIT_PART(dept, '/', 1) ORDER BY employees_count DESC;Результат:
Такой подход удобен, когда в одном поле хранится иерархия.
Отрицательный номер части в PostgreSQL 14+
В PostgreSQL 14 и новее
SPLIT_PARTумеет брать части с конца строки.Для этого третий аргумент можно сделать отрицательным.
Например:
SELECT SPLIT_PART('eng/backend/payments', '/', -1) AS last_part;Результат:
-1означает «первая часть с конца», то есть последняя часть строки.Можно взять предпоследнюю часть:
SELECT SPLIT_PART('eng/backend/payments', '/', -2) AS second_from_end;Результат:
Это удобно, когда длина строки заранее неизвестна.
Например, путь может быть таким:
А может быть таким:
А может быть таким:
Если вам всегда нужна последняя часть, отрицательный индекс сильно упрощает запрос.
SELECT SPLIT_PART(dept, '/', -1) AS final_dept FROM employees;Но есть важный нюанс: если ваш проект работает на PostgreSQL 13 или старше, отрицательный индекс в
SPLIT_PARTне поддерживается. Такой запрос может завершиться ошибкой.Поэтому перед использованием отрицательных индексов лучше проверить версию PostgreSQL.
SELECT version();Если версия старая, последний сегмент можно получить другими способами, например через регулярные выражения.
SELECT regexp_replace('eng/backend/payments', '^.*/', '') AS last_part;Результат:
Но для новичка главное запомнить простую мысль:
SPLIT_PART и пустые части
Иногда в строке могут быть два разделителя подряд.
Например:
Если разделитель — точка, то между двумя точками находится пустая часть.
Проверим:
SELECT SPLIT_PART('a..c', '.', 1) AS part_1, SPLIT_PART('a..c', '.', 2) AS part_2, SPLIT_PART('a..c', '.', 3) AS part_3;Результат:
Вторая часть пустая.
Это не ошибка функции. Это значит, что в исходной строке действительно есть пустой сегмент между двумя разделителями.
Такое часто встречается в грязных данных:
Поэтому, если формат строки важен, иногда стоит дополнительно проверять, что нужные части не пустые.
Например:
SELECT id, status FROM orders WHERE SPLIT_PART(status, ':', 1) <> '' AND SPLIT_PART(status, ':', 2) <> '' AND SPLIT_PART(status, ':', 3) <> '';Такой запрос оставит только строки, где все три части заполнены.
SPLIT_PART не заменяет нормальную структуру данных
SPLIT_PARTочень удобен, но им не стоит злоупотреблять.Если в поле
statusхранится строка:это может быть нормально для логов, временного импорта или технических событий.
Но если это важные бизнес-данные, часто лучше хранить их в отдельных колонках:
Тогда запросы будут проще:
SELECT * FROM orders WHERE currency = 'eur';Вместо:
SELECT * FROM orders WHERE SPLIT_PART(status, ':', 3) = 'eur';Почему это важно?
Потому что отдельные колонки:
SPLIT_PARTотлично подходит для разбора строк, но если вы постоянно достаёте из одной строки одни и те же части, возможно, данные стоит хранить иначе.SPLIT_PART в WHERE
SPLIT_PARTможно использовать не только вSELECT, но и вWHERE.Например, выбрать пользователей с доменом
gmail.com:SELECT id, email FROM users WHERE SPLIT_PART(email, '@', 2) = 'gmail.com';Это удобно и читаемо.
Но на больших таблицах стоит помнить о производительности. Если вы применяете функцию к колонке в условии, обычный индекс по
emailможет не помочь так эффективно, как простой поиск по готовому значению.Для маленьких таблиц или разовой аналитики это обычно не проблема.
Но если такой фильтр используется часто, можно подумать о более правильном решении:
Для обучения достаточно понимать идею:
SPLIT_PART в GROUP BY
Если вы выводите результат
SPLIT_PARTи хотите по нему группировать, нужно использовать то же выражение вGROUP BY.Например:
SELECT SPLIT_PART(email, '@', 2) AS domain, COUNT(*) AS users_count FROM users GROUP BY SPLIT_PART(email, '@', 2) ORDER BY users_count DESC;В PostgreSQL также можно написать короче через номер колонки:
SELECT SPLIT_PART(email, '@', 2) AS domain, COUNT(*) AS users_count FROM users GROUP BY 1 ORDER BY users_count DESC;GROUP BY 1означает:В нашем случае первая колонка — это:
SPLIT_PART(email, '@', 2)Для учебных статей лучше сначала писать полное выражение. Так понятнее, что именно происходит.
А когда вы уже уверенно читаете SQL, можно использовать короткую запись.
Когда SPLIT_PART подходит хорошо
SPLIT_PARTхорошо подходит, когда:Хорошие примеры:
Например, из кода:
можно достать страну, город и номер:
SELECT SPLIT_PART('RU-MOW-001', '-', 1) AS country_code, SPLIT_PART('RU-MOW-001', '-', 2) AS city_code, SPLIT_PART('RU-MOW-001', '-', 3) AS item_number;Результат:
Это простой, понятный и читаемый сценарий для
SPLIT_PART.Когда SPLIT_PART уже недостаточно
SPLIT_PARTстановится неудобным, когда формат строки сложный.Например:
Например:
Здесь простой разрез по одному символу уже не такой удобный.
В таких случаях лучше использовать другие инструменты:
regexp_match;regexp_replace;regexp_split_to_array;Главная идея:
Аналог в MySQL: SUBSTRING_INDEX
В MySQL функции
SPLIT_PARTнет.Похожую задачу часто решают через
SUBSTRING_INDEX.Синтаксис:
Но работает она немного иначе.
Если
countположительный, MySQL берёт всё до указанного количества разделителей слева.Пример:
SELECT SUBSTRING_INDEX('a.b.c.d', '.', 2) AS result;Результат:
То есть это не ровно вторая часть, а первые две части вместе.
Если
countотрицательный, MySQL берёт части справа.SELECT SUBSTRING_INDEX('a.b.c.d', '.', -1) AS result;Результат:
Для email это удобно. Чтобы получить домен:
SELECT SUBSTRING_INDEX(email, '@', -1) AS domain FROM users;Из строки:
получится:
А вот чтобы получить ровно вторую часть строки
a.b.c.d, нужен вложенный вызов:SELECT SUBSTRING_INDEX( SUBSTRING_INDEX('a.b.c.d', '.', 2), '.', -1 ) AS second_part;Результат:
Логика такая:
SUBSTRING_INDEX('a.b.c.d', '.', 2)берётa.b;SUBSTRING_INDEX(..., '.', -1)берёт последнюю часть изa.b;b.То есть в MySQL можно сделать то же самое, но синтаксис менее прямой, чем в PostgreSQL.
Аналог в ClickHouse: splitByChar и splitByString
В ClickHouse задача обычно решается через массивы.
Функция
splitByCharразбивает строку по символу и возвращает массив частей.Например:
SELECT splitByChar('@', 'anna@gmail.com');Результат будет похож на массив:
Чтобы взять нужный элемент массива, используют индекс.
В ClickHouse индексация массивов начинается с
1.Домен email:
SELECT splitByChar('@', email)[2] AS domain FROM users;Если разделитель состоит из нескольких символов, используют
splitByString.Например:
SELECT splitByString('--', '2026--06--17')[2] AS month;Результат:
Чтобы взять последний элемент массива, можно использовать
arrayElementс отрицательным индексом:SELECT arrayElement(splitByChar('/', 'eng/backend/payments'), -1) AS last_part;Результат:
В ClickHouse подход немного другой:
Краткое сравнение PostgreSQL, MySQL и ClickHouse
В PostgreSQL:
SELECT SPLIT_PART('a.b.c', '.', 2);Результат:
В MySQL:
SELECT SUBSTRING_INDEX(SUBSTRING_INDEX('a.b.c', '.', 2), '.', -1);Результат:
В ClickHouse:
SELECT splitByChar('.', 'a.b.c')[2];Результат:
Идея везде одна:
Но синтаксис отличается.
PostgreSQL в этом случае выглядит особенно удобно, потому что
SPLIT_PARTсразу возвращает нужное поле.Практические шаблоны
Домен из email
SELECT SPLIT_PART(email, '@', 2) AS domain FROM users;Локальная часть email до @
SELECT SPLIT_PART(email, '@', 1) AS local_part FROM users;Первая часть статуса
SELECT SPLIT_PART(status, ':', 1) AS payment_state FROM orders;Вторая часть статуса
SELECT SPLIT_PART(status, ':', 2) AS payment_method FROM orders;Третья часть статуса
SELECT SPLIT_PART(status, ':', 3) AS currency FROM orders;Верхний уровень отдела
SELECT SPLIT_PART(dept, '/', 1) AS top_level_dept FROM employees;Последняя часть пути в PostgreSQL 14+
SELECT SPLIT_PART(dept, '/', -1) AS last_dept FROM employees;Группировка пользователей по доменам
SELECT SPLIT_PART(email, '@', 2) AS domain, COUNT(*) AS users_count FROM users GROUP BY SPLIT_PART(email, '@', 2) ORDER BY users_count DESC;Фильтр по домену
SELECT id, email FROM users WHERE SPLIT_PART(email, '@', 2) = 'gmail.com';Что важно запомнить
SPLIT_PART— это функция PostgreSQL для простого разделения строки.Она используется так:
Например:
SELECT SPLIT_PART('anna@gmail.com', '@', 2);Результат:
Главные правила:
1, а не с0;'';SPLIT_PARTхорошо подходит для простых форматов:Но если формат строки сложный, лучше использовать регулярные выражения, массивы, JSON-функции или хранить данные в отдельных колонках.
Короткий вывод
SPLIT_PARTпомогает быстро достать нужную часть строки по разделителю.Например, домен из email:
SELECT SPLIT_PART(email, '@', 2) AS domain FROM users;Или валюту из составного статуса:
SELECT SPLIT_PART(status, ':', 3) AS currency FROM orders;Это простая и читаемая функция, которая отлично подходит для учебных задач, аналитических запросов и быстрой обработки строк.
Главное — помнить, что
SPLIT_PARTне проверяет качество данных за вас. Если строка пришла в неожиданном формате, функция не упадёт, а может вернуть пустую строку. Поэтому в важных отчётах лучше дополнительно проверять, что нужная часть действительно существует и не пустая.