В PostgreSQL есть удобная пара функций для работы с символами на уровне их числовых кодов:
ascii(text) возвращает код первого символа строки;
chr(integer) делает обратное — возвращает символ по его коду.
На первый взгляд функции кажутся редкими и «низкоуровневыми». Но на практике они часто помогают там, где обычного взгляда на строку уже не хватает: найти табуляцию, отличить обычный пробел от странного невидимого символа, собрать многострочный текст, сгенерировать буквенные метки вроде A, B, C или проверить, с какой буквы начинается значение.
Главная мысль: в PostgreSQL с кодировкой UTF-8 ascii работает не просто с байтом, а с кодовой точкой Unicode. Поэтому ascii и chr в такой базе хорошо сочетаются друг с другом: одна функция превращает символ в число, другая — число обратно в символ.
Зачем вообще нужны коды символов
Обычно мы работаем со строками как с текстом:
SELECT name
FROM users;
Но иногда важно увидеть не то, как символ выглядит, а что на самом деле лежит внутри строки.
Например, в данных может быть:
- обычный пробел;
- табуляция;
- перенос строки;
- неразрывный пробел;
- символ из другой раскладки;
- невидимый управляющий символ после импорта из Excel, CSV или внешней системы.
Глазом такие вещи часто не поймать. Строка выглядит нормально, а фильтр не срабатывает, сортировка странная, поиск не находит совпадение.
Вот тут и помогает ascii: она показывает числовой код первого символа строки.
Функция ASCII: код первого символа
Функция ascii(text) возвращает числовой код первого символа строки.
Важно: именно первого символа. Всё, что идёт дальше, функция игнорирует.
SELECT ascii('A');
SELECT ascii('a');
SELECT ascii('Apple');
SELECT ascii('');
Результат будет таким:
65
97
65
0
Разберём по шагам:
- у символа
A код 65;
- у символа
a код 97;
- у строки
Apple первый символ тоже A, поэтому результат снова 65;
- для пустой строки PostgreSQL возвращает 0.
Это удобно, когда нас интересует не вся строка, а только её начало.
Например, можно посмотреть, с каких букв начинаются имена пользователей:
SELECT
ascii(upper(left(name, 1))) AS first_code,
count(*) AS users_count
FROM users
GROUP BY first_code
ORDER BY first_code;
Что здесь происходит:
left(name, 1) берёт первый символ имени.
upper(...) приводит его к верхнему регистру.
ascii(...) превращает символ в числовой код.
count(*) считает, сколько пользователей попало в каждую группу.
Такой запрос может быть полезен для простой диагностики: например, понять, есть ли в именах неожиданные символы, пустые значения или буквы не из того алфавита.
Отбор по диапазону букв
Раз коды букв идут в понятном порядке, их можно сравнивать как числа.
Например, нужно выбрать пользователей, чьи имена начинаются с латинских букв от A до M:
SELECT id, name
FROM users
WHERE ascii(upper(left(name, 1))) BETWEEN ascii('A') AND ascii('M');
Такой запрос читается почти как обычное условие:
возьми первую букву имени, приведи к верхнему регистру, получи её код и проверь, находится ли он между кодами A и M.
Это хороший приём для латинских букв, технических кодов, артикулов, коротких идентификаторов и других данных, где вы точно понимаете допустимый диапазон символов.
Но важно помнить: алфавиты, языки и Unicode сложнее, чем просто A–Z. Для полноценной языковой сортировки, поиска по локали и работы с разными регистрами лучше использовать обычные строковые инструменты PostgreSQL, а не пытаться вручную сравнивать коды символов.
Функция CHR: символ по коду
Функция chr(integer) делает обратную операцию: принимает числовой код и возвращает символ.
SELECT chr(65);
SELECT chr(97);
SELECT chr(8364);
Результат:
A
a
€
То есть:
chr(65) возвращает A;
chr(97) возвращает a;
chr(8364) возвращает знак евро.
В базе PostgreSQL с кодировкой UTF-8 chr умеет работать не только с базовыми ASCII-символами, но и с Unicode-кодами.
Например:
SELECT chr(233);
Вернёт символ с кодом 233 — латинскую букву с акцентом.
Есть и ограничения:
chr(0) в PostgreSQL запрещён;
- слишком большое или недопустимое значение вызовет ошибку;
NULL на входе даст NULL на выходе.
То есть PostgreSQL не будет молча возвращать «какой-нибудь странный символ», если код некорректный. Он скорее честно остановит запрос ошибкой.
Генерация буквенных меток A, B, C
Один из самых понятных сценариев для ascii и chr — генерация буквенных меток.
Допустим, нужно получить метки A, B, C, D, E для отчёта:
SELECT chr(ascii('A') + (n - 1)) AS label
FROM generate_series(1, 5) AS g(n);
Результат:
A
B
C
D
E
Логика простая:
ascii('A') даёт код буквы A.
n - 1 даёт смещение: 0, 1, 2, 3, 4.
chr(...) превращает новый код обратно в символ.
Так можно сделать короткие буквенные обозначения для строк отчёта, блоков, групп или временных категорий.
Например, пронумеруем первые пять оплаченных заказов буквами:
WITH numbered_orders AS (
SELECT
id,
amount,
row_number() OVER (ORDER BY id) AS rn
FROM orders
WHERE status = 'paid'
)
SELECT
chr(ascii('A') + (rn - 1)) AS label,
id,
amount
FROM numbered_orders
WHERE rn <= 5
ORDER BY rn;
Получится что-то вроде:
A | 101 | 1200
B | 102 | 850
C | 103 | 4300
D | 104 | 700
E | 105 | 1990
Для маленьких списков это удобно и наглядно.
Но есть важная граница: после Z идут уже не буквы.
SELECT chr(ascii('Z') + 1);
Результатом будет не новая буква, а символ после Z в таблице кодов.
Поэтому для меток длиннее 26 элементов нужно заранее продумать логику: например, переходить к AA, AB, AC или хранить метки в отдельной таблице.
Управляющие символы: перенос строки и табуляция
chr особенно полезен для символов, которые сложно увидеть или неудобно писать прямо в строке.
Самые частые:
chr(9) — табуляция;
chr(10) — перевод строки;
chr(13) — возврат каретки;
chr(13) || chr(10) — перенос строки в формате CRLF, который часто встречается в Windows-файлах.
Например, соберём карточку пользователя в несколько строк:
SELECT
name || chr(10) || email AS contact_card
FROM users
WHERE id = 1;
Результат может выглядеть так:
Alex
alex@example.com
Снаружи это одно текстовое значение, но внутри него есть символ перевода строки.
Можно собрать и небольшой текстовый отчёт. Например, строки разделим переводом строки, а поля внутри строки — табуляцией:
SELECT string_agg(
id || chr(9) || amount || chr(9) || status,
chr(10) ORDER BY id
) AS report
FROM orders
WHERE status = 'paid';
Такой результат удобно скопировать в текстовый файл или быстро посмотреть в консоли.
Конечно, для настоящей CSV-выгрузки лучше использовать специальные инструменты экспорта. Но для диагностики, быстрых отчётов и служебных строк chr(9) и chr(10) очень выручают.
Как найти невидимые символы в данных
Представим ситуацию: в таблице есть имена, и часть из них почему-то не находится обычным поиском. Визуально всё похоже на нормальный текст, но что-то не так.
Можно посмотреть первый символ и его код:
SELECT
name,
left(name, 1) AS first_char,
ascii(left(name, 1)) AS first_code
FROM users
ORDER BY first_code;
Если в начале строки случайно затесалась табуляция, перенос строки или другой управляющий символ, это станет заметно по коду.
Можно также проверить строки, которые начинаются не с латинской буквы:
SELECT
id,
name,
ascii(upper(left(name, 1))) AS first_code
FROM users
WHERE ascii(upper(left(name, 1))) NOT BETWEEN ascii('A') AND ascii('Z');
Такой запрос не говорит, что данные обязательно плохие. Он говорит: «посмотри сюда внимательнее». Там могут быть кириллические имена, цифры, пробелы, пустые строки или действительно мусор после импорта.
Для проверки качества данных это очень полезный подход.
ASCII и Unicode в PostgreSQL
Название ascii может немного сбивать с толку. Кажется, будто функция должна работать только с классической ASCII-таблицей, где символы лежат в диапазоне от 0 до 127.
Но в PostgreSQL с кодировкой UTF-8 функция ascii возвращает кодовую точку Unicode первого символа.
То есть она не просто берёт первый байт строки. Она понимает символ целиком.
Например:
SELECT ascii('A');
SELECT ascii('€');
Первый запрос вернёт 65, а второй — Unicode-код символа евро.
Именно поэтому в PostgreSQL работает обратимость:
SELECT chr(ascii('A'));
SELECT chr(ascii('€'));
В обоих случаях мы берём символ, получаем его код, а потом по этому коду собираем символ обратно.
Это можно воспринимать как простой круг:
symbol -> ascii -> number -> chr -> symbol
Для UTF-8 базы PostgreSQL такая связка хорошо работает не только с латиницей.
Важная разница с MySQL
Самая неприятная ловушка появляется не внутри PostgreSQL, а при переносе запросов в другие СУБД.
В MySQL функция ASCII() ведёт себя иначе: она возвращает значение первого байта строки, а не полноценную Unicode-кодовую точку символа.
На обычной латинице разницы почти не видно:
SELECT ASCII('A');
И PostgreSQL, и MySQL дадут ожидаемый код 65.
Но на символах вне базового ASCII результат может отличаться. Например, буква с акцентом, кириллица или эмодзи в UTF-8 занимают несколько байт. PostgreSQL воспринимает первый символ как Unicode-символ, а MySQL ASCII() смотрит на первый байт.
Для полной Unicode-кодовой точки в MySQL используют другую функцию — ORD().
Обратная операция в MySQL тоже устроена иначе: для сборки символа по коду применяют CHAR(...), и иногда важно явно указывать кодировку, например CHAR(... USING utf8mb4).
Поэтому запрос, который отлично работает в PostgreSQL, не стоит без проверки переносить в MySQL:
SELECT chr(ascii('A'));
В PostgreSQL это естественная пара функций. В MySQL имена и поведение будут другими.
Что помнить про ClickHouse
В ClickHouse похожие задачи тоже решаются, но функции называются и работают не совсем так, как в PostgreSQL.
Там есть char, которая может принимать несколько числовых значений и собирать строку. Для работы с Unicode-кодовыми точками используются другие функции и подходы, связанные с UTF-8.
Главный практический совет простой: если переносите SQL между PostgreSQL, MySQL и ClickHouse, обязательно проверьте функции на тестовой таблице с разными символами.
Минимальный набор тестов:
- латинская буква;
- пустая строка;
NULL;
- символ с акцентом;
- кириллица;
- знак евро;
- эмодзи;
- строка с табуляцией или переносом строки.
Проблемы почти никогда не проявляются на чистых данных вроде A, B, C. Они появляются там, где в строках есть Unicode, невидимые символы и реальные данные из внешних источников.
Производительность: почему обычный индекс может не помочь
Есть ещё один важный практический момент.
Если написать условие так:
SELECT id, name
FROM users
WHERE ascii(upper(left(name, 1))) BETWEEN ascii('A') AND ascii('M');
PostgreSQL должен вычислить выражение ascii(upper(left(name, 1))) для строк таблицы.
Обычный индекс по колонке name не всегда сможет помочь такому запросу напрямую, потому что фильтр применяется не к самому name, а к результату функции от name.
Если таблица маленькая, вы этого даже не заметите. Но на большой таблице такой запрос может стать дорогим.
Что можно сделать:
- создать выражательный индекс;
- вынести первый код символа в generated column;
- заранее нормализовать данные на этапе загрузки;
- использовать отдельную витрину или view для аналитических проверок.
Например, выражательный индекс может выглядеть так:
CREATE INDEX users_first_name_code_idx
ON users ((ascii(upper(left(name, 1)))));
После этого PostgreSQL получает шанс использовать индекс именно под такое выражение.
Это не значит, что нужно индексировать каждую функцию в каждом запросе. Но если фильтр часто используется на большой таблице, его стоит вынести из разряда «просто удобная проверка» в нормальное правило данных.
Когда лучше вынести логику отдельно
Если вы один раз проверяете подозрительные данные, можно спокойно писать ascii прямо в запросе.
Но если такая логика повторяется в отчётах, проверках качества, миграциях или регулярных загрузках, лучше не копировать выражение по всему проекту.
Варианты:
- сделать
view с уже рассчитанным кодом первого символа;
- добавить generated column;
- добавить
CHECK, если нужно запретить неправильные значения;
- нормализовать строку в staging-таблице перед загрузкой в основную;
- оформить правило в отдельном SQL-слое, который легко тестировать.
Например:
CREATE VIEW users_with_first_code AS
SELECT
id,
name,
ascii(upper(left(name, 1))) AS first_code
FROM users;
Теперь аналитик или разработчик может использовать готовое поле:
SELECT id, name
FROM users_with_first_code
WHERE first_code BETWEEN ascii('A') AND ascii('M');
Так запросы становятся чище, а правило — заметнее и безопаснее.
Главное про ASCII и CHR
ascii(text) в PostgreSQL возвращает код первого символа строки. Если строка пустая, результатом будет 0. Остальная часть строки не учитывается.
chr(integer) возвращает символ по числовому коду. В PostgreSQL с UTF-8 это работает не только для базового ASCII, но и для Unicode-символов.
Связка chr(ascii(x)) в PostgreSQL с UTF-8 работает как понятное преобразование туда и обратно: символ → код → символ.
Самые частые практические сценарии:
- сгенерировать буквенные метки A, B, C;
- вставить перенос строки через
chr(10);
- вставить табуляцию через
chr(9);
- найти невидимые символы в данных;
- проверить первый символ строки по числовому коду;
- диагностировать странности после импорта.
Главная ловушка — переносимость. В PostgreSQL ascii работает с кодовой точкой Unicode, а в MySQL ASCII() смотрит на первый байт. Поэтому запросы с такими функциями нельзя бездумно переносить между PostgreSQL, MySQL и ClickHouse.
И главный практический вывод: ascii и chr — это не функции «для таблицы символов из учебника». Это полезные инструменты для диагностики строк, генерации служебного текста и аккуратной работы с невидимыми символами в реальных данных.
В PostgreSQL есть удобная пара функций для работы с символами на уровне их числовых кодов:
ascii(text)возвращает код первого символа строки;chr(integer)делает обратное — возвращает символ по его коду.На первый взгляд функции кажутся редкими и «низкоуровневыми». Но на практике они часто помогают там, где обычного взгляда на строку уже не хватает: найти табуляцию, отличить обычный пробел от странного невидимого символа, собрать многострочный текст, сгенерировать буквенные метки вроде A, B, C или проверить, с какой буквы начинается значение.
Главная мысль: в PostgreSQL с кодировкой UTF-8
asciiработает не просто с байтом, а с кодовой точкой Unicode. Поэтомуasciiиchrв такой базе хорошо сочетаются друг с другом: одна функция превращает символ в число, другая — число обратно в символ.Зачем вообще нужны коды символов
Обычно мы работаем со строками как с текстом:
SELECT name FROM users;Но иногда важно увидеть не то, как символ выглядит, а что на самом деле лежит внутри строки.
Например, в данных может быть:
Глазом такие вещи часто не поймать. Строка выглядит нормально, а фильтр не срабатывает, сортировка странная, поиск не находит совпадение.
Вот тут и помогает
ascii: она показывает числовой код первого символа строки.Функция ASCII: код первого символа
Функция
ascii(text)возвращает числовой код первого символа строки.Важно: именно первого символа. Всё, что идёт дальше, функция игнорирует.
SELECT ascii('A'); SELECT ascii('a'); SELECT ascii('Apple'); SELECT ascii('');Результат будет таким:
Разберём по шагам:
Aкод 65;aкод 97;Appleпервый символ тожеA, поэтому результат снова 65;Это удобно, когда нас интересует не вся строка, а только её начало.
Например, можно посмотреть, с каких букв начинаются имена пользователей:
SELECT ascii(upper(left(name, 1))) AS first_code, count(*) AS users_count FROM users GROUP BY first_code ORDER BY first_code;Что здесь происходит:
left(name, 1)берёт первый символ имени.upper(...)приводит его к верхнему регистру.ascii(...)превращает символ в числовой код.count(*)считает, сколько пользователей попало в каждую группу.Такой запрос может быть полезен для простой диагностики: например, понять, есть ли в именах неожиданные символы, пустые значения или буквы не из того алфавита.
Отбор по диапазону букв
Раз коды букв идут в понятном порядке, их можно сравнивать как числа.
Например, нужно выбрать пользователей, чьи имена начинаются с латинских букв от A до M:
SELECT id, name FROM users WHERE ascii(upper(left(name, 1))) BETWEEN ascii('A') AND ascii('M');Такой запрос читается почти как обычное условие:
Это хороший приём для латинских букв, технических кодов, артикулов, коротких идентификаторов и других данных, где вы точно понимаете допустимый диапазон символов.
Но важно помнить: алфавиты, языки и Unicode сложнее, чем просто A–Z. Для полноценной языковой сортировки, поиска по локали и работы с разными регистрами лучше использовать обычные строковые инструменты PostgreSQL, а не пытаться вручную сравнивать коды символов.
Функция CHR: символ по коду
Функция
chr(integer)делает обратную операцию: принимает числовой код и возвращает символ.SELECT chr(65); SELECT chr(97); SELECT chr(8364);Результат:
То есть:
chr(65)возвращаетA;chr(97)возвращаетa;chr(8364)возвращает знак евро.В базе PostgreSQL с кодировкой UTF-8
chrумеет работать не только с базовыми ASCII-символами, но и с Unicode-кодами.Например:
SELECT chr(233);Вернёт символ с кодом 233 — латинскую букву с акцентом.
Есть и ограничения:
chr(0)в PostgreSQL запрещён;NULLна входе дастNULLна выходе.То есть PostgreSQL не будет молча возвращать «какой-нибудь странный символ», если код некорректный. Он скорее честно остановит запрос ошибкой.
Генерация буквенных меток A, B, C
Один из самых понятных сценариев для
asciiиchr— генерация буквенных меток.Допустим, нужно получить метки A, B, C, D, E для отчёта:
SELECT chr(ascii('A') + (n - 1)) AS label FROM generate_series(1, 5) AS g(n);Результат:
Логика простая:
ascii('A')даёт код буквы A.n - 1даёт смещение: 0, 1, 2, 3, 4.chr(...)превращает новый код обратно в символ.Так можно сделать короткие буквенные обозначения для строк отчёта, блоков, групп или временных категорий.
Например, пронумеруем первые пять оплаченных заказов буквами:
WITH numbered_orders AS ( SELECT id, amount, row_number() OVER (ORDER BY id) AS rn FROM orders WHERE status = 'paid' ) SELECT chr(ascii('A') + (rn - 1)) AS label, id, amount FROM numbered_orders WHERE rn <= 5 ORDER BY rn;Получится что-то вроде:
Для маленьких списков это удобно и наглядно.
Но есть важная граница: после
Zидут уже не буквы.SELECT chr(ascii('Z') + 1);Результатом будет не новая буква, а символ после Z в таблице кодов.
Поэтому для меток длиннее 26 элементов нужно заранее продумать логику: например, переходить к
AA,AB,ACили хранить метки в отдельной таблице.Управляющие символы: перенос строки и табуляция
chrособенно полезен для символов, которые сложно увидеть или неудобно писать прямо в строке.Самые частые:
chr(9)— табуляция;chr(10)— перевод строки;chr(13)— возврат каретки;chr(13) || chr(10)— перенос строки в формате CRLF, который часто встречается в Windows-файлах.Например, соберём карточку пользователя в несколько строк:
SELECT name || chr(10) || email AS contact_card FROM users WHERE id = 1;Результат может выглядеть так:
Снаружи это одно текстовое значение, но внутри него есть символ перевода строки.
Можно собрать и небольшой текстовый отчёт. Например, строки разделим переводом строки, а поля внутри строки — табуляцией:
SELECT string_agg( id || chr(9) || amount || chr(9) || status, chr(10) ORDER BY id ) AS report FROM orders WHERE status = 'paid';Такой результат удобно скопировать в текстовый файл или быстро посмотреть в консоли.
Конечно, для настоящей CSV-выгрузки лучше использовать специальные инструменты экспорта. Но для диагностики, быстрых отчётов и служебных строк
chr(9)иchr(10)очень выручают.Как найти невидимые символы в данных
Представим ситуацию: в таблице есть имена, и часть из них почему-то не находится обычным поиском. Визуально всё похоже на нормальный текст, но что-то не так.
Можно посмотреть первый символ и его код:
SELECT name, left(name, 1) AS first_char, ascii(left(name, 1)) AS first_code FROM users ORDER BY first_code;Если в начале строки случайно затесалась табуляция, перенос строки или другой управляющий символ, это станет заметно по коду.
Можно также проверить строки, которые начинаются не с латинской буквы:
SELECT id, name, ascii(upper(left(name, 1))) AS first_code FROM users WHERE ascii(upper(left(name, 1))) NOT BETWEEN ascii('A') AND ascii('Z');Такой запрос не говорит, что данные обязательно плохие. Он говорит: «посмотри сюда внимательнее». Там могут быть кириллические имена, цифры, пробелы, пустые строки или действительно мусор после импорта.
Для проверки качества данных это очень полезный подход.
ASCII и Unicode в PostgreSQL
Название
asciiможет немного сбивать с толку. Кажется, будто функция должна работать только с классической ASCII-таблицей, где символы лежат в диапазоне от 0 до 127.Но в PostgreSQL с кодировкой UTF-8 функция
asciiвозвращает кодовую точку Unicode первого символа.То есть она не просто берёт первый байт строки. Она понимает символ целиком.
Например:
SELECT ascii('A'); SELECT ascii('€');Первый запрос вернёт 65, а второй — Unicode-код символа евро.
Именно поэтому в PostgreSQL работает обратимость:
SELECT chr(ascii('A')); SELECT chr(ascii('€'));В обоих случаях мы берём символ, получаем его код, а потом по этому коду собираем символ обратно.
Это можно воспринимать как простой круг:
Для UTF-8 базы PostgreSQL такая связка хорошо работает не только с латиницей.
Важная разница с MySQL
Самая неприятная ловушка появляется не внутри PostgreSQL, а при переносе запросов в другие СУБД.
В MySQL функция
ASCII()ведёт себя иначе: она возвращает значение первого байта строки, а не полноценную Unicode-кодовую точку символа.На обычной латинице разницы почти не видно:
SELECT ASCII('A');И PostgreSQL, и MySQL дадут ожидаемый код 65.
Но на символах вне базового ASCII результат может отличаться. Например, буква с акцентом, кириллица или эмодзи в UTF-8 занимают несколько байт. PostgreSQL воспринимает первый символ как Unicode-символ, а MySQL
ASCII()смотрит на первый байт.Для полной Unicode-кодовой точки в MySQL используют другую функцию —
ORD().Обратная операция в MySQL тоже устроена иначе: для сборки символа по коду применяют
CHAR(...), и иногда важно явно указывать кодировку, напримерCHAR(... USING utf8mb4).Поэтому запрос, который отлично работает в PostgreSQL, не стоит без проверки переносить в MySQL:
SELECT chr(ascii('A'));В PostgreSQL это естественная пара функций. В MySQL имена и поведение будут другими.
Что помнить про ClickHouse
В ClickHouse похожие задачи тоже решаются, но функции называются и работают не совсем так, как в PostgreSQL.
Там есть
char, которая может принимать несколько числовых значений и собирать строку. Для работы с Unicode-кодовыми точками используются другие функции и подходы, связанные с UTF-8.Главный практический совет простой: если переносите SQL между PostgreSQL, MySQL и ClickHouse, обязательно проверьте функции на тестовой таблице с разными символами.
Минимальный набор тестов:
NULL;Проблемы почти никогда не проявляются на чистых данных вроде A, B, C. Они появляются там, где в строках есть Unicode, невидимые символы и реальные данные из внешних источников.
Производительность: почему обычный индекс может не помочь
Есть ещё один важный практический момент.
Если написать условие так:
SELECT id, name FROM users WHERE ascii(upper(left(name, 1))) BETWEEN ascii('A') AND ascii('M');PostgreSQL должен вычислить выражение
ascii(upper(left(name, 1)))для строк таблицы.Обычный индекс по колонке
nameне всегда сможет помочь такому запросу напрямую, потому что фильтр применяется не к самомуname, а к результату функции отname.Если таблица маленькая, вы этого даже не заметите. Но на большой таблице такой запрос может стать дорогим.
Что можно сделать:
Например, выражательный индекс может выглядеть так:
CREATE INDEX users_first_name_code_idx ON users ((ascii(upper(left(name, 1)))));После этого PostgreSQL получает шанс использовать индекс именно под такое выражение.
Это не значит, что нужно индексировать каждую функцию в каждом запросе. Но если фильтр часто используется на большой таблице, его стоит вынести из разряда «просто удобная проверка» в нормальное правило данных.
Когда лучше вынести логику отдельно
Если вы один раз проверяете подозрительные данные, можно спокойно писать
asciiпрямо в запросе.Но если такая логика повторяется в отчётах, проверках качества, миграциях или регулярных загрузках, лучше не копировать выражение по всему проекту.
Варианты:
viewс уже рассчитанным кодом первого символа;CHECK, если нужно запретить неправильные значения;Например:
CREATE VIEW users_with_first_code AS SELECT id, name, ascii(upper(left(name, 1))) AS first_code FROM users;Теперь аналитик или разработчик может использовать готовое поле:
SELECT id, name FROM users_with_first_code WHERE first_code BETWEEN ascii('A') AND ascii('M');Так запросы становятся чище, а правило — заметнее и безопаснее.
Главное про ASCII и CHR
ascii(text)в PostgreSQL возвращает код первого символа строки. Если строка пустая, результатом будет 0. Остальная часть строки не учитывается.chr(integer)возвращает символ по числовому коду. В PostgreSQL с UTF-8 это работает не только для базового ASCII, но и для Unicode-символов.Связка
chr(ascii(x))в PostgreSQL с UTF-8 работает как понятное преобразование туда и обратно: символ → код → символ.Самые частые практические сценарии:
chr(10);chr(9);Главная ловушка — переносимость. В PostgreSQL
asciiработает с кодовой точкой Unicode, а в MySQLASCII()смотрит на первый байт. Поэтому запросы с такими функциями нельзя бездумно переносить между PostgreSQL, MySQL и ClickHouse.И главный практический вывод:
asciiиchr— это не функции «для таблицы символов из учебника». Это полезные инструменты для диагностики строк, генерации служебного текста и аккуратной работы с невидимыми символами в реальных данных.