sqlpostgresqlstringleft

LEFT и RIGHT в SQL: как взять начало или конец строки

Разбираем LEFT и RIGHT в PostgreSQL: как брать крайние символы, маскировать карты, использовать отрицательную длину и не ошибиться при переносе в MySQL.

8 мин чтенияСправочникsql · postgresql · string · left · right · mysql

Иногда из строки нужно достать только её край.

Например:

  • первые 3 буквы кода;
  • первые 6 цифр номера карты;
  • последние 4 цифры телефона;
  • год из промокода PROMO-2026;
  • первую букву отдела или категории.

Для таких задач не обязательно писать длинный SUBSTRING. В PostgreSQL есть две простые функции:

LEFT(string, n)
RIGHT(string, n)

LEFT берёт символы с начала строки.

RIGHT берёт символы с конца строки.

Выглядит просто, но у этих функций есть несколько нюансов: маскирование данных, отрицательная длина, поведение с пробелами и переносимость в MySQL или ClickHouse. Разберём всё спокойно и на понятных примерах.

Зачем нужны LEFT и RIGHT

Представьте, что у нас есть промокод:

PROMO-2026

Из него нужно получить:

  • PROMO — начало строки;
  • 2026 — конец строки.

Через LEFT и RIGHT это читается почти как обычный русский текст:

SELECT LEFT('PROMO-2026', 5)  AS promo_prefix,
       RIGHT('PROMO-2026', 4) AS promo_year;

Результат:

promo_prefix | promo_year
-------------+-----------
PROMO        | 2026

LEFT('PROMO-2026', 5) означает: возьми 5 символов слева.

RIGHT('PROMO-2026', 4) означает: возьми 4 символа справа.

Главная прелесть этих функций в том, что нам не нужно думать о начальной позиции, считать длину строки или вручную искать, откуда начинать обрезку. Мы просто говорим базе: «дай мне край строки».

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

У обеих функций одинаковая идея:

LEFT(строка, количество_символов)
RIGHT(строка, количество_символов)

Например:

SELECT LEFT('database', 4)  AS first_part,
       RIGHT('database', 4) AS last_part;

Результат:

first_part | last_part
-----------+----------
data       | base

LEFT взял первые четыре символа: data.

RIGHT взял последние четыре символа: base.

В PostgreSQL эти функции считают именно символы, а не байты. Поэтому с обычным UTF-8 текстом на русском языке всё работает ожидаемо:

SELECT LEFT('Москва', 3)  AS city_start,
       RIGHT('Москва', 3) AS city_end;

Результат:

city_start | city_end
-----------+---------
Мос        | ква

Что будет, если попросить больше символов, чем есть в строке

Если строка короче, чем указанное количество символов, PostgreSQL не выдаст ошибку.

Он просто вернёт всю строку целиком:

SELECT LEFT('SQL', 10)  AS left_result,
       RIGHT('SQL', 10) AS right_result;

Результат:

left_result | right_result
------------+-------------
SQL         | SQL

Это удобно: не нужно заранее проверять длину строки.

Что будет с NULL

Если на вход приходит NULL, результатом тоже будет NULL.

SELECT LEFT(NULL, 3) AS result;

Результат:

result
------
NULL

Это обычное поведение SQL-функций: неизвестное значение на входе даёт неизвестное значение на выходе.

Если вам нужно вместо NULL получить, например, пустую строку, используйте COALESCE:

SELECT LEFT(COALESCE(phone, ''), 4) AS phone_start
FROM users;

Пример: достать префикс статуса

Допустим, в таблице orders есть колонка status, где статус хранится в виде строки:

paid-eu
paid-us
new-eu
cancelled-us

Иногда нам нужно быстро посмотреть первые символы статуса. Например, все значения, начинающиеся с paid.

SELECT id,
       status,
       LEFT(status, 4) AS status_prefix
FROM orders;

Результат может быть таким:

id | status       | status_prefix
---+--------------+--------------
1  | paid-eu      | paid
2  | paid-us      | paid
3  | new-eu       | new-
4  | cancelled-us | canc

Здесь LEFT(status, 4) берёт первые четыре символа из каждого значения в колонке status.

Это удобно, когда часть строки имеет фиксированную длину.

Пример: показать последние 4 цифры карты

Один из самых частых примеров для RIGHT — маскирование номера карты.

Пользователю не нужно показывать весь номер. Обычно достаточно последних четырёх цифр:

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

Результат:

last4
-----
4242

Теперь добавим маску:

