Практика SQL
4 мин чтения·

Строковые функции SQL с примерами

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

ЗадачаФункции
Склеить строкиCONCAT, CONCAT_WS, GROUP_CONCAT / STRING_AGG
Вырезать часть строкиSUBSTRING, LEFT, RIGHT
ПочиститьTRIM, REPLACE
Сменить регистрUPPER, LOWER
Найти и измеритьPOSITION, LIKE, LENGTH / CHAR_LENGTH

Разберём каждую группу на примерах из базы данных сервиса доставки — все запросы можно выполнять прямо в статье.

Склеить: CONCAT и CONCAT_WS

CONCAT объединяет любое число строк в одну:

MySQL 8.1
SELECT CONCAT(name, ' — ', category) AS title
FROM products
ORDER BY product_id
LIMIT 3;
title
Margherita Pizza — Food
Caesar Salad — Food
Cheeseburger — Food

Когда разделитель один и тот же, удобнее CONCAT_WS («with separator»): первый аргумент — разделитель, дальше — склеиваемые части:

MySQL 8.1
SELECT CONCAT_WS(', ', country, acquisition_channel) FROM users;
-- 'Germany, Ads'

В PostgreSQL и стандартном SQL есть ещё оператор ||: name || ' — ' || category. В MySQL по умолчанию || означает логическое ИЛИ, поэтому в переносимом коде надёжнее CONCAT.

Ловушка NULL в конкатенации

На NULL функции склейки ведут себя по-разному, и это популярный источник пустых строк в отчётах:

  • MySQL: CONCAT('a', NULL, 'b') возвращает NULL — одно пустое значение «отравляет» весь результат.
  • PostgreSQL: CONCAT('a', NULL, 'b') возвращает abNULL просто пропускается. Но оператор || ведёт себя как MySQL: 'a' || NULL даёт NULL.

Если в склеиваемых столбцах возможны пропуски, оберните их в COALESCE — или используйте CONCAT_WS, который игнорирует NULL в обеих СУБД.

Что вернёт CONCAT('Заказ №', order_id, ' от ', promo_code) в MySQL, если promo_code равен NULL?

Склеить группу строк: GROUP_CONCAT и STRING_AGG

Отдельная суперсила — собрать в одну строку значения из нескольких строк таблицы. Соберём список товаров каждой категории:

В MySQL это делает GROUP_CONCAT:

MySQL 8.1
SELECT category, GROUP_CONCAT(name ORDER BY name SEPARATOR ', ') AS product_list
FROM products
GROUP BY category
ORDER BY category;

В PostgreSQL ту же задачу решает STRING_AGG:

PostgreSQL 17.5
SELECT category, STRING_AGG(name, ', ' ORDER BY name) AS product_list
FROM products
GROUP BY category
ORDER BY category;
categoryproduct_list
DrinksCoca-Cola 0.5L, Fresh Orange Juice, Green Smoothie, Latte, Mineral Water 1L
FoodCaesar Salad, Cheeseburger, Margherita Pizza, Pad Thai, Sushi Set
GroceryOlive Oil 500ml, Rice 1kg
SnacksChips Pack, Dark Chocolate, Granola Bar

Функции называются по-разному — GROUP_CONCAT в MySQL, STRING_AGG в PostgreSQL, — но делают одно и то же. Это агрегатные функции, как SUM или COUNT, поэтому работают вместе с GROUP BY.

Какая функция соберёт значения из нескольких строк таблицы в один список через запятую в PostgreSQL?

Вырезать: SUBSTRING, LEFT, RIGHT

SUBSTRING(строка, позиция, длина) вырезает фрагмент строки; нумерация символов начинается с единицы. В связке с POSITION, которая находит позицию подстроки, получаются практичные приёмы — например, выделить домен из email:

MySQL 8.1
SELECT email, SUBSTRING(email, POSITION('@' IN email) + 1) AS domain
FROM users
ORDER BY user_id
LIMIT 3;
emaildomain
jessicalopez@gmail.comgmail.com
joseph.garcia@yahoo.comyahoo.com
margaret.anderson3@hotmail.comhotmail.com

POSITION('@' IN email) возвращает номер позиции символа @, а SUBSTRING без третьего аргумента забирает всё от этой позиции до конца строки. Попробуйте сами: вытащите из email логин — часть до символа @. Понадобится третий аргумент и POSITION('@' IN email) - 1.

Для краёв строки есть сокращения: LEFT(строка, n) — первые n символов, RIGHT(строка, n) — последние n.

Почистить: TRIM и REPLACE

TRIM убирает пробелы с обоих концов строки — первая помощь при работе с данными, введёнными людьми:

MySQL 8.1
SELECT TRIM('  SUMMER20  ');
-- 'SUMMER20'

REPLACE(строка, что_искать, на_что_заменить) заменяет все вхождения подстроки:

MySQL 8.1
SELECT phone, REPLACE(REPLACE(phone, '-', ''), ' ', '') AS normalized
FROM users
WHERE user_id IN (1, 4)
ORDER BY user_id;
phonenormalized
+1 196 378 5287+11963785287
1-508-436-743415084367434

Вложенные REPLACE — обычный приём для нормализации телефонов и артикулов перед сравнением. Для замен по шаблону, а не по точной подстроке, существует REGEXP_REPLACE — про шаблоны мы подробно рассказываем в уроке Оператор REGEXP.

Частный случай очистки — пустые строки и строки из пробелов вместо NULL. Эту задачу решает связка COALESCE(NULLIF(TRIM(...), ''), ...), которую мы разобрали в статье про COALESCE.

Регистр и длина: UPPER, LOWER, LENGTH

UPPER и LOWER переводят строку в верхний и нижний регистр. Классическое применение — сравнение без учёта регистра: LOWER(email) = LOWER('User@Mail.com').

С длиной строки есть нюанс, который стоит знать заранее:

  • MySQL: LENGTH() считает байты, а CHAR_LENGTH()символы. Для латиницы это одно и то же, но кириллица в UTF-8 занимает два байта на букву: LENGTH('привет') вернёт 12, а CHAR_LENGTH('привет') — 6.
  • PostgreSQL: LENGTH() сразу считает символы — LENGTH('привет') вернёт 6.

Если нужна длина текста с точки зрения человека — используйте CHAR_LENGTH: она работает одинаково в обеих СУБД.

Различия MySQL и PostgreSQL: сводная таблица

ЗадачаMySQLPostgreSQL
Склейка строкCONCAT, CONCAT_WSCONCAT, CONCAT_WS, ||
CONCAT с NULLВернёт NULLПропустит NULL
Агрегация строкGROUP_CONCATSTRING_AGG
Длина в символахCHAR_LENGTHLENGTH или CHAR_LENGTH
Позиция подстрокиPOSITION, LOCATEPOSITION, STRPOS
Замена по шаблонуREGEXP_REPLACEREGEXP_REPLACE

Что дальше

Читайте по теме