Jhonatan Pinheiro
Carregando página...
Jhonatan Pinheiro
Carregando página...
Jhonatan Pinheiro
Carregando página...
Os cinco bancos relacionais mais usados, lado a lado: criação de tabela, paginação, upsert, administração e backup — com a mesma tarefa resolvida em cada um.
Todos falam SQL. Nenhum fala o mesmo SQL.
Quem trabalha com dados no Brasil cedo ou tarde encontra os cinco: o Firebird do sistema legado que roda há vinte anos, o MySQL do site, o SQL Server do ERP, o PostgreSQL do produto novo e o Oracle do banco/telecom. Este guia mostra o que muda entre eles — com o comando equivalente lado a lado.
| PostgreSQL | MySQL | SQL Server | Oracle | Firebird | |
|---|---|---|---|---|---|
| Licença | Open source (PostgreSQL) | Open source (GPL) + comercial | Comercial (Express grátis) | Comercial (XE grátis) | Open source (IPL/IDPL) |
| Origem | 1986, Berkeley | 1995, MySQL AB → Oracle | 1989, Microsoft | 1979, Oracle Corp. | 2000, fork do InterBase |
| Forte em | Extensibilidade, padrão SQL, JSON | Leitura web, simplicidade, ecossistema | Integração Microsoft, BI, ferramental | Escala corporativa, PL/SQL, RAC | Leveza, zero administração, embarcado |
| Linguagem procedural | PL/pgSQL (e outras) | SQL/PSM | T-SQL | PL/SQL | PSQL |
| Custo típico | Só infraestrutura | Baixo | Médio/alto por core | Alto por core | Só infraestrutura |
| Cara de uso | Produto novo, SaaS, analytics | Site, CMS, app web | ERP, corporativo Windows | Banco, telecom, ERP grande | Sistema desktop, ponto de venda |
Três observações que economizam discussão:
O mais aderente ao padrão SQL e o mais extensível dos cinco. Virou o default de mercado para produto novo — e é o que este blog usa nos exemplos.
Conectar:
psql -h localhost -U app -d loja
psql "postgresql://app:senha@localhost:5432/loja"Criar tabela e autoincremento:
CREATE TABLE clientes (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, -- padrão 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 e funções 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;Administração:
-- Usuário e permissão mínima
CREATE USER app_web WITH PASSWORD 'senha-forte';
GRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA public TO app_web;
-- Metadados
\dt -- tabelas (psql)
\d clientes -- estrutura da tabela
SELECT * FROM pg_stat_activity WHERE state <> 'idle';
-- Plano de execução
EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM pedidos WHERE cliente_id = 42;# Backup e restore
pg_dump -Fc -U app loja > loja.dump
pg_restore -d loja -j 4 loja.dumpPontos fortes: JSONB indexável, extensões (PostGIS, pgvector, TimescaleDB), CTEs e window functions completas, tipos customizados, índices GIN/GiST/BRIN, replicação lógica.
Pegadinhas: MVCC exige atenção a vacuum e bloat; identificadores sem aspas viram minúsculas; conexão é processo, então pooling (PgBouncer) é praticamente obrigatório em escala.
O banco da web. Simples de subir, ecossistema gigante, presente em qualquer hospedagem. MariaDB é o fork comunitário, compatível na maior parte.
Conectar:
mysql -h localhost -u app -p lojaCriar tabela e 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;Use sempre utf8mb4. O antigo
utf8do MySQL guarda no máximo 3 bytes por caractere e quebra com emoji.
Consultas e funções 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;Administração:
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.sqlPontos fortes: simplicidade, replicação com GTID, enorme base de conhecimento, ótimo desempenho em leitura com InnoDB bem configurado.
Pegadinhas: historicamente permissivo com dado inválido (confira o sql_mode); DDL não é transacional; CTE e window function só a partir da 8.0; comparação de texto insensível a maiúsculas por padrão, o que surpreende quem vem do PostgreSQL.
O banco corporativo do mundo Microsoft. Ferramental de primeira (SSMS, Profiler, Query Store) e integração natural com .NET, Power BI e Azure.
Conectar:
sqlcmd -S localhost -U sa -P senha -d lojaCriar tabela e 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 e funções 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);Administração:
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';
-- Consultas mais 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 consultaBACKUP DATABASE loja TO DISK = 'D:\bkp\loja.bak' WITH COMPRESSION, INIT;
RESTORE DATABASE loja FROM DISK = 'D:\bkp\loja.bak' WITH RECOVERY;Pontos fortes: Query Store (histórico de planos), índices columnstore para BI, Always Encrypted, integração com o ecossistema Microsoft, ferramental gráfico maduro.
Pegadinhas: licenciamento por core pesa no orçamento; NOLOCK espalhado pelo código é leitura suja disfarçada de otimização; collation definido na instalação é trabalhoso de mudar depois.
O banco das operações críticas de grande porte. Recursos de disponibilidade e escala que os outros levaram décadas para alcançar — a um custo proporcional.
Conectar:
sqlplus app/senha@//localhost:1521/XEPDB1Criar tabela e 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 do 12c: sequence + trigger
CREATE SEQUENCE seq_clientes START WITH 1 INCREMENT BY 1;Consultas e funções 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 precisa de 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);Administração:
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;Pontos fortes: PL/SQL maduríssimo, RAC (cluster ativo-ativo), Data Guard, particionamento avançado, AWR para diagnóstico histórico, flashback (voltar a tabela a um instante anterior).
Pegadinhas: string vazia é tratada como NULL — comportamento único entre os cinco; FROM dual obrigatório; licenciamento complexo (recurso ativado por engano vira fatura); identificadores em maiúsculas por padrão.
O menos comentado e o mais presente do que parece: milhares de sistemas comerciais brasileiros — ponto de venda, gestão, automação comercial — rodam sobre Firebird há décadas. Herdeiro do InterBase da Borland, aberto em 2000.
Por que ele sobrevive: o servidor inteiro cabe em poucos megabytes, o banco é um único arquivo .fdb, praticamente não exige DBA e pode rodar embarcado dentro da aplicação. Para software distribuído a centenas de clientes, isso vale mais que qualquer recurso avançado.
Conectar:
isql -user SYSDBA -password senha /dados/loja.fdbCriar tabela e 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 e funções típicas:
SELECT FIRST 10 SKIP 20 * FROM produtos ORDER BY preco DESC; -- sintaxe própria
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; -- equivalente ao dual do OracleUpsert:
UPDATE OR INSERT INTO metricas (dia, acessos)
VALUES (CURRENT_DATE, 1)
MATCHING (dia);Procedure e execução em bloco:
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);Administração:
CREATE USER app_web PASSWORD 'senha-forte';
GRANT SELECT, INSERT, UPDATE ON clientes TO app_web;
-- Metadados ficam nas tabelas 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; -- mostra o plano das consultas seguintes no isql# Backup lógico (gbak) e manutenção (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 # limpa versões antigas
gstat -h /dados/loja.fdb # estatísticas do arquivoPontos fortes: pegada mínima, banco em arquivo único, versão embarcada, instalação e operação simples, licença livre, ótima retrocompatibilidade.
Pegadinhas: ecossistema e comunidade menores; versões antigas (2.5) ainda em produção sem recursos modernos; requer sweep periódico para não acumular versões de registro; ferramental de monitoramento bem mais limitado que o dos outros quatro.
Esta é a tabela para deixar aberta ao lado do editor.
Limitar linhas:
| Banco | Sintaxe |
|---|---|
| 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:
| Banco | Sintaxe |
|---|---|
| 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 |
Data e hora atuais:
| Banco | Sintaxe |
|---|---|
| PostgreSQL | NOW() · CURRENT_DATE |
| MySQL | NOW() · CURDATE() |
| SQL Server | SYSDATETIME() · GETDATE() |
| Oracle | SYSTIMESTAMP · SYSDATE FROM dual |
| Firebird | CURRENT_TIMESTAMP · CURRENT_DATE |
Concatenar texto:
| Banco | Sintaxe |
|---|---|
| PostgreSQL | `a |
| MySQL | CONCAT(a, b) |
| SQL Server | a + b · CONCAT(a, b) |
| Oracle | `a |
| Firebird | `a |
Tratar NULL:
| Banco | Sintaxe |
|---|---|
| PostgreSQL | COALESCE(a, b) |
| MySQL | IFNULL(a, b) · COALESCE |
| SQL Server | ISNULL(a, b) · COALESCE |
| Oracle | NVL(a, b) · COALESCE |
| Firebird | COALESCE(a, b) |
Inserir ou atualizar (upsert):
| Banco | Sintaxe |
|---|---|
| PostgreSQL | INSERT ... ON CONFLICT DO UPDATE |
| MySQL | INSERT ... ON DUPLICATE KEY UPDATE |
| SQL Server | MERGE |
| Oracle | MERGE |
| Firebird | UPDATE OR INSERT ... MATCHING |
Listar tabelas:
| Banco | Comando |
|---|---|
| PostgreSQL | \dt ou 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 o plano de execução:
| Banco | Comando |
|---|---|
| PostgreSQL | EXPLAIN (ANALYZE, BUFFERS) ... |
| MySQL | EXPLAIN ANALYZE ... |
| SQL Server | SET SHOWPLAN_ALL ON ou plano gráfico no SSMS |
| Oracle | EXPLAIN PLAN FOR ... + DBMS_XPLAN.DISPLAY |
| Firebird | SET PLAN ON |
Backup:
| Banco | Comando |
|---|---|
| PostgreSQL | pg_dump / pg_basebackup |
| MySQL | mysqldump / XtraBackup |
| SQL Server | BACKUP DATABASE ... TO DISK |
| Oracle | RMAN BACKUP DATABASE |
| Firebird | gbak -b |
O que sempre dá trabalho, em ordem de dor:
NUMBER do Oracle, VARCHAR2, DATETIME2, TINYINT(1) como booleano do MySQL — cada um exige mapeamento explícito.'' IS NULL.Estratégia que costuma funcionar:
# 1. Converta o schema com ferramenta e revise à mão
pgloader mysql://user@host/loja postgresql://user@host/loja
# 2. Carregue os dados em staging e valide contagens e somas
# 3. Rode a aplicação contra o novo banco em paralelo (shadow), comparando resultados
# 4. Vire com replicação lógica para reduzir a janela de paradaE a regra que evita projeto de dois anos: migre por necessidade concreta (fim de suporte, custo de licença, recurso que falta), nunca por preferência técnica.
Um caminho curto para a decisão:
E o critério que costuma pesar mais que todos os outros: o banco que a sua equipe sabe operar às três da manhã. Recurso avançado não substitui quem entende o que está acontecendo quando o sistema para.