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:
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:
SELECT '42'::integer AS qty,
'2026-06-17'::date AS signup_day;
SELECT CAST(99.99 AS integer) AS rounded;
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:
SELECT * FROM employees WHERE salary > 50000;
SELECT * FROM orders WHERE status = 1;
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:
SELECT email::integer FROM users;
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:
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:
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.
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:
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.
La conversion de tipos aparece en todas partes: extraes un numero de una columna de texto, conviertes una cadena en fecha o redondeas un
numerica entero para un informe. SQL ofrece elCASTestandar 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 / totalconvierte solocount(*), y la division se ejecuta despues, asi que la fraccion entera no se convierte.(a + b)::int.::es una extension de PostgreSQL, no SQL estandar. MySQL no tiene ese operador, asi que dejaCASTen 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 obtienes100, no99; la parte decimal no se descarta. Si de verdad quieres truncar hacia cero, no conviertas directamente: usatrunc(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 vuelve0y distorsiona la seleccion sin avisar. PostgreSQL es mas estricto y exige unCASTexplicito, 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 integerConviene aclarar un malentendido habitual. Un
CASTescalar normal en PostgreSQL no tiene clausulaON ERRORniDEFAULT: la consulta siguiente no funciona ni en PostgreSQL 16 ni en 17 -- devuelve un error de sintaxis, noNULLen 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 ERRORsi existe en PostgreSQL, pero solo dentro de las funciones SQL/JSONJSON_VALUE,JSON_QUERYyJSON_TABLEanadidas en la version 17, y no tiene nada que ver con una conversion escalar de tipo. Asi que para unCASTnormal 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:
NULLse convierte a unNULLdel tipo destino sin error, asi que unCASTno 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 equivalenteCONVERT(x, type), pero los tipos destino y el comportamiento difieren de PostgreSQL:CAST(x AS SIGNED)oUNSIGNED; las versiones antiguas no aceptaban la palabra claveINTEGERenCAST.CONVERTse encarga no solo de los tipos, sino tambien de los juegos de caracteres:CONVERT(name USING utf8mb4)recodifica la cadena.0oNULLcon 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 especializadastoInt32,toDate,toFloat64. En flujos con datos sucios, donde PostgreSQL te obligaria a escribir un prefiltro, ClickHouse ofrece variantes listas con los sufijosOrNullyOrZeroque devuelvenNULLo cero en vez de lanzar una excepcion:-- ClickHouse: never throws, returns NULL on bad input SELECT toInt32OrNull(amount) FROM orders;Regla corta: usa
CASTcuando 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 denumericaintegery filtra las cadenas sucias por adelantado. Cada motor tiene su propio mecanismo seguro: en PostgreSQL es un prefiltro con expresion regular (opg_input_is_validen 16+), en ClickHouse las funciones con sufijoOrNull-- y no debes esperar unON ERRORinexistente en unCASTescalar.