to_hex — это функция PostgreSQL, которая берёт целое число и возвращает его шестнадцатеричное представление строкой.
Например:
SELECT to_hex(255) AS result;
Результат:
Шестнадцатеричный формат часто называют просто hex. Он использует цифры от 0 до 9 и буквы от a до f.
В обычной десятичной системе после 9 идёт 10.
В шестнадцатеричной после 9 идут a, b, c, d, e, f, а уже потом 10.
Такой формат удобен там, где число по смыслу похоже не просто на «количество», а на набор байтов или битов:
- цвет в формате
RGB;
- маска прав доступа;
- набор флагов;
- технический идентификатор;
- дамп значения для отладки;
- часть протокола или бинарного формата.
Например, число 16711680 в обычном виде выглядит не очень понятно. А в hex это ff0000, то есть красный цвет в формате RGB.
SELECT to_hex(16711680) AS color_hex;
Результат:
to_hex не делает число «лучше» и не меняет его смысл. Он просто показывает то же значение в другой системе счисления — часто более удобной для программиста.
Базовый синтаксис
В PostgreSQL функция принимает целое число и возвращает текст:
SELECT to_hex(255) AS result;
Ещё несколько примеров:
SELECT to_hex(4096) AS result;
Результат:
SELECT to_hex(16777215) AS result;
Результат:
Важные свойства результата:
- буквы возвращаются в нижнем регистре;
- префикса
0x нет;
- ведущие нули не добавляются;
- результат имеет тип
text.
То есть PostgreSQL вернёт ff, а не 0xff, не FF и не 000000ff.
Если нужен префикс 0x, добавьте его сами:
SELECT '0x' || to_hex(255) AS result;
Результат:
Какие типы принимает to_hex
to_hex работает с целыми типами:
Простые числовые литералы обычно подходят:
SELECT to_hex(255) AS result;
Но если значение имеет тип numeric, PostgreSQL не будет автоматически угадывать, как его правильно превратить в hex.
Например, так можно получить ошибку:
SELECT to_hex(255.0);
Лучше привести значение явно:
SELECT to_hex(255.0::int) AS result;
То же самое касается значений из колонок.
Допустим, зарплата хранится как numeric:
SELECT
id,
to_hex(salary::int) AS salary_hex
FROM employees;
Но здесь нужно понимать смысл: приведение numeric к int может округлить или обрезать значение в зависимости от ситуации и привести к ошибке, если число не помещается в integer.
Поэтому to_hex лучше применять там, где исходное значение действительно целое по смыслу.
Фиксированная ширина через lpad
to_hex не добавляет ведущие нули.
SELECT to_hex(15) AS result;
Результат:
Но для байта обычно хотят видеть две hex-цифры: 0f.
Для этого удобно использовать lpad.
SELECT lpad(to_hex(15), 2, '0') AS result;
Результат:
lpad дополняет строку слева до нужной длины.
Шаблон такой:
SELECT lpad(to_hex(value), width, '0') AS hex_value
FROM some_table;
Где width — нужная длина hex-строки.
Например:
2 символа — один байт;
6 символов — цвет RGB;
8 символов — 32-битное значение;
16 символов — 64-битное значение.
Пример: цвет в формате RGB
Один из самых понятных примеров — цвет.
В вебе цвет часто записывают так:
#ff0000
Это красный цвет:
ff — красный канал;
00 — зелёный канал;
00 — синий канал.
Если цвет хранится в базе как целое число, его можно красиво вывести через to_hex.
SELECT
id,
'#' || lpad(to_hex(theme_color), 6, '0') AS css_color
FROM users;
Если theme_color равен 16711680, результат будет:
Почему нужен lpad до 6 символов?
Потому что для RGB нужно ровно шесть hex-цифр: по две на каждый канал.
Например, число 255 — это синий цвет #0000ff, если воспринимать число как RGB.
Без lpad получится просто ff, и это уже не полноценный CSS-цвет.
SELECT
'#' || lpad(to_hex(255), 6, '0') AS css_color;
Результат:
Пример: маска прав доступа
Ещё один частый случай — флаги, упакованные в одно число.
Допустим, права пользователя хранятся битами:
| Бит |
Значение |
Смысл |
| 0 |
1 |
чтение |
| 1 |
2 |
запись |
| 2 |
4 |
админ |
Если у пользователя есть чтение и админ-доступ, число будет 5, потому что 1 + 4 = 5.
В десятичном виде 5 читается нормально. Но когда флагов много, hex часто удобнее.
SELECT
id,
permissions,
to_hex(permissions) AS permissions_hex
FROM users;
Для проверки конкретного бита используют побитовое И.
SELECT
id,
permissions,
to_hex(permissions) AS permissions_hex,
(permissions & 4) <> 0 AS has_admin
FROM users
WHERE (permissions & 4) <> 0;
Здесь выражение:
(permissions & 4) <> 0
проверяет, включён ли бит со значением 4.
Если он включён, пользователь имеет админский флаг.
Почему hex удобен для флагов
Допустим, есть число 255.
В десятичном виде это просто:
255
А в hex:
ff
Если дополнить до 8 символов:
SELECT lpad(to_hex(255), 8, '0') AS flags_hex;
Получится:
Для программиста это сразу выглядит как младший байт, где выставлены все 8 бит.
Если число большое, hex-вид часто читается легче, чем длинная десятичная запись.
SELECT
305419896 AS value_dec,
to_hex(305419896) AS value_hex;
Результат:
| value_dec |
value_hex |
| 305419896 |
12345678 |
Hex-значение 12345678 хорошо показывает структуру байтов. Десятичное 305419896 такой структуры не показывает.
Отладка: вывести число в decimal, hex и bit
Когда вы разбираетесь с флагами, удобно вывести значение сразу в нескольких видах.
SELECT
id,
permissions AS value_dec,
to_hex(permissions) AS value_hex,
permissions::bit(32) AS value_bits
FROM users;
Так можно увидеть:
- исходное десятичное число;
- hex-представление;
- двоичное представление.
Например:
| value_dec |
value_hex |
value_bits |
| 5 |
5 |
00000000000000000000000000000101 |
В двоичном виде хорошо видно, какие именно биты включены.
Если колонка имеет тип bigint, используйте ширину 64.
SELECT
id,
flags AS value_dec,
to_hex(flags) AS value_hex,
flags::bit(64) AS value_bits
FROM events;
Отрицательные числа: главная ловушка
С положительными числами всё выглядит просто:
SELECT to_hex(255) AS result;
Результат:
Но с отрицательными числами поведение может удивить.
SELECT to_hex(-1) AS result;
Результат:
PostgreSQL не возвращает -1 в hex-виде. Он показывает представление числа в дополнительном коде.
Для начинающего это можно объяснить так: на уровне битов отрицательные целые числа хранятся не как «минус и число», а как специальный битовый шаблон. Для -1 в 32-битном целом это все единицы:
11111111111111111111111111111111
В hex это:
ffffffff
Если явно использовать bigint, ширина будет уже 64 бита:
SELECT to_hex(-1::bigint) AS result;
Результат:
То есть результат зависит от типа входного значения:
integer — 32 бита;
bigint — 64 бита.
Это особенно важно, если вы сравниваете hex-строки, делаете экспорт или разбираете бинарный формат.
Почему ширина типа важна
Посмотрим на одно и то же значение -1 в двух типах:
SELECT
to_hex(-1::int) AS int_hex,
to_hex(-1::bigint) AS bigint_hex;
Результат:
| int_hex |
bigint_hex |
ffffffff |
ffffffffffffffff |
По смыслу оба значения — -1.
Но hex-строки разные, потому что разная ширина представления.
Поэтому в технических форматах важно заранее договориться:
- это 32-битное значение или 64-битное;
- нужны ли ведущие нули;
- допускаются ли отрицательные числа;
- нужно ли показывать префикс
0x;
- в каком регистре хранить hex-строки.
Если это не зафиксировать, одинаковые по смыслу значения могут выглядеть по-разному.
Граничные значения
Полезно посмотреть на максимальное значение integer.
SELECT to_hex(2147483647) AS max_int_hex;
Результат:
А вот следующее значение уже не помещается в integer, но помещается в bigint.
SELECT to_hex(2147483648::bigint) AS value_hex;
Результат:
Это важный момент: большие числа лучше явно приводить к bigint, чтобы не упереться в диапазон integer.
Обратное преобразование: hex обратно в число
В PostgreSQL нет простой встроенной функции вида from_hex.
Но hex-строку можно превратить обратно в число через битовую строку.
Для 32-битного числа удобно использовать такой приём:
SELECT ('x' || lpad('ff', 8, '0'))::bit(32)::int AS result;
Результат:
Почему lpad до 8 символов?
Потому что 32 бита — это 4 байта, а каждый байт записывается двумя hex-цифрами. Значит, нужно 8 hex-символов.
Ещё пример:
SELECT ('x' || lpad('1000', 8, '0'))::bit(32)::int AS result;
Результат:
Полный путь туда-обратно:
SELECT ('x' || lpad(to_hex(48879), 8, '0'))::bit(32)::int AS result;
Результат:
Для bigint используйте 64 бита и 16 hex-символов:
SELECT ('x' || lpad('ff', 16, '0'))::bit(64)::bigint AS result;
Результат:
Осторожно с обратным преобразованием отрицательных чисел
С положительными значениями всё спокойно.
Но если hex-строка описывает число в дополнительном коде, результат может стать отрицательным.
Например:
SELECT ('x' || 'ffffffff')::bit(32)::int AS result;
Результат:
Это логично: ffffffff в 32-битном знаковом представлении — это -1.
А вот если взять 64 бита:
SELECT ('x' || 'ffffffffffffffff')::bit(64)::bigint AS result;
Результат:
Поэтому при обратном преобразовании всегда задавайте правильную ширину: bit(32) или bit(64).
bytea: hex для байтов, а не для чисел
to_hex подходит для целых чисел.
Но если у вас настоящие байты, например bytea, хеш или бинарные данные, используйте другие функции:
Превратить байты в hex-строку:
SELECT encode('PG'::bytea, 'hex') AS result;
Результат:
Превратить hex-строку обратно в байты:
SELECT decode('5047', 'hex') AS result;
Результат будет значением типа bytea.
То есть правило такое:
to_hex — для чисел.
encode и decode — для байтов.
Не путайте эти задачи.
Если вы храните хеш, бинарный ключ или сырой пакет данных, это не to_hex.
Если у вас целое число, которое удобно показать в шестнадцатеричном виде, это как раз to_hex.
Пример: хранить и показывать бинарный хеш
Допустим, в таблице есть колонка с хешем типа bytea.
CREATE TABLE files (
id bigint,
hash_value bytea
);
Чтобы показать хеш в читаемом hex-виде:
SELECT
id,
encode(hash_value, 'hex') AS hash_hex
FROM files;
А если hex пришёл из внешней системы и его нужно сохранить как байты:
INSERT INTO files (id, hash_value)
VALUES (1, decode('5047', 'hex'));
Здесь to_hex вообще не нужен, потому что мы работаем не с числом, а с массивом байтов.
to_hex и индексы
Если вы фильтруете по исходному числу, используйте число.
SELECT
id,
flags
FROM events
WHERE flags = 255;
Так обычный индекс по колонке flags может помочь.
Но если вы фильтруете по результату функции:
SELECT
id,
flags
FROM events
WHERE to_hex(flags) = 'ff';
обычный индекс по flags может не подойти, потому что условие построено уже не по числу, а по выражению.
Если такой поиск действительно нужен часто, можно создать функциональный индекс:
CREATE INDEX events_flags_hex_idx
ON events (to_hex(flags));
Но чаще правильнее сравнивать сами числа, а to_hex использовать только для вывода.
SELECT
id,
flags,
to_hex(flags) AS flags_hex
FROM events
WHERE flags = 255;
Так запрос остаётся понятным и обычно лучше дружит с индексами.
Отличия от MySQL
В MySQL есть функция HEX.
Она похожа по идее, но ведёт себя иначе.
SELECT HEX(255) AS result;
Результат в MySQL:
Главное отличие: MySQL возвращает буквы в верхнем регистре.
В PostgreSQL:
SELECT to_hex(255) AS result;
Результат:
Если вы сравниваете hex-строки из разных систем, приводите регистр явно:
SELECT lower(hex_value) AS hex_value_normalized
FROM imported_values;
Для обратного преобразования в MySQL часто используют CONV.
SELECT CONV('ff', 16, 10) AS result;
Результат:
Для байтов в MySQL есть UNHEX.
SELECT UNHEX('5047') AS result;
Ещё одна особенность MySQL: HEX может работать не только с числами, но и со строками. В PostgreSQL to_hex так не работает: для байтов нужен encode.
Отличия от ClickHouse
В ClickHouse есть функция hex.
SELECT hex(255) AS result;
Она обычно возвращает hex в верхнем регистре.
Для обратного преобразования байтов используется unhex.
SELECT unhex('FF') AS result;
Если данные собираются из PostgreSQL, MySQL и ClickHouse, заранее приведите формат к одному виду:
- нижний или верхний регистр;
- с префиксом
0x или без;
- с ведущими нулями или без;
- фиксированная ширина или короткая запись.
Иначе одинаковые значения могут не совпасть как строки.
Например:
| Источник |
Значение |
| PostgreSQL |
ff |
| MySQL |
FF |
| ClickHouse |
FF |
По смыслу это одно и то же число. Но строковое сравнение без нормализации может сказать, что значения разные.
Что зафиксировать перед экспортом hex
Если hex-значение уходит во внешний файл, API, отчёт или другую базу, лучше заранее записать правила формата.
Например:
- используется нижний регистр;
- префикса
0x нет;
- цвет всегда занимает 6 символов;
- 32-битные флаги всегда занимают 8 символов;
- 64-битные флаги всегда занимают 16 символов;
- отрицательные числа запрещены или описаны отдельно;
- bytea-значения кодируются через
encode;
- числа кодируются через
to_hex.
Иначе через пару месяцев может появиться набор значений вроде:
ff
FF
0xff
000000ff
Все они могут означать одно и то же число 255, но для строкового сравнения это четыре разные строки.
Частые ошибки
Первая ошибка — ждать префикс 0x.
SELECT to_hex(255) AS result;
PostgreSQL вернёт ff, а не 0xff.
Если префикс нужен:
SELECT '0x' || to_hex(255) AS result;
Вторая ошибка — забыть ведущие нули.
SELECT '#' || to_hex(255) AS css_color;
Результат будет #ff, а не #0000ff.
Правильно:
SELECT '#' || lpad(to_hex(255), 6, '0') AS css_color;
Третья ошибка — использовать to_hex для bytea.
SELECT to_hex(hash_value)
FROM files;
Если hash_value имеет тип bytea, так делать не нужно. Используйте encode.
SELECT encode(hash_value, 'hex') AS hash_hex
FROM files;
Четвёртая ошибка — не учитывать отрицательные числа.
SELECT to_hex(-1) AS result;
Результат будет ffffffff, а не -1.
Пятая ошибка — сравнивать hex-строки из разных СУБД без нормализации регистра.
SELECT lower(hex_value) AS hex_value_normalized
FROM imported_values;
Практические шаблоны
Число в hex:
SELECT to_hex(value) AS value_hex
FROM numbers;
Hex с префиксом:
SELECT '0x' || to_hex(value) AS value_hex
FROM numbers;
Один байт, всегда два символа:
SELECT lpad(to_hex(value), 2, '0') AS byte_hex
FROM numbers;
Цвет RGB, всегда шесть символов:
SELECT '#' || lpad(to_hex(theme_color), 6, '0') AS css_color
FROM users;
32-битное значение, всегда восемь символов:
SELECT lpad(to_hex(flags), 8, '0') AS flags_hex
FROM events;
64-битное значение, всегда шестнадцать символов:
SELECT lpad(to_hex(flags::bigint), 16, '0') AS flags_hex
FROM events;
Hex обратно в int:
SELECT ('x' || lpad('ff', 8, '0'))::bit(32)::int AS value_int;
Hex обратно в bigint:
SELECT ('x' || lpad('ff', 16, '0'))::bit(64)::bigint AS value_bigint;
Байты в hex:
SELECT encode(data_bytes, 'hex') AS data_hex
FROM files;
Hex обратно в байты:
SELECT decode('5047', 'hex') AS data_bytes;
Главное
to_hex в PostgreSQL переводит целое число в hex-строку.
SELECT to_hex(255) AS result;
Результат:
Функция принимает integer и bigint.
Результат возвращается в нижнем регистре, без префикса 0x и без ведущих нулей.
Если нужна фиксированная ширина, используйте lpad.
SELECT lpad(to_hex(15), 2, '0') AS result;
Для цветов удобно делать так:
SELECT '#' || lpad(to_hex(theme_color), 6, '0') AS css_color
FROM users;
Для отрицательных чисел to_hex показывает представление в дополнительном коде. Поэтому to_hex(-1::int) вернёт ffffffff, а to_hex(-1::bigint) — ffffffffffffffff.
Для байтов, хешей и bytea используйте не to_hex, а encode и decode.
SELECT encode('PG'::bytea, 'hex') AS result;
В MySQL и ClickHouse hex-функции обычно возвращают буквы в верхнем регистре, а PostgreSQL to_hex — в нижнем. При переносе данных нормализуйте регистр через lower или upper.
Перед экспортом hex-значений всегда фиксируйте формат: регистр, ширину, ведущие нули, префикс 0x, тип числа и поведение отрицательных значений. Именно на этих мелочах чаще всего ломаются сравнения, дампы и интеграции.
to_hex— это функция PostgreSQL, которая берёт целое число и возвращает его шестнадцатеричное представление строкой.Например:
SELECT to_hex(255) AS result;Результат:
ffШестнадцатеричный формат часто называют просто
hex. Он использует цифры от0до9и буквы отaдоf.В обычной десятичной системе после
9идёт10.В шестнадцатеричной после
9идутa,b,c,d,e,f, а уже потом10.Такой формат удобен там, где число по смыслу похоже не просто на «количество», а на набор байтов или битов:
RGB;Например, число
16711680в обычном виде выглядит не очень понятно. А в hex этоff0000, то есть красный цвет в форматеRGB.SELECT to_hex(16711680) AS color_hex;Результат:
ff0000to_hexне делает число «лучше» и не меняет его смысл. Он просто показывает то же значение в другой системе счисления — часто более удобной для программиста.Базовый синтаксис
В PostgreSQL функция принимает целое число и возвращает текст:
SELECT to_hex(255) AS result;Ещё несколько примеров:
SELECT to_hex(4096) AS result;Результат:
1000SELECT to_hex(16777215) AS result;Результат:
ffffffВажные свойства результата:
0xнет;text.То есть PostgreSQL вернёт
ff, а не0xff, неFFи не000000ff.Если нужен префикс
0x, добавьте его сами:SELECT '0x' || to_hex(255) AS result;Результат:
0xffКакие типы принимает to_hex
to_hexработает с целыми типами:integer;bigint.Простые числовые литералы обычно подходят:
SELECT to_hex(255) AS result;Но если значение имеет тип
numeric, PostgreSQL не будет автоматически угадывать, как его правильно превратить в hex.Например, так можно получить ошибку:
SELECT to_hex(255.0);Лучше привести значение явно:
SELECT to_hex(255.0::int) AS result;То же самое касается значений из колонок.
Допустим, зарплата хранится как
numeric:SELECT id, to_hex(salary::int) AS salary_hex FROM employees;Но здесь нужно понимать смысл: приведение
numericкintможет округлить или обрезать значение в зависимости от ситуации и привести к ошибке, если число не помещается вinteger.Поэтому
to_hexлучше применять там, где исходное значение действительно целое по смыслу.Фиксированная ширина через lpad
to_hexне добавляет ведущие нули.SELECT to_hex(15) AS result;Результат:
fНо для байта обычно хотят видеть две hex-цифры:
0f.Для этого удобно использовать
lpad.SELECT lpad(to_hex(15), 2, '0') AS result;Результат:
0flpadдополняет строку слева до нужной длины.Шаблон такой:
SELECT lpad(to_hex(value), width, '0') AS hex_value FROM some_table;Где
width— нужная длина hex-строки.Например:
2символа — один байт;6символов — цветRGB;8символов — 32-битное значение;16символов — 64-битное значение.Пример: цвет в формате RGB
Один из самых понятных примеров — цвет.
В вебе цвет часто записывают так:
Это красный цвет:
ff— красный канал;00— зелёный канал;00— синий канал.Если цвет хранится в базе как целое число, его можно красиво вывести через
to_hex.SELECT id, '#' || lpad(to_hex(theme_color), 6, '0') AS css_color FROM users;Если
theme_colorравен16711680, результат будет:#ff0000Почему нужен
lpadдо6символов?Потому что для
RGBнужно ровно шесть hex-цифр: по две на каждый канал.Например, число
255— это синий цвет#0000ff, если воспринимать число какRGB.Без
lpadполучится простоff, и это уже не полноценный CSS-цвет.SELECT '#' || lpad(to_hex(255), 6, '0') AS css_color;Результат:
#0000ffПример: маска прав доступа
Ещё один частый случай — флаги, упакованные в одно число.
Допустим, права пользователя хранятся битами:
124Если у пользователя есть чтение и админ-доступ, число будет
5, потому что1 + 4 = 5.В десятичном виде
5читается нормально. Но когда флагов много, hex часто удобнее.SELECT id, permissions, to_hex(permissions) AS permissions_hex FROM users;Для проверки конкретного бита используют побитовое И.
SELECT id, permissions, to_hex(permissions) AS permissions_hex, (permissions & 4) <> 0 AS has_admin FROM users WHERE (permissions & 4) <> 0;Здесь выражение:
(permissions & 4) <> 0проверяет, включён ли бит со значением
4.Если он включён, пользователь имеет админский флаг.
Почему hex удобен для флагов
Допустим, есть число
255.В десятичном виде это просто:
А в hex:
Если дополнить до 8 символов:
SELECT lpad(to_hex(255), 8, '0') AS flags_hex;Получится:
000000ffДля программиста это сразу выглядит как младший байт, где выставлены все 8 бит.
Если число большое, hex-вид часто читается легче, чем длинная десятичная запись.
SELECT 305419896 AS value_dec, to_hex(305419896) AS value_hex;Результат:
12345678Hex-значение
12345678хорошо показывает структуру байтов. Десятичное305419896такой структуры не показывает.Отладка: вывести число в decimal, hex и bit
Когда вы разбираетесь с флагами, удобно вывести значение сразу в нескольких видах.
SELECT id, permissions AS value_dec, to_hex(permissions) AS value_hex, permissions::bit(32) AS value_bits FROM users;Так можно увидеть:
Например:
500000000000000000000000000000101В двоичном виде хорошо видно, какие именно биты включены.
Если колонка имеет тип
bigint, используйте ширину64.SELECT id, flags AS value_dec, to_hex(flags) AS value_hex, flags::bit(64) AS value_bits FROM events;Отрицательные числа: главная ловушка
С положительными числами всё выглядит просто:
SELECT to_hex(255) AS result;Результат:
ffНо с отрицательными числами поведение может удивить.
SELECT to_hex(-1) AS result;Результат:
ffffffffPostgreSQL не возвращает
-1в hex-виде. Он показывает представление числа в дополнительном коде.Для начинающего это можно объяснить так: на уровне битов отрицательные целые числа хранятся не как «минус и число», а как специальный битовый шаблон. Для
-1в 32-битном целом это все единицы:В hex это:
Если явно использовать
bigint, ширина будет уже 64 бита:SELECT to_hex(-1::bigint) AS result;Результат:
ffffffffffffffffТо есть результат зависит от типа входного значения:
integer— 32 бита;bigint— 64 бита.Это особенно важно, если вы сравниваете hex-строки, делаете экспорт или разбираете бинарный формат.
Почему ширина типа важна
Посмотрим на одно и то же значение
-1в двух типах:SELECT to_hex(-1::int) AS int_hex, to_hex(-1::bigint) AS bigint_hex;Результат:
ffffffffffffffffffffffffПо смыслу оба значения —
-1.Но hex-строки разные, потому что разная ширина представления.
Поэтому в технических форматах важно заранее договориться:
0x;Если это не зафиксировать, одинаковые по смыслу значения могут выглядеть по-разному.
Граничные значения
Полезно посмотреть на максимальное значение
integer.SELECT to_hex(2147483647) AS max_int_hex;Результат:
7fffffffА вот следующее значение уже не помещается в
integer, но помещается вbigint.SELECT to_hex(2147483648::bigint) AS value_hex;Результат:
80000000Это важный момент: большие числа лучше явно приводить к
bigint, чтобы не упереться в диапазонinteger.Обратное преобразование: hex обратно в число
В PostgreSQL нет простой встроенной функции вида
from_hex.Но hex-строку можно превратить обратно в число через битовую строку.
Для 32-битного числа удобно использовать такой приём:
SELECT ('x' || lpad('ff', 8, '0'))::bit(32)::int AS result;Результат:
Почему
lpadдо8символов?Потому что 32 бита — это 4 байта, а каждый байт записывается двумя hex-цифрами. Значит, нужно 8 hex-символов.
Ещё пример:
SELECT ('x' || lpad('1000', 8, '0'))::bit(32)::int AS result;Результат:
Полный путь туда-обратно:
SELECT ('x' || lpad(to_hex(48879), 8, '0'))::bit(32)::int AS result;Результат:
Для
bigintиспользуйте 64 бита и 16 hex-символов:SELECT ('x' || lpad('ff', 16, '0'))::bit(64)::bigint AS result;Результат:
Осторожно с обратным преобразованием отрицательных чисел
С положительными значениями всё спокойно.
Но если hex-строка описывает число в дополнительном коде, результат может стать отрицательным.
Например:
SELECT ('x' || 'ffffffff')::bit(32)::int AS result;Результат:
Это логично:
ffffffffв 32-битном знаковом представлении — это-1.А вот если взять 64 бита:
SELECT ('x' || 'ffffffffffffffff')::bit(64)::bigint AS result;Результат:
Поэтому при обратном преобразовании всегда задавайте правильную ширину:
bit(32)илиbit(64).bytea: hex для байтов, а не для чисел
to_hexподходит для целых чисел.Но если у вас настоящие байты, например
bytea, хеш или бинарные данные, используйте другие функции:encode;decode.Превратить байты в hex-строку:
SELECT encode('PG'::bytea, 'hex') AS result;Результат:
5047Превратить hex-строку обратно в байты:
SELECT decode('5047', 'hex') AS result;Результат будет значением типа
bytea.То есть правило такое:
Не путайте эти задачи.
Если вы храните хеш, бинарный ключ или сырой пакет данных, это не
to_hex.Если у вас целое число, которое удобно показать в шестнадцатеричном виде, это как раз
to_hex.Пример: хранить и показывать бинарный хеш
Допустим, в таблице есть колонка с хешем типа
bytea.CREATE TABLE files ( id bigint, hash_value bytea );Чтобы показать хеш в читаемом hex-виде:
SELECT id, encode(hash_value, 'hex') AS hash_hex FROM files;А если hex пришёл из внешней системы и его нужно сохранить как байты:
INSERT INTO files (id, hash_value) VALUES (1, decode('5047', 'hex'));Здесь
to_hexвообще не нужен, потому что мы работаем не с числом, а с массивом байтов.to_hex и индексы
Если вы фильтруете по исходному числу, используйте число.
SELECT id, flags FROM events WHERE flags = 255;Так обычный индекс по колонке
flagsможет помочь.Но если вы фильтруете по результату функции:
SELECT id, flags FROM events WHERE to_hex(flags) = 'ff';обычный индекс по
flagsможет не подойти, потому что условие построено уже не по числу, а по выражению.Если такой поиск действительно нужен часто, можно создать функциональный индекс:
CREATE INDEX events_flags_hex_idx ON events (to_hex(flags));Но чаще правильнее сравнивать сами числа, а
to_hexиспользовать только для вывода.SELECT id, flags, to_hex(flags) AS flags_hex FROM events WHERE flags = 255;Так запрос остаётся понятным и обычно лучше дружит с индексами.
Отличия от MySQL
В MySQL есть функция
HEX.Она похожа по идее, но ведёт себя иначе.
SELECT HEX(255) AS result;Результат в MySQL:
FFГлавное отличие: MySQL возвращает буквы в верхнем регистре.
В PostgreSQL:
SELECT to_hex(255) AS result;Результат:
ffЕсли вы сравниваете hex-строки из разных систем, приводите регистр явно:
SELECT lower(hex_value) AS hex_value_normalized FROM imported_values;Для обратного преобразования в MySQL часто используют
CONV.SELECT CONV('ff', 16, 10) AS result;Результат:
255Для байтов в MySQL есть
UNHEX.SELECT UNHEX('5047') AS result;Ещё одна особенность MySQL:
HEXможет работать не только с числами, но и со строками. В PostgreSQLto_hexтак не работает: для байтов нуженencode.Отличия от ClickHouse
В ClickHouse есть функция
hex.SELECT hex(255) AS result;Она обычно возвращает hex в верхнем регистре.
Для обратного преобразования байтов используется
unhex.SELECT unhex('FF') AS result;Если данные собираются из PostgreSQL, MySQL и ClickHouse, заранее приведите формат к одному виду:
0xили без;Иначе одинаковые значения могут не совпасть как строки.
Например:
ffFFFFПо смыслу это одно и то же число. Но строковое сравнение без нормализации может сказать, что значения разные.
Что зафиксировать перед экспортом hex
Если hex-значение уходит во внешний файл, API, отчёт или другую базу, лучше заранее записать правила формата.
Например:
0xнет;encode;to_hex.Иначе через пару месяцев может появиться набор значений вроде:
Все они могут означать одно и то же число
255, но для строкового сравнения это четыре разные строки.Частые ошибки
Первая ошибка — ждать префикс
0x.SELECT to_hex(255) AS result;PostgreSQL вернёт
ff, а не0xff.Если префикс нужен:
SELECT '0x' || to_hex(255) AS result;Вторая ошибка — забыть ведущие нули.
SELECT '#' || to_hex(255) AS css_color;Результат будет
#ff, а не#0000ff.Правильно:
SELECT '#' || lpad(to_hex(255), 6, '0') AS css_color;Третья ошибка — использовать
to_hexдляbytea.SELECT to_hex(hash_value) FROM files;Если
hash_valueимеет типbytea, так делать не нужно. Используйтеencode.SELECT encode(hash_value, 'hex') AS hash_hex FROM files;Четвёртая ошибка — не учитывать отрицательные числа.
SELECT to_hex(-1) AS result;Результат будет
ffffffff, а не-1.Пятая ошибка — сравнивать hex-строки из разных СУБД без нормализации регистра.
SELECT lower(hex_value) AS hex_value_normalized FROM imported_values;Практические шаблоны
Число в hex:
SELECT to_hex(value) AS value_hex FROM numbers;Hex с префиксом:
SELECT '0x' || to_hex(value) AS value_hex FROM numbers;Один байт, всегда два символа:
SELECT lpad(to_hex(value), 2, '0') AS byte_hex FROM numbers;Цвет
RGB, всегда шесть символов:SELECT '#' || lpad(to_hex(theme_color), 6, '0') AS css_color FROM users;32-битное значение, всегда восемь символов:
SELECT lpad(to_hex(flags), 8, '0') AS flags_hex FROM events;64-битное значение, всегда шестнадцать символов:
SELECT lpad(to_hex(flags::bigint), 16, '0') AS flags_hex FROM events;Hex обратно в
int:SELECT ('x' || lpad('ff', 8, '0'))::bit(32)::int AS value_int;Hex обратно в
bigint:SELECT ('x' || lpad('ff', 16, '0'))::bit(64)::bigint AS value_bigint;Байты в hex:
SELECT encode(data_bytes, 'hex') AS data_hex FROM files;Hex обратно в байты:
SELECT decode('5047', 'hex') AS data_bytes;Главное
to_hexв PostgreSQL переводит целое число в hex-строку.SELECT to_hex(255) AS result;Результат:
ffФункция принимает
integerиbigint.Результат возвращается в нижнем регистре, без префикса
0xи без ведущих нулей.Если нужна фиксированная ширина, используйте
lpad.SELECT lpad(to_hex(15), 2, '0') AS result;Для цветов удобно делать так:
SELECT '#' || lpad(to_hex(theme_color), 6, '0') AS css_color FROM users;Для отрицательных чисел
to_hexпоказывает представление в дополнительном коде. Поэтомуto_hex(-1::int)вернётffffffff, аto_hex(-1::bigint)—ffffffffffffffff.Для байтов, хешей и
byteaиспользуйте неto_hex, аencodeиdecode.SELECT encode('PG'::bytea, 'hex') AS result;В MySQL и ClickHouse hex-функции обычно возвращают буквы в верхнем регистре, а PostgreSQL
to_hex— в нижнем. При переносе данных нормализуйте регистр черезlowerилиupper.Перед экспортом hex-значений всегда фиксируйте формат: регистр, ширину, ведущие нули, префикс
0x, тип числа и поведение отрицательных значений. Именно на этих мелочах чаще всего ломаются сравнения, дампы и интеграции.