Как объединить строки в SQL
Карьерник — квиз-тренажёр в Telegram с 1500+ вопросами для собесов аналитика. SQL, Python, A/B, метрики. Бесплатно.
Зачем нужно объединять строки
Два частых сценария. Первый — склейка строк в одной записи: first_name + ' ' + last_name → full_name. Второй — агрегация по группе: собрать все теги пользователя в одну строку через запятую.
На собесе спрашивают оба варианта. Junior знает только CONCAT. Middle знает STRING_AGG (Postgres) или GROUP_CONCAT (MySQL) для группирующей агрегации. Senior обсудит NULL-behavior, разделители, sorting внутри агрегата.
В статье — все способы:
- CONCAT / || — простая склейка
- STRING_AGG в Postgres
- GROUP_CONCAT в MySQL
- LISTAGG в Oracle / Snowflake
- Работа с NULL
- Сортировка внутри агрегата
1. CONCAT — простая склейка
-- Postgres, MySQL, MSSQL
SELECT CONCAT(first_name, ' ', last_name) AS full_name FROM users;
-- Postgres alt: ||
SELECT first_name || ' ' || last_name AS full_name FROM users;NULL handling
-- Postgres / MSSQL: || с NULL → NULL для всего
SELECT 'Hello ' || NULL || ' World'; -- NULL
-- CONCAT пропускает NULL
SELECT CONCAT('Hello ', NULL, ' World'); -- 'Hello World'
-- для защиты — COALESCE
SELECT 'Hello ' || COALESCE(name, '') || ' World';2. CONCAT_WS — с разделителем
-- Postgres, MySQL
SELECT CONCAT_WS(' ', first_name, middle_name, last_name) AS full_name
FROM users;
-- 'John Edward Smith'
-- автоматически пропускает NULL3. STRING_AGG в Postgres
Агрегация строк в группе:
SELECT
user_id,
STRING_AGG(tag, ', ') AS all_tags
FROM user_tags
GROUP BY user_id;Результат:
user_id | all_tags
1 | vip, premium, early-adopter
2 | free, newС сортировкой
STRING_AGG(tag, ', ' ORDER BY tag) AS tags_sortedС distinct
STRING_AGG(DISTINCT tag, ', ' ORDER BY tag)4. GROUP_CONCAT в MySQL
SELECT
user_id,
GROUP_CONCAT(tag SEPARATOR ', ') AS all_tags
FROM user_tags
GROUP BY user_id;С сортировкой:
GROUP_CONCAT(tag ORDER BY tag SEPARATOR ', ')С distinct:
GROUP_CONCAT(DISTINCT tag ORDER BY tag SEPARATOR ', ')Лимит длины
В MySQL default лимит 1024 символа. Увеличить:
SET SESSION group_concat_max_len = 1000000;5. LISTAGG (Oracle, Snowflake)
SELECT
user_id,
LISTAGG(tag, ', ') WITHIN GROUP (ORDER BY tag) AS all_tags
FROM user_tags
GROUP BY user_id;6. Практика: список товаров в заказе
SELECT
o.order_id,
STRING_AGG(
p.name || ' (x' || oi.quantity || ')',
', '
ORDER BY p.name
) AS items_summary
FROM orders o
JOIN order_items oi ON oi.order_id = o.order_id
JOIN products p ON p.id = oi.product_id
GROUP BY o.order_id;Результат:
order_id | items_summary
1001 | Apple (x2), Bread (x1), Milk (x3)7. Склеить emails через запятую
Для email-кампании:
SELECT STRING_AGG(email, ', ') AS email_list
FROM users
WHERE is_subscribed;8. Теги как массив → строка
-- массив ['a', 'b', 'c'] → 'a,b,c'
SELECT ARRAY_TO_STRING(tags, ',') FROM posts;9. Multi-column aggregation
Собрать все имена и возрасты в одной строке:
SELECT
country,
STRING_AGG(name || ' (' || age || ')', ', ' ORDER BY age DESC) AS people
FROM users
GROUP BY country;10. JSON alternative
Вместо склейки — иногда лучше JSON:
-- Postgres
SELECT
user_id,
JSON_AGG(tag ORDER BY tag) AS tags_json
FROM user_tags
GROUP BY user_id;
-- ["premium", "vip"]Для dashboard / API ответа удобнее.
Частые ошибки
1. GROUP_CONCAT лимит
В MySQL по умолчанию 1024. На больших группах — данные обрезаются. Увеличивайте group_concat_max_len.
2. || с NULL
В Postgres 'a' || NULL → NULL. Защищайтесь через COALESCE.
3. Порядок случайный без ORDER BY
Без сортировки теги могут идти в разном порядке. Для детерминизма — ORDER BY.
4. Дубликаты
Без DISTINCT один тег будет повторяться. STRING_AGG(DISTINCT tag, ',').
Связанные темы
FAQ
STRING_AGG в старом Postgres?
Доступен с 9.0. Если старше — array_to_string(array_agg(tag), ',').
В ClickHouse?
groupArray → массив, потом arrayStringConcat(arr, ',').
GROUP_CONCAT с LIMIT?
Не напрямую. Через subquery с LIMIT до агрегации.
В BigQuery?
STRING_AGG(tag, ', ' ORDER BY tag) — синтаксис аналогичен Postgres.
Тренируйте SQL — откройте тренажёр с 1500+ вопросами для собесов.