All posts

PostgreSQL 19 Is a Graph Database Now — And You Don't Need Neo4j

KynodexKynodex
12 min read
PostgreSQL 19 Is a Graph Database Now — And You Don't Need Neo4j

PostgreSQL 19 Beta 1 shipped on June 4, 2026 with native property graph queries built into core. No extension. No new storage engine. No data migration. Your existing tables, exposed as a graph, queried with ISO-standard pattern matching syntax.

If you've ever spun up Neo4j alongside Postgres just to answer "who follows whom," or written a 40-line recursive CTE to find a dependency cycle, this release is for you.


Introduction

For years, teams with graph-shaped data faced an uncomfortable choice: stay in PostgreSQL and hack relationships with recursive CTEs, or spin up a dedicated graph database and manage yet another system — with all the data duplication, sync complexity, and operational overhead that implies.

PostgreSQL 19 removes that trade-off for a large class of workloads. It implements SQL/PGQ (SQL Property Graph Queries), formally ISO/IEC 9075-16:2023 — Part 16 of the SQL:2023 standard.

The design decision that makes this work: property graphs are read-only views over your existing relational tables. Your data stays where it is. You tell PostgreSQL which tables are vertices and which are edges. That's it.

This post covers what shipped, the actual syntax from the official docs, what it can and can't do, and whether you should migrate off your graph database.


The Backstory: A Two-Year Patch

This didn't appear overnight.

In February 2024, Peter Eisentraut announced a prototype SQL/PGQ implementation on the pgsql-hackers mailing list, following an initial discussion at the FOSDEM developer meeting. Nearly two years later, that patch had grown to 118 files changed, ~14,800 lines added, with Peter Eisentraut and Ashutosh Bapat as primary authors, Junwang Zhao reviewing, and Ajay Pal and Henson Choi testing.

The latest iteration (v20260113) consolidated features across every prior version — cyclic path patterns, access permissions, RLS support, graph element functions like LABELS() and PROPERTY_NAMES(), multi-pattern path matching, ECPG support, property collation rules, and pg_overexplain integration.

Timeline:

  • Feb 2024 — Prototype announced on pgsql-hackers

  • April 2026 — PostgreSQL 19 feature freeze

  • June 4, 2026 — PostgreSQL 19 Beta 1 released with SQL/PGQ

  • September 2026 — Expected general availability


How It Actually Works

The core model

A property graph is a set of vertices and edges. Each edge has a source vertex and a destination vertex — all edges are directed. Vertices and edges are collectively called elements.

Each element has one or more labels (analogous to table row types — they define the structure), and each label has zero or more properties (analogous to columns — they hold the data).

The critical architectural point, straight from the docs: PostgreSQL defines a property graph as a kind of read-only view over relational tables. The actual data stays in tables. It's exposed as a graph for querying. This is in direct contrast to native graph databases, where data is physically stored in a graph structure.

And this matters more than it sounds: both relational queries and graph queries use the same query planning and execution infrastructure — and can be mixed in a single query.

The two new constructs

PostgreSQL 19 introduces exactly two SQL constructs:

  1. CREATE PROPERTY GRAPH — defines the graph over your tables

  2. GRAPH_TABLE — queries it with pattern matching


Real Syntax: A Working Example

Here's the canonical example from the official PostgreSQL 19 documentation. Start with ordinary relational tables:

sql

CREATE TABLE products (
    product_no integer PRIMARY KEY,
    name varchar,
    price numeric
);

CREATE TABLE customers (
    customer_id integer PRIMARY KEY,
    name varchar,
    address varchar
);

CREATE TABLE orders (
    order_id integer PRIMARY KEY,
    ordered_when date
);

CREATE TABLE order_items (
    order_items_id integer PRIMARY KEY,
    order_id integer REFERENCES orders (order_id),
    product_no integer REFERENCES products (product_no),
    quantity integer
);

CREATE TABLE customer_orders (
    customer_orders_id integer PRIMARY KEY,
    customer_id integer REFERENCES customers (customer_id),
    order_id integer REFERENCES orders (order_id)
);

Nothing unusual here. Now expose it as a graph:

sql

CREATE PROPERTY GRAPH myshop
    VERTEX TABLES (
        products,
        customers,
        orders
    )
    EDGE TABLES (
        order_items SOURCE orders DESTINATION products,
        customer_orders SOURCE customers DESTINATION orders
    );

The first three tables become vertices. The junction tables become edges. The foreign-key definitions correspond to the fact that edges link two vertices — so if your schema has proper PKs and FKs, the definition is this terse.

Querying with GRAPH_TABLE

sql

-- get list of customers active today
SELECT customer_name
FROM GRAPH_TABLE (myshop
  MATCH (c IS customers)-[IS customer_orders]->(o IS orders
         WHERE o.ordered_when = current_date)
  COLUMNS (c.name AS customer_name)
);

