Ez a cikk jelenleg oroszul van — az angol fordítás folyamatban van.
В 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;
Так мы сначала оставляем только значения, похожие на число, а потом сравниваем их как числа.
Но в идеальном мире такие преобразования лучше делать не каждый раз в отчётном запросе, а на этапе загрузки данных.
То есть:
- загрузили сырые данные во временную таблицу;
- проверили и очистили значения;
- переложили их в нормальную таблицу с правильными типами.
Например, сумма заказа должна храниться как 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 у каждого значения есть тип.
Число — это число. Дата — это дата. Текст — это текст. Время — это время.
Пока данные аккуратные, об этом можно почти не думать. Но в реальных проектах всё быстро становится интереснее.
Например:
'42', а нам нужно число42;'2026-06-17';'1999.99';text, а сравнивать его пытаются с числом.В такие моменты нужно явно сказать базе:
Для этого в SQL используется приведение типов.
В PostgreSQL чаще всего встречаются два варианта:
CAST(value AS type)и короткая форма:
value::typeОба варианта делают одну и ту же работу: приводят значение к нужному типу прямо в запросе.
Что такое приведение типов простыми словами
Приведение типа — это когда мы просим базу данных временно посмотреть на значение как на другой тип.
Например, у нас есть строка:
'42'Для человека это выглядит как число. Но для базы это может быть именно текст, потому что значение заключено в кавычки.
Если мы хотим использовать его как число, можно написать:
SELECT CAST('42' AS integer);Или в стиле PostgreSQL:
SELECT '42'::integer;Результат:
Теперь база воспринимает это значение как целое число.
Важно понимать: такой
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-коде оно встречается очень часто, потому что короче и читается быстро.
Можно запомнить так:
Если вы учитесь SQL, полезно знать оба варианта. В рабочих проектах вы почти наверняка встретите и тот, и другой.
Простые примеры приведения типов
Текст в число
Допустим, есть строка
'100', а нам нужно число.SELECT '100'::integer AS value_int;Результат:
Теперь это значение можно использовать в вычислениях:
SELECT '100'::integer + 50 AS total;Результат:
Без приведения типов база могла бы воспринимать
'100'именно как текст, а не как число.Текст в дату
Допустим, дата пришла строкой:
Можно привести её к типу
date:SELECT '2026-06-17'::date AS created_day;Результат:
Теперь с этой датой можно нормально работать как с датой: сравнивать, прибавлять интервалы, использовать в фильтрах.
Например:
SELECT '2026-06-17'::date + INTERVAL '7 days' AS next_week;Результат:
Число в текст
Иногда нужно наоборот превратить число в текст.
Например:
SELECT 123::text AS value_text;Результат:
Визуально результат похож, но тип уже другой. Для базы это теперь строка, а не число.
Такое может понадобиться, например, когда нужно склеить число с текстом:
SELECT 'Order #' || id::text AS order_label FROM orders;Если
id = 15, результат будет:Пример из реальной жизни: импорт данных из CSV
Одна из самых частых ситуаций — импорт данных.
Допустим, из CSV-файла загрузили заказы во временную таблицу
orders_raw.В ней все поля текстовые:
Пока
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;Результат:
Теперь эти значения можно использовать в расчётах:
SELECT SUM(amount_text::numeric) AS total_revenue FROM orders_raw;Так мы превращаем грязные или сырые данные из импорта в нормальные типы, с которыми база умеет работать правильно.
Приведение типа не всегда безопасно
Очень важный момент:
CASTработает только тогда, когда значение действительно можно привести к нужному типу.Например, это сработает:
SELECT '42'::integer;А это уже нет:
SELECT 'hello'::integer;PostgreSQL выдаст ошибку примерно такого смысла:
Почему?
Потому что строку
'hello'невозможно превратить в целое число.То же самое с датами.
Это сработает:
SELECT '2026-06-17'::date;А это может упасть:
SELECT 'not a date'::date;Когда вы приводите одно конкретное значение, ошибка очевидна. Но в таблицах опасность больше.
Представьте таблицу
orders_raw:Запрос:
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]+)?$';Оно пропустит значения вроде:
Но не пропустит:
Для учебных задач важно запомнить сам принцип:
NULL и пустая строка — это разные вещи
Есть ещё одна тонкость.
NULLнормально приводится к другому типу.Например:
SELECT NULL::integer;Результат будет:
Ошибки не будет, потому что
NULLозначает отсутствие значения. База просто получает «отсутствующее целое число».А вот пустая строка — это уже не
NULL.SELECT ''::integer;Такой запрос упадёт с ошибкой, потому что пустую строку нельзя превратить в число.
В данных из CSV или API это встречается постоянно:
Вторая строка может выглядеть как «пусто», но для базы это может быть именно пустая строка
'', а неNULL.Один из способов обработать это — использовать
NULLIF.SELECT NULLIF(amount_text, '')::numeric AS amount FROM orders_raw;NULLIF(amount_text, '')означает:После этого
NULLуже спокойно приводится кnumeric.Но если кроме пустых строк могут быть значения вроде
'error', всё равно нужна дополнительная проверка.Приведение numeric к integer: важный нюанс
Иногда в отчётах нужно показать число без дробной части.
Например, средний чек получился таким:
И хочется вывести его как целое число.
Можно написать:
SELECT CAST(1499.75 AS integer);или:
SELECT 1499.75::integer;В PostgreSQL при таком приведении число будет округлено до целого.
Например:
SELECT 99.99::integer AS value;Результат:
Это важный момент.
Приведение к
integer— это не то же самое, что просто отбросить дробную часть.Если вам нужно именно отбросить дробную часть, используйте
trunc.SELECT trunc(99.99)::integer AS value;Результат:
А если нужно математическое округление, можно использовать
round.SELECT round(99.99)::integer AS value;Результат:
В отчётах это важно. Например, средний чек
999.60и999.40могут по-разному отображаться в зависимости от того, округляете вы значение или отбрасываете дробную часть.Пример: средний чек по странам
Допустим, есть таблицы
usersиorders.users:orders:Хотим посчитать средний чек по странам и показать его как целое число.
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;Результат может быть таким:
Если нужно вывести средний чек как целое число с округлением:
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;Такой запрос вернёт долю с двумя знаками после запятой.
Например:
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;Так мы сначала оставляем только значения, похожие на число, а потом сравниваем их как числа.
Но в идеальном мире такие преобразования лучше делать не каждый раз в отчётном запросе, а на этапе загрузки данных.
То есть:
Например, сумма заказа должна храниться как
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;означает:
Но приведение типов может упасть, если значение нельзя преобразовать.
SELECT 'hello'::integer;Такой запрос завершится ошибкой.
Поэтому с сырыми данными из CSV, API или пользовательского ввода нужно быть внимательным:
NULLIF;toInt32OrNull;CASTне является безопаснымtry_cast.Также важно помнить про округление. В PostgreSQL приведение
numericкintegerокругляет значение:SELECT 99.99::integer;Результат:
Если нужно отбросить дробную часть, используйте:
SELECT trunc(99.99)::integer;Результат:
Короткий вывод
CASTи::— это способ явно управлять типами в SQL-запросе.Они помогают превратить текст в число, строку в дату, число в текст или результат вычисления в нужный формат для отчёта.
Для PostgreSQL можно использовать оба варианта:
CAST(value AS type)и:
value::typeЕсли важна переносимость между разными базами, выбирайте
CAST. Если пишете чисто PostgreSQL-запрос, короткая форма::часто удобнее.Главное — помнить: приведение типов не магия. База не сможет превратить любой текст в число или любую строку в дату. Поэтому в реальных проектах хороший SQL-разработчик не просто пишет
::integer, а думает о качестве данных, возможных ошибках и правильных типах в таблицах.