> SmartGate 41 752 9863

Конкатенация строк

Склеивание текста в PostgreSQL

Теория

Допустим нам захотелось собрать готовый текст из нескольких частей. Например:
• показать полное имя пользователя из имени и фамилии;
• собрать подпись вида 'Москва / gmail.com';
• сделать код товара из кусочков названия, категории и идентификатора;
• подготовить более удобный для чтения текст прямо в результате запроса.

Такое склеивание строк и называется конкатенацией. У слова «конкатенация», кстати, есть много похожих по смыслу определений: сцепление, соединение и так далее.

Так вот, в контексте PostgreSQL конкатенация — это когда мы берём несколько текстовых фрагментов и соединяем их в одну строку. Для этого есть несколько способов:
• функция CONCAT();
• функция CONCAT_WS();
• оператор ||.

На первый взгляд они делают одно и то же, но на практике между ними есть важная разница, особенно когда в одном из фрагментов встречается NULL.

Достаточно часто при конкатенации вы будете использовать уже знакомые вам инструменты:
• строковые функции вроде LEFT, RIGHT, UPPER;
• подстановка через COALESCE;
• условная логика через CASE;

Синтаксис

SELECT CONCAT(first_name, ' ', last_name),
       CONCAT_WS(' / ', city, email),
       first_name || ' ' || last_name
FROM table_name;
SQL
Общий смысл у всех трёх вариантов один: они помогают склеить несколько частей в одну строку.

Что важно понимать по каждому из них:
CONCAT(part1, part2, part3) — просто соединяет части друг за другом;
CONCAT(part1,'разделитель', part2,'разделитель', part3) — соединяет части друг за другом и между ними вставляет разделитель;
CONCAT_WS('разделитель', part1, part2, part3) — тоже склеивает части, но автоматически вставляет между ними указанный в самом начале разделитель;
part1 || part2 — оператор склеивания строк, делает тоже самое, но есть но...

Теперь, про то самое но:
CONCAT в PostgreSQL спокойно переживает NULL и просто пропускает такой фрагмент;
CONCAT_WS тоже пропускает NULL, а разделитель ставит только между реальными значениями;
• оператор || ведёт себя строже: если одна из частей при конкатенации равна NULL, весь результат склейки станет NULL.

Это очень важная разница. Например:
CONCAT('SQL', NULL, 'PRO') вернёт 'SQLPRO';
'SQL' || NULL || 'PRO' вернёт NULL.

Когда и что использовать:
CONCAT удобно, когда фрагментов немного или когда между ними нужны разные разделители;
CONCAT_WS особенно хорош, когда нужно склеить много частей через один и тот же разделитель;
CONCAT_WS не подойдет, если между разными частями должны стоять разные разделители;
|| удобен для коротких и простых выражений, но с ним нужно внимательнее следить за NULL.

Ещё одна особенность PostgreSQL: CONCAT и CONCAT_WS сами умеют приводить многие значения к тексту. Поэтому, если вы склеиваете строку, число или дату, дополнительно переводить тип данных ::text часто вообще не нужно. Но если вы хотите явно контролировать формат значения, приведение типа явным образом не будет лишним.

Как упоминалось выше в конкатенацию можно спокойно вкладывать уже знакомые функции. Например, запись CONCAT(UPPER(LEFT(city, 3)), '-', user_id) сначала возьмёт первые три буквы города, переведёт их в верхний регистр, а потом склеит с дефисом и идентификатором.

Примеры

1. Собираем полное имя пользователя

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

🔄 Попробуйте изменить запрос:

  • • Замените пробел на ' - ' и посмотрите, как изменится результат
  • • Попробуйте тот же сценарий через оператор ||

2. Склеиваем сразу несколько столбцов через один разделитель

Здесь хорошо видно удобство функции CONCAT_WS. Когда нужно склеить сразу несколько столбцов используя один и тот же разделитель, запись получается заметно чище, чем если после каждого столбца добавлять разделитель через функцию CONCAT. И если один из фрагментов равен NULL, функция не будет лепить лишний разделитель в пустоту.

🔄 Попробуйте изменить запрос:

  • • Перепишите этот же запрос через обычный CONCAT и сравните, насколько запись станет длиннее
  • • Добавьте в склейку ещё и registration_date и посмотрите, что PostgreSQL сам приведёт дату к тексту

3. Сравниваем поведение CONCAT и оператора || при NULL

Это один из самых важных примеров темы. Он сразу показывает разницу в поведении. CONCAT пропустит NULL и всё равно соберёт строку, а оператор || на таком месте даст NULL.

🔄 Попробуйте изменить запрос:

  • • Замените NULL на '-' и сравните оба результата
  • • Попробуйте защитить оператор || через COALESCE(NULL, '')

4. Используем CONCAT, когда разделители между частями разные

В этом примере необходим CONCAT, а вот CONCAT_WS не подойдет. Здесь между разными частями нужны разные разделители: пробел, потом квадратные скобки.

🔄 Попробуйте изменить запрос:

  • • Замените квадратные скобки на круглые: '(' и ')'
  • • Добавьте в конец строки ещё и email, отделив его через ' - '

4. Собираем условный шифр товара из нескольких кусков

Более интересный и сложный сценарий. Мы не просто склеиваем готовые значения, а сначала извлекаем нужные куски из разных полей, переводим их в нужный вид и только потом собираем итоговый код. В таких задачах конкатенация особенно хорошо показывает себя вместе со строковыми функциями.

🔄 Попробуйте изменить запрос:

  • • Сделайте код короче: возьмите не четыре, а три символа из product_name
  • • Поменяйте разделитель '-' на '_'

6. Собираем шифр заказа через оператор || и защищаемся от NULL

Этот пример показывает дополнительный способ работать с оператором ||, если в одной из частей может быть NULL, например в столбце статусов. Мы заранее подставляем запасной текст через COALESCE, и тогда вся строка не разваливается, если вдруг там будет NULL.

🔄 Попробуйте изменить запрос:

  • • Уберите функцию COALESCE и посмотрите, что произойдёт у заказов без статуса
  • • Добавьте в шифр ещё и worker_id

Типичные ошибки

Считают, что CONCAT и оператор || всегда ведут себя одинаково
Нет, не всегда. При NULL это как раз не так. CONCAT спокойно пропускает пустой фрагмент, а || может вернуть NULL для всей склейки.

Забывают, что у CONCAT_WS первый аргумент — это разделитель
В записи CONCAT_WS(' / ', city, email) строка ' / ' — это не обычная часть результата, а именно разделитель между остальными значениями.

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

Думают, что число или дата обязательно требуют ::text в CONCAT
Во многих обычных случаях PostgreSQL сам приведёт такие значения к тексту внутри CONCAT и CONCAT_WS. Явное приведение нужно не всегда, а только когда вы хотите жёстко контролировать формат или работаете в более чувствительном сценарии.

Забывают защититься от NULL при использовании ||
Если в одной из частей может быть NULL, рядом часто нужен COALESCE. Например: first_name || ' ' || COALESCE(city, '').

Пытаются собрать сложный код без промежуточной логики
Когда в строку нужно включить куски разных полей, почти всегда удобнее сначала применить LEFT, RIGHT, SUBSTRING, UPPER или другие функции, а уже потом делать конкатенацию.

Практика

Проверь себя

Ответьте на вопросы, чтобы закрепить материал:

1
В какой ситуации обычно особенно удобна функция CONCAT_WS?
2
Что вернёт выражение CONCAT('SQL', NULL, 'PRO')?
3
Что вернёт выражение CONCAT_WS('/', 'SQL', NULL, 'PRO')? Введите результат без кавычек.
4
Что произойдёт, если одна из частей в склейке через оператор || окажется равна NULL?
5
Какой вариант обычно уместнее, если между частями строки должны стоять разные разделители: пробел, потом скобки, потом дефис?
6
Напишите запрос к таблице workers, который выведет worker_id, first_name, last_name и столбец worker_line. В worker_line нужно склеить имя, фамилию и должность через разделитель ' | ' с помощью CONCAT_WS. Оставьте только работников из отделов 1 и 2. Отсортируйте результат по worker_id.
7
Напишите запрос к таблице orders, который выведет order_id, order_date и столбец order_label. В order_label нужно через CONCAT собрать строку вида 'Заказ #15 / user 3'. Оставьте только заказы без статуса и отсортируйте результат по order_date от более новой к более старой.
8
Что в PostgreSQL обычно верно для CONCAT и CONCAT_WS при работе с числами и датами?