sqlpostgresqlstringslpad

LPAD y RPAD en SQL: rellenar cadenas hasta un ancho fijo

Como usar LPAD y RPAD para rellenar cadenas a un ancho fijo, hacer zero-padding de ids y numeros de factura, y construir columnas de ancho fijo para exportaciones.

3 min de lecturaReferencesql · postgresql · strings · lpad · rpad · formatting
Este artículo está actualmente en ruso — la traducción está en curso.

Когда нужно превратить 42 в 000042 или выровнять имена в колонке фиксированной ширины, в дело вступают LPAD и RPAD. Это функции дополнения строк: они добивают значение до заданной длины слева или справа символами-заполнителями. Разберём типичные сценарии и подводные камни.

У дополнения две практические задачи. Первая — ведущие нули: LPAD превращает id вроде 7 в код 000007, чтобы счета, заказы и платёжные ссылки имели одинаковую ширину и сортировались как числа. Вторая — выравнивание: RPAD добивает имена и коды стран пробелами до фиксированной длины, когда выгрузка идёт без разделителей, как в банковских и legacy-форматах. В обоих случаях исходное значение не меняется — мы лишь строим из него представление нужной ширины, поэтому форматирование держат в SELECT, view или generated column, а не в хранимой колонке.

Сигнатура и базовый принцип

Обе функции принимают три аргумента: исходную строку, целевую длину и строку-заполнитель.

-- LPAD pads on the LEFT, RPAD pads on the RIGHT
SELECT LPAD('42', 6, '0')      AS padded_left,   -- '000042'
       RPAD('John', 10, '.')   AS padded_right;  -- 'John......'

Ключевые правила:

  • LPAD(string, length, fill) добавляет fill слева, пока строка не достигнет length.
  • RPAD(string, length, fill) делает то же самое, но справа.
  • Третий аргумент необязателен: по умолчанию заполнитель — пробел.
  • Аргумент должен быть текстом, поэтому числовые колонки сначала приводите к типу: id::text.
-- Cast numeric ids to text before padding
SELECT LPAD(id::text, 6, '0') AS user_code
FROM users
ORDER BY id
LIMIT 5;

Zero-padding для id и номеров счетов

Самый частый случай — человекочитаемые коды с ведущими нулями. Идентификатор пользователя 7 превращается в USR-000007, номер заказа — в счёт фиксированного формата.

-- Build a stable user code like USR-000007
SELECT id,
       'USR-' || LPAD(id::text, 6, '0') AS user_code
FROM users
ORDER BY id;

-- Invoice numbers with a year prefix: INV-2026-000123
SELECT o.id,
       'INV-2026-' || LPAD(o.id::text, 6, '0') AS invoice_no
FROM orders o
WHERE o.status = 'paid'
ORDER BY o.id;

Почему именно LPAD, а не хранение готовой строки? Код всегда выводится из числового id, остаётся отсортированным как число и не занимает лишнего места в таблице. Форматирование — забота слоя представления, а не хранения.

Обрезка: когда строка длиннее цели

Главная ловушка: если исходная строка длиннее length, LPAD и RPAD не расширяют результат, а обрезают его до целевой длины. Никакой ошибки при этом не возникает.

-- The string is longer than the target -> it gets TRUNCATED
SELECT LPAD('1234567', 6, '0') AS clipped;  -- '123456', not '1234567'

Это критично для счётчиков: когда id вырастет до семи цифр, поле в шесть символов молча потеряет первую цифру. Защищайтесь явной проверкой ширины:

-- GREATEST guarantees the field never silently clips
SELECT LPAD(id::text, GREATEST(6, LENGTH(id::text)), '0') AS safe_code
FROM users;

Ловушка: заполнитель может быть длиннее одного символа, и тогда он повторяется и обрезается по символам. LPAD('7', 5, 'ab') даст abab7 — заполнитель циклически дополняется слева, а лишние символы отбрасываются.

Колонки фиксированной ширины для выгрузок

Старые системы и банковские форматы часто требуют файлы с колонками фиксированной ширины, без разделителей. Здесь RPAD выравнивает текст влево, а LPAD — числа вправо.

-- Fixed-width export line: name(20) + country(2) + amount(12)
SELECT RPAD(u.name, 20, ' ')
    || RPAD(u.country, 2, ' ')
    || LPAD(o.amount::text, 12, '0') AS export_line
FROM users u
JOIN orders o ON o.user_id = u.id
ORDER BY u.id;

Полезные приёмы:

  • Текстовые поля (имена, отделы) выравнивайте RPAD влево пробелами.
  • Числовые поля (суммы, количества) выравнивайте LPAD вправо нулями.
  • Помните про обрезку: длинное name будет урезано до 20 символов. Если это недопустимо, заранее проверяйте LENGTH(name).
-- Pad employee names and right-align salary for a report
SELECT RPAD(name, 25, ' ') || LPAD(salary::text, 10, ' ') AS row_line
FROM employees
ORDER BY dept, name;

Отличия MySQL и ClickHouse

Базовый синтаксис LPAD/RPAD совпадает в PostgreSQL и MySQL, но детали расходятся:

  • В MySQL у LPAD/RPAD все три аргумента обязательны — заполнитель по умолчанию не подставляется.
  • MySQL так же обрезает строки, которые длиннее цели.
  • MySQL мягче с типами: число часто приводится к строке неявно, но надёжнее писать CAST(id AS CHAR).
-- MySQL: fill argument is required, cast id explicitly
SELECT LPAD(CAST(id AS CHAR), 6, '0') AS user_code
FROM users;

В ClickHouse функции называются leftPad и rightPad (есть и алиасы LPAD/RPAD), но логика та же:

-- ClickHouse: leftPad / rightPad
SELECT leftPad(toString(id), 6, '0') AS user_code
FROM users;

Расхождения между движками прячутся не в обычных значениях, а в краях: что вернёт LPAD для NULL, для уже слишком длинной строки, для пустого заполнителя '' и для строки с многобайтовыми символами. По длине здесь важна разница между числом символов и числом байтов: в PostgreSQL и ClickHouse LPAD/leftPad отсчитывают символы, так что кириллица или эмодзи дают ту ширину, которую вы ожидаете, тогда как байтовый подсчёт в выгрузке фиксированной ширины ломает разметку колонок. Перед переносом между PostgreSQL, MySQL и ClickHouse прогоните маленькую таблицу с NULL, пустой строкой, значением длиннее цели и Unicode-именем — на чистых данных движки совпадают, а ровно на этих случаях расходятся.

И ещё одно про производительность: LPAD(id::text, 6, '0') в WHERE или JOIN считается по каждой строке и закрывает путь к обычному индексу по id. Если вы фильтруете или соединяете именно по дополненному коду, держите сравнение на стороне исходного числа (id = 42), а LPAD оставляйте для вывода. Когда без поиска по готовому коду не обойтись, материализуйте его в generated column и постройте индекс уже по ней — тогда дополнение считается один раз, а не на каждый запрос.

Вывод: LPAD дополняет слева, RPAD — справа, по умолчанию пробелом. Приводите числа к тексту перед дополнением, помните про молчаливую обрезку длинных значений и используйте эти функции для генерируемых кодов и выгрузок фиксированной ширины, а не для хранения уже отформатированных строк.

Practica con ejercicios reales

Resuelve ejercicios en el entrenador de SQL con corrección instantánea y pistas.

Abrir el entrenador