SELECT '**** ' || RIGHT('4111111111114242', 4) AS masked_card;

Результат:

masked_card
-----------
**** 4242

На реальной таблице это может выглядеть так:

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

Результат:

id | masked_card
---+------------
1  | **** 4242
2  | **** 1881
3  | **** 9002

Важный практический момент: в настоящих платёжных системах полный номер карты обычно не хранят в обычной таблице. Это чувствительные данные, для них есть строгие требования безопасности. Часто в базе хранят только last4, платёжный токен и техническую информацию от платёжного провайдера.

Но как учебный пример RIGHT(card_number, 4) хорошо показывает суть: функция берёт хвост строки.

Ловушка с пробелами и дефисами

Допустим, номер карты хранится не так:

4111111111114242

А так:

4111 1111 1111 4242

На первый взгляд всё нормально. Последние четыре символа всё равно будут 4242.

SELECT RIGHT('4111 1111 1111 4242', 4) AS last4;

Результат:

last4
-----
4242

Но если строка хранится с пробелом в конце, результат может стать неожиданным:

SELECT RIGHT('4111 1111 1111 4242 ', 4) AS last4;

Результат будет уже не чистым 4242, потому что последним символом является пробел:

last4
-----
242

Поэтому перед маскированием данные лучше привести к нормальному виду: убрать пробелы и дефисы.

Например:

SELECT RIGHT(
         REPLACE(REPLACE(card_number, ' ', ''), '-', ''),
         4
       ) AS last4
FROM payment_methods;

Здесь мы сначала убираем пробелы и дефисы, а потом берём последние четыре цифры.

Более читаемо это можно оформить через CTE:

WITH cleaned_cards AS (
  SELECT id,
         REPLACE(REPLACE(card_number, ' ', ''), '-', '') AS clean_card_number
  FROM payment_methods
)
SELECT id,
       '**** ' || RIGHT(clean_card_number, 4) AS masked_card
FROM cleaned_cards;

Такой запрос проще читать: сначала мы готовим чистый номер, потом используем его для маски.

Пример: получить BIN карты

LEFT часто используют, чтобы достать первые цифры карты.

Первые 6–8 цифр карты называют BIN или IIN. По ним можно определить платёжную систему, банк-эмитент и другую техническую информацию.

Например:

SELECT LEFT('4111111111114242', 6) AS bin;

Результат:

bin
------
411111

На таблице:

SELECT id,
       LEFT(card_number, 6) AS card_bin
FROM payment_methods;

Если в номере могут быть пробелы или дефисы, снова лучше сначала очистить строку:

SELECT id,
       LEFT(REPLACE(REPLACE(card_number, ' ', ''), '-', ''), 6) AS card_bin
FROM payment_methods;

Главное правило здесь простое: LEFT и RIGHT не понимают смысл строки. Они не знают, где «цифры карты», где «разделители», где «лишний пробел». Они просто отрезают указанное количество символов с нужной стороны.

Поэтому качество результата зависит от того, насколько чистые данные лежат в колонке.

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

У LEFT и RIGHT в PostgreSQL есть интересная возможность: можно передать отрицательное число.

Обычно мы пишем так:

SELECT LEFT('order-12345', 5);

И получаем первые 5 символов:

order

Но если передать отрицательное значение, смысл меняется.

SELECT LEFT('order-12345', -5) AS without_last_5;

Результат:

without_last_5
--------------
order-

LEFT('order-12345', -5) означает: верни строку без последних 5 символов.

То есть PostgreSQL взял строку:

order-12345

И убрал справа:

12345

Осталось:

order-

С RIGHT работает наоборот:

SELECT RIGHT('order-12345', -6) AS without_first_6;

Результат:

without_first_6
---------------
12345

RIGHT('order-12345', -6) означает: верни строку без первых 6 символов.

Первые 6 символов здесь — это order-, поэтому остаётся 12345.

Где полезна отрицательная длина

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

Например, у нас есть коды заказов:

ORDER-10001
ORDER-10002
ORDER-10003

И мы хотим убрать префикс ORDER-.

Можно сделать так:

SELECT RIGHT('ORDER-10001', -6) AS order_number;

Результат:

order_number
------------
10001

А если нужно убрать известный суффикс, например .csv:

SELECT LEFT('sales_report.csv', -4) AS file_name;

Результат:

file_name
---------
sales_report

То есть:

  • LEFT(text, -n) убирает n символов справа;
  • RIGHT(text, -n) убирает n символов слева.

Но злоупотреблять этим не стоит. Для новичка такой код может быть неочевидным. Иногда явный SUBSTRING или REPLACE читается лучше, особенно если запрос будет поддерживать команда.

Что будет, если отрицательное число больше длины строки

Если вы попросите убрать больше символов, чем есть в строке, PostgreSQL вернёт пустую строку.

SELECT LEFT('SQL', -10) AS result;

Результат:

result
------

Это не NULL и не ошибка. Это именно пустая строка.

Такое поведение может быть опасным: данные могут «исчезнуть» молча. Поэтому отрицательную длину лучше использовать там, где вы уверены в формате строки.

Например, если все значения точно заканчиваются на .csv, то LEFT(file_name, -4) выглядит нормально.

А если формат плавает, лучше сначала проверить условие:

SELECT CASE
         WHEN file_name LIKE '%.csv'
           THEN LEFT(file_name, -4)
         ELSE file_name
       END AS clean_file_name
FROM files;

Так мы убираем .csv только там, где он действительно есть.

LEFT и RIGHT против SUBSTRING

SUBSTRING — более универсальная функция. Она умеет брать кусок строки почти откуда угодно: из начала, середины или конца.

Например, LEFT(name, 3) можно заменить так:

SELECT SUBSTRING(name FROM 1 FOR 3)
FROM users;

Но выглядит это длиннее.

Аналог RIGHT(name, 3) через SUBSTRING ещё менее приятный:

SELECT SUBSTRING(name FROM length(name) - 3 + 1)
FROM users;

Здесь уже приходится считать длину строки и не ошибиться на единицу.

Поэтому правило простое:

если нужен край строки — используйте LEFT или RIGHT.

Если нужен кусок из середины — используйте SUBSTRING.

Например, первые 3 символа email:

SELECT LEFT(email, 3) AS email_prefix
FROM users;

Последние 2 символа кода страны:

SELECT RIGHT(region_code, 2) AS country_code
FROM regions;

А вот если нужно достать символы с 4-го по 8-й, лучше уже SUBSTRING:

SELECT SUBSTRING(product_code FROM 4 FOR 5) AS middle_part
FROM products;

Если нужно резать строку не по длине, а по разделителю

Важно понимать: LEFT и RIGHT режут строку по количеству символов, а не по смыслу.

Например, есть email:

anna@example.com

Можно взять первые 4 символа:

SELECT LEFT('anna@example.com', 4) AS login_part;

Результат:

login_part
----------
anna

Но это сработало только потому, что имя anna состоит из четырёх символов.

Если email будет другой:

alexey@example.com

LEFT(email, 4) вернёт alex, хотя логин целиком — alexey.

Если нужно достать часть до @, лучше использовать split_part:

SELECT split_part(email, '@', 1) AS email_login
FROM users;

А домен после @:

SELECT split_part(email, '@', 2) AS email_domain
FROM users;

То есть выбор функции зависит от задачи:

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

Пример: группировка по первой букве

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

employees
---------
id
name
dept
salary

В колонке dept хранится отдел:

Analytics
Backend
Billing
Design
Data

Мы хотим посчитать, сколько сотрудников приходится на первую букву отдела.

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

Результат:

dept_initial | employees_count
-------------+----------------
A            | 4
B            | 9
D            | 6

Здесь LEFT(dept, 1) берёт первую букву отдела, а GROUP BY собирает строки в группы по этой букве.

Такой запрос читается очень просто: «сгруппируй сотрудников по первой букве отдела».

LEFT и RIGHT в WHERE: осторожно с индексами

LEFT и RIGHT удобно использовать в SELECT, когда мы просто форматируем вывод.

Например:

SELECT id,
       '**** ' || RIGHT(card_number, 4) AS masked_card
FROM payment_methods;

Здесь функция просто красиво показывает данные. На индексы это обычно не влияет.

Но другое дело — когда функция стоит в WHERE.

Например:

SELECT *
FROM payment_methods
WHERE LEFT(card_number, 6) = '411111';

Такой запрос логически понятный: найти карты, у которых первые 6 цифр равны 411111.

Но для обычного B-tree индекса по card_number это уже не просто поиск по колонке. База должна применить функцию LEFT(card_number, 6) к значениям и сравнить результат.

Если такой запрос выполняется редко, ничего страшного.

Но если это частый фильтр на большой таблице, лучше подумать об индексе по выражению:

CREATE INDEX payment_methods_card_bin_idx
ON payment_methods (LEFT(card_number, 6));

