Skip to content
Back to Blog

Diferencias de sintaxis SQL entre PostgreSQL y MySQL

El 80% del SQL es portable; el otro 20% (UPSERT, booleanos, operadores de conjunto, auto-incremento) es donde fallan las migraciones. Esta guía mapea las diferencias reales de sintaxis entre PostgreSQL y MySQL con ejemplos lado a lado.

SZ
Founder, Molixa
12 min read
Compartir
Diferencias de sintaxis SQL entre PostgreSQL y MySQL
Table of contents10 sections

La mayor parte de la sintaxis de PostgreSQL vs MySQL que escribes a diario es idéntica. SELECT, JOIN, WHERE, GROUP BY y un INSERT básico funcionan igual en ambos. El problema es el otro 20 por ciento, los rincones específicos de cada dialecto donde una consulta que funciona limpia en MySQL arroja un error de sintaxis en Postgres (y viceversa). Esta guía mapea esos rincones con ejemplos lado a lado para que puedas portar consultas sin prueba y error.

Si estás migrando una base de datos, escribiendo una consulta que debe ejecutarse en ambos motores, o simplemente tratando de entender por qué un fragmento de Stack Overflow no funciona en tu consola, la respuesta casi siempre es una de un puñado de diferencias conocidas. UPSERT, booleanos, operadores de conjunto, auto-incremento y manejo de cadenas causan la gran mayoría del dolor al portar.

Sintaxis de PostgreSQL vs MySQL: El 20% que realmente causa problemas#

Las comparaciones a nivel empresarial de estas dos bases de datos se centran en rendimiento, licencias y replicación. Útiles para elegir una pila, inútiles cuando tienes una consulta rota frente a ti. Lo que necesitas es la tabla de sintaxis.

Aquí está la versión resumida de dónde divergen los dialectos. El resto de esta guía expande cada fila con ejemplos reales.

CaracterísticaPostgreSQLMySQL
UPSERTINSERT ... ON CONFLICT ... DO UPDATEINSERT ... ON DUPLICATE KEY UPDATE
Auto-incrementoSERIAL / GENERATED ... AS IDENTITYAUTO_INCREMENT
BooleanoBOOLEAN nativo (true/false)alias TINYINT(1), almacena 0/1
Operadores de conjuntoINTERSECT, EXCEPT soportadosINTERSECT/EXCEPT solo desde 8.0.31+
Concatenación de cadenasoperador ||CONCAT() (|| significa OR)
Sensibilidad a mayúsculasidentificadores se convierten a minúsculasdepende del SO y la configuración de la tabla
Comillascomillas dobles para identificadorescomillas invertidas para identificadores
LIMIT/OFFSETLIMIT n OFFSET mLIMIT m, n o LIMIT n OFFSET m

Regla rápida: si una consulta falla después de una migración, revisa primero UPSERT, comparaciones booleanas y comillas en identificadores. Esos tres causan la mayoría de los problemas.

UPSERT: ON CONFLICT vs ON DUPLICATE KEY UPDATE#

Este es el mayor problema al portar consultas, y no tiene una traducción automática limpia. Ambos motores permiten insertar una fila y actualizarla si ya existe una clave, pero la sintaxis es completamente diferente.

MySQL usa ON DUPLICATE KEY UPDATE, que se activa en cualquier colisión de clave única o primaria:

INSERT INTO users (id, email, login_count)
VALUES (1, 'a@example.com', 1)
ON DUPLICATE KEY UPDATE login_count = login_count + 1;

PostgreSQL usa ON CONFLICT, y te obliga a nombrar el objetivo del conflicto (la columna o restricción en la que esperas que ocurra la colisión):

INSERT INTO users (id, email, login_count)
VALUES (1, 'a@example.com', 1)
ON CONFLICT (id) DO UPDATE
SET login_count = users.login_count + 1;

Dos cosas confunden a la gente aquí. Primero, Postgres requiere el objetivo del conflicto, mientras que MySQL lo infiere de cualquier clave única que se haya violado. Segundo, la forma de referenciar la fila entrante difiere. MySQL expone los nuevos valores a través de VALUES(col) (sintaxis antigua) o un alias de fila, mientras que Postgres los expone a través de la pseudo-tabla especial EXCLUDED:

