> SmartGate

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

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

Теория

Допустим у вас появилась необходимость собрать готовый текст из нескольких частей.

Например:

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

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

Так вот, в контексте PostgreSQL конкатенация — это когда мы берём несколько текстовых фрагментов и соединяем их в одну строку. Для этого есть несколько способов, а точнее сказать функций:

• функция CONCAT();
• функция CONCAT_WS();
• оператор ||.

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

Достаточно часто при конкатенации вы будете использовать уже знакомые вам инструменты:

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

Синтаксис

SELECT CONCAT(first_name, ' ', last_name), -- соединяем имя, пробел и фамилию
       CONCAT_WS(' / ', city, email),      -- соединяем город и 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, отделив его через ' - '

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

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

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

  • • Сделайте код короче: возьмите не четыре, а три символа из 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, '').

Практика

Проверь себя

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

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 при работе с числами и датами?