Тогда PostgreSQL сможет быстрее искать именно по результату выражения LEFT(card_number, 6).

Другой хороший вариант — хранить BIN отдельно в колонках card_bin и last4.

Это часто даже лучше с точки зрения архитектуры: если вы регулярно ищете или группируете по первым/последним цифрам, эти значения можно хранить в отдельных колонках, а не вычислять каждый раз на лету.

Как это работает в MySQL

В MySQL функции LEFT и RIGHT тоже есть:

SELECT LEFT('PROMO-2026', 5)  AS promo_prefix,
       RIGHT('PROMO-2026', 4) AS promo_year;

С положительной длиной поведение такое же:

promo_prefix | promo_year
-------------+-----------
PROMO        | 2026

Но есть важное отличие: MySQL не работает с отрицательной длиной так же, как PostgreSQL.

В PostgreSQL:

SELECT LEFT('abc', -1);

вернёт:

ab

А в MySQL отрицательная длина даст пустую строку.

Поэтому если вы переносите запросы из PostgreSQL в MySQL, будьте осторожны с такими конструкциями:

SELECT LEFT(file_name, -4), RIGHT(order_code, -6)
FROM files;

В MySQL их лучше переписать через SUBSTRING и CHAR_LENGTH.

Например, вместо PostgreSQL-варианта:

SELECT LEFT(file_name, -4) AS name_without_ext
FROM files;

в MySQL можно написать:

SELECT LEFT(file_name, CHAR_LENGTH(file_name) - 4) AS name_without_ext
FROM files;

Или использовать другие функции, если задача связана с разделителями.

Как это работает в ClickHouse

В ClickHouse тоже есть функции left и right.

Обычно они используются так:

SELECT left('PROMO-2026', 5)  AS promo_prefix,
       right('PROMO-2026', 4) AS promo_year;

Результат будет тем же:

promo_prefix | promo_year
-------------+-----------
PROMO        | 2026

Но при переносе запросов между разными СУБД лучше быть особенно внимательным к отрицательным значениям, кодировкам и версиям движка.

Самый переносимый вариант — использовать LEFT и RIGHT с положительной длиной. Такие запросы обычно переезжают между PostgreSQL, MySQL и ClickHouse почти без сюрпризов.

А вот отрицательную длину лучше воспринимать как удобную возможность PostgreSQL, но не как универсальный SQL-стандарт.

Частые ошибки новичков

Первая ошибка — использовать LEFT там, где нужно резать по разделителю.

Например, LEFT(email, 5) не означает «достать логин из email». Это просто первые 5 символов. Для email лучше использовать split_part.

Вторая ошибка — забывать про пробелы, дефисы и мусор в данных.

RIGHT(phone, 4) может вернуть не последние 4 цифры, а последние 4 символа. Если в конце строки есть пробел, результат будет неправильным.

Третья ошибка — использовать функцию в WHERE на большой таблице и ждать, что обычный индекс всё ускорит.

SELECT * FROM payment_methods
WHERE LEFT(card_number, 6) = '411111';

Для частых запросов нужен индекс по выражению или отдельная колонка.

Четвёртая ошибка — переносить отрицательную длину в MySQL и ожидать поведения как в PostgreSQL.

LEFT('abc', -1) в PostgreSQL это один результат, в MySQL — другой.

Коротко

LEFT и RIGHT — простые функции для случаев, когда нужно взять начало или конец строки.

SELECT LEFT('PROMO-2026', 5)  AS prefix,
       RIGHT('PROMO-2026', 4) AS suffix;

LEFT берёт символы слева.

RIGHT берёт символы справа.

Если количество символов больше длины строки, PostgreSQL вернёт всю строку.

Если на вход пришёл NULL, результатом будет NULL.

Отрицательная длина в PostgreSQL работает как обрезка с противоположного конца:

SELECT LEFT('sales.csv', -4) AS without_extension;

Результат:

without_extension
-----------------
sales

Для маскирования, кодов, префиксов и суффиксов LEFT и RIGHT читаются гораздо проще, чем SUBSTRING.

Главное — помнить: эти функции не понимают смысл данных. Они просто берут указанное количество символов с края строки. Поэтому перед маскированием очищайте строки от пробелов и дефисов, не используйте отрицательную длину без уверенности в формате и не забывайте про индексы, если применяете LEFT или RIGHT в WHERE.

Закрепи на практике

Решай задачи в SQL-тренажёре с мгновенной проверкой и подсказками.

Открыть тренажёр