Иногда из строки нужно достать только её край.
Например:
- первые 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.
Иногда из строки нужно достать только её край.
Например:
PROMO-2026;Для таких задач не обязательно писать длинный
SUBSTRING. В PostgreSQL есть две простые функции:LEFT(string, n) RIGHT(string, n)LEFTберёт символы с начала строки.RIGHTберёт символы с конца строки.Выглядит просто, но у этих функций есть несколько нюансов: маскирование данных, отрицательная длина, поведение с пробелами и переносимость в MySQL или ClickHouse. Разберём всё спокойно и на понятных примерах.
Зачем нужны LEFT и RIGHT
Представьте, что у нас есть промокод:
Из него нужно получить:
PROMO— начало строки;2026— конец строки.Через
LEFTиRIGHTэто читается почти как обычный русский текст:SELECT LEFT('PROMO-2026', 5) AS promo_prefix, RIGHT('PROMO-2026', 4) AS promo_year;Результат:
LEFT('PROMO-2026', 5)означает: возьми 5 символов слева.RIGHT('PROMO-2026', 4)означает: возьми 4 символа справа.Главная прелесть этих функций в том, что нам не нужно думать о начальной позиции, считать длину строки или вручную искать, откуда начинать обрезку. Мы просто говорим базе: «дай мне край строки».
Базовый синтаксис
У обеих функций одинаковая идея:
Например:
SELECT LEFT('database', 4) AS first_part, RIGHT('database', 4) AS last_part;Результат:
LEFTвзял первые четыре символа:data.RIGHTвзял последние четыре символа:base.В PostgreSQL эти функции считают именно символы, а не байты. Поэтому с обычным UTF-8 текстом на русском языке всё работает ожидаемо:
Результат:
Что будет, если попросить больше символов, чем есть в строке
Если строка короче, чем указанное количество символов, PostgreSQL не выдаст ошибку.
Он просто вернёт всю строку целиком:
SELECT LEFT('SQL', 10) AS left_result, RIGHT('SQL', 10) AS right_result;Результат:
Это удобно: не нужно заранее проверять длину строки.
Что будет с NULL
Если на вход приходит
NULL, результатом тоже будетNULL.SELECT LEFT(NULL, 3) AS result;Результат:
Это обычное поведение SQL-функций: неизвестное значение на входе даёт неизвестное значение на выходе.
Если вам нужно вместо
NULLполучить, например, пустую строку, используйтеCOALESCE:SELECT LEFT(COALESCE(phone, ''), 4) AS phone_start FROM users;Пример: достать префикс статуса
Допустим, в таблице
ordersесть колонкаstatus, где статус хранится в виде строки:Иногда нам нужно быстро посмотреть первые символы статуса. Например, все значения, начинающиеся с
paid.SELECT id, status, LEFT(status, 4) AS status_prefix FROM orders;Результат может быть таким:
Здесь
LEFT(status, 4)берёт первые четыре символа из каждого значения в колонкеstatus.Это удобно, когда часть строки имеет фиксированную длину.
Пример: показать последние 4 цифры карты
Один из самых частых примеров для
RIGHT— маскирование номера карты.Пользователю не нужно показывать весь номер. Обычно достаточно последних четырёх цифр:
SELECT RIGHT('4111111111114242', 4) AS last4;Результат:
Теперь добавим маску:
SELECT '**** ' || RIGHT('4111111111114242', 4) AS masked_card;Результат:
На реальной таблице это может выглядеть так:
SELECT u.id, '**** ' || RIGHT(pm.card_number, 4) AS masked_card FROM users u JOIN payment_methods pm ON pm.user_id = u.id;Результат:
Важный практический момент: в настоящих платёжных системах полный номер карты обычно не хранят в обычной таблице. Это чувствительные данные, для них есть строгие требования безопасности. Часто в базе хранят только
last4, платёжный токен и техническую информацию от платёжного провайдера.Но как учебный пример
RIGHT(card_number, 4)хорошо показывает суть: функция берёт хвост строки.Ловушка с пробелами и дефисами
Допустим, номер карты хранится не так:
А так:
На первый взгляд всё нормально. Последние четыре символа всё равно будут
4242.SELECT RIGHT('4111 1111 1111 4242', 4) AS last4;Результат:
Но если строка хранится с пробелом в конце, результат может стать неожиданным:
SELECT RIGHT('4111 1111 1111 4242 ', 4) AS last4;Результат будет уже не чистым
4242, потому что последним символом является пробел:Поэтому перед маскированием данные лучше привести к нормальному виду: убрать пробелы и дефисы.
Например:
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;Результат:
На таблице:
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 символов:
Но если передать отрицательное значение, смысл меняется.
SELECT LEFT('order-12345', -5) AS without_last_5;Результат:
LEFT('order-12345', -5)означает: верни строку без последних 5 символов.То есть PostgreSQL взял строку:
И убрал справа:
Осталось:
С
RIGHTработает наоборот:SELECT RIGHT('order-12345', -6) AS without_first_6;Результат:
RIGHT('order-12345', -6)означает: верни строку без первых 6 символов.Первые 6 символов здесь — это
order-, поэтому остаётся12345.Где полезна отрицательная длина
Отрицательная длина удобна, когда вы знаете, сколько символов нужно убрать, но не хотите считать полную длину строки.
Например, у нас есть коды заказов:
И мы хотим убрать префикс
ORDER-.Можно сделать так:
SELECT RIGHT('ORDER-10001', -6) AS order_number;Результат:
А если нужно убрать известный суффикс, например
.csv:SELECT LEFT('sales_report.csv', -4) AS file_name;Результат:
То есть:
LEFT(text, -n)убираетnсимволов справа;RIGHT(text, -n)убираетnсимволов слева.Но злоупотреблять этим не стоит. Для новичка такой код может быть неочевидным. Иногда явный
SUBSTRINGилиREPLACEчитается лучше, особенно если запрос будет поддерживать команда.Что будет, если отрицательное число больше длины строки
Если вы попросите убрать больше символов, чем есть в строке, PostgreSQL вернёт пустую строку.
SELECT LEFT('SQL', -10) AS 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:
Можно взять первые 4 символа:
SELECT LEFT('anna@example.com', 4) AS login_part;Результат:
Но это сработало только потому, что имя
annaсостоит из четырёх символов.Если email будет другой:
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;Пример: группировка по первой букве
Допустим, у нас есть таблица сотрудников:
В колонке
deptхранится отдел:Мы хотим посчитать, сколько сотрудников приходится на первую букву отдела.
SELECT LEFT(dept, 1) AS dept_initial, count(*) AS employees_count FROM employees GROUP BY LEFT(dept, 1) ORDER BY dept_initial;Результат:
Здесь
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;С положительной длиной поведение такое же:
Но есть важное отличие: MySQL не работает с отрицательной длиной так же, как PostgreSQL.
В PostgreSQL:
SELECT LEFT('abc', -1);вернёт:
А в 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;Результат будет тем же:
Но при переносе запросов между разными СУБД лучше быть особенно внимательным к отрицательным значениям, кодировкам и версиям движка.
Самый переносимый вариант — использовать
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;Результат:
Для маскирования, кодов, префиксов и суффиксов
LEFTиRIGHTчитаются гораздо проще, чемSUBSTRING.Главное — помнить: эти функции не понимают смысл данных. Они просто берут указанное количество символов с края строки. Поэтому перед маскированием очищайте строки от пробелов и дефисов, не используйте отрицательную длину без уверенности в формате и не забывайте про индексы, если применяете
LEFTилиRIGHTвWHERE.