The equivalent relational query:

sql

SELECT customers.name
FROM customers
JOIN customer_orders USING (customer_id)
JOIN orders USING (order_id)
WHERE orders.ordered_when = current_date;

At two hops, the relational version is arguably fine. At five hops with conditional branching, it isn't — and that's where SQL/PGQ earns its place.

Labels: making queries readable

By default, table and column names are exposed as labels and properties. But graph convention uses singular nouns for vertices and verb phrases for edges. You can remap:

sql

CREATE PROPERTY GRAPH myshop
    VERTEX TABLES (
        products LABEL product,
        customers LABEL customer,
        orders LABEL "order"
    )
    EDGE TABLES (
        order_items SOURCE orders DESTINATION products LABEL contains,
        customer_orders SOURCE customers DESTINATION orders LABEL has_placed
    );

Now the query reads like a sentence:

sql

SELECT customer_name
FROM GRAPH_TABLE (myshop
  MATCH (c IS customer)-[IS has_placed]->(o IS "order"
         WHERE o.ordered_when = current_date)
  COLUMNS (c.name AS customer_name)
);

Gotcha worth flagging: note "order" is quoted. Run it unquoted and you get a syntax error — order is a reserved keyword.

Multi-label: one graph, multiple logical views

This is the feature most people will miss and shouldn't. You can apply the same label to multiple element tables:

sql

CREATE PROPERTY GRAPH myshop
    VERTEX TABLES (
        products LABEL product,
        customers LABEL customer LABEL person PROPERTIES (name),
        orders LABEL "order",
        employees LABEL employee LABEL person PROPERTIES (employee_name AS name)
    )
    EDGE TABLES (
        order_items SOURCE orders DESTINATION products LABEL contains,
        customer_orders SOURCE customers DESTINATION orders LABEL has
    );

Now a query matching IS person automatically considers both customers and employees. The constraint: when multiple element tables share a label, properties must match in number, name, and type — which is why employee_name is aliased to name above.

The strategic value: the same relational data can be exposed through multiple co-existing logical graph views, each surfacing a different property set — without duplicating a single row.

