sqlpostgresqlcastmysql

CAST и :: в SQL: как явно приводить типы в PostgreSQL

CAST(x AS type) и оператор :: явно приводят значение к другому типу; разбираем округление numeric, ошибки на грязных строках и отличия MySQL и ClickHouse.

11 мин чтенияСправочникsql · postgresql · cast · mysql · clickhouse · types

В SQL у каждого значения есть тип.

Число — это число. Дата — это дата. Текст — это текст. Время — это время.

Пока данные аккуратные, об этом можно почти не думать. Но в реальных проектах всё быстро становится интереснее.

Например:

  • из формы на сайте пришла строка '42', а нам нужно число 42;
  • из CSV-файла импортировали дату как текст '2026-06-17';
  • API прислал цену строкой '1999.99';
  • в отчёте нужно показать средний чек без копеек;
  • в таблице одно поле хранится как text, а сравнивать его пытаются с числом.

В такие моменты нужно явно сказать базе:

Возьми это значение и попробуй интерпретировать его как другой тип.

Для этого в SQL используется приведение типов.

В PostgreSQL чаще всего встречаются два варианта:

CAST(value AS type)

и короткая форма:

value::type

Оба варианта делают одну и ту же работу: приводят значение к нужному типу прямо в запросе.

Что такое приведение типов простыми словами

Приведение типа — это когда мы просим базу данных временно посмотреть на значение как на другой тип.

Например, у нас есть строка:

'42'

Для человека это выглядит как число. Но для базы это может быть именно текст, потому что значение заключено в кавычки.

Если мы хотим использовать его как число, можно написать:

SELECT CAST('42' AS integer);

Или в стиле PostgreSQL:

SELECT '42'::integer;

Результат:

42

Теперь база воспринимает это значение как целое число.

Важно понимать: такой CAST не меняет данные в таблице навсегда. Он работает только внутри конкретного запроса.

Например:

SELECT price_text::numeric
FROM products;

Этот запрос покажет price_text как число, но сама колонка price_text в таблице не станет numeric. Если она была текстовой, она такой и останется.

То есть CAST — это не изменение структуры таблицы. Это инструкция для текущего запроса:

В этом месте попробуй использовать значение как другой тип.

CAST и :: — в чём разница

В PostgreSQL есть два популярных способа привести тип.

Первый — стандартный SQL-вариант:

CAST(value AS type)

Например:

SELECT CAST('2026-06-17' AS date) AS signup_date;

Второй — короткая форма PostgreSQL:

value::type

То же самое можно записать так:

SELECT '2026-06-17'::date AS signup_date;

Для PostgreSQL эти два запроса делают одно и то же.

Ещё пример:

SELECT CAST('42' AS integer);

и:

SELECT '42'::integer;

Оба варианта вернут число 42.

Когда использовать CAST, а когда ::

Если вы пишете запрос, который должен быть более переносимым между разными базами данных, лучше использовать CAST.

Например:

SELECT CAST(amount AS integer) AS amount_int
FROM orders;

Такую форму проще понять не только PostgreSQL-разработчику, но и человеку, который работает с MySQL, ClickHouse или другой SQL-базой.

А вот :: — это удобное сокращение PostgreSQL.

SELECT amount::integer AS amount_int
FROM orders;

В PostgreSQL-коде оно встречается очень часто, потому что короче и читается быстро.

Можно запомнить так:

CAST(value AS type) — более универсальный вариант. value::type — короткий и привычный вариант для PostgreSQL.

Если вы учитесь SQL, полезно знать оба варианта. В рабочих проектах вы почти наверняка встретите и тот, и другой.

Простые примеры приведения типов

Текст в число

Допустим, есть строка '100', а нам нужно число.

SELECT '100'::integer AS value_int;

Результат:

value_int
---------
100

Теперь это значение можно использовать в вычислениях:

SELECT '100'::integer + 50 AS total;

Результат:

total
-----
150

Без приведения типов база могла бы воспринимать '100' именно как текст, а не как число.

Текст в дату

Допустим, дата пришла строкой:

2026-06-17

Можно привести её к типу date:

SELECT '2026-06-17'::date AS created_day;

Результат:

created_day
------------
2026-06-17

Теперь с этой датой можно нормально работать как с датой: сравнивать, прибавлять интервалы, использовать в фильтрах.

Например:

SELECT '2026-06-17'::date + INTERVAL '7 days' AS next_week;

