sqlpostgresqlstringleft

LEFT and RIGHT in SQL: First and Last Characters of a String in PostgreSQL

How LEFT and RIGHT grab the first or last N characters of a string in PostgreSQL: masking cards, prefixes, negative length, and how they compare to SUBSTRING.

4 min čteníReferencesql · postgresql · string · left · right · mysql
Tento článek je momentálně v ruštině — anglický překlad se připravuje.

Когда нужно взять первые три цифры кода или последние четыре цифры телефона, не стоит писать громоздкий SUBSTRING. В PostgreSQL для этого есть две короткие функции: LEFT берёт символы с начала строки, RIGHT — с конца. Разберём их на практике и заодно поймём, где у них острые края.

Главное отличие от SUBSTRING в том, что LEFT и RIGHT берут край строки фиксированной длины и не требуют ни начальной позиции, ни вычисления length(). LEFT('PROMO-2026', 5) всегда вернёт первые пять символов, а RIGHT('PROMO-2026', 4) — последние четыре, независимо от того, насколько длинна строка дальше. Именно поэтому они идеальны для BIN-кодов карт, последних цифр телефона, односимвольных префиксов отдела и других случаев, где известна длина края, а не его содержимое. Ниже разберём синтаксис, маскирование карт, неочевидную отрицательную длину и поведение в MySQL и ClickHouse.

Базовый синтаксис

Обе функции принимают строку и число символов: LEFT(string, n) и RIGHT(string, n). Считают именно символы, а не байты, поэтому с UTF-8 проблем нет.

SELECT LEFT('PROMO-2026', 5)  AS head,   -- 'PROMO'
       RIGHT('PROMO-2026', 4) AS tail;    -- '2026'

На реальной таблице это удобно для извлечения префиксов и суффиксов. Допустим, в orders.status хранится строка вроде paid-eu, и нам нужны первые буквы статуса:

SELECT id,
       LEFT(status, 4) AS status_head
FROM orders
LIMIT 5;

Ключевые свойства:

  • Результат имеет тип text.
  • Если n больше длины строки, вернётся вся строка целиком, без ошибки и без дополнения пробелами.
  • NULL на входе даёт NULL на выходе.

Маскирование номера карты

Классический случай — показать только последние четыре цифры платёжной карты, а остальное скрыть. Здесь RIGHT незаменим:

SELECT RIGHT('4111111111114242', 4) AS last4;   -- '4242'

Чтобы получить привычный формат **** 4242, склеим маску с хвостом. Предположим, есть колонка с номером карты пользователя:

SELECT u.id,
       '**** ' || RIGHT(card_number, 4) AS masked_card
FROM users u
JOIN payment_methods pm ON pm.user_id = u.id;

Аналогично LEFT помогает вытащить BIN — первые шесть цифр, по которым определяют банк-эмитент:

SELECT LEFT(card_number, 6) AS bin
FROM payment_methods;

Ловушка: LEFT и RIGHT режут именно символы, а не «логические» части. Если в номере есть пробелы или дефисы (4111 1111 1111 4242), RIGHT(..., 4) вернёт 4242 только при условии, что хвост чистый. Сначала уберите разделители через REPLACE, иначе можно случайно показать пробел вместо цифры.

Отрицательная длина

Это самая недооценённая возможность. Если передать отрицательное n, PostgreSQL отсчитает символы с противоположного конца и вернёт остаток.

  • LEFT(s, -k) возвращает строку без последних k символов.
  • RIGHT(s, -k) возвращает строку без первых k символов.
SELECT LEFT('order-12345', -5)  AS without_tail,   -- 'order-'
       RIGHT('order-12345', -6) AS without_head;     -- '12345'

Это удобно, когда длина «обрезаемой» части известна, а длина строки — нет. Например, отрезать фиксированный суффикс домена у email-доменов или убрать известный префикс. Но будьте осторожны: если -k по модулю больше длины строки, вернётся пустая строка '', а не NULL — легко потерять данные молча.

LEFT и RIGHT против SUBSTRING

SUBSTRING мощнее, но многословнее. Любой LEFT/RIGHT можно выразить через него:

-- LEFT(name, 3) is equivalent to:
SELECT SUBSTRING(name FROM 1 FOR 3) FROM users;

-- RIGHT(name, 3) is equivalent to:
SELECT SUBSTRING(name FROM length(name) - 3 + 1) FROM users;

Видно, что эквивалент RIGHT требует вычислять length() вручную и легко ошибиться на единицу. Поэтому правило простое:

  • Нужен фиксированный префикс или суффикс — берите LEFT или RIGHT, код читается мгновенно.
  • Нужен срез из середины или динамическая позиция — берите SUBSTRING.
  • Нужна обрезка по шаблону, а не по длине — смотрите в сторону split_part или регулярных выражений.

Реальный пример: сгруппировать сотрудников по первой букве отдела через LEFT, что куда чище, чем SUBSTRING:

SELECT LEFT(dept, 1) AS dept_initial,
       count(*)       AS people
FROM employees
GROUP BY LEFT(dept, 1)
ORDER BY dept_initial;

MySQL и ClickHouse

Хорошая новость: LEFT и RIGHT есть в MySQL с тем же синтаксисом LEFT(str, len) и RIGHT(str, len), так что запросы переносятся почти без правок.

  • MySQL не поддерживает отрицательную длину: LEFT('abc', -1) вернёт пустую строку, а не «всё кроме последнего символа». Это главное отличие при миграции.
  • ClickHouse тоже имеет left и right; отрицательный аргумент там работает как в PostgreSQL, отсчитывая с конца.
  • В MySQL обе функции считают символы согласно текущей кодировке столбца, что обычно совпадает с поведением PostgreSQL для UTF-8.
-- Works identically in PostgreSQL and MySQL
SELECT LEFT(email, 3)  AS prefix,
       RIGHT(email, 3) AS suffix
FROM users;

При переносе таких запросов между движками расхождение почти всегда даёт отрицательная длина: в PostgreSQL и ClickHouse LEFT('abc', -1) вернёт 'ab', а в MySQL — пустую строку. Если ваш запрос использовал LEFT(s, -k) для отсечения хвоста, в MySQL придётся переписать его через SUBSTRING с length(). Маскирование карт, BIN и фиксированные префиксы переносятся как есть, потому что используют только положительную длину.

Ещё один практический момент — индексы. Когда LEFT(card_number, 6) = '411111' стоит в WHERE, обычный B-tree индекс по card_number не задействуется, потому что фильтр идёт по выражению, а не по самой колонке. Если такой запрос частый, постройте выражательный индекс CREATE INDEX ON payment_methods (LEFT(card_number, 6)) или храните BIN отдельной generated column. Для разовых выборок и маскирования в SELECT это неважно: там LEFT и RIGHT просто форматируют вывод и на план не влияют.

Вывод: LEFT и RIGHT — самый быстрый способ взять край строки. Помните про отрицательную длину как про «обрезать с другого конца», не забывайте чистить разделители перед маскированием и держите в голове, что в MySQL отрицательный аргумент не работает.

Procvičujte na reálných úlohách

Řešte úlohy v SQL trenéru s okamžitým hodnocením a nápovědami.

Otevřít trenéra