-- PostgreSQL referenciando el valor entrante
ON CONFLICT (id) DO UPDATE
SET login_count = EXCLUDED.login_count;

Si solo recuerdas una diferencia de este artículo, que sea esta. UPSERT es donde la mayoría de las consultas fallan silenciosamente al traducirse.

Auto-Increment: SERIAL e IDENTITY vs AUTO_INCREMENT#

Las claves primarias auto-incrementales existen en ambos motores, pero se declaran de manera diferente.

MySQL asigna AUTO_INCREMENT a la columna:

CREATE TABLE orders (
  id INT AUTO_INCREMENT PRIMARY KEY,
  total DECIMAL(10,2)
);

PostgreSQL tiene dos enfoques. El más antiguo y comúnmente visto es SERIAL, que es una abreviatura que crea una secuencia internamente:

CREATE TABLE orders (
  id SERIAL PRIMARY KEY,
  total NUMERIC(10,2)
);

El enfoque moderno y compatible con el estándar SQL (recomendado en Postgres 10 y posteriores) es GENERATED ... AS IDENTITY:

CREATE TABLE orders (
  id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  total NUMERIC(10,2)
);

Una diferencia sutil relacionada: DECIMAL de MySQL y NUMERIC de Postgres son funcionalmente el mismo tipo de precisión arbitraria, y ambos aceptan cualquiera de las dos palabras clave. Pero NUMERIC es el nombre estándar SQL que verá en la documentación de Postgres, así que no se sorprenda al migrar en la dirección opuesta.

Booleanos: BOOLEAN nativo vs TINYINT(1)#

PostgreSQL tiene un tipo BOOLEAN real que almacena true y false (y NULL). Puedes compararlo directamente:

SELECT * FROM users WHERE is_active = true;
SELECT * FROM users WHERE is_active;  -- también válido en Postgres

MySQL no tiene booleanos nativos. BOOLEAN y BOOL son alias de TINYINT(1), que almacena enteros. true se asigna a 1 y false a 0. Esto funciona en su mayoría, pero presenta dos problemas:

  • Las comparaciones con los literales true/false funcionan porque MySQL los trata como 1/0, pero tus datos almacenados son enteros, por lo que un SELECT devuelve 1 y 0, no true y false.
  • Cualquier valor distinto de 0 se considera verdadero en el mundo entero de MySQL, por lo que las columnas TINYINT(1) técnicamente pueden contener 2, 5 o -1 si tu aplicación es descuidada.

Al migrar una columna booleana de Postgres a MySQL, espera que los datos aparezcan como 0/1 y ajusta cualquier código de aplicación o mapeo ORM que esperaba las cadenas true/false.

Operadores de conjuntos: INTERSECT y EXCEPT#

PostgreSQL tiene soporte completo para los operadores de conjuntos estándar: UNION, UNION ALL, INTERSECT y EXCEPT. Todos se comportan según lo descrito en el estándar SQL.

SELECT id FROM tabla_a
INTERSECT
SELECT id FROM tabla_b;

MySQL fue el rezagado durante años. UNION y UNION ALL siempre funcionaron, pero INTERSECT y EXCEPT llegaron solo en MySQL 8.0.31. Si trabajas con una versión anterior de MySQL, no puedes usarlos y debes reescribir la lógica.

La solución clásica en MySQL para INTERSECT es un INNER JOIN o una subconsulta con IN:

-- INTERSECT reescrito para MySQL antiguo
SELECT DISTINCT a.id
FROM tabla_a a
JOIN tabla_b b ON a.id = b.id;

Y para EXCEPT, un patrón LEFT JOIN ... IS NULL o NOT IN / NOT EXISTS:

-- EXCEPT reescrito para MySQL antiguo
SELECT a.id
FROM tabla_a a
LEFT JOIN tabla_b b ON a.id = b.id
WHERE b.id IS NULL;

Ten cuidado con el manejo de NULL. NOT IN con una subconsulta que devuelva algún valor NULL puede producir inesperadamente un conjunto de resultados vacío. NOT EXISTS y el patrón LEFT JOIN ... IS NULL son más seguros cuando pueden aparecer NULLs.