Результат:

next_week
---------------------
2026-06-24 00:00:00

Число в текст

Иногда нужно наоборот превратить число в текст.

Например:

SELECT 123::text AS value_text;

Результат:

value_text
----------
123

Визуально результат похож, но тип уже другой. Для базы это теперь строка, а не число.

Такое может понадобиться, например, когда нужно склеить число с текстом:

SELECT 'Order #' || id::text AS order_label
FROM orders;

Если id = 15, результат будет:

Order #15

Пример из реальной жизни: импорт данных из CSV

Одна из самых частых ситуаций — импорт данных.

Допустим, из CSV-файла загрузили заказы во временную таблицу orders_raw.

В ней все поля текстовые:

id | amount_text | created_at_text
---+-------------+----------------
1  | 1500.50     | 2026-06-17
2  | 2300.00     | 2026-06-18
3  | 990.90      | 2026-06-19

Пока amount_text — это текст. created_at_text — тоже текст.

Но для нормальной работы нам нужны:

  • сумма заказа как numeric;
  • дата заказа как date.

Запрос может быть таким:

SELECT
  id::integer AS id,
  amount_text::numeric AS amount,
  created_at_text::date AS created_at
FROM orders_raw;

Результат:

id | amount  | created_at
---+---------+------------
1  | 1500.50 | 2026-06-17
2  | 2300.00 | 2026-06-18
3  | 990.90  | 2026-06-19

Теперь эти значения можно использовать в расчётах:

SELECT
  SUM(amount_text::numeric) AS total_revenue
FROM orders_raw;

Так мы превращаем грязные или сырые данные из импорта в нормальные типы, с которыми база умеет работать правильно.

Приведение типа не всегда безопасно

Очень важный момент: CAST работает только тогда, когда значение действительно можно привести к нужному типу.

Например, это сработает:

SELECT '42'::integer;

А это уже нет:

SELECT 'hello'::integer;

PostgreSQL выдаст ошибку примерно такого смысла:

invalid input syntax for type integer

Почему?

Потому что строку 'hello' невозможно превратить в целое число.

То же самое с датами.

Это сработает:

SELECT '2026-06-17'::date;

А это может упасть:

SELECT 'not a date'::date;

Когда вы приводите одно конкретное значение, ошибка очевидна. Но в таблицах опасность больше.

Представьте таблицу orders_raw:

id | amount_text
---+------------
1  | 1500
2  | 2300
3  | error
4  | 990

Запрос:

SELECT amount_text::integer
FROM orders_raw;

упадёт на строке error.

И неважно, что остальные строки хорошие. Одной плохой строки достаточно, чтобы весь запрос завершился ошибкой.

Как защититься от плохих строк

В PostgreSQL у обычного CAST нет удобной встроенной формы в стиле «попробуй привести, а если не получилось — верни NULL».

То есть в PostgreSQL нельзя просто написать универсальный безопасный CAST, который молча переживёт любые плохие данные.

Поэтому обычно используют предварительную проверку.

Например, если мы хотим привести текст к целому числу, можно сначала оставить только строки, которые похожи на число:

SELECT amount_text::integer AS amount
FROM orders_raw
WHERE amount_text ~ '^[0-9]+$';

Здесь условие:

amount_text ~ '^[0-9]+$'

означает:

значение должно состоять только из цифр от начала до конца строки.

Тогда строка '1500' пройдёт проверку, а строка 'error' — нет.

Если нужно разрешить дробные числа, можно использовать такое условие:

SELECT amount_text::numeric AS amount
FROM orders_raw
WHERE amount_text ~ '^[0-9]+(\.[0-9]+)?$';

Оно пропустит значения вроде:

1500
1500.50
99.99

Но не пропустит:

abc
12abc
hello

Для учебных задач важно запомнить сам принцип:

Сначала проверяем, что строка похожа на нужный тип. Потом приводим её через CAST или ::.

NULL и пустая строка — это разные вещи

Есть ещё одна тонкость.

NULL нормально приводится к другому типу.

Например:

SELECT NULL::integer;

Результат будет:

NULL

Ошибки не будет, потому что NULL означает отсутствие значения. База просто получает «отсутствующее целое число».

А вот пустая строка — это уже не NULL.

SELECT ''::integer;

Такой запрос упадёт с ошибкой, потому что пустую строку нельзя превратить в число.

