Jhonatan Pinheiro
Cargando página...
Jhonatan Pinheiro
Cargando página...
Jhonatan Pinheiro
Cargando página...
Las cinco bases de datos relacionales más usadas, lado a lado: creación de tablas, paginación, upsert, administración y backup en cada una.
Todos hablan SQL. Ninguno habla el mismo SQL.
Quien trabaja con datos en Brasil tarde o temprano se encuentra con los cinco: el Firebird del sistema heredado que lleva veinte años funcionando, el MySQL del sitio web, el SQL Server del ERP, el PostgreSQL del producto nuevo y el Oracle del banco o la telco. Esta guía muestra lo que cambia entre ellos — con el comando equivalente lado a lado.
| PostgreSQL | MySQL | SQL Server | Oracle | Firebird | |
|---|---|---|---|---|---|
| Licencia | Open source (PostgreSQL) | Open source (GPL) + comercial | Comercial (Express gratis) | Comercial (XE gratis) | Open source (IPL/IDPL) |
| Origen | 1986, Berkeley | 1995, MySQL AB → Oracle | 1989, Microsoft | 1979, Oracle Corp. | 2000, fork de InterBase |
| Fuerte en | Extensibilidad, estándar SQL, JSON | Lectura web, simplicidad, ecosistema | Integración Microsoft, BI, herramientas | Escala corporativa, PL/SQL, RAC | Ligereza, cero administración, embebido |
| Lenguaje procedural | PL/pgSQL (y otros) | SQL/PSM | T-SQL | PL/SQL | PSQL |
| Coste típico | Solo infraestructura | Bajo | Medio/alto por core | Alto por core | Solo infraestructura |
| Uso típico | Producto nuevo, SaaS, analytics | Sitio web, CMS, app web | ERP, corporativo Windows | Banca, telco, ERP grande | Sistema de escritorio, punto de venta |
Tres observaciones que ahorran discusión:
El más apegado al estándar SQL y el más extensible de los cinco. Se ha convertido en el valor por defecto del mercado para un producto nuevo — y es el que usa este blog en los ejemplos.
Conectar:
psql -h localhost -U app -d loja
psql "postgresql://app:senha@localhost:5432/loja"Crear tabla y autoincremento:
CREATE TABLE clientes (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, -- estándar SQL moderno
nome VARCHAR(120) NOT NULL,
email VARCHAR(200) UNIQUE NOT NULL,
dados JSONB NOT NULL DEFAULT '{}',
criado_em TIMESTAMPTZ NOT NULL DEFAULT NOW()
);Consultas y funciones típicas:
SELECT * FROM produtos ORDER BY preco DESC LIMIT 10 OFFSET 20;
SELECT nome || ' — ' || categoria AS descricao FROM produtos;
SELECT DATE_TRUNC('month', criado_em) AS mes, SUM(total) FROM pedidos GROUP BY 1;
SELECT payload->>'tipo' AS tipo FROM eventos WHERE payload @> '{"tipo":"compra"}';Upsert:
INSERT INTO metricas (dia, acessos) VALUES (CURRENT_DATE, 1)
ON CONFLICT (dia) DO UPDATE SET acessos = metricas.acessos + 1;Administración:
-- Usuario y permiso mínimo
CREATE USER app_web WITH PASSWORD 'senha-forte';
GRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA public TO app_web;
-- Metadatos
\dt -- tablas (psql)
\d clientes -- estructura de la tabla
SELECT * FROM pg_stat_activity WHERE state <> 'idle';
-- Plan de ejecución
EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM pedidos WHERE cliente_id = 42;# Backup y restore
pg_dump -Fc -U app loja > loja.dump
pg_restore -d loja -j 4 loja.dumpPuntos fuertes: JSONB indexable, extensiones (PostGIS, pgvector, TimescaleDB), CTEs y window functions completas, tipos personalizados, índices GIN/GiST/BRIN, replicación lógica.
Trampas: el MVCC exige atención al vacuum y al bloat; los identificadores sin comillas pasan a minúsculas; una conexión es un proceso, así que el pooling (PgBouncer) es prácticamente obligatorio a escala.
La base de datos de la web. Sencilla de levantar, ecosistema gigantesco, presente en cualquier hosting. MariaDB es el fork comunitario, compatible en su mayor parte.
Conectar:
mysql -h localhost -u app -p lojaCrear tabla y autoincremento:
CREATE TABLE clientes (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
nome VARCHAR(120) NOT NULL,
email VARCHAR(200) NOT NULL UNIQUE,
criado_em DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;Usa siempre utf8mb4. El antiguo
utf8de MySQL guarda como máximo 3 bytes por carácter y se rompe con los emojis.
Consultas y funciones típicas:
SELECT * FROM produtos ORDER BY preco DESC LIMIT 10 OFFSET 20;
SELECT CONCAT(nome, ' — ', categoria) AS descricao FROM produtos;
SELECT DATE_FORMAT(criado_em, '%Y-%m') AS mes, SUM(total) FROM pedidos GROUP BY 1;
SELECT JSON_EXTRACT(dados, '$.tipo') FROM eventos;Upsert:
INSERT INTO metricas (dia, acessos) VALUES (CURRENT_DATE, 1)
ON DUPLICATE KEY UPDATE acessos = acessos + 1;Administración:
CREATE USER 'app_web'@'%' IDENTIFIED BY 'senha-forte';
GRANT SELECT, INSERT, UPDATE ON loja.* TO 'app_web'@'%';
FLUSH PRIVILEGES;
SHOW TABLES;
DESCRIBE clientes;
SHOW CREATE TABLE clientes;
SHOW FULL PROCESSLIST;
EXPLAIN ANALYZE SELECT * FROM pedidos WHERE cliente_id = 42;mysqldump --single-transaction --routines --triggers loja > loja.sql
mysql loja < loja.sqlPuntos fuertes: simplicidad, replicación con GTID, una base de conocimiento enorme, gran rendimiento en lectura con un InnoDB bien configurado.
Trampas: históricamente permisivo con los datos inválidos (revisa el sql_mode); el DDL no es transaccional; las CTE y las window functions solo a partir de la 8.0; la comparación de texto es insensible a mayúsculas por defecto, lo que sorprende a quien viene de PostgreSQL.
La base de datos corporativa del mundo Microsoft. Herramientas de primera (SSMS, Profiler, Query Store) e integración natural con .NET, Power BI y Azure.
Conectar:
sqlcmd -S localhost -U sa -P senha -d lojaCrear tabla y autoincremento:
CREATE TABLE clientes (
id BIGINT IDENTITY(1,1) PRIMARY KEY,
nome NVARCHAR(120) NOT NULL,
email NVARCHAR(200) NOT NULL UNIQUE,
criado_em DATETIME2 NOT NULL DEFAULT SYSDATETIME()
);Consultas y funciones típicas:
SELECT TOP 10 * FROM produtos ORDER BY preco DESC;
SELECT * FROM produtos ORDER BY preco DESC
OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;
SELECT nome + ' — ' + categoria AS descricao FROM produtos;
SELECT FORMAT(criado_em, 'yyyy-MM') AS mes, SUM(total) FROM pedidos GROUP BY FORMAT(criado_em, 'yyyy-MM');
SELECT ISNULL(telefone, 'sem contato') FROM clientes;Upsert:
MERGE INTO metricas AS destino
USING (SELECT CAST(GETDATE() AS DATE) AS dia) AS origem
ON destino.dia = origem.dia
WHEN MATCHED THEN UPDATE SET acessos = destino.acessos + 1
WHEN NOT MATCHED THEN INSERT (dia, acessos) VALUES (origem.dia, 1);Administración:
CREATE LOGIN app_web WITH PASSWORD = 'senha-forte';
CREATE USER app_web FOR LOGIN app_web;
ALTER ROLE db_datareader ADD MEMBER app_web;
SELECT name FROM sys.tables;
EXEC sp_help 'clientes';
-- Las consultas más caras
SELECT TOP 10 total_worker_time/execution_count AS media_cpu, text
FROM sys.dm_exec_query_stats CROSS APPLY sys.dm_exec_sql_text(sql_handle)
ORDER BY media_cpu DESC;
SET STATISTICS IO, TIME ON; -- diagnóstico de consultasBACKUP DATABASE loja TO DISK = 'D:\bkp\loja.bak' WITH COMPRESSION, INIT;
RESTORE DATABASE loja FROM DISK = 'D:\bkp\loja.bak' WITH RECOVERY;Puntos fuertes: Query Store (histórico de planes), índices columnstore para BI, Always Encrypted, integración con el ecosistema Microsoft, herramientas gráficas maduras.
Trampas: la licencia por core pesa en el presupuesto; el NOLOCK repartido por el código es una lectura sucia disfrazada de optimización; la collation definida en la instalación es laboriosa de cambiar después.
La base de datos de las operaciones críticas de gran tamaño. Funciones de disponibilidad y escala que a las demás les llevó décadas alcanzar — a un coste proporcional.
Conectar:
sqlplus app/senha@//localhost:1521/XEPDB1Crear tabla y autoincremento:
CREATE TABLE clientes (
id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, -- 12c+
nome VARCHAR2(120) NOT NULL,
email VARCHAR2(200) NOT NULL UNIQUE,
criado_em TIMESTAMP DEFAULT SYSTIMESTAMP NOT NULL
);
-- Antes de la 12c: sequence + trigger
CREATE SEQUENCE seq_clientes START WITH 1 INCREMENT BY 1;Consultas y funciones típicas:
SELECT * FROM produtos ORDER BY preco DESC FETCH FIRST 10 ROWS ONLY;
SELECT nome || ' — ' || categoria AS descricao FROM produtos;
SELECT TO_CHAR(criado_em, 'YYYY-MM') AS mes, SUM(total) FROM pedidos GROUP BY TO_CHAR(criado_em, 'YYYY-MM');
SELECT NVL(telefone, 'sem contato') FROM clientes;
SELECT SYSDATE FROM dual; -- toda consulta necesita un FROM
SELECT ADD_MONTHS(SYSDATE, 1) FROM dual;Upsert:
MERGE INTO metricas d
USING (SELECT TRUNC(SYSDATE) AS dia FROM dual) o
ON (d.dia = o.dia)
WHEN MATCHED THEN UPDATE SET d.acessos = d.acessos + 1
WHEN NOT MATCHED THEN INSERT (dia, acessos) VALUES (o.dia, 1);Administración:
CREATE USER app_web IDENTIFIED BY "senha-forte";
GRANT CONNECT, RESOURCE TO app_web;
GRANT SELECT, INSERT, UPDATE ON loja.pedidos TO app_web;
SELECT table_name FROM user_tables;
DESC clientes;
SELECT sql_id, elapsed_time/executions AS media FROM v$sql ORDER BY media DESC FETCH FIRST 10 ROWS ONLY;
EXPLAIN PLAN FOR SELECT * FROM pedidos WHERE cliente_id = 42;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);# RMAN
rman target /
RMAN> BACKUP DATABASE PLUS ARCHIVELOG;
RMAN> RESTORE DATABASE;Puntos fuertes: un PL/SQL sumamente maduro, RAC (clúster activo-activo), Data Guard, particionado avanzado, AWR para el diagnóstico histórico, flashback (devolver una tabla a un instante anterior).
Trampas: la cadena vacía se trata como NULL — comportamiento único entre los cinco; el FROM dual es obligatorio; licenciamiento complejo (una función activada por error se convierte en factura); identificadores en mayúsculas por defecto.
El menos comentado y más presente de lo que parece: miles de sistemas comerciales brasileños — punto de venta, gestión, automatización comercial — llevan décadas funcionando sobre Firebird. Heredero del InterBase de Borland, abierto en 2000.
Por qué sobrevive: el servidor entero cabe en unos pocos megabytes, la base de datos es un único archivo .fdb, prácticamente no exige DBA y puede correr embebido dentro de la aplicación. Para software distribuido a cientos de clientes, eso vale más que cualquier función avanzada.
Conectar:
isql -user SYSDBA -password senha /dados/loja.fdbCrear tabla y autoincremento:
CREATE TABLE clientes (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, -- Firebird 3+
nome VARCHAR(120) NOT NULL,
email VARCHAR(200) NOT NULL UNIQUE,
criado_em TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
);
-- Firebird 2.5: generator + trigger
CREATE GENERATOR gen_clientes_id;
SET TERM ^ ;
CREATE TRIGGER trg_clientes_bi FOR clientes ACTIVE BEFORE INSERT POSITION 0 AS
BEGIN
IF (NEW.id IS NULL) THEN NEW.id = GEN_ID(gen_clientes_id, 1);
END^
SET TERM ; ^Consultas y funciones típicas:
SELECT FIRST 10 SKIP 20 * FROM produtos ORDER BY preco DESC; -- sintaxis propia
SELECT * FROM produtos ORDER BY preco DESC ROWS 21 TO 30; -- alternativa
SELECT nome || ' — ' || categoria AS descricao FROM produtos;
SELECT EXTRACT(YEAR FROM criado_em) AS ano, SUM(total) FROM pedidos GROUP BY 1;
SELECT COALESCE(telefone, 'sem contato') FROM clientes;
SELECT CURRENT_DATE FROM RDB$DATABASE; -- el equivalente al dual de OracleUpsert:
UPDATE OR INSERT INTO metricas (dia, acessos)
VALUES (CURRENT_DATE, 1)
MATCHING (dia);Procedimiento y ejecución en bloque:
SET TERM ^ ;
CREATE PROCEDURE total_do_cliente (p_id BIGINT)
RETURNS (total DECIMAL(12,2)) AS
BEGIN
SELECT COALESCE(SUM(total), 0) FROM pedidos WHERE cliente_id = :p_id INTO :total;
SUSPEND;
END^
SET TERM ; ^
SELECT * FROM total_do_cliente(42);Administración:
CREATE USER app_web PASSWORD 'senha-forte';
GRANT SELECT, INSERT, UPDATE ON clientes TO app_web;
-- Los metadatos están en las tablas de sistema RDB$
SELECT RDB$RELATION_NAME FROM RDB$RELATIONS WHERE RDB$SYSTEM_FLAG = 0;
SELECT RDB$FIELD_NAME FROM RDB$RELATION_FIELDS WHERE RDB$RELATION_NAME = 'CLIENTES';
SET PLAN ON; -- muestra el plan de las siguientes consultas en isql# Backup lógico (gbak) y mantenimiento (gfix)
gbak -b -user SYSDBA -password senha /dados/loja.fdb /backup/loja.fbk
gbak -c -user SYSDBA -password senha /backup/loja.fbk /dados/loja_restaurado.fdb
gfix -sweep -user SYSDBA -password senha /dados/loja.fdb # limpia versiones antiguas
gstat -h /dados/loja.fdb # estadísticas del archivoPuntos fuertes: huella mínima, base de datos en un único archivo, versión embebida, instalación y operación sencillas, licencia libre, excelente retrocompatibilidad.
Trampas: ecosistema y comunidad más pequeños; versiones antiguas (2.5) todavía en producción sin funciones modernas; necesita un sweep periódico para que no se acumulen versiones de registro; herramientas de monitorización bastante más limitadas que las de los otros cuatro.
Esta es la tabla que conviene dejar abierta junto al editor.
Limitar filas:
| Base de datos | Sintaxis |
|---|---|
| PostgreSQL | SELECT * FROM t ORDER BY x LIMIT 10 OFFSET 20 |
| MySQL | SELECT * FROM t ORDER BY x LIMIT 10 OFFSET 20 |
| SQL Server | SELECT TOP 10 * FROM t ORDER BY x |
| Oracle | SELECT * FROM t ORDER BY x FETCH FIRST 10 ROWS ONLY |
| Firebird | SELECT FIRST 10 SKIP 20 * FROM t ORDER BY x |
Autoincremento:
| Base de datos | Sintaxis |
|---|---|
| PostgreSQL | id BIGINT GENERATED ALWAYS AS IDENTITY |
| MySQL | id BIGINT AUTO_INCREMENT |
| SQL Server | id BIGINT IDENTITY(1,1) |
| Oracle | id NUMBER GENERATED ALWAYS AS IDENTITY |
| Firebird | id BIGINT GENERATED BY DEFAULT AS IDENTITY |
Fecha y hora actuales:
| Base de datos | Sintaxis |
|---|---|
| PostgreSQL | NOW() · CURRENT_DATE |
| MySQL | NOW() · CURDATE() |
| SQL Server | SYSDATETIME() · GETDATE() |
| Oracle | SYSTIMESTAMP · SYSDATE FROM dual |
| Firebird | CURRENT_TIMESTAMP · CURRENT_DATE |
Concatenar texto:
| Base de datos | Sintaxis |
|---|---|
| PostgreSQL | `a |
| MySQL | CONCAT(a, b) |
| SQL Server | a + b · CONCAT(a, b) |
| Oracle | `a |
| Firebird | `a |
Tratar el NULL:
| Base de datos | Sintaxis |
|---|---|
| PostgreSQL | COALESCE(a, b) |
| MySQL | IFNULL(a, b) · COALESCE |
| SQL Server | ISNULL(a, b) · COALESCE |
| Oracle | NVL(a, b) · COALESCE |
| Firebird | COALESCE(a, b) |
Insertar o actualizar (upsert):
| Base de datos | Sintaxis |
|---|---|
| PostgreSQL | INSERT ... ON CONFLICT DO UPDATE |
| MySQL | INSERT ... ON DUPLICATE KEY UPDATE |
| SQL Server | MERGE |
| Oracle | MERGE |
| Firebird | UPDATE OR INSERT ... MATCHING |
Listar tablas:
| Base de datos | Comando |
|---|---|
| PostgreSQL | \dt o information_schema.tables |
| MySQL | SHOW TABLES |
| SQL Server | SELECT name FROM sys.tables |
| Oracle | SELECT table_name FROM user_tables |
| Firebird | SELECT RDB$RELATION_NAME FROM RDB$RELATIONS |
Ver el plan de ejecución:
| Base de datos | Comando |
|---|---|
| PostgreSQL | EXPLAIN (ANALYZE, BUFFERS) ... |
| MySQL | EXPLAIN ANALYZE ... |
| SQL Server | SET SHOWPLAN_ALL ON o el plan gráfico en SSMS |
| Oracle | EXPLAIN PLAN FOR ... + DBMS_XPLAN.DISPLAY |
| Firebird | SET PLAN ON |
Backup:
| Base de datos | Comando |
|---|---|
| PostgreSQL | pg_dump / pg_basebackup |
| MySQL | mysqldump / XtraBackup |
| SQL Server | BACKUP DATABASE ... TO DISK |
| Oracle | RMAN BACKUP DATABASE |
| Firebird | gbak -b |
Lo que siempre da trabajo, en orden de dolor:
NUMBER de Oracle, VARCHAR2, DATETIME2, el TINYINT(1) de MySQL como booleano — cada uno exige un mapeo explícito.'' IS NULL.Una estrategia que suele funcionar:
# 1. Convierte el esquema con una herramienta y revísalo a mano
pgloader mysql://user@host/loja postgresql://user@host/loja
# 2. Carga los datos en staging y valida recuentos y sumas
# 3. Ejecuta la aplicación contra la nueva base en paralelo (shadow), comparando resultados
# 4. Haz el cambio con replicación lógica para reducir la ventana de paradaY la regla que evita un proyecto de dos años: migra por una necesidad concreta (fin de soporte, coste de licencia, una función que falta), nunca por preferencia técnica.
Un camino corto hacia la decisión:
Y el criterio que suele pesar más que todos los demás: la base de datos que tu equipo sabe operar a las tres de la madrugada. Una función avanzada no sustituye a quien entiende lo que está pasando cuando el sistema se para.