sqlpostgresqlto_hexbitwise

to_hex in PostgreSQL: Convert an Integer to a Hexadecimal String

How to_hex(int) turns a number into a hex string for colors, bit masks and flag debugging, how to reverse it, and how MySQL and ClickHouse differ.

3 min čitanjaReferencesql · postgresql · to_hex · bitwise · mysql · clickhouse
Ovaj članak trenutno je na ruskom — engleski prijevod je u izradi.

Функция to_hex(int) в PostgreSQL берёт целое число и возвращает его шестнадцатеричное представление строкой в нижнем регистре, без префикса 0x. Применяют её там, где число по смыслу — это набор байтов или битов: цвет, маска прав доступа, упакованные флаги, бинарный идентификатор или хеш. В таком виде значение читается ближе к формату протокола, дампа или отладочного вывода, чем десятичное. Ниже — практические сценарии to_hex и то, как сделать обратное преобразование hex обратно в число.

Важно сразу очертить границы функции. to_hex принимает только integer или bigint и ничего больше — ни numeric, ни text, ни bytea. Для отрицательных значений она не ставит минус, а отдаёт hex дополнительного кода (two's complement), причём его ширина зависит от типа входа: 32-битного или 64-битного. Эти два свойства — допустимые типы и поведение со знаком — определяют почти все подводные камни, которые встретятся дальше, поэтому держите их в голове при каждом вызове.

Базовый to_hex

Функция принимает integer или bigint и возвращает text:

SELECT to_hex(255);        -- 'ff'
SELECT to_hex(4096);       -- '1000'
SELECT to_hex(16777215);   -- 'ffffff'

Полезные детали:

  • Результат всегда в нижнем регистре и без ведущих нулей.
  • Префикса 0x нет — если он нужен для вывода, добавьте его сами: '0x' || to_hex(255).
  • Для отрицательных чисел PostgreSQL отдаёт hex дополнительного кода (two's complement), а не минус: to_hex(-1) даёт 'ffffffff' для 32-битного входа.

Если нужна фиксированная ширина (например, два символа на байт), дополните строку слева через lpad:

-- Always two hex digits, e.g. for a single byte channel
SELECT lpad(to_hex(15), 2, '0');   -- '0f'

Цвета и битовые маски

Классический случай — собрать hex-цвет из трёх каналов RGB. Допустим, в таблице пользователей мы храним цвет аватара как целое число:

SELECT id,
       '#' || lpad(to_hex(theme_color), 6, '0') AS css_color
FROM users
WHERE country = 'DE';

Так число 16711680 превращается в #ff0000 — чистый красный. lpad до шести символов гарантирует корректный CSS, даже когда старшие байты нулевые.

Второй частый сценарий — флаги прав, упакованные в одно число через побитовое ИЛИ. Hex делает такие маски читаемыми:

-- 1=read, 2=write, 4=admin packed into orders.status as a bitmask
SELECT id,
       status,
       to_hex(status)        AS status_hex,
       (status & 4) <> 0     AS is_admin
FROM orders
WHERE (status & 4) <> 0;

Отладка значений флагов

Когда непонятно, какие биты выставлены, выводите число сразу в трёх формах — десятичной, hex и двоичной:

SELECT salary,
       to_hex(salary::int)              AS hex,
       (salary::int)::bit(32)           AS bits
FROM employees
WHERE dept = 'engineering';

Ловушка: to_hex работает только с целыми типами. Если передать numeric или text, получите ошибку — приводите вход явно: to_hex(salary::int). И помните про знак: для bigint дополнительный код шире, поэтому to_hex(-1::bigint) вернёт 'ffffffffffffffff', а не 'ffffffff'.

Обратное преобразование hex в число

Прямой функции from_hex(int) в PostgreSQL нет, но есть аккуратный трюк через тип bit: префикс x перед hex-литералом задаёт битовую строку, которую можно привести к целому.

-- Parse hex text back into an integer
SELECT ('x' || lpad('ff', 8, '0'))::bit(32)::int;    -- 255
SELECT ('x' || lpad('1000', 8, '0'))::bit(32)::int;  -- 4096

lpad до восьми символов важен: bit(32) требует ровно 32 бита, то есть 8 hex-цифр. Для bigint берите bit(64) и lpad(..., 16, '0'). Полный цикл туда-обратно:

-- Round trip: int -> hex -> int
SELECT ('x' || lpad(to_hex(48879), 8, '0'))::bit(32)::int;  -- 48879

bytea и аналоги в MySQL

Для двоичных данных (bytea), а не одиночного числа, используйте encode и decode с форматом hex:

-- Bytes to hex text and back
SELECT encode('PG'::bytea, 'hex');      -- '5047'
SELECT decode('5047', 'hex');           -- bytea \x5047

encode(..., 'hex') идеален для хешей и сырых байтов; to_hex — для чисел.

В MySQL картина другая:

  • HEX(255) возвращает 'FF' (в верхнем регистре!) и работает и с числами, и со строками.
  • Обратно — CONV('ff', 16, 10) переводит hex в десятичное, или UNHEX('5047') для байтов.
  • CONV(255, 10, 16) — гибкая замена с любым основанием.
-- MySQL flavor
SELECT HEX(255),          -- 'FF'
       CONV('ff', 16, 10),-- '255'
       LPAD(HEX(15), 2, '0');  -- '0F'

ClickHouse даёт hex(255) (тоже верхний регистр) и unhex('FF') для обратного хода. При сравнении hex-строк между движками учитывайте регистр: to_hex в PostgreSQL отдаёт нижний, а HEX в MySQL и hex в ClickHouse — верхний, поэтому одинаковые по смыслу значения не совпадут при прямом сравнении. Если данные собираются из нескольких источников, приводите обе стороны к одному регистру через lower(...). Ещё одно отличие — сигнатура: PostgreSQL to_hex берёт только число, тогда как CONV в MySQL требует исходное и целевое основание, и их легко перепутать местами.

Итог по выбору функции прост: to_hex — для целых чисел, encode/decode с форматом 'hex' — для байтов и хешей, а обратное преобразование делается через тип bit в PostgreSQL и через CONV/UNHEX в MySQL. Перед тем как полагаться на hex-вывод, проверьте три вещи, на которых чаще всего ломается перенос: знаковые числа и ширину дополнительного кода (bit(32) против bit(64)), наличие или отсутствие префикса 0x и ведущих нулей, а также регистр между движками. Прогоните отдельный пример с отрицательным числом и с граничным значением вроде to_hex(2147483647), потому что именно на знаке и ширине типа to_hex ведёт себя иначе, чем кажется по to_hex(255). Если зафиксировать эти три параметра, hex-строки останутся стабильными между запросами, экспортом и сравнениями.

Vježbaj na stvarnim zadacima

Rješavaj zadatke u SQL treneru uz trenutno ocjenjivanje i savjete.

Otvori trener