В данных из CSV или API это встречается постоянно:

id | amount_text
---+------------
1  | 1500
2  |
3  | 990

Вторая строка может выглядеть как «пусто», но для базы это может быть именно пустая строка '', а не NULL.

Один из способов обработать это — использовать NULLIF.

SELECT NULLIF(amount_text, '')::numeric AS amount
FROM orders_raw;

NULLIF(amount_text, '') означает:

если amount_text равен пустой строке, замени его на NULL.

После этого NULL уже спокойно приводится к numeric.

Но если кроме пустых строк могут быть значения вроде 'error', всё равно нужна дополнительная проверка.

Приведение numeric к integer: важный нюанс

Иногда в отчётах нужно показать число без дробной части.

Например, средний чек получился таким:

1499.75

И хочется вывести его как целое число.

Можно написать:

SELECT CAST(1499.75 AS integer);

или:

SELECT 1499.75::integer;

В PostgreSQL при таком приведении число будет округлено до целого.

Например:

SELECT 99.99::integer AS value;

Результат:

value
-----
100

Это важный момент.

Приведение к integer — это не то же самое, что просто отбросить дробную часть.

Если вам нужно именно отбросить дробную часть, используйте trunc.

SELECT trunc(99.99)::integer AS value;

Результат:

value
-----
99

А если нужно математическое округление, можно использовать round.

SELECT round(99.99)::integer AS value;

Результат:

value
-----
100

В отчётах это важно. Например, средний чек 999.60 и 999.40 могут по-разному отображаться в зависимости от того, округляете вы значение или отбрасываете дробную часть.

Пример: средний чек по странам

Допустим, есть таблицы users и orders.

users:

id | country
---+---------
1  | Georgia
2  | Georgia
3  | Vietnam

orders:

id | user_id | amount
---+---------+--------
1  | 1       | 1500.50
2  | 2       | 2300.20
3  | 3       | 999.90

Хотим посчитать средний чек по странам и показать его как целое число.

SELECT
  u.country,
  AVG(o.amount) AS avg_amount
FROM users u
JOIN orders o ON o.user_id = u.id
GROUP BY u.country;

Результат может быть таким:

country | avg_amount
--------+-----------
Georgia | 1900.35
Vietnam | 999.90

Если нужно вывести средний чек как целое число с округлением:

SELECT
  u.country,
  ROUND(AVG(o.amount))::integer AS avg_amount
FROM users u
JOIN orders o ON o.user_id = u.id
GROUP BY u.country;

Если нужно именно отбросить дробную часть:

SELECT
  u.country,
  TRUNC(AVG(o.amount))::integer AS avg_amount
FROM users u
JOIN orders o ON o.user_id = u.id
GROUP BY u.country;

Главное — не делать это случайно. Лучше явно понимать, что нужно бизнесу: округление или отсечение дробной части.

Неявное и явное приведение типов

Иногда PostgreSQL может сам привести один тип к другому.

Например, если сравниваются совместимые числовые типы, база обычно справится сама:

SELECT *
FROM employees
WHERE salary > 50000;

Если salary хранится как numeric, а 50000 написано как целое число, PostgreSQL сможет нормально выполнить сравнение.

Но на такие автоматические преобразования не стоит слепо рассчитывать.

Например:

SELECT *
FROM orders
WHERE status = 1;

Если status — текстовая колонка, PostgreSQL не обязан угадывать, что вы хотели сравнить строку с числом. Такой запрос может завершиться ошибкой.

Правильнее писать так:

SELECT *
FROM orders
WHERE status = '1';

Или явно привести число к тексту:

SELECT *
FROM orders
WHERE status = CAST(1 AS text);

Но здесь важно задать себе вопрос: почему статус вообще сравнивается с числом? Возможно, проблема не в CAST, а в модели данных.

Если статус хранится как текст, логичнее сравнивать его с текстом:

WHERE status = 'paid'

А если статус — это числовой код, возможно, колонка должна быть числовой.

Почему PostgreSQL строже, чем MySQL

PostgreSQL обычно довольно строгий к типам. Если вы сравниваете текст с числом или пытаетесь привести плохую строку к числу, он часто сразу покажет ошибку.

Это может раздражать новичков, но на практике это плюс.

Потому что ошибка появляется сразу, а не прячется в отчёте.

