Типы данных и приведение типов
Как устроены типы данных в PostgreSQL и как переводить один тип в другой
Теория
Например, '2024-06-01' в типе DATE и '2024-06-01' в типе TEXT выглядят похоже только для нас, но для СУБД это разные значения. В первом случае это дата, во втором — просто строка. Для базы данных это две разные сущности, и обращаться с ними она будет по-разному.
Приведение типов - это операция, при помощи которой можно перевести (конвертировать) значение из одного типа в другой. То есть присвоить значению тот тип, в котором мы хотим использовать его дальше.
Это нужно в ситуациях, когда необходимо:
• текст превратить в число, чтобы считать;
• текст превратить в дату, чтобы сравнивать даты;
• дату превратить в timestamp, чтобы прибавить часы или интервал;
• число превратить в текст, если дальше его нужно обрабатывать как строку;
• значение перевести в numeric, если важна точность и предсказуемое округление.
Чаще всего в этой теме мы будем обсуждать типы:
• integer — целое число;
• numeric — точное дробное число;
• text — текст;
• date — только дата;
• timestamp — дата и время;
• boolean — логическое значение true или false.
В PostgreSQL есть две основные формы записи для преобразования типов:
• Функция CAST(value AS type);
• И оператор value::type.
Обе делают одно и то же. Разница только в том, как это записано. CAST() выглядит более универсально и чуть более официально, а запись через :: короче и в PostgreSQL встречается буквально на каждом шагу.
Главная мысль: тип данных — это не скучная техническая приписка, а часть смысла запроса. Как только это становится понятно, многие ошибки перестают быть загадкой.
Синтаксис
SELECT CAST(value AS numeric),
value::numeric,
some_timestamp::date,
some_number::text
FROM table_name;
SELECT CAST('2024-06-01' AS date),
'2024-06-01 12:30:00'::timestamp;
SQL
Короткая шпаргалка. Она нужна не для зубрёжки, а чтобы глазами быстро уловить, что во что обычно переводят.
| Пример | Результат | Что делает |
|---|---|---|
| '42'::integer | 42 | Превращает текст в целое число. |
| CAST('12.675' AS numeric) | 12.675 | Превращает текст в точное дробное число. |
| order_date::date | Только дата | Убирает время у значения timestamp. |
| registration_date::timestamp | Дата + 00:00:00 | Превращает дату в дату со временем. |
| price::text | Текст с числом | Позволяет использовать число как текст. |
| CAST('2024-12-31' AS date) | 31.12.2024 | Превращает текст в дату. |
Разложим механику по полочкам.
1. После значения мы указываем тип, в который хотим его перевести
Если вы пишете value::numeric, это читается так: возьми value и считай его значением типа numeric. Если вы пишете CAST(value AS numeric), смысл абсолютно тот же. Разница только во внешнем виде записи.
2. Приведение типа почти всегда делается не само по себе, а ради следующего шага
Обычно мы переводим значение не потому, что нам просто захотелось, а потому что дальше какая-то функция, операция или выражение ждёт другой тип. Например, LEFT() ждёт внутри значение текстового типа, а двухаргументный ROUND() в практических сценариях часто используют вместе с numeric.
3. Не каждое значение можно честно перевести в любой тип
Если в текстовом типе находится число, его можно превратить в integer или numeric. Но если в текстовом типе находится что-то вроде 'abc', никакого волшебства не произойдёт. PostgreSQL не будет гадать, что вы имели в виду, а просто вернёт ошибку, потому что 'abc'нельзя перевести в число.
4. При переводе date в timestamp PostgreSQL добавляет время 00:00:00
Это важный момент. База данных не выдумывает случайные часы и минуты, а честно ставит начало суток. Поэтому после такого преобразования можно прибавлять интервалы, часы и другие куски времени.
5. Перевод в text удобен для вывода, но с ним легко поторопиться
Как только число или дата стали текстом, дальше база данных уже смотрит на них как на строку символов. Это удобно, если нужно склеить, обрезать или красиво показать значение. Но для арифметики и нормальной работы с датами это плохая почва. Поэтому обычно сначала считают, сравнивают и фильтруют, а уже потом переводят результат в текст, если это действительно нужно.
Примеры
1. Берём только дату из поля timestamp
🔄 Попробуйте изменить запрос:
- • Добавьте в SELECT ещё и order_date::date - '2024-01-01'::date
- • Оставьте только заказы со статусом 'доставлен'
2. Превращаем дату в timestamp
🔄 Попробуйте изменить запрос:
- • Прибавьте к registration_date::timestamp интервал INTERVAL '12 hours' и посмотрите, как появится не просто дата, а конкретный момент дня
- • Попробуйте вместо INTERVAL '12 hours' прибавить INTERVAL '2 days'
3. Сначала задаём тип литералу, а потом уже что-то с ним делаем
🔄 Попробуйте изменить запрос:
- • Замените '12500.75' на '12 500.75' и посмотрите, почему красивая для человека запись не всегда нравится PostgreSQL
- • Замените INTERVAL '1 month' на INTERVAL '10 days' и сравните результат
4. Показываем случай, где numeric действительно нужен
🔄 Попробуйте изменить запрос:
- • Уберите последний ::numeric внутри ROUND() и посмотрите, какую ошибку вернёт PostgreSQL
- • Замените 2 на 1 и сравните, как меняется округление
5. Переводим число в текст, когда хотим обработать его как строку
🔄 Попробуйте изменить запрос:
- • Уберите ::text и посмотрите, почему LEFT перестанет понимать price
- • Замените LEFT на RIGHT и сравните результат
Типичные ошибки
Думают, что значение и его тип — это мелочь
На самом деле это часть логики запроса. Текст '42' и число 42 — не одно и то же. То же самое относится к дате и тексту, который просто похож на дату.
Путают CAST() и :: как будто это разные операции
Нет. CAST(value AS type) и value::type делают одно и то же. Разница только в записи.
Пытаются превратить в число или дату то, что туда честно не переводится
Например, 'abc'::integer или CAST('не дата' AS date) приведут к ошибке. Приведение типа не исправляет плохое значение, а только меняет его тип, если это вообще возможно.
Забывают, что при переводе date в timestamp появляется время 00:00:00
Это нормально. PostgreSQL не выдумывает случайное время, а просто добавляет начало суток.
Слишком рано переводят число или дату в text
Для вывода это удобно, но для вычислений уже плохо. Обычно сначала делают расчёты, фильтрацию и сравнение, а уже потом, если нужно, переводят результат в текст.
Не дают понятный псевдоним вычисляемому столбцу после приведения типа
Записи вроде order_date::date или CAST(price AS text) лучше сразу называть через AS: например order_day или price_text. Так результат читать заметно легче.
Практика
Проверь себя
Ответьте на вопросы, чтобы закрепить материал: