sqlpostgresqlcastmysql

Conversion de tipos en SQL: CAST y el operador :: en PostgreSQL

Como convertir tipos en SQL con CAST y el atajo ::, redondear numeric con cuidado, evitar errores de conversion y manejar las diferencias entre PostgreSQL, MySQL y ClickHouse.

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

La conversion de tipos aparece en todas partes: extraes un numero de una columna de texto, conviertes una cadena en fecha o redondeas un numeric a entero para un informe. SQL ofrece el CAST estandar y el practico atajo :: de PostgreSQL. Una conversion no cambia los datos guardados; solo le dice al planificador como interpretar una expresion. Lo importante es distinguir entre una conversion que solo reetiqueta un tipo sin riesgo y otra capaz de tumbar toda la consulta con una sola fila mala.

CAST frente a ::

El estandar SQL define la forma funcional CAST(value AS type). Prefierela en consultas portables y en codigo que pueda pasar de PostgreSQL a MySQL o ClickHouse, porque los tres motores la entienden:

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

PostgreSQL anade el operador ::, que es exactamente la misma conversion escrita mas corta. En codigo solo de PostgreSQL aparece constantemente:

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

Detalles utiles que muerden en consultas reales:

  • :: se enlaza con mucha fuerza, mas que la aritmetica: count(*)::numeric / total convierte solo count(*), y la division se ejecuta despues, asi que la fraccion entera no se convierte.
  • Para convertir una expresion compuesta, usa parentesis: (a + b)::int.
  • :: es una extension de PostgreSQL, no SQL estandar. MySQL no tiene ese operador, asi que deja CAST en consultas portables.

Conversiones habituales

Los casos mas frecuentes son texto a numero, texto a fecha y un valor calculado a un tipo comodo para un informe. En un ejemplo pequeno todo parece seguro, pero uno de ellos esconde una trampa de redondeo:

-- 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;

La trampa es semantica, no sintactica. CAST(99.99 AS integer) en PostgreSQL redondea al entero mas cercano, asi que obtienes 100, no 99; la parte decimal no se descarta. Si de verdad quieres truncar hacia cero, no conviertas directamente: usa trunc(99.99)::integer. En un informe la diferencia entre redondear y truncar se nota, por ejemplo en el 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;

Conversiones implicitas y explicitas

PostgreSQL realiza algunas conversiones por ti, de forma implicita, pero no conviene apoyarse en eso al disenar una consulta. Compara numeros de tipos compatibles por su cuenta, pero texto frente a numero en la misma condicion lo rechaza:

-- 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);

MySQL es mas permisivo aqui: convierte en silencio status = 1, transformando la cadena en numero segun sus propias reglas. Eso es comodo hasta el primer informe raro, porque una cadena que no parece un numero se vuelve 0 y distorsiona la seleccion sin avisar. PostgreSQL es mas estricto y exige un CAST explicito, pero el error sale de inmediato donde los datos no encajan con el modelo, en vez de en un informe terminado.

Errores de conversion y como protegerse

El riesgo principal de convertir son las cadenas sucias. Una sola fila con contenido no numerico tumba todo el SELECT, aunque los otros millones de valores se conviertan a la perfeccion:

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

Conviene aclarar un malentendido habitual. Un CAST escalar normal en PostgreSQL no tiene clausula ON ERROR ni DEFAULT: la consulta siguiente no funciona ni en PostgreSQL 16 ni en 17 -- devuelve un error de sintaxis, no NULL en las filas malas. No la copies a tu codigo; es un ejemplo de lo que no se debe hacer:

-- 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;

La clausula ON ERROR si existe en PostgreSQL, pero solo dentro de las funciones SQL/JSON JSON_VALUE, JSON_QUERY y JSON_TABLE anadidas en la version 17, y no tiene nada que ver con una conversion escalar de tipo. Asi que para un CAST normal hay un unico camino que funciona: primero filtrar el formato valido con una expresion regular y solo despues convertir el tipo:

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

En PostgreSQL 16+ tambien puedes comprobar un valor antes de convertir con pg_input_is_valid(text, type), que devuelve un booleano en lugar de lanzar un error:

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

Otro matiz: NULL se convierte a un NULL del tipo destino sin error, asi que un CAST no teme a los valores ausentes como tales. Lo que rompe la consulta no son los valores ausentes, sino las cadenas no vacias que no se analizan como el tipo destino. Por eso el prefiltro debe comprobar el contenido de la cadena, no solo si hay un valor, y conviene descartar tambien la cadena vacia '' antes de convertir.

MySQL y ClickHouse

En MySQL la conversion tambien se escribe como CAST(x AS type), ademas del casi equivalente CONVERT(x, type), pero los tipos destino y el comportamiento difieren de PostgreSQL:

  • Para un entero, MySQL usa CAST(x AS SIGNED) o UNSIGNED; las versiones antiguas no aceptaban la palabra clave INTEGER en CAST.
  • CONVERT se encarga no solo de los tipos, sino tambien de los juegos de caracteres: CONVERT(name USING utf8mb4) recodifica la cadena.
  • Ante una conversion fallida MySQL no falla por defecto; devuelve 0 o NULL con un aviso, asi que el error se pierde en silencio en los registros en vez de ser un fallo explicito como en PostgreSQL.
-- MySQL flavor
SELECT CAST(amount AS SIGNED) AS amount_int,
       CONVERT(created_at, DATE) AS day
FROM orders;

ClickHouse entiende tanto CAST(x AS type) como el operador ::, pero para las conversiones tipicas se suele recurrir a la familia de funciones especializadas toInt32, toDate, toFloat64. En flujos con datos sucios, donde PostgreSQL te obligaria a escribir un prefiltro, ClickHouse ofrece variantes listas con los sufijos OrNull y OrZero que devuelven NULL o cero en vez de lanzar una excepcion:

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

Regla corta: usa CAST cuando importa la portabilidad entre PostgreSQL, MySQL y ClickHouse, y :: cuando escribes codigo puro de PostgreSQL y valoras la brevedad. En cualquier caso, haz la conversion explicita, recuerda el redondeo al pasar de numeric a integer y filtra las cadenas sucias por adelantado. Cada motor tiene su propio mecanismo seguro: en PostgreSQL es un prefiltro con expresion regular (o pg_input_is_valid en 16+), en ClickHouse las funciones con sufijo OrNull -- y no debes esperar un ON ERROR inexistente en un CAST escalar.

Practica con ejercicios reales

Resuelve ejercicios en el entrenador de SQL con corrección instantánea y pistas.

Abrir el entrenador