В MySQL поведение часто мягче. В некоторых ситуациях MySQL может молча привести значение к другому типу. Например, строку, которая не похожа на число, он может превратить в 0 или вернуть предупреждение вместо явной ошибки.

С одной стороны, запрос «не падает». С другой стороны, данные могут исказиться незаметно.

PostgreSQL в этом смысле честнее:

если типы не совпадают или данные грязные, он заставляет разобраться с проблемой явно.

Для обучения и качественной аналитики это хороший подход.

Особенность оператора ::

У оператора :: есть нюанс: он применяется к тому выражению, которое стоит прямо перед ним.

Например:

SELECT count(*)::numeric / 100 AS ratio
FROM users;

Здесь к numeric приводится только результат count(*).

А потом уже выполняется деление на 100.

Обычно это как раз то, что нужно. Например, так часто делают, чтобы не получить целочисленное деление.

Но если вы хотите привести к типу всё выражение целиком, лучше использовать скобки.

Например:

SELECT (price * quantity)::integer AS total_int
FROM order_items;

Здесь сначала считается:

price * quantity

А потом результат приводится к integer.

Общее правило простое:

Если приводите к типу не одно значение, а выражение, используйте скобки.

Так запрос будет понятнее и для базы, и для человека, который будет читать его после вас.

Пример: доля заказов от общего количества

Допустим, нужно посчитать долю оплаченных заказов.

Плохой вариант:

SELECT
  COUNT(*) FILTER (WHERE status = 'paid') / COUNT(*) AS paid_ratio
FROM orders;

Если оба значения целые, можно получить не тот результат, который ожидали. Например, вместо 0.75 может получиться 0.

Лучше явно привести одно из чисел к numeric:

SELECT
  COUNT(*) FILTER (WHERE status = 'paid')::numeric / COUNT(*) AS paid_ratio
FROM orders;

Теперь база будет выполнять дробное деление.

Можно дополнительно округлить результат:

SELECT
  ROUND(
    COUNT(*) FILTER (WHERE status = 'paid')::numeric / COUNT(*),
    2
  ) AS paid_ratio
FROM orders;

Такой запрос вернёт долю с двумя знаками после запятой.

Например:

paid_ratio
----------
0.75

CAST в WHERE: когда нужно быть осторожным

Иногда хочется написать так:

SELECT *
FROM orders_raw
WHERE amount_text::numeric > 1000;

Если все значения в amount_text действительно числа, запрос сработает.

Но если попадётся строка 'error', запрос упадёт.

Поэтому для сырых данных лучше сначала проверить формат:

SELECT *
FROM orders_raw
WHERE amount_text ~ '^[0-9]+(\.[0-9]+)?$'
  AND amount_text::numeric > 1000;

Так мы сначала оставляем только значения, похожие на число, а потом сравниваем их как числа.

Но в идеальном мире такие преобразования лучше делать не каждый раз в отчётном запросе, а на этапе загрузки данных.

То есть:

  1. загрузили сырые данные во временную таблицу;
  2. проверили и очистили значения;
  3. переложили их в нормальную таблицу с правильными типами.

Например, сумма заказа должна храниться как numeric, а не как text.

Тогда обычный запрос будет проще:

SELECT *
FROM orders
WHERE amount > 1000;

И базе не придётся каждый раз превращать текст в число.

CAST не заменяет правильную модель данных

Приведение типов — полезный инструмент, но им не стоит лечить всё подряд.

Если в таблице дата хранится как текст, можно писать:

created_at_text::date

Но если это основная рабочая таблица, лучше задуматься: почему дата хранится не в типе date или timestamp?

Если цена хранится как текст, можно писать:

price_text::numeric

Но для нормальной таблицы цена должна быть числом.

CAST хорошо подходит для:

  • разовой обработки импорта;
  • приведения данных из внешних источников;
  • подготовки отчётов;
  • явного управления типами в выражениях;
  • исправления спорных мест в запросе.

Но если вы постоянно приводите одну и ту же колонку к одному и тому же типу, возможно, тип колонки выбран неправильно.

CAST в MySQL

В MySQL тоже есть CAST.

Пример:

SELECT CAST('42' AS SIGNED) AS value_int;

Для целых чисел в MySQL часто используют SIGNED или UNSIGNED.

SELECT CAST('42' AS SIGNED);
SELECT CAST('42' AS UNSIGNED);

Для даты:

SELECT CAST('2026-06-17' AS DATE);