Cadenas, comillas y concatenación#

Esta categoría causa los errores más desconcertantes porque los mismos caracteres significan cosas diferentes.

Comillas para identificadores#

PostgreSQL usa comillas dobles para identificadores (nombres de tablas y columnas) y comillas simples para literales de cadena:

SELECT "userName" FROM "Users" WHERE name = 'Alice';

MySQL usa acentos graves para identificadores de forma predeterminada:

SELECT `userName` FROM `Users` WHERE name = 'Alice';

Se le puede indicar a MySQL que acepte identificadores con comillas dobles activando el modo ANSI_QUOTES, pero de forma nativa, las comillas dobles alrededor de una cadena pueden comportarse como un literal de cadena, lo que genera errores confusos al copiar SQL de Postgres en una consola de MySQL.

Concatenación de cadenas#

PostgreSQL usa el operador estándar || para concatenar cadenas:

SELECT first_name || ' ' || last_name AS full_name FROM users;

En MySQL, || significa OR lógico de forma predeterminada, no concatenación. Debes usar la función CONCAT():

SELECT CONCAT(first_name, ' ', last_name) AS full_name FROM users;

Esto es un fallo silencioso a punto de ocurrir. Una concatenación con || copiada de Postgres no generará error en MySQL; se evaluará silenciosamente como un OR booleano y devolverá 0 o 1, lo cual es mucho más difícil de depurar que un error de sintaxis.

Sensibilidad a mayúsculas/minúsculas#

PostgreSQL convierte los identificadores sin comillas a minúsculas, por lo que MyTable y mytable son el mismo objeto a menos que los entrecomilles. La sensibilidad a mayúsculas/minúsculas de los identificadores en MySQL depende del sistema operativo y de la configuración lower_case_table_names, lo que significa que una consulta puede funcionar en el Mac de un desarrollador y fallar en un servidor de producción Linux. En caso de duda, elige una convención de nomenclatura consistente con minúsculas y guiones bajos y evitarás todo el problema.

LÍMITE, DESPLAZAMIENTO y Paginación#

Ambos motores admiten LIMIT n OFFSET m, que es la forma portátil recomendada:

SELECT * FROM products ORDER BY id LIMIT 10 OFFSET 20;

MySQL también acepta una abreviatura con coma que PostgreSQL no entiende:

-- Solo MySQL: saltar 20, tomar 10
SELECT * FROM products ORDER BY id LIMIT 20, 10;

Nótese que el orden está invertido en la forma con coma (desplazamiento primero, luego cantidad), lo que suele causar errores de página. Use la sintaxis explícita LIMIT ... OFFSET ... y sus consultas de paginación funcionarán correctamente en ambos sentidos.

Portar consultas sin adivinanzas#

Al mover una consulta entre estos dialectos, revise las características de alto riesgo en orden en lugar de ejecutarla a ciegas y leer mensajes de error. Una lista de verificación práctica:

  • Reemplace la sintaxis UPSERT (ON DUPLICATE KEY UPDATE por ON CONFLICT, o viceversa) y corrija la referencia de fila (VALUES() por EXCLUDED).
  • Cambie la concatenación de cadenas con || por CONCAT() cuando trabaje con MySQL.
  • Convierta las comillas en identificadores (backticks a comillas dobles, o viceversa).
  • Verifique las columnas booleanas y espere datos 0/1 en MySQL.
  • Reemplace INTERSECT/EXCEPT con joins si trabaja con MySQL anterior a 8.0.31.
  • Normalice las declaraciones de auto-incremento (SERIAL/IDENTITY frente a AUTO_INCREMENT).

Leer una consulta densa para encontrar estos puntos es mucho más fácil una vez que está formateada. Un muro de SQL en una sola línea oculta la cláusula ON CONFLICT y el || suelto. Nuestro formateador SQL multidialecto reformatea e indentiza consultas tanto para PostgreSQL como para MySQL, para que pueda ver la estructura claramente antes de empezar a traducir. Para un análisis más profundo de cómo el formateo difiere entre motores, consulte nuestra guía sobre cómo formatear una consulta SQL para legibilidad entre dialectos.

