Postado originalmente
É muito comum quando temos 2 sistemas que se relacionam em algum momento um precisar conectar no outro banco de dados. Seja pela aplicação(falarei sobre em outro post) ou mesmo direto numa consulta no banco de dados.
Imagina conseguir fazer uma consulta onde fazemos um inner join usando uma tabela de cada banco de dados, como a seguir estando conectado no banco de dados loja e consultando produto que vem de catalogo:
select * from vendas v inner join produtos p on p.id=v.produto_id
Quanto usamos serviços como Heroku temos isso via interface(chamado data link) e outros lugares com possíveis outros nomes, mas por baixo dos panos estão usando uma extensão do Postgres chamada [fdw](https://www.postgresql.org/docs/current/postgres-fdw.html)
Meu objetivo é mostrar como conseguimos alcançar isso para o ambiente de desenvolvimento que é onde vamos escrever esse código então precisamos validá-lo
Configuração
Para começar eu vou deixar o script para criação de 2 bancos de dados para seguirmos o tutorial
create database catalogo
-- Cria a tabela 'produtos'
CREATE TABLE produtos (
id SERIAL PRIMARY KEY,
nome VARCHAR(255) NOT NULL,
quantidade INT NOT NULL,
valor DECIMAL(10, 2) NOT NULL
);
-- Insere uma lista de 10 produtos na tabela
INSERT INTO produtos (nome, quantidade, valor) VALUES
('Notebook Gamer', 15, 7500.50),
('Monitor 4K 27"', 25, 1899.90),
('Teclado Mecânico RGB', 50, 350.00),
('Mouse Sem Fio Ergonômico', 80, 120.75),
('Headset 7.1 Surround', 40, 499.99),
('Cadeira Gamer Confort', 20, 1200.00),
('SSD NVMe 1TB', 60, 850.00),
('Placa de Vídeo RTX 4070', 10, 4500.00),
('Memória RAM 16GB DDR5', 100, 650.25),
('Webcam Full HD', 75, 250.00);
create database loja
-- Cria a tabela 'usuarios'
CREATE TABLE usuarios (
id SERIAL PRIMARY KEY,
nome VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE
);
-- Cria a tabela 'vendas'
-- Esta tabela se relaciona com 'usuarios' e 'produtos'
CREATE TABLE vendas (
id SERIAL PRIMARY KEY,
usuario_id INT NOT NULL,
produto_id INT NOT NULL,
quantidade INT NOT NULL CHECK (quantidade > 0),
data_venda TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
-- Define as chaves estrangeiras para garantir a integridade dos dados
CONSTRAINT fk_usuario FOREIGN KEY(usuario_id) REFERENCES usuarios(id)
);
-- II. INSERIR DADOS DE EXEMPLO
-- Insere 10 usuários na tabela 'usuarios'
INSERT INTO usuarios (nome, email) VALUES
('Ana Silva', 'ana.silva@email.com'),
('Bruno Costa', 'bruno.costa@email.com'),
('Carlos Pereira', 'carlos.pereira@email.com'),
('Daniela Martins', 'daniela.martins@email.com'),
('Eduardo Almeida', 'eduardo.almeida@email.com'),
('Fernanda Lima', 'fernanda.lima@email.com'),
('Gustavo Oliveira', 'gustavo.oliveira@email.com'),
('Helena Souza', 'helena.souza@email.com'),
('Igor Santos', 'igor.santos@email.com'),
('Juliana Ribeiro', 'juliana.ribeiro@email.com');
-- Insere 50 registros de vendas na tabela 'vendas'
-- As datas são variadas para simular um histórico de vendas.
INSERT INTO vendas (usuario_id, produto_id, quantidade, data_venda) VALUES
(1, 3, 1, '2025-07-01 10:30:00'),
(5, 1, 1, '2025-07-01 11:15:00'),
(2, 5, 2, '2025-07-02 14:00:00'),
(10, 8, 1, '2025-07-03 09:05:00'),
(3, 7, 1, '2025-07-04 18:20:00'),
(7, 2, 1, '2025-07-05 12:00:00'),
(4, 10, 3, '2025-07-06 16:45:00'),
(8, 4, 1, '2025-07-07 11:50:00'),
(6, 6, 1, '2025-07-08 13:10:00'),
(9, 9, 2, '2025-07-09 10:00:00'),
(1, 4, 1, '2025-07-10 15:00:00'),
(2, 8, 1, '2025-07-11 17:25:00'),
(5, 2, 1, '2025-07-11 19:00:00'),
(7, 1, 1, '2025-07-12 08:30:00'),
(3, 9, 4, '2025-07-13 11:40:00'),
(10, 5, 1, '2025-07-14 20:00:00'),
(4, 7, 1, '2025-07-15 21:05:00'),
(6, 3, 2, '2025-07-16 09:15:00'),
(8, 6, 1, '2025-07-17 10:00:00'),
(1, 10, 1, '2025-07-18 14:55:00'),
(9, 1, 1, '2025-07-19 16:20:00'),
(5, 5, 1, '2025-07-20 18:30:00'),
(2, 7, 2, '2025-07-21 12:10:00'),
(7, 4, 3, '2025-07-22 13:00:00'),
(3, 2, 1, '2025-07-22 14:40:00'),
(8, 8, 1, '2025-07-23 09:00:00'),
(4, 9, 1, '2025-07-23 11:20:00'),
(10, 3, 1, '2025-07-24 16:00:00'),
(6, 1, 1, '2025-07-24 17:00:00'),
(1, 6, 1, '2025-07-25 19:10:00'),
(2, 10, 2, '2025-07-26 10:45:00'),
(5, 4, 1, '2025-07-26 11:00:00'),
(9, 5, 1, '2025-07-26 15:30:00'),
(7, 7, 1, '2025-07-27 13:25:00'),
(3, 1, 1, '2025-07-27 14:00:00'),
(4, 8, 1, '2025-07-28 10:10:00'),
(8, 2, 2, '2025-07-28 11:30:00'),
(10, 9, 1, '2025-07-28 12:00:00'),
(6, 5, 1, '2025-07-29 18:00:00'),
(1, 7, 1, '2025-07-29 18:45:00'),
(9, 3, 3, '2025-07-29 20:15:00'),
(5, 6, 1, '2025-07-30 09:30:00'),
(2, 4, 1, '2025-07-30 10:00:00'),
(7, 10, 1, '2025-07-30 14:20:00'),
(3, 8, 1, '2025-07-30 15:00:00'),
(4, 1, 1, '2025-07-30 16:10:00'),
(8, 9, 2, '2025-07-30 17:40:00'),
(10, 2, 1, '2025-07-31 08:50:00'),
(6, 7, 1, '2025-07-31 09:00:00'),
(1, 5, 1, '2025-07-31 09:15:00');
Agora que temos 2 bancos de dados, podemos começar.Temos alguns passos para configurar a conexão, sendo eles:
Criar a extensão
loja=# CREATE EXTENSION IF NOT EXISTS postgres_fdw;
CREATE EXTENSION
Checando se tudo está ok
loja=# select * from pg_extension;
oid | extname | extowner | extnamespace | extrelocatable | extversion | extconfig | extcondition
---------+--------------+----------+--------------+----------------+------------+-----------+--------------
13895 | plpgsql | 10 | 11 | f | 1.0 | |
7549345 | postgres_fdw | 10 | 2200 | t | 1.1 | |
(2 rows)
Criar o servidor no banco de dados:
Criei um server chamado foreigndb_fdw, vamos usar esse nome na hora de importar as tabelas, tbm fiz a configuração para o outro banco de dados, basicamente mudou o dbname já que ambos rodam localmente
loja=# CREATE SERVER foreigndb_fdw FOREIGN DATA WRAPPER postgres_fdw OPTIONS (host '127.0.0.1', port '5432', dbname 'catalogo');
CREATE SERVER
Checando se tudo está ok
loja=# \des
List of foreign servers
Name | Owner | Foreign-data wrapper
---------------+--------------+----------------------
foreigndb_fdw | fdwuser | postgres_fdw
(1 row)
Criar user mapping:
Podemos fazer manual trocando fdwuser para o usuário que vamos conectar ou usar o próximo comando que vai selecionar o usuário conectado
Eu já tenho um usuário chamado fdwuser, então uso o mesmo assim não preciso configurar permissões e outras configurações, como é um ambiente local podemos deixar isso simples. No primeiro comando eu passo a mesma senha que uso para conectar, na segunda opção não há senha então só criar o mapping do usuário
loja=# CREATE USER MAPPING FOR fdwuser SERVER foreigndb_fdw OPTIONS (user 'fdwuser', password 'secret');
CREATE USER MAPPING
Comando lendo o usuário logado, o usuário que estamos executando os comandos
SELECT format('CREATE USER MAPPING FOR %I SERVER foreigndb_fdw OPTIONS (user %L)', current_user, current_user) \gexec
Checando se tudo está ok
loja=# select * from pg_user_mapping;
oid | umuser | umserver | umoptions
---------+--------+----------+---------------------
7549353 | 10 | 7549352 | {user=fdwuser}
(1 row)
loja=# select * from pg_user_mappings;
umid | srvid | srvname | umuser | usename | umoptions
---------+---------+---------------+--------+--------------+---------------------
7549353 | 7549352 | foreigndb_fdw | 10 | fdwuser | {user=fdwuser}
(1 row)
Depois que fizemos as configurações agora falta um último passo, importar as tabelas utilizadas no banco de dados. Aqui eu tenho algumas notas que farei na sequência, mas vou deixar um exemplo de como funcionaria.
loja=# IMPORT FOREIGN SCHEMA public FROM SERVER foreigndb_fdw INTO public;
IMPORT FOREIGN SCHEMA
Notas sobre o import
- Sempre que criar novas tabelas precisará fazer o import para que traga a nova tabela;
- Caso tenha conflito com nome de tabela, ambos banco de dados tendo uma tabela com o mesmo nome, podemos criar um schema. Note que que comando acima é feito no final INTO public troque public para o nome do novo schema, e na hora da consulta tem que fazer nome-schema.tabela se criamos um schema catalogo seria assim catalogo.produtos
- O import não traz enum ou coisas do tipo, neste caso eu copio o script de criação e executo no outro banco de dados, assim ele funcionará porque agora terá esses items customizados
- Eu fiz um import de todas tabelas, mas podemos restringir com algumas opções:
- EXCEPT para excluir tabelas, IMPORT FOREIGN SCHEMA public EXCEPT (produtos)
- LIMIT TO para selecionar apenas algumas IMPORT FOREIGN SCHEMA public LIMIT TO (produtos)
Deletando tudo
Hora de limpar a casa, detelando a extensão já irá remover as configurações de server, users mappings e o import das tabelas.
loja=# DROP EXTENSION IF EXISTS postgres_fdw CASCADE;
NOTICE: drop cascades to 3 other objects
DETAIL: drop cascades to server foreigndb_fdw
drop cascades to user mapping for renato.suero on server foreigndb_fdw
drop cascades to foreign table produtos
DROP EXTENSION
Top comments (0)