PostgreSQL 19: consultas de grafo nativas com SQL/PGQ — sem precisar de um novo banco

Published on: 2026-06-27
Post image
pt postgresql postgresql-19 sql-pgq grafos banco-de-dados sql neo4j graph-database iso-sql-2023 performance

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. 🚀