Explicit keys (when your schema isn't clean)

The terse definitions above require primary keys on all tables and appropriate foreign keys for each edge. If your schema doesn't cooperate, specify keys explicitly:

sql

CREATE PROPERTY GRAPH myshop
    VERTEX TABLES (
        products KEY (product_no),
        customers KEY (customer_id),
        orders KEY (order_id)
    )
    EDGE TABLES (
        order_items KEY (order_items_id)
            SOURCE KEY (order_id) REFERENCES orders (order_id)
            DESTINATION KEY (product_no) REFERENCES products (product_no),
        customer_orders KEY (customer_orders_id)
            SOURCE KEY (customer_id) REFERENCES customers (customer_id)
            DESTINATION KEY (order_id) REFERENCES orders (order_id)
    );

Why This Matters More Than "Postgres Has Graphs Now"

1. It's an ISO standard, not a vendor language

This is the underrated part. Unlike Neo4j's Cypher or Amazon Neptune's Gremlin, SQL/PGQ queries are portable across any SQL:2023-compliant database. You're not writing yourself into a vendor lock-in. Oracle has already implemented the standard via PGQL. Others will follow.

For teams making a 5-year architectural bet, standard-compliance is worth more than a marginal performance edge.

2. Graph and relational queries mix freely

Because SQL/PGQ uses the same planner and executor, you can join a GRAPH_TABLE result directly against ordinary tables. From published examples:

sql

SELECT p2_name, place.name
FROM GRAPH_TABLE (G
  MATCH
    (p1:Person)-[:Likes]->(m:Message),
    (p2:Person)-[:Likes]->(m),
    (p1)-[:Knows]->(p2)
  COLUMNS (
    p1.name AS p1_name,
    p1.place_id AS p1_place_id,
    p2.name AS p2_name
  )
) g
JOIN Place p ON g.p1_place_id = p.id

Try that across a Postgres/Neo4j boundary. You can't — not without application-layer glue and two round trips.

3. It kills the dual-database sync problem

Running a separate graph database alongside PostgreSQL means data duplication, sync complexity (CDC pipelines, eventual consistency windows, reconciliation jobs), and another system to monitor, back up, and secure. For teams whose graph workload is a feature rather than the product, that overhead was never justified — it was just the only option.


Honest Limitations

This is where most coverage of this release goes soft. It shouldn't.

Fixed-depth traversals in the initial implementation. The Beta 1 implementation covers fixed-depth pattern matching. Variable-length traversals — the [:KNOWS*1..5] pattern that Cypher users reach for constantly — are not fully covered in the initial release. Check the current beta spec before assuming your traversal pattern works.

Read-only. Property graphs are views. You cannot INSERT into a graph. All writes go through the underlying tables. That's a clean design, but it means SQL/PGQ is a query interface, not a graph storage engine.

No in-memory graph algorithms. PageRank, community detection, centrality measures, shortest-path with weights — the algorithm library that makes Neo4j GDS valuable isn't part of the SQL standard. If your workload is graph analytics rather than graph queries, Postgres 19 doesn't replace your tooling.

Billion-edge workloads still have specialized homes. Native graph databases store data in a graph structure with adjacency-optimized physical layouts. Postgres stores rows in heap pages and traverses via index lookups. At extreme scale with deep traversals, the physical storage difference is not something a query planner can paper over.

It's beta. PostgreSQL 19 Beta 1 released June 4, 2026, with GA expected September 2026. All syntax reflects the current beta specification and may change before final release. Do not ship this to production yet.

Documentation is still thin. The core docs (Section 5.15 and Section 7.9) cover the syntax, but the ecosystem — tooling, ORMs, query optimization guidance, real-world benchmarks — has not caught up. You'll be early.


Should You Migrate Off Your Graph Database?

A decision framework, not a recommendation:

Move to PostgreSQL 19 SQL/PGQ if:

  • Your graph workload is a feature of a larger relational application — social follows, org hierarchies, permission trees, dependency graphs, recommendation joins

  • You're currently maintaining Postgres + Neo4j with a sync pipeline and the graph data originates in Postgres anyway

  • Your traversals are fixed-depth or bounded — most product features are

  • You need graph and relational data in the same query

  • ISO standard portability matters to your architecture

Stay on a native graph database if:

  • You run variable-length traversals across deep, unbounded paths

  • You depend on graph algorithm libraries — PageRank, community detection, weighted shortest path

  • Your dataset is billion-edge scale with latency SLAs on deep traversals

  • Graph is the product, not a feature of it

The honest summary: PostgreSQL 19 doesn't make graph databases irrelevant. It brings the 80% use case in-house, for free, in a database you already run and already know how to operate.

For most engineering teams, that 80% is the entire reason they were running a second database.


What This Means for AI and RAG Systems

One angle worth flagging for teams building AI infrastructure: GraphRAG architectures currently require a graph database in the stack.

The standard pattern — extract entities and relationships from documents, build a knowledge graph, traverse it to assemble retrieval context — has meant running Neo4j or similar alongside your vector store and your primary database. Three systems, two sync pipelines.

With SQL/PGQ plus pgvector in the same instance, that collapses to one. Entity relationships as a property graph, embeddings as vectors, source documents as rows — all in Postgres, all queryable in a single statement, all with one backup strategy and one connection pool.

That's not a theoretical benefit. For teams building production RAG, the operational surface area of a three-database architecture is where the reliability problems live.


Key Takeaways

  • PostgreSQL 19 implements SQL/PGQ (ISO/IEC 9075-16:2023) — native property graph queries over existing relational tables, no extension, no new storage engine, no migration.

  • Two constructs do the work: CREATE PROPERTY GRAPH to define the graph, GRAPH_TABLE with MATCH to query it. Property graphs are read-only views — your data never moves.

  • Graph and relational queries share the same planner and executor and can be mixed in a single query. That's the capability no Postgres+Neo4j architecture can match.

  • It's a standard, not a vendor language. SQL/PGQ queries port across any SQL:2023-compliant database. Cypher and Gremlin don't.

  • Know the limits before you migrate. Fixed-depth traversals in the initial implementation, no in-memory graph algorithms, read-only, and still in beta until September 2026 GA.

  • The 80% case is now in-house. For teams whose graph workload is a feature rather than the product, the second database was always overhead. That's over.


Conclusion

The most consequential thing about PostgreSQL 19's SQL/PGQ implementation isn't that Postgres can do graphs. Extensions like Apache AGE have offered that for years.

It's that graph queries are now standard SQL, in core, over your existing tables, sharing the same planner as everything else you run. The architectural tax of graph-shaped data — a second database, a sync pipeline, a separate query language, a separate operational runbook — is gone for the majority of workloads that were paying it.

If you're evaluating a graph database today, wait for September. If you're running one alongside Postgres and your graph data originates in Postgres, start benchmarking the beta now.


References and Documentation

Official PostgreSQL 19 documentation:

Standard:

  • ISO/IEC 9075-16:2023 — SQL Part 16: Property Graph Queries (SQL/PGQ)

Community and vendor resources:

Academic:


At Kynodex, we architect production data infrastructure for AI systems — including GraphRAG pipelines, vector search, and the routing layer that makes them work at scale. If your team is evaluating whether SQL/PGQ can collapse your stack, talk to us.

Powered by Synscribe

Comments

No comments yet. Be the first to start the conversation.

Ready to build?

Turn your AI vision into a production system

We build the AI infrastructure that powers your next stage of growth.

Book a Strategy Call