Строковые функции SQL — полный гайд с примерами
Коротко
Строковые функции преобразуют и анализируют текстовые данные: склеивают, обрезают, заменяют, приводят к единому регистру. Аналитику данных они нужны для чистки данных, парсинга UTM-меток, извлечения доменов из email, стандартизации названий. На собеседованиях по SQL строковые функции встречаются в задачах на обработку сырых данных — когда нужно привести хаос к порядку одним запросом.
CONCAT — склейка строк
CONCAT соединяет две и более строк в одну. Если один из аргументов NULL, CONCAT в PostgreSQL и MySQL заменит его на пустую строку (не вернёт NULL).
SELECT CONCAT(first_name, ' ', last_name) AS full_name
FROM usersВ PostgreSQL чаще используют оператор ||:
SELECT first_name || ' ' || last_name AS full_name
FROM usersРазница: || вернёт NULL, если хотя бы один аргумент NULL. CONCAT — нет. На практике это важно, когда в данных есть пропуски.
CONCAT_WS — склейка с разделителем. Первый аргумент — разделитель, остальные — значения. Пропускает NULL-аргументы:
SELECT CONCAT_WS(', ', city, street, building) AS address
FROM clients
-- если street IS NULL, вернёт 'Москва, 12', а не 'Москва, , 12'SUBSTRING / SUBSTR — извлечение подстроки
Вырезает часть строки по позиции и длине.
-- SUBSTRING(строка FROM начало FOR длина)
SELECT SUBSTRING(phone FROM 1 FOR 3) AS country_code
FROM users
-- альтернативный синтаксис
SELECT SUBSTRING(email, 1, POSITION('@' IN email) - 1) AS username
FROM usersПозиции в SQL начинаются с 1, а не с 0.
LENGTH / CHAR_LENGTH — длина строки
SELECT email, LENGTH(email) AS email_length
FROM users
WHERE LENGTH(email) > 50LENGTH в PostgreSQL возвращает количество символов. В MySQL LENGTH возвращает количество байтов (для UTF-8 кириллица займёт 2 байта на символ), а CHAR_LENGTH — количество символов. На собеседовании могут уточнить разницу.
UPPER / LOWER — регистр
Приводят строку к верхнему или нижнему регистру. Базовый приём для стандартизации данных.
SELECT *
FROM users
WHERE LOWER(email) = 'ivan@example.com'
SELECT UPPER(city) AS city_upper
FROM clientsЧастый кейс — поиск без учёта регистра. В PostgreSQL есть ILIKE (аналог LIKE, но регистронезависимый), но в MySQL и других СУБД приходится оборачивать в LOWER.
TRIM / LTRIM / RTRIM — удаление пробелов
TRIM убирает пробелы (или указанные символы) с обоих концов строки. LTRIM — слева, RTRIM — справа.
SELECT TRIM(' Москва ') -- 'Москва'
SELECT LTRIM(' Москва ') -- 'Москва '
SELECT RTRIM(' Москва ') -- ' Москва'
-- удаление конкретного символа (PostgreSQL)
SELECT TRIM(BOTH '/' FROM '/api/v1/') -- 'api/v1'На практике TRIM спасает от невидимых пробелов в данных, которые ломают JOIN и группировки. Если GROUP BY city выдаёт отдельно «Москва» и «Москва » — это оно.
REPLACE — замена подстроки
Заменяет все вхождения подстроки на другую.
SELECT REPLACE(phone, '-', '') AS clean_phone
FROM users
-- '8-999-123-45-67' → '89991234567'
SELECT REPLACE(REPLACE(city, 'г.', ''), 'г ', '') AS clean_city
FROM clientsPOSITION / STRPOS — поиск подстроки
Возвращает позицию первого вхождения подстроки. Если не найдена — 0.
-- PostgreSQL: POSITION(подстрока IN строка)
SELECT POSITION('@' IN email) AS at_pos
FROM users
-- PostgreSQL также: STRPOS(строка, подстрока)
SELECT STRPOS(url, '?') AS query_start
FROM page_viewsLEFT / RIGHT — начало и конец строки
LEFT возвращает N символов с начала, RIGHT — с конца.
SELECT LEFT(phone, 1) AS first_digit
FROM users
SELECT RIGHT(card_number, 4) AS last_four_digits
FROM paymentsSPLIT_PART — разбиение по разделителю (PostgreSQL)
Разбивает строку по разделителю и возвращает N-ю часть.
SELECT SPLIT_PART(email, '@', 2) AS domain
FROM users
-- 'ivan@gmail.com' → 'gmail.com'
SELECT SPLIT_PART(utm_campaign, '_', 1) AS campaign_type
FROM traffic
-- 'brand_search_msk' → 'brand'В MySQL аналога нет — приходится комбинировать SUBSTRING и LOCATE.
INITCAP — каждое слово с заглавной (PostgreSQL)
SELECT INITCAP('иван петров') -- 'Иван Петров'
SELECT INITCAP('МОСКВА') -- 'Москва'Полезно для стандартизации имён и городов. В MySQL эту функцию приходится эмулировать.
Практические примеры
Извлечение домена из email
SELECT
SPLIT_PART(email, '@', 2) AS domain,
COUNT(*) AS user_count
FROM users
GROUP BY SPLIT_PART(email, '@', 2)
ORDER BY user_count DESCЧистка телефонных номеров
SELECT
REPLACE(REPLACE(REPLACE(REPLACE(phone, '-', ''), ' ', ''), '(', ''), ')', '') AS clean_phone
FROM usersПарсинг UTM-параметров из URL
SELECT
SPLIT_PART(
SPLIT_PART(url, 'utm_source=', 2),
'&', 1
) AS utm_source
FROM page_views
WHERE url LIKE '%utm_source=%'Стандартизация названий городов
SELECT
INITCAP(TRIM(LOWER(
REPLACE(REPLACE(city, 'г.', ''), 'г ', '')
))) AS clean_city
FROM clientsPostgreSQL vs MySQL: ключевые различия
| Функция | PostgreSQL | MySQL |
|---|---|---|
| Конкатенация | || и CONCAT |
CONCAT (оператор || не работает) |
| LENGTH | символы | байты (используйте CHAR_LENGTH) |
| SPLIT_PART | есть | нет (SUBSTRING_INDEX) |
| INITCAP | есть | нет |
| ILIKE | есть | нет (используйте LOWER + LIKE) |
| STRPOS | есть | нет (используйте LOCATE) |
На собеседованиях чаще спрашивают PostgreSQL-синтаксис, но знать отличия полезно — могут уточнить.
Типичные ошибки
Забыть про NULL. Оператор || в PostgreSQL вернёт NULL, если хотя бы один аргумент NULL. Используйте CONCAT или COALESCE.
LENGTH вместо CHAR_LENGTH в MySQL. Для кириллицы LENGTH('Москва') = 12 (байтов), а CHAR_LENGTH('Москва') = 6 (символов).
Индексация с 0. В SQL позиции начинаются с 1. SUBSTRING('abc', 0, 2) не даст ожидаемый результат — это не Python.
TRIM не помогает. Если после TRIM данные всё ещё не матчатся — проверьте на невидимые символы: табуляции, неразрывные пробелы (CHAR(160)). Используйте REPLACE(string, CHR(160), '').
Вопросы с собеседований
— Чем отличается CONCAT от оператора || в PostgreSQL?
— CONCAT игнорирует NULL-аргументы (заменяет на пустую строку). Оператор || вернёт NULL, если хотя бы один операнд NULL. Поэтому 'Hello' || NULL = NULL, а CONCAT('Hello', NULL) = 'Hello'.
— Как извлечь домен из email одним запросом?
— В PostgreSQL: SPLIT_PART(email, '@', 2). Универсальный способ: SUBSTRING(email FROM POSITION('@' IN email) + 1).
— Чем LENGTH отличается от CHAR_LENGTH? — В PostgreSQL оба возвращают количество символов. В MySQL LENGTH возвращает байты, а CHAR_LENGTH — символы. Для латиницы разницы нет, для кириллицы и других многобайтовых кодировок — есть.
— Как привести данные к единому регистру для корректного JOIN?
— Оберните обе стороны в LOWER (или UPPER): WHERE LOWER(a.city) = LOWER(b.city). Лучше стандартизовать данные заранее, потому что функции на столбцах убивают использование индексов.
— Напишите запрос: вывести пользователей, у которых email на gmail.com, и первую букву имени — заглавную.
— SELECT INITCAP(name) AS name, email FROM users WHERE email LIKE '%@gmail.com'. Или через LOWER: WHERE LOWER(email) LIKE '%@gmail.com' — чтобы поймать и 'GMAIL.COM'.
Потренируйтесь решать задачи — откройте тренажёр с 1500+ вопросами для подготовки к собеседованиям аналитиков.
FAQ
Какие строковые функции спрашивают на собеседованиях чаще всего?
CONCAT, SUBSTRING, TRIM, LOWER/UPPER и REPLACE. Обычно не спрашивают отдельно «расскажите про TRIM» — дают задачу на чистку данных, где нужно скомбинировать несколько функций. Типичный формат: «напишите запрос, который из сырых данных извлечёт нужное поле».
Строковые функции работают с NULL?
Большинство строковых функций вернут NULL, если аргумент NULL. Исключение — CONCAT, который заменяет NULL на пустую строку. Если в данных есть пропуски, оборачивайте в COALESCE: LOWER(COALESCE(city, '')).
Влияют ли строковые функции на производительность?
Да. Функции на столбцах (LOWER(city), TRIM(name)) не дают использовать обычные индексы. Если фильтруете часто — создайте функциональный индекс: CREATE INDEX idx_users_email_lower ON users (LOWER(email)). Или стандартизуйте данные при вставке.
Как тренироваться
Строковые функции лучше всего учатся на задачах с реальными данными — чистка email-ов, парсинг URL, стандартизация имён. В тренажёре Карьерник есть задачи на обработку строк и другие темы SQL — с разборами. Больше вопросов — в разделе с примерами.