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.

3 min readReferencesql · postgresql · string · left · right · mysql

When you need the first three digits of a code or the last four digits of a phone number, reaching for a verbose SUBSTRING is overkill. PostgreSQL gives you two short functions: LEFT takes characters from the start of a string, RIGHT takes them from the end. Let's work through them and find the sharp edges along the way.

Basic syntax

Both functions take a string and a character count: LEFT(string, n) and RIGHT(string, n). They count characters, not bytes, so UTF-8 is not a problem.

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

On a real table this is handy for pulling out prefixes and suffixes. Say orders.status holds a string like paid-eu and you want the leading letters of the status:

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

Key properties:

  • The result type is text.
  • If n is larger than the string length, you get the whole string back, with no error and no padding.
  • A NULL input gives a NULL output.

Masking a card number

The classic case is showing only the last four digits of a payment card and hiding the rest. Here RIGHT is exactly the tool:

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

To get the familiar **** 4242 format, concatenate a mask with the tail. Assume there is a column holding the user's card number:

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

Likewise LEFT helps extract the BIN, the first six digits that identify the issuing bank:

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

Gotcha: LEFT and RIGHT cut characters, not "logical" parts. If the number contains spaces or dashes (4111 1111 1111 4242), RIGHT(..., 4) returns 4242 only when the tail is clean. Strip separators with REPLACE first, or you might accidentally show a space instead of a digit.

Negative length

This is the most underrated feature. If you pass a negative n, PostgreSQL counts characters from the opposite end and returns the remainder.

  • LEFT(s, -k) returns the string without its last k characters.
  • RIGHT(s, -k) returns the string without its first k characters.
SELECT LEFT('order-12345', -5)  AS without_tail,   -- 'order-'
       RIGHT('order-12345', -6) AS without_head;     -- '12345'

This shines when you know the length of the part to trim but not the length of the string. For example, drop a fixed domain suffix or remove a known prefix. Be careful, though: if -k is larger in magnitude than the string length, you get an empty string '', not NULL, so it is easy to lose data silently.

LEFT and RIGHT vs SUBSTRING

SUBSTRING is more powerful but wordier. Any LEFT/RIGHT can be expressed with it:

-- 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;

Notice the RIGHT equivalent forces you to compute length() by hand and makes off-by-one errors easy. So the rule is simple:

  • Need a fixed prefix or suffix: use LEFT or RIGHT, the code reads instantly.
  • Need a slice from the middle or a dynamic position: use SUBSTRING.
  • Need trimming by a pattern rather than a length: look at split_part or regular expressions.

A realistic example: group employees by the first letter of their department with LEFT, which is far cleaner than SUBSTRING:

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

MySQL and ClickHouse

Good news: LEFT and RIGHT exist in MySQL with the same LEFT(str, len) and RIGHT(str, len) syntax, so queries port over with almost no edits.

  • MySQL does not support negative length: LEFT('abc', -1) returns an empty string, not "everything except the last character." This is the main difference when migrating.
  • ClickHouse also has left and right; a negative argument there works like PostgreSQL, counting from the end.
  • In MySQL both functions count characters according to the column's current character set, which usually matches PostgreSQL's behavior for UTF-8.
-- Works identically in PostgreSQL and MySQL
SELECT LEFT(email, 3)  AS prefix,
       RIGHT(email, 3) AS suffix
FROM users;

Bottom line: LEFT and RIGHT are the fastest way to grab the edge of a string. Remember negative length as "trim from the other end," always clean separators before masking, and keep in mind that the negative argument does not work in MySQL.

Practice on real tasks

Solve tasks in the SQL trainer with instant grading and hints.

Open trainer