El síntoma: cada rama devuelve las mismas columnas, pero una posición no comparte un tipo utilizable
En UNION, UNION ALL, INTERSECT y EXCEPT, las columnas se emparejan por posición. Tener el mismo número de columnas es solo la primera comprobación: cada posición debe poder convertirse a un tipo común según las reglas del motor.
PostgreSQL resuelve un tipo común para cada columna y falla cuando los tipos no pertenecen a una categoría compatible o no existe una conversión implícita válida. SQL Server también exige tipos compatibles y aplica su prioridad de tipos para decidir qué conversión intentar. MySQL es más permisivo en varios casos y puede construir un tipo de resultado a partir de valores de tipos distintos. SQLite usa tipado dinámico, por lo que puede aceptar combinaciones que otros motores rechazan. Que una unión funcione en un motor no demuestra que sea portable.
Ejemplo: dos columnas a cada lado, pero tipos incompatibles en PostgreSQL
La forma de ambas ramas es idéntica: orden y valor. El conflicto está en la segunda posición, donde una rama produce un entero y la otra texto.
SELECT 1 AS orden,
42::integer AS valor
UNION ALL
SELECT 2,
'sin-dato'::text;Con las reglas de resolución de tipos de PostgreSQL, integer y text no forman una categoría común para esa columna y la unión se rechaza. En SQL Server una combinación equivalente puede intentar convertir el texto al tipo de mayor prioridad y fallar si el valor no es convertible. MySQL, en cambio, documenta que SELECT 1 UNION SELECT 'a' es válido y construye un resultado capaz de contener ambos valores.
El mismo SQL puede comportarse distinto según el motor
| Motor | Qué revisar | Consecuencia práctica |
|---|---|---|
| PostgreSQL | Resuelve un tipo común por columna; si las categorías no son compatibles o no existe conversión implícita, falla. | Conviene declarar el tipo objetivo con CAST cuando la semántica es realmente común. |
| SQL Server | Las columnas deben ser compatibles mediante conversión implícita y se aplica prioridad de tipos. | Un texto no convertible puede provocar un error al intentar llevarlo a un tipo con mayor prioridad. |
| MySQL | Los tipos correspondientes deberían coincidir, pero el tipo final puede ampliarse o adaptarse considerando todas las ramas. | Una consulta aceptada puede no ser portable a motores con resolución más estricta. |
| SQLite | El tipo pertenece al valor y el motor usa tipado dinámico. | Puede conservar valores de clases distintas en la misma columna de una consulta compuesta. |
Comprobación local: SQLite acepta valores heterogéneos
La siguiente consulta se ejecutó localmente con SQLite durante esta revisión. El resultado muestra que la misma columna contiene un entero en una fila y texto en otra.
WITH combinados AS (
SELECT 1 AS orden, 42 AS valor
UNION ALL
SELECT 2, 'sin-dato'
)
SELECT orden,
valor,
typeof(valor) AS tipo
FROM combinados
ORDER BY orden;| orden | valor | tipo |
|---|---|---|
| 1 | 42 | integer |
| 2 | sin-dato | text |
Este resultado no contradice a PostgreSQL o SQL Server: SQLite tiene un sistema de tipos diferente. Si tu consulta debe viajar entre motores, define el tipo de salida de forma deliberada en vez de depender de coerciones implícitas.
Corrección: decide el significado de la columna y convierte hacia ese contrato
Antes de añadir CAST, pregunta qué representa la posición. Si valor es una etiqueta que a veces contiene números y a veces texto, convertir ambas ramas a texto puede ser correcto. Si representa un importe, convertir 'sin-dato' a texto solo oculta un problema de calidad: deberías limpiar el dato o representarlo como NULL.
SELECT 1 AS orden,
CAST(42 AS VARCHAR(20)) AS valor
UNION ALL
SELECT 2,
CAST('sin-dato' AS VARCHAR(20));La conversión explícita documenta la intención y reduce diferencias entre motores. Aun así, el tipo elegido debe representar el dominio real del dato; no uses texto como salida universal solo para silenciar errores.
Método de diagnóstico
- Cuenta las columnas de cada rama. Si no coinciden, corrige primero la forma de la fila.
- Compara los tipos posición por posición. No te fijes solo en los nombres de alias.
- Define qué significa cada posición. ¿Es un importe, una fecha, un identificador o una etiqueta?
- Revisa las reglas del motor. Una coerción implícita aceptada por MySQL o SQLite puede fallar en PostgreSQL o SQL Server.
- Usa CAST solo hacia un tipo semánticamente correcto. Si el origen contiene valores inválidos, corrige los datos o trátalos de forma explícita.
Error frecuente: convertir todo a texto para que UNION deje de fallar
Un CAST puede esconder una incompatibilidad de significado
Si una rama contiene importes y la otra estados como 'pendiente', ambas pueden convertirse a texto, pero eso no convierte la columna resultante en un dato coherente. El objetivo no es que el motor acepte la consulta: es que cada posición represente el mismo concepto.
Práctica correctiva
Combina precios actuales con precios importados como texto
La fuente actual entrega precio como número. La fuente legacy entrega precio_texto, pero en este ejercicio todos sus valores contienen números válidos. Devuelve codigo y precio como valor numérico en una sola lista.
precio_texto a DECIMAL(10,2) antes de unir.Solución razonada
WITH actuales(codigo, precio) AS (
VALUES ('A', 19.90), ('B', 25.00)
),
legacy(codigo, precio_texto) AS (
VALUES ('C', '12.50'), ('D', '30.00')
)
SELECT codigo,
CAST(precio AS DECIMAL(10,2)) AS precio
FROM actuales
UNION ALL
SELECT codigo,
CAST(precio_texto AS DECIMAL(10,2))
FROM legacy
ORDER BY codigo;La conversión no se hace para «engañar» a UNION: el ejercicio declara que ambas columnas representan precios y que el texto legacy contiene números válidos. En la comprobación local con SQLite, el resultado contiene cuatro filas: A 19.9, B 25.0, C 12.5 y D 30.
Qué debes recordar
- El mismo número de columnas no garantiza tipos compatibles.
- Las columnas se emparejan por posición, no por nombre.
- PostgreSQL y SQL Server pueden rechazar o fallar en conversiones que MySQL o SQLite aceptan de otra forma.
CASTdebe expresar un tipo común real, no ocultar datos incoherentes.
Conceptos relacionados
Fuentes técnicas
La resolución de tipos y las diferencias entre motores de esta guía se contrastaron con documentación primaria: