REVERSE — это строковая функция, которая переворачивает строку задом наперёд.
Звучит как игрушка:
SELECT REVERSE('postgres') AS result;
Результат:
sergtsop
Но на практике REVERSE бывает полезен не только для забавных примеров. С его помощью можно:
- проверить, является ли слово палиндромом;
- достать последний кусок строки после разделителя;
- развернуть текст для отчёта или отладки;
- превратить поиск «заканчивается на» в поиск «начинается с»;
- построить индексируемый поиск по суффиксу, например по домену email.
Главная идея такая: REVERSE меняет местами начало и конец строки. А это важно, потому что в SQL многие операции гораздо удобнее делать с начала строки, чем с конца.
Что делает REVERSE
Функция REVERSE принимает строку и возвращает эту же строку в обратном порядке.
SELECT REVERSE('hello') AS result;
Результат:
olleh
Ещё несколько простых примеров:
SELECT REVERSE('SQL') AS result;
Результат:
LQS
SELECT REVERSE('12345') AS result;
Результат:
54321
SELECT REVERSE('abc-def') AS result;
Результат:
fed-cba
Важно: REVERSE переворачивает именно символы строки. Он не понимает смысл текста, не знает, где слова, где домены, где фамилии, а где коды. Для него строка — это просто последовательность символов.
Базовый синтаксис
Синтаксис очень простой:
REVERSE(string)
Например:
SELECT REVERSE('database') AS reversed_text;
Результат:
esabatad
Можно применять функцию к колонке таблицы:
SELECT
id,
name,
REVERSE(name) AS reversed_name
FROM users;
Если в таблице есть данные:
id | name
---+-------
1 | Alex
2 | Maria
3 | Ivan
результат будет таким:
id | name | reversed_name
---+-------+--------------
1 | Alex | xelA
2 | Maria | airaM
3 | Ivan | navI
В обычном интерфейсе такие перевёрнутые имена, конечно, не нужны. Но для проверок, поиска с конца и некоторых технических задач функция очень удобна.
Что будет с NULL
Если передать в REVERSE значение NULL, результат тоже будет NULL.
SELECT REVERSE(NULL) AS result;
Результат:
NULL
Это обычное поведение SQL: если значения нет, то и переворачивать нечего.
Если вместо NULL нужна пустая строка, используйте COALESCE:
SELECT REVERSE(COALESCE(name, '')) AS reversed_name
FROM users;
COALESCE(name, '') означает: если name равен NULL, возьми пустую строку.
Простая проверка: палиндромы
Палиндром — это строка, которая читается одинаково слева направо и справа налево.
Например:
level
radar
anna
Если строка равна самой себе в перевёрнутом виде, значит это палиндром.
SELECT
word,
word = REVERSE(word) AS is_palindrome
FROM words;
Если данные такие:
word
------
level
hello
radar
sql
результат будет таким:
word | is_palindrome
------+--------------
level | true
hello | false
radar | true
sql | false
Для простых слов это работает отлично.
Палиндромы без учёта регистра
Есть нюанс: SQL сравнивает строки буквально. Поэтому Anna и annA — это разные строки.
SELECT 'Anna' = REVERSE('Anna') AS result;
REVERSE('Anna') вернёт:
annA
А значит сравнение даст false.
Чтобы не учитывать регистр, можно привести обе стороны к нижнему регистру через LOWER:
SELECT
name,
LOWER(name) = REVERSE(LOWER(name)) AS is_palindrome
FROM users;
Теперь Anna, ANNA и anna будут проверяться одинаково.
Пример:
SELECT LOWER('Anna') = REVERSE(LOWER('Anna')) AS result;
Результат:
true
Сначала строка превращается в anna, потом переворачивается в anna, и сравнение проходит успешно.
Палиндромы с пробелами и знаками препинания
Иногда палиндромом считается не одно слово, а целая фраза. Например:
А роза упала на лапу Азора
Если сравнивать такую строку буквально, пробелы, регистр и знаки препинания будут мешать.
В PostgreSQL можно сначала нормализовать строку: привести к нижнему регистру и убрать всё лишнее.
Например для латиницы и цифр:
SELECT
phrase,
regexp_replace(LOWER(phrase), '[^a-z0-9]', '', 'g') AS normalized_phrase
FROM phrases;
Если была строка:
A man, a plan, a canal: Panama!
после нормализации получится:
amanaplanacanalpanama
Теперь можно сравнить нормализованную строку с её разворотом:
SELECT
phrase,
regexp_replace(LOWER(phrase), '[^a-z0-9]', '', 'g')
=
REVERSE(regexp_replace(LOWER(phrase), '[^a-z0-9]', '', 'g')) AS is_palindrome
FROM phrases;
Запрос длинный, но логика простая:
- Приводим фразу к нижнему регистру.
- Убираем пробелы и знаки препинания.
- Переворачиваем результат.
- Сравниваем исходную нормализованную строку с перевёрнутой.
Для новичка главное понять сам принцип: перед проверкой палиндрома строку часто нужно привести к единому виду.
Почему REVERSE полезен для поиска с конца
Самое интересное применение REVERSE — поиск по окончанию строки.
Например, нужно найти всех пользователей с email на @gmail.com.
Обычно хочется написать так:
SELECT id, email
FROM users
WHERE email LIKE '%@gmail.com';
Этот запрос понятный: найти email, которые заканчиваются на @gmail.com.
Но для базы данных это не самый удобный поиск. Шаблон начинается с %, а значит перед @gmail.com может быть что угодно:
alex@gmail.com
maria@gmail.com
some.long.name@gmail.com
Обычный B-tree индекс хорошо работает, когда поиск начинается с начала строки:
SELECT * FROM users WHERE email LIKE 'alex%';
Но когда шаблон начинается с %, база часто не может использовать обычный индекс эффективно и вынуждена просматривать много строк.
И вот здесь появляется трюк с REVERSE.
Как суффикс превращается в префикс
Суффикс — это окончание строки.
Например, у email:
alex@gmail.com
суффикс:
@gmail.com
Если перевернуть email, получится:
moc.liamg@xela
Теперь бывший конец строки оказался в начале.
То есть поиск:
заканчивается на @gmail.com
после разворота превращается в:
начинается с moc.liamg@
А поиск по началу строки индексам уже нравится гораздо больше.
Пример:
SELECT
email,
REVERSE(email) AS reversed_email
FROM users;
Результат:
email | reversed_email
-----------------+-------------------
alex@gmail.com | moc.liamg@xela
maria@gmail.com | moc.liamg@airam
ivan@yahoo.com | moc.oohay@navi
Теперь все Gmail-адреса начинаются с одинакового префикса:
moc.liamg@
Поиск email по домену через REVERSE
В PostgreSQL можно написать так:
SELECT id, email
FROM users
WHERE REVERSE(email) LIKE REVERSE('%@gmail.com');
Разберём, что происходит.
Обычный шаблон:
%@gmail.com
означает: любая строка, которая заканчивается на @gmail.com.
Если его перевернуть:
SELECT REVERSE('%@gmail.com');
получится:
moc.liamg@%
А это уже шаблон вида:
начинается с moc.liamg@
То есть:
SELECT * FROM users WHERE REVERSE(email) LIKE 'moc.liamg@%';
Так мы превратили поиск по окончанию в поиск по началу.
Индекс по развёрнутой строке
Сам по себе REVERSE в WHERE ещё не гарантирует ускорение. Если просто написать WHERE REVERSE(email) LIKE 'moc.liamg@%', база всё равно должна где-то взять результат REVERSE(email).
Чтобы такой поиск стал быстрым на большой таблице, в PostgreSQL можно создать функциональный индекс:
CREATE INDEX idx_users_email_reversed
ON users (REVERSE(email) text_pattern_ops);
После этого запрос:
SELECT id, email
FROM users
WHERE REVERSE(email) LIKE REVERSE('%@gmail.com');
может использовать индекс по развёрнутому email.
Идея такая:
- в таблице хранится обычный email;
- в индексе база хранит результат
REVERSE(email);
- когда мы ищем по
REVERSE(email), база может идти по индексу, а не пересчитывать всё с нуля для каждой строки.
Почему выражение должно совпадать
Важный момент: выражение в запросе должно совпадать с выражением в индексе.
Если индекс создан так:
CREATE INDEX idx_users_email_reversed
ON users (REVERSE(email) text_pattern_ops);
то в запросе тоже должно быть REVERSE(email) LIKE ....
Если написать немного иначе, например REVERSE(LOWER(email)) LIKE ..., это уже другое выражение.
Для него нужен отдельный индекс:
CREATE INDEX idx_users_email_lower_reversed
ON users (REVERSE(LOWER(email)) text_pattern_ops);
И тогда запрос должен использовать такое же выражение:
SELECT id, email
FROM users
WHERE REVERSE(LOWER(email)) LIKE REVERSE(LOWER('%@gmail.com'));
Простое правило: если вы хотите, чтобы функциональный индекс помог, функция в WHERE должна быть такой же, как в индексе.
Когда индекс по REVERSE действительно нужен
Не нужно создавать такой индекс на всякий случай.
Он полезен, если у вас часто бывают запросы вида:
SELECT * FROM users WHERE email LIKE '%@gmail.com';
или:
SELECT * FROM files WHERE filename LIKE '%.pdf';
или:
SELECT * FROM links WHERE url LIKE '%.ru';
То есть когда вы регулярно ищете строки по окончанию.
Примеры задач:
- найти пользователей по домену email;
- найти файлы по расширению;
- найти URL по доменной зоне;
- найти коды, которые заканчиваются определённым суффиксом;
- ускорить частый поиск «заканчивается на».
Но если таблица маленькая или такой поиск бывает раз в год, отдельный индекс может быть лишним. Индексы занимают место и замедляют вставку и обновление данных, потому что их тоже нужно поддерживать.
REVERSE не ускоряет обычный поиск по началу
Индекс по REVERSE(email) помогает искать по окончанию строки.
Но он не заменяет обычный индекс по email.
Например, такой запрос:
SELECT id, email
FROM users
WHERE email LIKE 'alex%';
ищет по началу email. Для него нужен обычный индекс по email, а не индекс по REVERSE(email).
Запомните:
email LIKE 'alex%' — поиск по началу, нужен обычный индекс;
email LIKE '%@gmail.com' — поиск по окончанию, может помочь индекс по REVERSE(email);
email LIKE '%gmail%' — поиск внутри строки, лучше смотреть в сторону других инструментов, например полнотекстового поиска или trigram-индексов в PostgreSQL.
REVERSE особенно хорош именно для строгого сценария «строка заканчивается на что-то».
Достать последний сегмент строки
Ещё один полезный сценарий: нужно получить последний кусок строки после разделителя.
Например, есть домен:
mail.corp.example.com
Нужно получить последнюю часть:
com
Если разделитель — точка, можно развернуть строку, взять первый сегмент, а потом развернуть обратно.
SELECT REVERSE(SPLIT_PART(REVERSE('mail.corp.example.com'), '.', 1)) AS last_part;
Результат:
com
Разберём по шагам.
Исходная строка:
mail.corp.example.com
После REVERSE:
moc.elpmaxe.proc.liam
Теперь первый сегмент до точки:
moc
Разворачиваем его обратно:
com
И получаем последний сегмент исходной строки.
Пример с расширением файла
Допустим, в таблице есть имена файлов:
filename
----------------
report.pdf
photo.jpg
archive.tar.gz
README
Нужно достать расширение после последней точки.
SELECT
filename,
REVERSE(SPLIT_PART(REVERSE(filename), '.', 1)) AS extension
FROM files;
Результат:
filename | extension
---------------+----------
report.pdf | pdf
photo.jpg | jpg
archive.tar.gz | gz
README | README
Последний пример показывает ограничение: если в строке нет точки, SPLIT_PART вернёт всю строку. Поэтому для реального отчёта лучше добавить проверку.
Например:
SELECT
filename,
CASE
WHEN filename LIKE '%.%' THEN REVERSE(SPLIT_PART(REVERSE(filename), '.', 1))
ELSE NULL
END AS extension
FROM files;
Теперь для README будет NULL, потому что расширения нет.
filename | extension
---------------+----------
report.pdf | pdf
photo.jpg | jpg
archive.tar.gz | gz
README | NULL
Так результат становится честнее.
Последний сегмент URL или пути
Та же идея работает с URL и путями.
Например, есть путь:
/catalog/products/42
Нужно получить последнюю часть:
42
Запрос:
SELECT REVERSE(SPLIT_PART(REVERSE('/catalog/products/42'), '/', 1)) AS last_segment;
Результат:
42
Или для таблицы:
SELECT
path,
REVERSE(SPLIT_PART(REVERSE(path), '/', 1)) AS last_segment
FROM pages;
Если данные такие:
path
---------------------
/catalog/products/42
/users/15
/articles/sql/reverse
результат будет таким:
path | last_segment
----------------------+-------------
/catalog/products/42 | 42
/users/15 | 15
/articles/sql/reverse | reverse
Это простой способ «читать строку с конца», когда обычные функции удобнее работают с началом строки.
Важно: REVERSE не переворачивает элементы списка
Допустим, есть строка:
mon,tue,wed
Если применить к ней REVERSE, получится:
SELECT REVERSE('mon,tue,wed') AS result;
Результат:
dew,eut,nom
Это не список в обратном порядке. Это просто вся строка, прочитанная справа налево.
Если вам нужно получить:
wed,tue,mon
то есть перевернуть именно элементы списка, а не символы, нужно сначала разбить строку на элементы, поменять порядок и собрать обратно.
В PostgreSQL это можно сделать так:
SELECT string_agg(part, ',' ORDER BY ord DESC) AS reversed_list
FROM unnest(string_to_array('mon,tue,wed', ',')) WITH ORDINALITY AS t(part, ord);
Результат:
wed,tue,mon
Здесь происходит несколько шагов:
string_to_array('mon,tue,wed', ',') превращает строку в массив.
unnest(...) WITH ORDINALITY разворачивает массив в строки и добавляет номер элемента.
ORDER BY ord DESC сортирует элементы в обратном порядке.
string_agg(part, ',') склеивает их обратно в строку.
Это важная разница:
SELECT REVERSE('mon,tue,wed');
даёт:
dew,eut,nom
А разворот элементов списка даёт:
wed,tue,mon
REVERSE переворачивает символы, а не смысловые части строки.
REVERSE и Unicode
С обычной латиницей и кириллицей REVERSE обычно ведёт себя ожидаемо.
Например:
SELECT REVERSE('Привет') AS result;
Результат:
тевирП
Но с Unicode есть тонкость.
Некоторые символы могут состоять не из одного простого символа, а из нескольких кодовых точек. Например, буква с комбинируемым акцентом или некоторые эмодзи с модификаторами.
Визуально человек видит один символ, а технически внутри строки может быть несколько частей.
REVERSE обычно переставляет именно технические символы, а не «видимые человеческие буквы» как единые графемы. Поэтому в сложных Unicode-строках результат может выглядеть странно.
Для обычных email, доменов, кодов, имён файлов и простых русских строк это редко становится проблемой. Но если вы работаете с текстом на разных языках, диакритикой и эмодзи, лучше отдельно протестировать поведение на своих данных.
REVERSE в PostgreSQL
В PostgreSQL функция называется REVERSE.
Пример:
SELECT REVERSE('postgres') AS result;
Результат:
sergtsop
Функцию можно использовать в SELECT:
SELECT
id,
email,
REVERSE(email) AS email_reversed
FROM users;
В WHERE:
SELECT id, email
FROM users
WHERE REVERSE(email) LIKE REVERSE('%@gmail.com');
И в функциональном индексе:
CREATE INDEX idx_users_email_reversed
ON users (REVERSE(email) text_pattern_ops);
Для поиска по шаблону LIKE в PostgreSQL иногда важен операторный класс text_pattern_ops, особенно если база работает не в локали C. Если вы только учитесь, можно запомнить проще: для индексируемого префиксного LIKE по тексту в PostgreSQL часто используют специальный индекс с text_pattern_ops.
REVERSE в MySQL
В MySQL функция тоже называется REVERSE.
SELECT REVERSE('mysql') AS result;
Результат:
lqsym
Её можно применять к колонкам:
SELECT
id,
email,
REVERSE(email) AS email_reversed
FROM users;
Для поиска по окончанию идея такая же:
SELECT id, email
FROM users
WHERE REVERSE(email) LIKE REVERSE('%@gmail.com');
Но с индексами нужно быть внимательнее. В MySQL часто используют generated column: отдельную вычисляемую колонку, где хранится развёрнутый email, и индекс уже по ней.
Например идея может выглядеть так:
ALTER TABLE users
ADD COLUMN email_reversed VARCHAR(255)
GENERATED ALWAYS AS (REVERSE(email)) STORED;
CREATE INDEX idx_users_email_reversed
ON users (email_reversed);
После этого можно искать так:
SELECT id, email
FROM users
WHERE email_reversed LIKE REVERSE('%@gmail.com');
Такой подход делает поиск по окончанию более удобным для индексации.
REVERSE в ClickHouse
В ClickHouse есть разные варианты разворота строки.
Для простых байтовых строк может использоваться reverse, а для UTF-8 строк лучше использовать reverseUTF8.
Пример для обычной ASCII-строки:
SELECT reverse('clickhouse') AS result;
Результат:
esuohkcilc
Для русских строк лучше использовать UTF-8 вариант:
SELECT reverseUTF8('Привет') AS result;
Результат:
тевирП
Если использовать байтовый разворот на не-ASCII строках, можно получить испорченную последовательность байтов и некорректный текст. Поэтому простое правило такое:
- для ASCII-строк можно использовать
reverse;
- для кириллицы и другого UTF-8 текста лучше использовать
reverseUTF8.
Что тестировать при переносе между СУБД
На простых строках всё выглядит одинаково:
hello
postgres
12345
Но реальные проблемы обычно появляются на краях.
Перед переносом логики между PostgreSQL, MySQL и ClickHouse стоит проверить такие значения:
hello
Привет
alex@gmail.com
mail.corp.example.com
a.b.c
''
NULL
🙂
é
Особенно важно проверить:
- пустую строку;
NULL;
- кириллицу;
- эмодзи;
- символы с акцентами;
- строки с несколькими разделителями;
- строки без разделителя;
- email и домены, по которым будет поиск.
Так вы заранее увидите, где функция работает ожидаемо, а где нужно добавить отдельную обработку.
Когда REVERSE лучше не использовать
REVERSE полезен, но не стоит применять его везде подряд.
Он не нужен, если задача проще решается обычными функциями.
Например, если вам нужно просто проверить, что email начинается с alex, пишите:
SELECT * FROM users WHERE email LIKE 'alex%';
Не нужно разворачивать строку.
Если нужно найти подстроку в любом месте:
SELECT * FROM users WHERE email LIKE '%gmail%';
REVERSE тоже не решает задачу идеально. Для такого поиска лучше подходят другие инструменты, например trigram-индексы в PostgreSQL.
Если нужно разобрать сложный URL, возможно, лучше использовать специальные функции, регулярные выражения или обработку на уровне приложения.
REVERSE особенно хорош там, где вы действительно хотите посмотреть на строку с конца.
Частая ошибка: думать, что REVERSE понимает структуру строки
Допустим, есть строка:
a,b,c
REVERSE не знает, что это список из трёх элементов. Он просто перевернёт символы:
SELECT REVERSE('a,b,c') AS result;
Результат:
c,b,a
В этом конкретном случае кажется, что всё хорошо. Но это случайность, потому что элементы состоят из одного символа.
Если строка такая:
mon,tue,wed
то результат будет уже не тем, что нужно:
dew,eut,nom
Поэтому важно понимать: REVERSE работает с символами, а не с элементами списка, словами, доменами или путями. Если вам нужно перевернуть части строки, сначала нужно эти части выделить.
Практический пример: найти пользователей с Gmail
Допустим, есть таблица:
id | email
---+-------------------
1 | alex@gmail.com
2 | maria@yahoo.com
3 | ivan@gmail.com
4 | test@company.com
Обычный запрос:
SELECT id, email
FROM users
WHERE email LIKE '%@gmail.com';
Результат:
id | email
---+----------------
1 | alex@gmail.com
3 | ivan@gmail.com
Для маленькой таблицы этого достаточно.
Но если таблица большая и такие запросы выполняются часто, можно использовать идею с развёрнутым email.
SELECT id, email
FROM users
WHERE REVERSE(email) LIKE REVERSE('%@gmail.com');
Логически результат будет тем же:
id | email
---+----------------
1 | alex@gmail.com
3 | ivan@gmail.com
Но теперь окончание email превратилось в начало перевёрнутой строки. А это уже можно индексировать через функциональный индекс в PostgreSQL или через вычисляемую колонку в других СУБД.
Практический пример: получить доменную зону
Допустим, есть домены:
host
----------------------
google.com
mail.example.org
school.quality-academy.ru
Нужно получить последнюю часть после точки:
com
org
ru
Запрос:
SELECT
host,
REVERSE(SPLIT_PART(REVERSE(host), '.', 1)) AS tld
FROM domains;
Результат:
host | tld
--------------------------+-----
google.com | com
mail.example.org | org
school.quality-academy.ru | ru
Здесь tld — это top-level domain, доменная зона.
Разворот помогает взять последний сегмент так, будто он первый.
Практический пример: проверить коды, которые заканчиваются на суффикс
Допустим, у заказов есть код:
order_code
------------
QA-2026-001-RU
QA-2026-002-KZ
QA-2026-003-RU
QA-2026-004-GE
Нужно найти коды, которые заканчиваются на -RU.
Обычный запрос:
SELECT order_code
FROM orders
WHERE order_code LIKE '%-RU';
Через REVERSE:
SELECT order_code
FROM orders
WHERE REVERSE(order_code) LIKE REVERSE('%-RU');
REVERSE('%-RU') превратится в:
UR-%
То есть база будет искать перевёрнутые строки, которые начинаются с UR-.
На маленьких таблицах разницы почти не будет. На больших таблицах и при правильном индексе такой подход может быть полезен.
Коротко
REVERSE — это функция, которая возвращает строку в обратном порядке.
SELECT REVERSE('postgres');
Результат:
sergtsop
Она полезна, когда нужно:
- развернуть строку для отображения или проверки;
- проверить палиндром;
- получить последний сегмент строки;
- работать с расширениями файлов;
- искать строки, которые заканчиваются на определённый суффикс;
- превратить поиск по окончанию в поиск по началу;
- построить индексируемый поиск по суффиксу.
Главные правила:
REVERSE переворачивает символы, а не слова и не элементы списка;
NULL на входе даёт NULL на выходе;
- для простых строк функция ведёт себя предсказуемо;
- для сложного Unicode лучше отдельно тестировать результат;
- поиск
LIKE '%suffix' можно переписать через REVERSE, чтобы суффикс стал префиксом;
- для ускорения такого поиска нужен подходящий индекс;
- индекс по
REVERSE(column) помогает искать по окончанию, но не заменяет обычный индекс по самой колонке.
Самая полезная мысль: REVERSE нужен не только для того, чтобы «перевернуть слово». Он помогает посмотреть на строку с конца, а многие задачи после этого становятся гораздо проще.
REVERSE— это строковая функция, которая переворачивает строку задом наперёд.Звучит как игрушка:
SELECT REVERSE('postgres') AS result;Результат:
Но на практике
REVERSEбывает полезен не только для забавных примеров. С его помощью можно:Главная идея такая:
REVERSEменяет местами начало и конец строки. А это важно, потому что в SQL многие операции гораздо удобнее делать с начала строки, чем с конца.Что делает REVERSE
Функция
REVERSEпринимает строку и возвращает эту же строку в обратном порядке.SELECT REVERSE('hello') AS result;Результат:
Ещё несколько простых примеров:
SELECT REVERSE('SQL') AS result;Результат:
SELECT REVERSE('12345') AS result;Результат:
SELECT REVERSE('abc-def') AS result;Результат:
Важно:
REVERSEпереворачивает именно символы строки. Он не понимает смысл текста, не знает, где слова, где домены, где фамилии, а где коды. Для него строка — это просто последовательность символов.Базовый синтаксис
Синтаксис очень простой:
Например:
SELECT REVERSE('database') AS reversed_text;Результат:
Можно применять функцию к колонке таблицы:
SELECT id, name, REVERSE(name) AS reversed_name FROM users;Если в таблице есть данные:
результат будет таким:
В обычном интерфейсе такие перевёрнутые имена, конечно, не нужны. Но для проверок, поиска с конца и некоторых технических задач функция очень удобна.
Что будет с NULL
Если передать в
REVERSEзначениеNULL, результат тоже будетNULL.SELECT REVERSE(NULL) AS result;Результат:
Это обычное поведение SQL: если значения нет, то и переворачивать нечего.
Если вместо
NULLнужна пустая строка, используйтеCOALESCE:SELECT REVERSE(COALESCE(name, '')) AS reversed_name FROM users;COALESCE(name, '')означает: еслиnameравенNULL, возьми пустую строку.Простая проверка: палиндромы
Палиндром — это строка, которая читается одинаково слева направо и справа налево.
Например:
Если строка равна самой себе в перевёрнутом виде, значит это палиндром.
SELECT word, word = REVERSE(word) AS is_palindrome FROM words;Если данные такие:
результат будет таким:
Для простых слов это работает отлично.
Палиндромы без учёта регистра
Есть нюанс: SQL сравнивает строки буквально. Поэтому
AnnaиannA— это разные строки.SELECT 'Anna' = REVERSE('Anna') AS result;REVERSE('Anna')вернёт:А значит сравнение даст
false.Чтобы не учитывать регистр, можно привести обе стороны к нижнему регистру через
LOWER:SELECT name, LOWER(name) = REVERSE(LOWER(name)) AS is_palindrome FROM users;Теперь
Anna,ANNAиannaбудут проверяться одинаково.Пример:
SELECT LOWER('Anna') = REVERSE(LOWER('Anna')) AS result;Результат:
Сначала строка превращается в
anna, потом переворачивается вanna, и сравнение проходит успешно.Палиндромы с пробелами и знаками препинания
Иногда палиндромом считается не одно слово, а целая фраза. Например:
Если сравнивать такую строку буквально, пробелы, регистр и знаки препинания будут мешать.
В PostgreSQL можно сначала нормализовать строку: привести к нижнему регистру и убрать всё лишнее.
Например для латиницы и цифр:
SELECT phrase, regexp_replace(LOWER(phrase), '[^a-z0-9]', '', 'g') AS normalized_phrase FROM phrases;Если была строка:
после нормализации получится:
Теперь можно сравнить нормализованную строку с её разворотом:
SELECT phrase, regexp_replace(LOWER(phrase), '[^a-z0-9]', '', 'g') = REVERSE(regexp_replace(LOWER(phrase), '[^a-z0-9]', '', 'g')) AS is_palindrome FROM phrases;Запрос длинный, но логика простая:
Для новичка главное понять сам принцип: перед проверкой палиндрома строку часто нужно привести к единому виду.
Почему REVERSE полезен для поиска с конца
Самое интересное применение
REVERSE— поиск по окончанию строки.Например, нужно найти всех пользователей с email на
@gmail.com.Обычно хочется написать так:
SELECT id, email FROM users WHERE email LIKE '%@gmail.com';Этот запрос понятный: найти email, которые заканчиваются на
@gmail.com.Но для базы данных это не самый удобный поиск. Шаблон начинается с
%, а значит перед@gmail.comможет быть что угодно:Обычный B-tree индекс хорошо работает, когда поиск начинается с начала строки:
SELECT * FROM users WHERE email LIKE 'alex%';Но когда шаблон начинается с
%, база часто не может использовать обычный индекс эффективно и вынуждена просматривать много строк.И вот здесь появляется трюк с
REVERSE.Как суффикс превращается в префикс
Суффикс — это окончание строки.
Например, у email:
суффикс:
Если перевернуть email, получится:
Теперь бывший конец строки оказался в начале.
То есть поиск:
после разворота превращается в:
А поиск по началу строки индексам уже нравится гораздо больше.
Пример:
SELECT email, REVERSE(email) AS reversed_email FROM users;Результат:
Теперь все Gmail-адреса начинаются с одинакового префикса:
Поиск email по домену через REVERSE
В PostgreSQL можно написать так:
SELECT id, email FROM users WHERE REVERSE(email) LIKE REVERSE('%@gmail.com');Разберём, что происходит.
Обычный шаблон:
означает: любая строка, которая заканчивается на
@gmail.com.Если его перевернуть:
SELECT REVERSE('%@gmail.com');получится:
А это уже шаблон вида:
То есть:
SELECT * FROM users WHERE REVERSE(email) LIKE 'moc.liamg@%';Так мы превратили поиск по окончанию в поиск по началу.
Индекс по развёрнутой строке
Сам по себе
REVERSEвWHEREещё не гарантирует ускорение. Если просто написатьWHERE REVERSE(email) LIKE 'moc.liamg@%', база всё равно должна где-то взять результатREVERSE(email).Чтобы такой поиск стал быстрым на большой таблице, в PostgreSQL можно создать функциональный индекс:
CREATE INDEX idx_users_email_reversed ON users (REVERSE(email) text_pattern_ops);После этого запрос:
SELECT id, email FROM users WHERE REVERSE(email) LIKE REVERSE('%@gmail.com');может использовать индекс по развёрнутому email.
Идея такая:
REVERSE(email);REVERSE(email), база может идти по индексу, а не пересчитывать всё с нуля для каждой строки.Почему выражение должно совпадать
Важный момент: выражение в запросе должно совпадать с выражением в индексе.
Если индекс создан так:
CREATE INDEX idx_users_email_reversed ON users (REVERSE(email) text_pattern_ops);то в запросе тоже должно быть
REVERSE(email) LIKE ....Если написать немного иначе, например
REVERSE(LOWER(email)) LIKE ..., это уже другое выражение.Для него нужен отдельный индекс:
CREATE INDEX idx_users_email_lower_reversed ON users (REVERSE(LOWER(email)) text_pattern_ops);И тогда запрос должен использовать такое же выражение:
SELECT id, email FROM users WHERE REVERSE(LOWER(email)) LIKE REVERSE(LOWER('%@gmail.com'));Простое правило: если вы хотите, чтобы функциональный индекс помог, функция в
WHEREдолжна быть такой же, как в индексе.Когда индекс по REVERSE действительно нужен
Не нужно создавать такой индекс на всякий случай.
Он полезен, если у вас часто бывают запросы вида:
SELECT * FROM users WHERE email LIKE '%@gmail.com';или:
SELECT * FROM files WHERE filename LIKE '%.pdf';или:
SELECT * FROM links WHERE url LIKE '%.ru';То есть когда вы регулярно ищете строки по окончанию.
Примеры задач:
Но если таблица маленькая или такой поиск бывает раз в год, отдельный индекс может быть лишним. Индексы занимают место и замедляют вставку и обновление данных, потому что их тоже нужно поддерживать.
REVERSE не ускоряет обычный поиск по началу
Индекс по
REVERSE(email)помогает искать по окончанию строки.Но он не заменяет обычный индекс по
email.Например, такой запрос:
SELECT id, email FROM users WHERE email LIKE 'alex%';ищет по началу email. Для него нужен обычный индекс по
email, а не индекс поREVERSE(email).Запомните:
email LIKE 'alex%'— поиск по началу, нужен обычный индекс;email LIKE '%@gmail.com'— поиск по окончанию, может помочь индекс поREVERSE(email);email LIKE '%gmail%'— поиск внутри строки, лучше смотреть в сторону других инструментов, например полнотекстового поиска или trigram-индексов в PostgreSQL.REVERSEособенно хорош именно для строгого сценария «строка заканчивается на что-то».Достать последний сегмент строки
Ещё один полезный сценарий: нужно получить последний кусок строки после разделителя.
Например, есть домен:
Нужно получить последнюю часть:
Если разделитель — точка, можно развернуть строку, взять первый сегмент, а потом развернуть обратно.
SELECT REVERSE(SPLIT_PART(REVERSE('mail.corp.example.com'), '.', 1)) AS last_part;Результат:
Разберём по шагам.
Исходная строка:
После
REVERSE:Теперь первый сегмент до точки:
Разворачиваем его обратно:
И получаем последний сегмент исходной строки.
Пример с расширением файла
Допустим, в таблице есть имена файлов:
Нужно достать расширение после последней точки.
SELECT filename, REVERSE(SPLIT_PART(REVERSE(filename), '.', 1)) AS extension FROM files;Результат:
Последний пример показывает ограничение: если в строке нет точки,
SPLIT_PARTвернёт всю строку. Поэтому для реального отчёта лучше добавить проверку.Например:
SELECT filename, CASE WHEN filename LIKE '%.%' THEN REVERSE(SPLIT_PART(REVERSE(filename), '.', 1)) ELSE NULL END AS extension FROM files;Теперь для
READMEбудетNULL, потому что расширения нет.Так результат становится честнее.
Последний сегмент URL или пути
Та же идея работает с URL и путями.
Например, есть путь:
Нужно получить последнюю часть:
Запрос:
SELECT REVERSE(SPLIT_PART(REVERSE('/catalog/products/42'), '/', 1)) AS last_segment;Результат:
Или для таблицы:
SELECT path, REVERSE(SPLIT_PART(REVERSE(path), '/', 1)) AS last_segment FROM pages;Если данные такие:
результат будет таким:
Это простой способ «читать строку с конца», когда обычные функции удобнее работают с началом строки.
Важно: REVERSE не переворачивает элементы списка
Допустим, есть строка:
Если применить к ней
REVERSE, получится:SELECT REVERSE('mon,tue,wed') AS result;Результат:
Это не список в обратном порядке. Это просто вся строка, прочитанная справа налево.
Если вам нужно получить:
то есть перевернуть именно элементы списка, а не символы, нужно сначала разбить строку на элементы, поменять порядок и собрать обратно.
В PostgreSQL это можно сделать так:
SELECT string_agg(part, ',' ORDER BY ord DESC) AS reversed_list FROM unnest(string_to_array('mon,tue,wed', ',')) WITH ORDINALITY AS t(part, ord);Результат:
Здесь происходит несколько шагов:
string_to_array('mon,tue,wed', ',')превращает строку в массив.unnest(...) WITH ORDINALITYразворачивает массив в строки и добавляет номер элемента.ORDER BY ord DESCсортирует элементы в обратном порядке.string_agg(part, ',')склеивает их обратно в строку.Это важная разница:
SELECT REVERSE('mon,tue,wed');даёт:
А разворот элементов списка даёт:
REVERSEпереворачивает символы, а не смысловые части строки.REVERSE и Unicode
С обычной латиницей и кириллицей
REVERSEобычно ведёт себя ожидаемо.Например:
Результат:
Но с Unicode есть тонкость.
Некоторые символы могут состоять не из одного простого символа, а из нескольких кодовых точек. Например, буква с комбинируемым акцентом или некоторые эмодзи с модификаторами.
Визуально человек видит один символ, а технически внутри строки может быть несколько частей.
REVERSEобычно переставляет именно технические символы, а не «видимые человеческие буквы» как единые графемы. Поэтому в сложных Unicode-строках результат может выглядеть странно.Для обычных email, доменов, кодов, имён файлов и простых русских строк это редко становится проблемой. Но если вы работаете с текстом на разных языках, диакритикой и эмодзи, лучше отдельно протестировать поведение на своих данных.
REVERSE в PostgreSQL
В PostgreSQL функция называется
REVERSE.Пример:
SELECT REVERSE('postgres') AS result;Результат:
Функцию можно использовать в
SELECT:SELECT id, email, REVERSE(email) AS email_reversed FROM users;В
WHERE:SELECT id, email FROM users WHERE REVERSE(email) LIKE REVERSE('%@gmail.com');И в функциональном индексе:
CREATE INDEX idx_users_email_reversed ON users (REVERSE(email) text_pattern_ops);Для поиска по шаблону
LIKEв PostgreSQL иногда важен операторный классtext_pattern_ops, особенно если база работает не в локалиC. Если вы только учитесь, можно запомнить проще: для индексируемого префиксногоLIKEпо тексту в PostgreSQL часто используют специальный индекс сtext_pattern_ops.REVERSE в MySQL
В MySQL функция тоже называется
REVERSE.SELECT REVERSE('mysql') AS result;Результат:
Её можно применять к колонкам:
SELECT id, email, REVERSE(email) AS email_reversed FROM users;Для поиска по окончанию идея такая же:
SELECT id, email FROM users WHERE REVERSE(email) LIKE REVERSE('%@gmail.com');Но с индексами нужно быть внимательнее. В MySQL часто используют generated column: отдельную вычисляемую колонку, где хранится развёрнутый email, и индекс уже по ней.
Например идея может выглядеть так:
ALTER TABLE users ADD COLUMN email_reversed VARCHAR(255) GENERATED ALWAYS AS (REVERSE(email)) STORED; CREATE INDEX idx_users_email_reversed ON users (email_reversed);После этого можно искать так:
SELECT id, email FROM users WHERE email_reversed LIKE REVERSE('%@gmail.com');Такой подход делает поиск по окончанию более удобным для индексации.
REVERSE в ClickHouse
В ClickHouse есть разные варианты разворота строки.
Для простых байтовых строк может использоваться
reverse, а для UTF-8 строк лучше использоватьreverseUTF8.Пример для обычной ASCII-строки:
SELECT reverse('clickhouse') AS result;Результат:
Для русских строк лучше использовать UTF-8 вариант:
Результат:
Если использовать байтовый разворот на не-ASCII строках, можно получить испорченную последовательность байтов и некорректный текст. Поэтому простое правило такое:
reverse;reverseUTF8.Что тестировать при переносе между СУБД
На простых строках всё выглядит одинаково:
Но реальные проблемы обычно появляются на краях.
Перед переносом логики между PostgreSQL, MySQL и ClickHouse стоит проверить такие значения:
Особенно важно проверить:
NULL;Так вы заранее увидите, где функция работает ожидаемо, а где нужно добавить отдельную обработку.
Когда REVERSE лучше не использовать
REVERSEполезен, но не стоит применять его везде подряд.Он не нужен, если задача проще решается обычными функциями.
Например, если вам нужно просто проверить, что email начинается с
alex, пишите:SELECT * FROM users WHERE email LIKE 'alex%';Не нужно разворачивать строку.
Если нужно найти подстроку в любом месте:
SELECT * FROM users WHERE email LIKE '%gmail%';REVERSEтоже не решает задачу идеально. Для такого поиска лучше подходят другие инструменты, например trigram-индексы в PostgreSQL.Если нужно разобрать сложный URL, возможно, лучше использовать специальные функции, регулярные выражения или обработку на уровне приложения.
REVERSEособенно хорош там, где вы действительно хотите посмотреть на строку с конца.Частая ошибка: думать, что REVERSE понимает структуру строки
Допустим, есть строка:
REVERSEне знает, что это список из трёх элементов. Он просто перевернёт символы:SELECT REVERSE('a,b,c') AS result;Результат:
В этом конкретном случае кажется, что всё хорошо. Но это случайность, потому что элементы состоят из одного символа.
Если строка такая:
то результат будет уже не тем, что нужно:
Поэтому важно понимать:
REVERSEработает с символами, а не с элементами списка, словами, доменами или путями. Если вам нужно перевернуть части строки, сначала нужно эти части выделить.Практический пример: найти пользователей с Gmail
Допустим, есть таблица:
Обычный запрос:
SELECT id, email FROM users WHERE email LIKE '%@gmail.com';Результат:
Для маленькой таблицы этого достаточно.
Но если таблица большая и такие запросы выполняются часто, можно использовать идею с развёрнутым email.
SELECT id, email FROM users WHERE REVERSE(email) LIKE REVERSE('%@gmail.com');Логически результат будет тем же:
Но теперь окончание email превратилось в начало перевёрнутой строки. А это уже можно индексировать через функциональный индекс в PostgreSQL или через вычисляемую колонку в других СУБД.
Практический пример: получить доменную зону
Допустим, есть домены:
Нужно получить последнюю часть после точки:
Запрос:
SELECT host, REVERSE(SPLIT_PART(REVERSE(host), '.', 1)) AS tld FROM domains;Результат:
Здесь
tld— это top-level domain, доменная зона.Разворот помогает взять последний сегмент так, будто он первый.
Практический пример: проверить коды, которые заканчиваются на суффикс
Допустим, у заказов есть код:
Нужно найти коды, которые заканчиваются на
-RU.Обычный запрос:
SELECT order_code FROM orders WHERE order_code LIKE '%-RU';Через
REVERSE:SELECT order_code FROM orders WHERE REVERSE(order_code) LIKE REVERSE('%-RU');REVERSE('%-RU')превратится в:То есть база будет искать перевёрнутые строки, которые начинаются с
UR-.На маленьких таблицах разницы почти не будет. На больших таблицах и при правильном индексе такой подход может быть полезен.
Коротко
REVERSE— это функция, которая возвращает строку в обратном порядке.SELECT REVERSE('postgres');Результат:
Она полезна, когда нужно:
Главные правила:
REVERSEпереворачивает символы, а не слова и не элементы списка;NULLна входе даётNULLна выходе;LIKE '%suffix'можно переписать черезREVERSE, чтобы суффикс стал префиксом;REVERSE(column)помогает искать по окончанию, но не заменяет обычный индекс по самой колонке.Самая полезная мысль:
REVERSEнужен не только для того, чтобы «перевернуть слово». Он помогает посмотреть на строку с конца, а многие задачи после этого становятся гораздо проще.