Durante anos, quem tinha dados em formato de grafo precisava escolher entre dois caminhos desconfortáveis: ficar no PostgreSQL e simular relacionamentos com CTEs recursivas, ou subir um banco de grafos dedicado, como o Neo4j, e passar a manter mais um sistema. O PostgreSQL 19 acaba com esse dilema ao trazer consultas de grafo nativas direto para o núcleo do banco — rodando sobre as tabelas que você já tem.
Este artigo é um guia prático do recurso: o que é o SQL/PGQ, como definir um grafo sobre tabelas relacionais existentes, exemplos reais de consulta, o que ainda não está pronto e as outras novidades que acompanham a versão. Um aviso antes de começar: até o momento, o PostgreSQL 19 está em beta, com lançamento oficial previsto para setembro de 2026, então a sintaxe mostrada reflete a especificação atual e pode mudar até a versão final.
O contexto: relacional ou grafo?
Dados com cara de rede — redes sociais, motores de recomendação, hierarquias de permissão, organogramas, cadeias de suprimento — sempre foram um ponto de atrito no mundo relacional. Dá para resolver com CTEs recursivas, mas a consulta fica verbosa e difícil de manter. A alternativa, um banco de grafos separado, resolve a sintaxe, mas adiciona um serviço novo, outra cópia dos dados e mais complexidade operacional.
O PostgreSQL 19 implementa o padrão SQL/PGQ (parte 16 da ISO SQL:2023), trazendo consultas de grafo de propriedades para dentro do banco relacional. Não há novo motor de armazenamento, nem serviço separado, nem migração de dados: as suas tabelas atuais passam a ser expostas como um grafo.
O que é o SQL/PGQ
O SQL/PGQ (Property Graph Queries) deixa você definir um grafo de propriedades sobre tabelas relacionais que já existem e consultar relacionamentos com uma sintaxe de casamento de padrões. O detalhe importante: internamente, as consultas de grafo são reescritas em operações relacionais comuns e usam os seus índices existentes. É semântica de grafo por cima do motor relacional — não um substituto dele.
Na prática: um grafo de rede social
O melhor jeito de entender é construir um grafo do zero. Comece com as tabelas que servem de base — uma de nós (usuários) e uma de arestas (quem segue quem).
-- Users (nodes)
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL
);
-- Follows relationships (edges)
CREATE TABLE follows (
follower_id INT NOT NULL REFERENCES users(id),
followed_id INT NOT NULL REFERENCES users(id),
since DATE DEFAULT CURRENT_DATE
);
INSERT INTO users (name) VALUES
('Alice'), ('Bob'), ('Carol'), ('Dave'), ('Eve');
INSERT INTO follows (follower_id, followed_id) VALUES
(1, 2), -- Alice -> Bob
(2, 3), -- Bob -> Carol
(3, 4), -- Carol -> Dave
(1, 5), -- Alice -> Eve
(5, 3); -- Eve -> Carol
Agora vem o pulo do gato: declarar um grafo de propriedades sobre essas tabelas. Nenhum dado é copiado — o grafo é uma visão sobre o relacional.
CREATE PROPERTY GRAPH social_graph
VERTEX TABLES (
users
LABEL person
PROPERTIES (id, name)
)
EDGE TABLES (
follows
SOURCE KEY (follower_id) REFERENCES users (id)
DESTINATION KEY (followed_id) REFERENCES users (id)
LABEL follows
PROPERTIES (since)
);
Com o grafo definido, a primeira consulta: quem a Alice segue? Repare na sintaxe de padrão, com o nó de origem, a aresta direcionada e o nó de destino.
SELECT *
FROM GRAPH_TABLE (social_graph
MATCH
(a IS person WHERE a.name = 'Alice')
-[IS follows]->
(b IS person)
COLUMNS (a.name AS follower, b.name AS following)
);
-- follower | following
-- Alice | Bob
-- Alice | Eve
Para “amigos de amigos”, basta encadear mais um salto no padrão:
SELECT DISTINCT friend_of_friend
FROM GRAPH_TABLE (social_graph
MATCH
(a IS person WHERE a.name = 'Alice')
-[IS follows]->
(b IS person)
-[IS follows]->
(c IS person)
COLUMNS (c.name AS friend_of_friend)
)
WHERE friend_of_friend <> 'Alice';
-- friend_of_friend
-- Carol
E como a consulta de grafo retorna um conjunto de resultados comum, dá para combinar livremente com o resto do SQL — CTEs, joins e agregações:
-- Count each user's 2nd-degree connections
WITH second_degree AS (
SELECT source, COUNT(*) AS fof_count
FROM GRAPH_TABLE (social_graph
MATCH
(a IS person)
-[IS follows]->
(b IS person)
-[IS follows]->
(c IS person)
COLUMNS (a.name AS source, c.name AS target)
)
GROUP BY source
)
SELECT u.name, COALESCE(s.fof_count, 0) AS friends_of_friends
FROM users u
LEFT JOIN second_degree s ON s.source = u.name
ORDER BY friends_of_friends DESC;
Um caso mais corporativo: grafo de conhecimento
O mesmo padrão serve para recomendação. Imagine um grafo de produtos com arestas de “quem comprou também levou”, com um peso de co-compra:
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name TEXT,
category TEXT
);
CREATE TABLE also_bought (
product_id INT REFERENCES products(id),
related_id INT REFERENCES products(id),
weight FLOAT DEFAULT 1.0 -- co-purchase frequency
);
CREATE PROPERTY GRAPH product_graph
VERTEX TABLES (
products LABEL item PROPERTIES (id, name, category)
)
EDGE TABLES (
also_bought
SOURCE KEY (product_id) REFERENCES products (id)
DESTINATION KEY (related_id) REFERENCES products (id)
LABEL related_to
PROPERTIES (weight)
);
Com isso, achar itens a dois saltos de distância de um produto vira uma consulta direta:
-- Items two hops away from "Wireless Headphones"
SELECT DISTINCT rec_name, rec_category
FROM GRAPH_TABLE (product_graph
MATCH
(a IS item WHERE a.name = 'Wireless Headphones')
-[IS related_to]->
(b IS item)
-[IS related_to]->
(c IS item)
COLUMNS (c.name AS rec_name, c.category AS rec_category)
)
WHERE rec_name <> 'Wireless Headphones';
O que ainda não é suportado
A implementação inicial do PostgreSQL 19 cobre casamento de padrão com profundidade fixa. A travessia de caminho de tamanho variável — padrões quantificados como +, * ou {2,5} — está planejada para uma versão futura. Na prática, dá para percorrer grafos de profundidade conhecida (2 saltos, 3 saltos), mas ainda não dá para escrever, em SQL/PGQ nativo, consultas de caminho mínimo ou alcance aberto.
Para caminhos de tamanho variável hoje, as CTEs recursivas continuam sendo a resposta:
-- Still the approach for "up to N hops" in PG 19
WITH RECURSIVE reachable AS (
SELECT followed_id AS id, 1 AS depth
FROM follows WHERE follower_id = 1 -- Alice's id
UNION ALL
SELECT f.followed_id, r.depth + 1
FROM follows f
JOIN reachable r ON f.follower_id = r.id
WHERE r.depth < 4
)
SELECT DISTINCT u.name, r.depth
FROM reachable r
JOIN users u ON u.id = r.id;
Outras novidades do PostgreSQL 19
Os grafos lideram as manchetes, mas a versão traz um bom pacote de melhorias. Duas merecem código. A primeira é o ON CONFLICT DO SELECT, um “pega-ou-cria” atômico, bem mais rápido que a antiga gambiarra com DO UPDATE SET col = EXCLUDED.col:
INSERT INTO users (email, name)
VALUES ('alice@example.com', 'Alice')
ON CONFLICT (email) DO SELECT
RETURNING *;
A segunda é o FOR PORTION OF, para atualizações temporais: ele divide a linha automaticamente quando você atualiza só parte de um intervalo de tempo.
UPDATE product_prices
FOR PORTION OF valid_range FROM '2025-07-01' TO '2025-10-01'
SET price = 34.99
WHERE product_id = 1;
O resto vale conhecer pela lista:
- GROUP BY ALL: agrupa automaticamente por todas as colunas não agregadas do SELECT, eliminando um erro clássico de copiar-e-colar.
- pg_plan_advice: módulo contrib para dicas oficiais de plano de execução, encerrando a dependência de extensões da comunidade como o pg_hint_plan.
- REPACK CONCURRENTLY: reconstrução de tabela online, com lock breve apenas na troca final — a tabela continua legível e gravável durante a operação.
- Autovacuum paralelo: vários workers podem fazer vacuum de índices diferentes ao mesmo tempo, reduzindo muito o tempo em tabelas com muitos índices.
- Saída JSON nativa no COPY TO: exporta tabelas direto em NDJSON ou array JSON, sem precisar de row_to_json().
Como testar agora
O PostgreSQL 19 já está em beta (primeiro beta por volta de maio de 2026), com GA previsto para setembro de 2026. Dá para experimentar via Docker:
# Beta build via Docker
docker pull postgres:19beta1
docker run --name pg19 -e POSTGRES_PASSWORD=secret -d postgres:19beta1
# Connect
docker exec -it pg19 psql -U postgres
Pontos de atenção
- Ainda é beta. A sintaxe do SQL/PGQ reflete a especificação atual e pode mudar até o lançamento oficial. Não leve para produção crítica antes do GA.
- Profundidade fixa por enquanto. Sem caminhos de tamanho variável, casos como caminho mínimo continuam dependendo de CTE recursiva.
- Não substitui todo banco de grafos. Travessias muito profundas, algoritmos de grafo em memória e cargas de bilhões de arestas ainda têm casas especializadas.
- Performance depende dos índices. Como as consultas viram operações relacionais, o desempenho segue as mesmas regras de sempre: índice bom nas chaves de origem e destino das arestas faz toda a diferença.
Por que isso importa
O peso dessa novidade não está só no recurso, e sim no que ele remove: a necessidade de decidir “relacional ou grafo” lá no início do projeto. Redes sociais, motores de recomendação, hierarquias de permissão, organogramas e cadeias de suprimento agora podem viver no mesmo banco dos seus dados transacionais, consultados com SQL padrão.
E tem um detalhe estratégico: o SQL/PGQ é um padrão ISO. Diferente do Cypher (Neo4j) ou do Gremlin (Amazon Neptune), as consultas que você escrever tendem a ser portáveis entre qualquer banco compatível com SQL:2023. Isso não é pouca coisa.
O PostgreSQL 19 não torna os bancos de grafos irrelevantes para todo cenário — travessias de tamanho variável, algoritmos em memória e grafos gigantes continuam tendo seu lugar. Mas ele traz os 80% dos casos para dentro de casa, de graça, no banco que você já opera. De olho daqui para frente: vale testar a sintaxe no beta e acompanhar quando os caminhos de tamanho variável chegarem — é aí que o SQL/PGQ fecha de vez a lacuna. 🚀