Si su trabajo también implica enviar resultados de consultas a una API o configuración, el mismo principio de legibilidad se aplica a sus cargas útiles. Nuestro formateador JSON limpia la capa de datos, y el tutorial sobre cómo formatear JSON en JavaScript cubre los patrones de JSON.stringify que se combinan con la salida de la base de datos.

La conclusión sobre la sintaxis de PostgreSQL vs MySQL#

Las diferencias de sintaxis entre PostgreSQL y MySQL se concentran, no están dispersas. Las sentencias estándar SELECT, JOIN y las agregaciones son portables; los problemas se agrupan en torno a UPSERT, booleanos, operadores de conjuntos, concatenación de cadenas, comillas para identificadores y auto-incremento. Aprende esos seis y podrás migrar la gran mayoría de consultas reales sin sorpresas.

Mantén este modelo mental: cuando algo falla después de una migración, la causa casi siempre es una de las esquinas del dialecto mencionadas, no tu lógica central. Primero formatea la consulta, escanea las cláusulas de alto riesgo, tradúcelas deliberadamente y convertirás un frustrante juego de adivinanzas en una solución rápida y mecánica.

Preguntas Frecuentes#

¿Cuál es la mayor diferencia de sintaxis entre PostgreSQL y MySQL? UPSERT es la diferencia práctica más grande. PostgreSQL usa INSERT ... ON CONFLICT ... DO UPDATE con un objetivo de conflicto explícito y la pseudo-tabla EXCLUDED, mientras que MySQL usa INSERT ... ON DUPLICATE KEY UPDATE e infiere la clave. No hay una traducción automática limpia, por lo que es la cláusula con más probabilidades de romperse al migrar una consulta.

¿Por qué falla la concatenación de cadenas de PostgreSQL en MySQL? PostgreSQL usa el operador || para concatenar cadenas, pero en MySQL || significa OR lógico por defecto. Copiar una consulta de Postgres a MySQL no dará un error de sintaxis; devolverá silenciosamente 0 o 1 en lugar de una cadena unida. Use la función CONCAT() en MySQL para obtener el mismo resultado.

¿MySQL tiene un tipo BOOLEAN real como PostgreSQL? No. PostgreSQL tiene un tipo BOOLEAN nativo que almacena true/false. En MySQL, BOOLEAN y BOOL son alias de TINYINT(1), que almacena enteros, por lo que true se convierte en 1 y false en 0. Al migrar columnas booleanas, espere que los datos aparezcan como 0/1 y actualice cualquier código que esperaba valores de cadena.

¿Puedo usar INTERSECT y EXCEPT en MySQL? Solo en MySQL 8.0.31 y posteriores. PostgreSQL ha soportado INTERSECT y EXCEPT durante mucho tiempo. En versiones anteriores de MySQL debe reescribirlos, usando un inner join o subconsulta IN para INTERSECT, y un patrón LEFT JOIN ... IS NULL o NOT EXISTS para EXCEPT, teniendo cuidado con el manejo de NULL.

¿La mayor parte del SQL es portátil entre PostgreSQL y MySQL? Sí. Aproximadamente el 80 por ciento del SQL cotidiano, incluyendo SELECT, JOIN, WHERE, GROUP BY e INSERT básico, es idéntico o lo suficientemente cercano para ejecutarse en ambos motores. Las diferencias se concentran en UPSERT, booleanos, operadores de conjuntos, comillas en identificadores, concatenación de cadenas y auto-incremento, por lo que una lista de verificación enfocada es mejor que leer mensajes de error uno por uno.

¿Cuál es la forma más rápida de detectar diferencias de dialecto en una consulta? Formatee la consulta primero para que la estructura sea visible, luego busque las cláusulas de alto riesgo. Un formateador que soporte ambos dialectos, como el formateador SQL de Molixa, indentará y alineará el SQL para que ON CONFLICT, || y las diferencias en comillas resalten en lugar de esconderse en una sola línea densa.

More from Molixa

Try Molixa Tools

50+ free AI tools for content creation, SEO, coding, and more. No signup, no watermark.

Explore all tools