Также в MySQL есть функция CONVERT.

SELECT CONVERT('2026-06-17', DATE);

Идея похожая: привести значение к нужному типу.

Но поведение MySQL при ошибках может отличаться от PostgreSQL. MySQL часто ведёт себя мягче и может вернуть 0, NULL или предупреждение там, где PostgreSQL сразу остановил бы запрос ошибкой.

Поэтому при переносе запросов между PostgreSQL и MySQL важно не просто заменить синтаксис, а проверить поведение на плохих данных.

CAST в ClickHouse

В ClickHouse тоже есть CAST.

Например:

SELECT CAST('42' AS Int32);

Также ClickHouse поддерживает короткую запись через :::

SELECT '42'::Int32;

Но в ClickHouse очень часто используют специальные функции:

SELECT toInt32('42');
SELECT toDate('2026-06-17');
SELECT toFloat64('99.99');

Для грязных данных в ClickHouse есть удобные варианты, которые не падают на плохом значении.

Например:

SELECT toInt32OrNull(amount_text)
FROM orders_raw;

Если значение можно привести к числу, функция вернёт число. Если нельзя — вернёт NULL.

Есть и вариант с нулём:

SELECT toInt32OrZero(amount_text)
FROM orders_raw;

Если привести значение не получилось, будет 0.

Но с OrZero нужно быть аккуратнее: иногда ноль может выглядеть как настоящее значение и испортить аналитику. Для отчётов чаще безопаснее использовать NULL, потому что его легче заметить и обработать.

Полезные шаблоны

Текст в целое число

SELECT '42'::integer;

или:

SELECT CAST('42' AS integer);

Текст в число с дробной частью

SELECT '99.99'::numeric;

Текст в дату

SELECT '2026-06-17'::date;

Дата и время в текст

SELECT created_at::text
FROM users;

Число в текст

SELECT id::text
FROM orders;

Безопаснее обработать пустую строку

SELECT NULLIF(amount_text, '')::numeric
FROM orders_raw;

Проверить число перед приведением

SELECT amount_text::numeric
FROM orders_raw
WHERE amount_text ~ '^[0-9]+(\.[0-9]+)?$';

Привести результат выражения

SELECT (price * quantity)::numeric AS total
FROM order_items;

Сделать дробное деление

SELECT paid_orders::numeric / total_orders AS paid_ratio
FROM report;

Что важно запомнить

CAST и :: нужны для явного приведения типов.

Стандартная форма:

CAST(value AS type)

Короткая форма PostgreSQL:

value::type

Они не меняют данные в таблице навсегда, а только говорят базе, как интерпретировать значение в конкретном запросе.

Пример:

SELECT '42'::integer;

означает:

возьми строку '42' и используй её как целое число.

Но приведение типов может упасть, если значение нельзя преобразовать.

SELECT 'hello'::integer;

Такой запрос завершится ошибкой.

Поэтому с сырыми данными из CSV, API или пользовательского ввода нужно быть внимательным:

  • пустую строку можно обработать через NULLIF;
  • плохие значения лучше отфильтровать заранее;
  • для чисел можно использовать проверку регулярным выражением;
  • в ClickHouse можно использовать функции вроде toInt32OrNull;
  • в PostgreSQL обычный CAST не является безопасным try_cast.

Также важно помнить про округление. В PostgreSQL приведение numeric к integer округляет значение:

SELECT 99.99::integer;

Результат:

100

Если нужно отбросить дробную часть, используйте:

SELECT trunc(99.99)::integer;

Результат:

99

Короткий вывод

CAST и :: — это способ явно управлять типами в SQL-запросе.

Они помогают превратить текст в число, строку в дату, число в текст или результат вычисления в нужный формат для отчёта.

Для PostgreSQL можно использовать оба варианта:

CAST(value AS type)

и:

value::type

Если важна переносимость между разными базами, выбирайте CAST. Если пишете чисто PostgreSQL-запрос, короткая форма :: часто удобнее.

Главное — помнить: приведение типов не магия. База не сможет превратить любой текст в число или любую строку в дату. Поэтому в реальных проектах хороший SQL-разработчик не просто пишет ::integer, а думает о качестве данных, возможных ошибках и правильных типах в таблицах.

Закрепи на практике

Решай задачи в SQL-тренажёре с мгновенной проверкой и подсказками.

Открыть тренажёр