Система типов: примитивы, DECIMAL, TIMESTAMP, тип number
Trino понимает ANSI SQL, но он движок запросов без собственного хранилища — и это формирует его систему типов особым образом. Тип в Trino — это не описание того, как данные лежат на диске (за раскладку отвечает источник: Parquet, ORC, строки PostgreSQL). Тип в Trino — это контракт обработки в памяти: как значение представлено в Block’е, какие операции над ним определены, как оно сериализуется в exchange между воркерами.
Числовые типы SQL: INT, BIGINT, NUMERIC — почему деньги не FLOAT Текст и время: TEXT, TIMESTAMP, часовые поясаЭтот урок открывает модуль про SQL в Trino. Мы разберём примитивные типы, затем подробно — два, где скрыта вся тонкость: DECIMAL с его границей точности и TIMESTAMP с параметром precision. И отдельно — тип number, появившийся в релизе 480 как ответ на фундаментальное ограничение DECIMAL.
Примитивные типы: карта
Trino делит примитивы на несколько семейств. Зафиксируем их таблицей — это рабочая карта, к которой вы будете возвращаться.
| Семейство | Типы | Заметка |
|---|---|---|
| Логический | BOOLEAN | true / false |
| Целочисленные | TINYINT, SMALLINT, INTEGER, BIGINT | 8, 16, 32, 64 бита со знаком |
| Плавающая точка | REAL, DOUBLE | 32 и 64 бита IEEE 754 |
| Точные дробные | DECIMAL(p, s) | фиксированная точность и масштаб |
| Строки | VARCHAR(n), CHAR(n), VARBINARY | текст и двоичные данные |
| Дата и время | DATE, TIME, TIMESTAMP, с WITH TIME ZONE и без, INTERVAL | параметризуются precision |
| Прочее | UUID, IPADDRESS, JSON, HYPERLOGLOG | специализированные |
Целочисленные и плавающие типы прямолинейны и совпадают по семантике с тем, что вы знаете из других СУБД. BIGINT — рабочая лошадка для целых; INTEGER экономит память, когда диапазона хватает. DOUBLE — для научных вычислений, где приемлема погрешность представления. На двух семействах остановимся подробно, потому что именно там инженеры допускают ошибки.
DECIMAL: точность ценой границы
DECIMAL(p, s) хранит число с фиксированной точностью: p (precision) — общее число значащих десятичных цифр, s (scale) — сколько из них после запятой. DECIMAL(10, 2) — это до 10 цифр всего, 2 после запятой: максимум 99999999.99.
Зачем он нужен, если есть DOUBLE. DOUBLE — это IEEE 754, двоичная плавающая точка. Многие десятичные дроби в двоичной системе непредставимы точно — классический пример, 0.1 в DOUBLE хранится с крошечной погрешностью. Для денег это недопустимо: суммирование миллиона транзакций накопит ошибку, и баланс не сойдётся на копейки. DECIMAL хранит число как масштабированное целое и потому представляет десятичные дроби точно. Правило простое: деньги, ставки, всё, где «должно сходиться до копейки», — DECIMAL, не DOUBLE.
SELECT DECIMAL '0.1' + DECIMAL '0.2' AS dec_sum,
DOUBLE '0.1' + DOUBLE '0.2' AS dbl_sum;
dec_sum | dbl_sum
---------+------------------------
0.3 | 0.30000000000000004
Видно ровно проблему: DECIMAL даёт точное 0.3, DOUBLE — приближение с хвостом.
Но у DECIMAL есть жёсткая граница. Максимальная precision — 38 значащих цифр. Это не произвол: Trino представляет DECIMAL точностью до 18 цифр в одном 64-битном long, а от 19 до 38 — в 128-битном представлении. 38 цифр — это потолок 128-битного целого. Свыше — некуда положить.
Для подавляющего большинства задач 38 цифр с запасом хватает. Но есть области, где не хватает: научные расчёты с очень большими или очень малыми величинами, криптография, финансовая математика с длинными промежуточными произведениями, агрегаты, где промежуточный результат перерастает 38 цифр ещё до округления. Здесь DECIMAL упирается в стену, а DOUBLE неприемлем из-за погрешности.
Тип number: высокоточная десятичная арифметика
Чтобы пробить эту стену, в релизе 480 (24 марта 2026) в Trino добавили тип number. Его легко принять за «обобщённый числовой примитив» — это ошибка. number — это узко-специализированный тип: высокоточное десятичное число переменного масштаба.
Разберём по частям, чем number отличается от DECIMAL.
Высокая точность. number снимает потолок в 38 цифр. Он держит примерно до 200 значащих десятичных цифр — на порядки больше DECIMAL. Это и есть прямой ответ на «стену DECIMAL» из предыдущего раздела.
Переменный масштаб. У DECIMAL масштаб фиксирован в типе: DECIMAL(10, 2) — всегда ровно 2 знака после запятой, тип статически параметризован (p, s). У number масштаб не зашит в тип — он переменный, значение само несёт свою точность и масштаб. Не нужно заранее объявлять (p, s): результат деления или произведения сохраняет столько знаков, сколько требуется.
Опора на BigDecimal. Под капотом number реализован поверх Java-класса BigDecimal — стандартного представления произвольной точности на JVM. Отсюда и возможности, и цена: BigDecimal — это объект в куче, не примитив; арифметика над ним кратно дороже арифметики над long. number — не бесплатная замена DECIMAL, а инструмент для случаев, где точности DECIMAL физически не хватает.
Infinity и NaN. В отличие от DECIMAL, number поддерживает специальные значения — положительную и отрицательную бесконечность и NaN (not-a-number). Это сближает его модель со стандартом плавающей точки в части особых значений, но без потери точности на обычных числах: number остаётся десятичным и точным.
number — не «улучшенный универсальный числовой тип» и не замена DECIMAL по умолчанию. Это специализированный тип для высокоточной десятичной арифметики там, где 38 цифр DECIMAL физически не хватает: длинные финансовые расчёты с большими промежуточными произведениями, научные величины экстремального порядка. За точность платят: BigDecimal под капотом — объект в куче, арифметика над ним кратно медленнее, чем над DECIMAL. Для денег и обычных дробей по-прежнему берите DECIMAL; number доставайте, только когда упёрлись в его потолок.
TIMESTAMP и параметр precision
Второе место, где прячется тонкость, — типы даты и времени. Главная идея: TIMESTAMP в Trino параметризуется точностью — TIMESTAMP(p), где p — число знаков дробной части секунды.
TIMESTAMP(0)— без долей секунды.TIMESTAMP(3)— миллисекунды.TIMESTAMP(6)— микросекунды; это значение по умолчанию, когда пишут простоTIMESTAMP.TIMESTAMP(9)— наносекунды.TIMESTAMP(12)— пикосекунды; верхняя граница.
Почему это важно на практике. Точность — часть типа, и она участвует в сравнениях и в чтении из источников. Источники хранят время с разной точностью: Parquet традиционно — миллисекунды или микросекунды, Iceberg v3 ввёл наносекундные timestamp’ы. Если объявить колонку с одной точностью, а данные приходят с другой, происходит приведение, и при сужении точности — усечение долей. Понимать p нужно, чтобы не потерять разряды молча.
Ортогонально точности — наличие или отсутствие часового пояса:
| Тип | Что хранит |
|---|---|
TIMESTAMP(p) | момент без привязки к зоне — «настенное время» |
TIMESTAMP(p) WITH TIME ZONE | момент вместе с часовым поясом — однозначная точка на оси времени |
Различие принципиально. TIMESTAMP WITH TIME ZONE задаёт абсолютный момент: его можно сравнивать между поясами без двусмысленности. TIMESTAMP без зоны — это «настенное время», и 12:00 в нём не отвечает на вопрос «в каком часовом поясе». Для событийных данных в распределённой системе почти всегда нужен вариант WITH TIME ZONE.
SELECT TIMESTAMP '2026-05-20 14:30:00.123456' AS ts_micro,
TIMESTAMP '2026-05-20 14:30:00.1' AS ts_short,
TIMESTAMP '2026-05-20 14:30:00 Europe/Berlin' AS ts_with_zone;
ts_micro | ts_short | ts_with_zone
----------------------------+-------------------------+--------------------------------------
2026-05-20 14:30:00.123456 | 2026-05-20 14:30:00.100 | 2026-05-20 14:30:00 Europe/Berlin
Литерал 14:30:00.123456 дал precision 6 (микросекунды), 14:30:00.1 — precision 3 после нормализации отображения. Точность вывелась из самого литерала.
Приведение типов: CAST и его границы
Trino строг к типам: операторы и функции требуют совместимых типов на входе, и неявных приведений меньше, чем в некоторых СУБД. Явное приведение — функция CAST(value AS type). Есть и TRY_CAST — возвращает NULL вместо ошибки, если приведение невозможно.
SELECT CAST('2026-05-20' AS DATE) AS to_date,
CAST(42 AS DECIMAL(5,2)) AS to_decimal,
TRY_CAST('не число' AS INTEGER) AS bad_cast;
to_date | to_decimal | bad_cast
------------+------------+----------
2026-05-20 | 42.00 | NULL
Где CAST теряет данные. Приведение DECIMAL(10,4) к DECIMAL(10,2) усечёт два знака. Приведение TIMESTAMP(9) к TIMESTAMP(3) отбросит микро- и наносекунды. Приведение BIGINT, не влезающего в INTEGER, даст ошибку переполнения. Тип в Trino — контракт, и CAST — единственная санкционированная точка его смены; делать это нужно осознанно, понимая, теряются ли разряды.
Попробуй сам
На песочнице курса (Trino 481):
-
Выполните
SELECT DECIMAL '0.1' + DECIMAL '0.2', DOUBLE '0.1' + DOUBLE '0.2';и зафиксируйте разницу. ЗатемSELECT typeof(DECIMAL '1.5'), typeof(DOUBLE '1.5'), typeof(CURRENT_TIMESTAMP);— функцияtypeofпоказывает точный тип выражения, включая precision. Объясните, какую precision получилCURRENT_TIMESTAMPи почему. -
Подумайте над сценарием для типа
number. Финансовая модель перемножает 30 множителей подряд, каждый —DECIMAL(20,10). Прикиньте, сколько значащих цифр может набрать промежуточное произведение и почемуDECIMAL(38, s)тут переполнится. Сформулируйте в двух предложениях, почему именноnumber(а неDOUBLE) — корректный выбор: одно про точность, одно про границу 38 цифр. -
Создайте две версии одной мысли про
TIMESTAMP. ВыполнитеSELECT CAST(TIMESTAMP '2026-05-20 10:00:00.123456789' AS TIMESTAMP(3));и объясните, что произошло с наносекундами и почему это молчаливая потеря данных, а не ошибка.