Tema
¿Qué es PostgreSQL?
PostgreSQL lleva más de 35 años de desarrollo y hoy es la base de datos más querida por los desarrolladores según las encuestas de Stack Overflow. En esta lección conocerás su historia, sus características principales, en qué se diferencia de MySQL y cómo decidir cuál usar en cada proyecto.
Concepto
PostgreSQL es un SGBD objeto-relacional de código abierto. "Relacional" porque organiza los datos en tablas con SQL; "objeto" porque además permite definir tipos propios, herencia de tablas, funciones en varios lenguajes y extensiones que añaden capacidades nuevas.
Si MySQL es un coche confiable y económico que encuentras en cualquier taller, PostgreSQL es una camioneta todoterreno con caja de herramientas incluida: igual de confiable, con más capacidades y ligeramente más exigente de configurar.
Un poco de historia
| Año | Hito |
|---|---|
| 1986 | Michael Stonebraker inicia POSTGRES en la Universidad de Berkeley (sucesor de Ingres) |
| 1995 | Se añade soporte SQL: Postgres95 |
| 1996 | Cambia de nombre a PostgreSQL y pasa a ser un proyecto comunitario |
| 2010 | Versión 9.0: replicación integrada |
| 2014 | 9.4: tipo JSONB |
| 2017 | 10: particionamiento declarativo, replicación lógica, IDENTITY |
| 2020s | Una versión mayor cada año (16, 17...), cada una con 5 años de soporte |
PostgreSQL no tiene dueño: lo desarrolla una comunidad global con apoyo de muchas empresas, bajo una licencia muy permisiva (similar a MIT/BSD). Nadie puede "comprarlo" ni cambiarle la licencia.
Características principales
| Característica | Qué significa para ti |
|---|---|
| Cumplimiento del estándar SQL | Lo que aprendiste en el curso de SQL funciona tal cual, incluidas CTEs, ventanas y FULL JOIN |
| ACID estricto | Transacciones confiables; hasta los cambios de estructura (CREATE, ALTER) son transaccionales |
| MVCC | Las lecturas no bloquean escrituras y viceversa |
| Tipos avanzados | JSONB, arrays, UUID, rangos, INET, geometría, enums, tipos propios |
| Índices variados | B-tree, Hash, GIN, GiST, SP-GiST, BRIN; parciales y de expresión |
| Búsqueda de texto completo | Integrada, con soporte para español |
| Extensiones | PostGIS (mapas), pgvector (IA/embeddings), pg_trgm, TimescaleDB, pg_cron... |
| Lenguajes procedurales | PL/pgSQL, y también Python, JavaScript (plv8), Perl |
| Seguridad | Roles granulares, Row Level Security (RLS), SSL |
DDL transaccional: una joya poco conocida
sql
BEGIN;
ALTER TABLE productos ADD COLUMN descripcion TEXT;
CREATE INDEX idx_productos_nombre ON productos (nombre);
-- algo salió mal en la migración...
ROLLBACK; -- ¡la columna y el índice desaparecen como si nada!En MySQL, cada ALTER TABLE hace commit automático: si una migración falla a la mitad, la base queda en un estado intermedio. En PostgreSQL, toda la migración puede ser atómica.
PostgreSQL vs MySQL
| Aspecto | PostgreSQL | MySQL |
|---|---|---|
| Licencia | PostgreSQL License (muy permisiva), comunitario | GPL / comercial, propiedad de Oracle |
| Estándar SQL | Muy apegado | Más permisivo, con extensiones propias |
| Comportamiento ante datos inválidos | Siempre estricto (rechaza) | Estricto solo si sql_mode lo indica |
CHECK | Siempre | Desde 8.0.16 |
| DDL transaccional | Sí | No |
| JSON | JSON y JSONB (binario, indexable con GIN) | JSON (binario), índices vía columnas generadas |
| Arrays | Sí | No |
| Enums | CREATE TYPE ... AS ENUM reutilizable | ENUM por columna |
| UUID nativo | Sí (gen_random_uuid()) | Se guarda como CHAR(36) o BINARY(16) |
FULL OUTER JOIN | Sí | No (se emula) |
| Vistas materializadas | Sí | No |
| Índices parciales y de expresión | Sí | Expresión desde 8.0.13; parciales no |
| Texto completo | Potente, con diccionarios por idioma | FULLTEXT más básico |
Mayúsculas en LIKE | Distingue (usa ILIKE) | Normalmente no distingue |
| Autoincremento | IDENTITY / SERIAL | AUTO_INCREMENT |
| Hosting compartido (cPanel) | Poco común | Casi siempre |
| Servicios en la nube | Supabase, Neon, RDS, Cloud SQL, Railway, Render | PlanetScale, RDS, Cloud SQL |
| Mantenimiento | Requiere VACUUM (automático con autovacuum) | Menos tareas visibles |
Diferencias de sintaxis que te encontrarás
| Necesidad | PostgreSQL | MySQL |
|---|---|---|
| Comillas para identificadores | "columna" | `columna` |
| Concatenar | 'a' || 'b' o CONCAT() | CONCAT() |
| Búsqueda sin mayúsculas | ILIKE | LIKE |
| Id del último INSERT | INSERT ... RETURNING id | LAST_INSERT_ID() |
| Fecha actual con hora | now() | NOW() |
| Conversión de tipos | '2024-01-01'::date o CAST(...) | CAST(...) |
| Limitar filas | LIMIT 10 OFFSET 20 | LIMIT 20, 10 o LIMIT 10 OFFSET 20 |
| "Insertar o actualizar" | INSERT ... ON CONFLICT (col) DO UPDATE | INSERT ... ON DUPLICATE KEY UPDATE |
| Booleanos | TRUE/FALSE reales | 1/0 |
sql
-- "Upsert" en PostgreSQL: inserta o actualiza el stock si el SKU ya existe
INSERT INTO productos (sku, nombre, precio, stock)
VALUES ('ELE-001', 'Audífonos Bluetooth', 899.00, 30)
ON CONFLICT (sku)
DO UPDATE SET stock = EXCLUDED.stock, precio = EXCLUDED.precio;Error común: nombres con mayúsculas
PostgreSQL convierte a minúsculas los identificadores sin comillas: CREATE TABLE Clientes crea clientes. Pero si usas comillas ("Clientes"), el nombre queda con mayúscula y siempre tendrás que escribirlo con comillas. Algunas herramientas (y Prisma por defecto con modelos en PascalCase) generan nombres así. Usa nombres en minúsculas con guion bajo (detalle_pedido) y te ahorrarás problemas.
¿Cuándo elegir cada uno?
| Elige PostgreSQL si... | Elige MySQL si... |
|---|---|
| Usas Supabase, Neon, Django, Rails o Prisma | El proyecto vivirá en hosting compartido con cPanel |
| Necesitas JSONB, arrays, búsqueda de texto o geodatos | Usas WordPress, WooCommerce, PrestaShop, Moodle |
| Quieres reglas estrictas y migraciones atómicas | El equipo o cliente ya tiene experiencia y herramientas MySQL |
| Haces reportes o analítica con SQL complejo | Necesitas la opción más barata y disponible |
| Planeas IA con embeddings (pgvector) | Es un proyecto Laravel tradicional en hosting |
Recomendación práctica
Para proyectos nuevos con Node.js, Next.js o servicios modernos en la nube, PostgreSQL es una apuesta excelente. Para sitios de clientes en hosting compartido y el ecosistema WordPress, MySQL. Saber ambos te hace mucho más contratable.
Ejemplo: un vistazo a lo que hace especial a Postgres
sql
-- Tabla de productos con tipos que MySQL no tiene (o no igual)
CREATE TABLE productos (
id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
uuid UUID NOT NULL DEFAULT gen_random_uuid() UNIQUE,
nombre TEXT NOT NULL,
precio NUMERIC(10,2) NOT NULL CHECK (precio >= 0),
etiquetas TEXT[] NOT NULL DEFAULT '{}', -- array de textos
atributos JSONB NOT NULL DEFAULT '{}', -- documento JSON binario
creado_en TIMESTAMPTZ NOT NULL DEFAULT now() -- fecha con zona horaria
);
INSERT INTO productos (nombre, precio, etiquetas, atributos) VALUES
('Audífonos Bluetooth', 899.00, ARRAY['audio', 'inalámbrico'],
'{"color": "negro", "bateria_horas": 30}'),
('Teclado mecánico', 1250.00, ARRAY['gamer', 'oficina'],
'{"switches": "red", "idioma": "es", "rgb": true}')
RETURNING id, uuid, nombre;
-- Productos con la etiqueta 'gamer'
SELECT nombre FROM productos WHERE 'gamer' = ANY (etiquetas);
-- Productos con RGB (consulta dentro del JSON)
SELECT nombre, atributos ->> 'switches' AS switches
FROM productos
WHERE atributos @> '{"rgb": true}';| nombre | switches |
|---|---|
| Teclado mecánico | red |
Verás todo esto a detalle en Tipos avanzados.
Ejercicios
- Menciona tres características de PostgreSQL que MySQL no tiene (o tiene de forma limitada).
- ¿Qué significa que el DDL sea transaccional y por qué ayuda en las migraciones?
- Un cliente quiere una tienda con WooCommerce en su hosting de cPanel. ¿Qué base de datos usarías? ¿Y para una app con Next.js y Supabase?
- Reescribe para PostgreSQL esta consulta de MySQL:
SELECT `nombre` FROM `clientes` WHERE nombre LIKE '%ana%';(sin importar mayúsculas).
Ver solución
- Por ejemplo: arrays,
JSONBindexable con GIN, vistas materializadas, índices parciales,FULL OUTER JOIN, DDL transaccional, UUID nativo, RLS. - Que los cambios de estructura (
CREATE,ALTER,DROP) participan en transacciones: si una migración de varios pasos falla, unROLLBACKdeja la base exactamente como estaba, sin estados intermedios. - WooCommerce en cPanel: MySQL/MariaDB (WordPress lo requiere y es lo que ofrece el hosting). Next.js + Supabase: PostgreSQL (Supabase es PostgreSQL).
sql
SELECT nombre FROM clientes WHERE nombre ILIKE '%ana%';Resumen
- PostgreSQL es un SGBD objeto-relacional, comunitario y de licencia permisiva, con más de 35 años de historia.
- Destaca por su apego al estándar, ACID estricto, DDL transaccional, tipos avanzados y extensiones.
- Frente a MySQL: más estricto y con más funciones; MySQL gana en disponibilidad en hosting compartido.
- Cuidado con las diferencias de sintaxis: comillas dobles,
ILIKE,RETURNING,ON CONFLICT. - Usa nombres en minúsculas para evitar el problema de las comillas.