sqlpostgresqlcastmysql

Conversao de tipos em SQL: CAST e o operador :: no PostgreSQL

Como converter tipos em SQL com CAST e o atalho ::, arredondar numeric com cuidado, evitar erros de conversao e lidar com as diferencas entre PostgreSQL, MySQL e ClickHouse.

5 min de leituraReferencesql · postgresql · cast · mysql · clickhouse · types

A conversao de tipos aparece em todo lugar: voce extrai um numero de uma coluna de texto, transforma uma string em data ou arredonda um numeric para inteiro em um relatorio. O SQL oferece o CAST padrao e o pratico atalho :: do PostgreSQL. Uma conversao nao altera os dados armazenados; ela apenas diz ao planejador como interpretar uma expressao. O importante e distinguir entre uma conversao que so reetiqueta um tipo sem risco e outra capaz de derrubar a consulta inteira com uma unica linha ruim.

CAST versus ::

O padrao SQL define a forma funcional CAST(value AS type). Prefira-a em consultas portaveis e em codigo que possa migrar do PostgreSQL para MySQL ou ClickHouse, porque os tres bancos a entendem:

SELECT CAST(amount AS integer) AS amount_int
FROM orders
WHERE status = 'paid';

O PostgreSQL adiciona o operador ::, que e exatamente a mesma conversao escrita de forma mais curta. Em codigo so de PostgreSQL ele aparece o tempo todo:

-- These two lines are equivalent in PostgreSQL
SELECT amount::integer FROM orders;
SELECT CAST(amount AS integer) FROM orders;

Detalhes uteis que mordem em consultas reais:

  • :: tem ligacao muito forte, mais que a aritmetica: count(*)::numeric / total converte apenas count(*), e a divisao acontece depois, entao a fracao inteira nao e convertida.
  • Para converter uma expressao composta, use parenteses: (a + b)::int.
  • :: e uma extensao do PostgreSQL, nao SQL padrao. O MySQL nao tem esse operador, entao mantenha CAST em consultas portaveis.

Conversoes comuns

Os casos mais frequentes sao texto para numero, texto para data e um valor calculado para um tipo conveniente em um relatorio. Em um exemplo pequeno tudo parece seguro, mas um deles esconde uma armadilha de arredondamento:

-- Text to integer and to date
SELECT '42'::integer AS qty,
       '2026-06-17'::date AS signup_day;

-- Casting numeric to integer rounds to nearest, it does not truncate
SELECT CAST(99.99 AS integer) AS rounded;   -- see the note below

-- Render a timestamp column as text
SELECT created_at::text FROM users LIMIT 5;

A armadilha e semantica, nao sintatica. CAST(99.99 AS integer) no PostgreSQL arredonda para o inteiro mais proximo, entao voce obtem 100, nao 99; a parte decimal nao e descartada. Se voce realmente quer truncar em direcao a zero, nao converta diretamente: use trunc(99.99)::integer. Em um relatorio a diferenca entre arredondar e truncar e visivel, por exemplo no ticket medio por pais:

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

Conversoes implicitas e explicitas

O PostgreSQL realiza algumas conversoes por voce, de forma implicita, mas nao convem depender disso ao projetar uma consulta. Ele compara numeros de tipos compativeis por conta propria, mas texto contra numero na mesma condicao ele recusa:

-- Implicit: integer compared with numeric just works
SELECT * FROM employees WHERE salary > 50000;

-- This FAILS in PostgreSQL: text vs integer, no implicit cast
SELECT * FROM orders WHERE status = 1;

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

O MySQL e mais permissivo aqui: converte em silencio status = 1, transformando a string em numero segundo as proprias regras. Isso e conveniente ate o primeiro relatorio estranho, porque uma string que nao parece um numero vira 0 e distorce a selecao sem avisar. O PostgreSQL e mais rigoroso e exige um CAST explicito, mas o erro aparece de imediato onde os dados nao batem com o modelo, em vez de em um relatorio pronto.

Erros de conversao e como se proteger

O risco principal de converter sao as strings sujas. Uma unica linha com conteudo nao numerico derruba todo o SELECT, mesmo que os outros milhoes de valores convertam perfeitamente:

-- Fails on the first row where email holds non-numeric text
SELECT email::integer FROM users;
-- ERROR: invalid input syntax for type integer

Vale esclarecer um equivoco comum. Um CAST escalar comum no PostgreSQL nao tem clausula ON ERROR nem DEFAULT: a consulta a seguir nao funciona nem no PostgreSQL 16 nem no 17 -- ela retorna um erro de sintaxe, nao NULL nas linhas ruins. Nao a copie para o seu codigo; este e um exemplo do que nao se deve fazer:

-- WRONG: this is NOT valid SQL, a scalar CAST has no ON ERROR clause
SELECT CAST(amount AS integer DEFAULT NULL ON ERROR) AS amount_int
FROM orders;

A clausula ON ERROR realmente existe no PostgreSQL, mas apenas dentro das funcoes SQL/JSON JSON_VALUE, JSON_QUERY e JSON_TABLE adicionadas na versao 17, e nao tem nada a ver com uma conversao escalar de tipo. Por isso, para um CAST comum ha um unico caminho que funciona: primeiro filtrar o formato valido com uma expressao regular e so depois converter o tipo:

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

No PostgreSQL 16+ voce tambem pode testar um valor antes de converter com pg_input_is_valid(text, type), que retorna um booleano em vez de lancar um erro:

-- Postgres 16+: skip rows that would fail the cast
SELECT amount::integer
FROM orders
WHERE pg_input_is_valid(amount::text, 'integer');

Outra sutileza: NULL converte para um NULL do tipo destino sem erro, entao um CAST nao teme valores ausentes em si. O que quebra a consulta nao sao os valores ausentes, mas as strings nao vazias que nao sao interpretadas como o tipo destino. Por isso o prefiltro deve verificar o conteudo da string, nao apenas se ha um valor, e convem descartar tambem a string vazia '' antes de converter.

MySQL e ClickHouse

No MySQL a conversao tambem se escreve como CAST(x AS type), alem do quase equivalente CONVERT(x, type), mas os tipos destino e o comportamento diferem do PostgreSQL:

  • Para um inteiro, o MySQL usa CAST(x AS SIGNED) ou UNSIGNED; versoes antigas nao aceitavam a palavra-chave INTEGER no CAST.
  • CONVERT cuida nao so dos tipos, mas tambem dos conjuntos de caracteres: CONVERT(name USING utf8mb4) recodifica a string.
  • Diante de uma conversao malsucedida o MySQL nao falha por padrao; ele retorna 0 ou NULL com um aviso, entao o erro se perde em silencio nos logs em vez de ser uma falha explicita como no PostgreSQL.
-- MySQL flavor
SELECT CAST(amount AS SIGNED) AS amount_int,
       CONVERT(created_at, DATE) AS day
FROM orders;

O ClickHouse entende tanto CAST(x AS type) quanto o operador ::, mas para as conversoes tipicas costuma-se recorrer a familia de funcoes especializadas toInt32, toDate, toFloat64. Em fluxos com dados sujos, onde o PostgreSQL obrigaria voce a escrever um prefiltro, o ClickHouse oferece variantes prontas com os sufixos OrNull e OrZero que retornam NULL ou zero em vez de lancar uma excecao:

-- ClickHouse: never throws, returns NULL on bad input
SELECT toInt32OrNull(amount) FROM orders;

Regra curta: use CAST quando a portabilidade entre PostgreSQL, MySQL e ClickHouse importa, e :: quando voce escreve codigo puro de PostgreSQL e valoriza a brevidade. De qualquer forma, faca a conversao explicita, lembre do arredondamento ao passar de numeric para integer e filtre as strings sujas com antecedencia. Cada banco tem o proprio mecanismo seguro: no PostgreSQL e um prefiltro com expressao regular (ou pg_input_is_valid no 16+), no ClickHouse as funcoes com sufixo OrNull -- e voce nao deve esperar um ON ERROR inexistente em um CAST escalar.

Pratique com exercícios reais

Resolva exercícios no treinador de SQL com correção instantânea e dicas.

Abrir o treinador