PostgreSQL 19 SQL/PGQ: Graph Queries

PostgreSQL 19 SQL/PGQ adds a graph-query layer over relational tables. Learn how it works, where it fits, and why Neo4j still matters.

PostgreSQL 19 SQL/PGQ is one of the more interesting additions to PostgreSQL in recent years. It brings the Property Graph Queries part of the SQL standard into PostgreSQL core.

At first glance, the feature can sound much bigger than it actually is:

PostgreSQL now supports graphs. Do we still need Neo4j?

The answer is yes, Neo4j and other native graph databases still have an important role.

PostgreSQL 19 is not turning PostgreSQL into a native graph database. Instead, SQL/PGQ gives us something that can be extremely useful in ordinary application databases:

a graph-oriented way to query relational data that already exists.

Your tables remain tables. Your foreign keys remain foreign keys. PostgreSQL does not create a second copy of the data or introduce a new graph storage engine.

Instead, you can describe some of those tables and relationships as a property graph, then query relationships using graph patterns instead of repeatedly reconstructing them through joins.

As of August 2026, PostgreSQL 19 is still in beta, with PostgreSQL 19 Beta 3 released on August 13, 2026. SQL/PGQ is one of the headline features planned for PostgreSQL 19. PostgreSQL does not recommend beta releases for production use, and the syntax can still change before general availability.

Let’s understand why this matters.


The Problem Is Not That SQL Cannot Query Relationships

Relational databases are perfectly capable of representing relationships.

Consider a very normal organization model.

We have two tables:

CREATE TABLE departments (
    department_id INT PRIMARY KEY,
    name TEXT NOT NULL
);

CREATE TABLE employees (
    id INT PRIMARY KEY,
    name TEXT NOT NULL,

    manager_id INT REFERENCES employees(id),

    department_id INT NOT NULL
        REFERENCES departments(department_id)
);
SQL

An employee belongs to a department:

Department membership through a foreign key Read left to right. Employee.department_id references Department.department_id. Department membership through a foreign key REFERENCES Employee.department_id Department.department_id

An employee can also report to another employee:

A manager is another employee row Read left to right. Employee.manager_id references Employee.id in the same table. A manager is another employee row REFERENCES Employee.manager_id Employee.id

Nothing unusual here.

Let’s add some data.

INSERT INTO departments (department_id, name)
VALUES
    (10, 'Engineering'),
    (20, 'Platform'),
    (30, 'Executive');

INSERT INTO employees (id, name, manager_id, department_id)
VALUES
    (1, 'Maya',  NULL, 30),
    (2, 'Arjun', 1,    20),
    (3, 'Neha',  2,    10);
SQL

Now imagine we receive this requirement:

Find Neha, her manager, and the department in which her manager works.


Solving It With Relational SQL

We need to join employees with itself because a manager is also an employee.

Then we need another join to find the manager’s department.

SELECT
    e.name AS employee_name,
    m.name AS manager_name,
    d.name AS manager_department
FROM employees e
JOIN employees m
    ON e.manager_id = m.id
JOIN departments d
    ON m.department_id = d.department_id
WHERE e.id = 3;
SQL

Result:

 employee_name | manager_name | manager_department
---------------+--------------+-------------------
 Neha          | Arjun        | Platform
TEXT

There is nothing inherently wrong with this query.

It is actually quite reasonable.

And this is important because graph discussions sometimes exaggerate the difficulty of relational SQL.

Two joins are not a database architecture crisis.

The interesting problem starts appearing when relationships become the thing we are primarily querying.


When Relationships Start Taking Over the Query

Suppose the requirement changes.

We now need:

Find Neha’s manager, her manager’s manager, and the department of each one.

Our query starts growing:

SELECT
    e.name AS employee_name,

    m1.name AS manager_name,
    d1.name AS manager_department,

    m2.name AS senior_manager_name,
    d2.name AS senior_manager_department

FROM employees e

JOIN employees m1
    ON e.manager_id = m1.id

JOIN departments d1
    ON m1.department_id = d1.department_id

JOIN employees m2
    ON m1.manager_id = m2.id

JOIN departments d2
    ON m2.department_id = d2.department_id

WHERE e.id = 3;
SQL

Still possible.

But notice what we are doing.

Conceptually, the question follows two REPORTS_TO relationships and then looks up where each manager WORKS_IN.

Yet the SQL query is dominated by implementation details such as employees.manager_id = employees.id and employees.department_id = departments.department_id.

The relationship exists conceptually in our domain as REPORTS_TO and WORKS_IN.

But SQL normally makes us reconstruct that relationship from columns every time we query it.

That is one of the problems SQL/PGQ tries to address.


Foreign Keys Are Not Named Traversal Paths

A foreign key tells PostgreSQL that employees.manager_id references employees.id. It provides referential integrity.

It does not, however, give our queries a reusable business relationship called REPORTS_TO.

Likewise, employees.department_id references departments.department_id, but SQL does not automatically give that relationship a reusable name such as WORKS_IN.

Applications repeatedly express those relationships through join predicates.

SQL/PGQ allows us to move those relationships into a graph definition.

The PostgreSQL documentation describes a property graph as a graph-oriented representation over existing relational data. The property graph behaves much like a read-only view over the underlying relational tables rather than introducing a separate graph storage system.

That distinction is fundamental.


Enter SQL/PGQ

SQL/PGQ is a graph lens over relational data Read left to right. Existing PostgreSQL tables pass through a property graph definition and become available to graph pattern queries without copying the data. A graph lens over existing tables RELATIONAL TABLES employees departments PROPERTY GRAPH Labels edges and paths No copied data GRAPH_TABLE Pattern matching Tabular result SAME POSTGRESQL STORAGE AND QUERY PLANNER

SQL/PGQ is the Property Graph Queries part of the SQL standard, defined in ISO/IEC 9075-16.

PostgreSQL 19 introduces constructs including CREATE PROPERTY GRAPH and GRAPH_TABLE.

CREATE PROPERTY GRAPH describes how relational tables should appear as a graph.

GRAPH_TABLE lets us match patterns against that graph.

The easiest mental model is:

SQL/PGQ adds a graph lens over relational data.

It does not move your data somewhere else.


Tables Become Vertices and Relationships Become Edges

For our organization example, we can think of Employee and Department as vertices, and REPORTS_TO and WORKS_IN as edges.

Conceptually:

Tables become vertices, joins become named edges Two rows read left to right. Employee reports to Employee, and Employee works in Department. Tables become vertices, joins become named edges REPORTS_TO WORKS_IN Employee Employee Employee Department

For our sample data:

The sample organization as a graph Read left to right across employees, then top to bottom into departments. Neha reports to Arjun, Arjun reports to Maya, and each employee works in one department. The sample organization as a graph REPORTS_TO REPORTS_TO WORKS_IN WORKS_IN WORKS_IN Neha Arjun Maya Engineering Platform Executive

Now the organization begins to look much closer to the domain we actually think about.


Creating the Property Graph

PostgreSQL 19 lets us map the existing tables into a property graph.

CREATE PROPERTY GRAPH organization_graph

VERTEX TABLES (
    employees
        LABEL employee,

    departments
        LABEL department
)

EDGE TABLES (

    employees AS reports
        SOURCE KEY (id)
            REFERENCES employees (id)

        DESTINATION KEY (manager_id)
            REFERENCES employees (id)

        LABEL reports_to,

    employees AS department_membership
        SOURCE KEY (id)
            REFERENCES employees (id)

        DESTINATION KEY (department_id)
            REFERENCES departments (department_id)

        LABEL works_in
);
SQL

Something interesting is happening here.

We did not create tables such as employee_nodes, reports_to_edges, or department_nodes.

The original tables remain the source of truth.

The same employees table participates as a vertex and in multiple relationships.

One alias exposes an employee row as a REPORTS_TO edge. Another exposes the same row as a WORKS_IN edge.

PostgreSQL explicitly allows tables to appear more than once within a property graph by assigning aliases to graph elements. Maya has a NULL manager_id, so her row does not produce a reports_to edge with a destination.

This lets us take relationships that were previously implicit in foreign-key columns and give them domain-specific names.


Querying the Graph

Remember our original requirement:

Find Neha, her manager, and the department where her manager works.

The relational version required two joins.

With SQL/PGQ, we can express the relationship itself:

SELECT *
FROM GRAPH_TABLE (
    organization_graph

    MATCH
        (e IS employee WHERE e.id = 3)
        -[IS reports_to]->
        (m IS employee)
        -[IS works_in]->
        (d IS department)

    COLUMNS (
        e.name AS employee_name,
        m.name AS manager_name,
        d.name AS manager_department
    )
);
SQL

The important part is:

The graph pattern expresses domain meaning Read left to right. Employee e reports to manager m, who works in department d. The graph pattern expresses domain meaning REPORTS_TO WORKS_IN Employee e Manager m Department d

You can almost read the query directly:

Find employee e, follow reports_to to manager m, then follow works_in to department d.

PostgreSQL’s graph query syntax uses parentheses for vertices, square brackets for edges, and arrows to represent edge direction.

The query now expresses the domain relationship, rather than repeatedly describing the foreign-key implementation.


Multi-Hop Queries and the Current Limits

The benefit becomes clearer when a question crosses several named relationships. To find Neha’s manager and that manager’s manager, the pattern describes the path directly:

A two-hop management chain Read left to right. Neha reports to Arjun, who reports to Maya. A two-hop management chain REPORTS_TO REPORTS_TO Neha Arjun Maya
SELECT *
FROM GRAPH_TABLE (
    organization_graph

    MATCH
        (e IS employee WHERE e.name = 'Neha')
        -[IS reports_to]->
        (m1 IS employee)
        -[IS reports_to]->
        (m2 IS employee)

    COLUMNS (
        e.name  AS employee,
        m1.name AS manager,
        m2.name AS managers_manager
    )
);
SQL

This is easier to read than another chain of employee aliases. The important boundary is that PostgreSQL 19’s initial implementation is strongest when the number of hops is known in advance.

The SQL/PGQ standard includes broader features, but quantified path patterns, shortest paths, graph types, and path constraints are not implemented in the initial PostgreSQL 19 feature set. For arbitrary-depth hierarchies, a recursive CTE may still be necessary. The limitation is documented in the PostgreSQL development discussion.


Does SQL/PGQ Make These Queries Faster?

Not automatically. SQL/PGQ changes how the relationship is expressed, not how PostgreSQL stores the underlying rows.

A graph relationship over relational data Read left to right. An Employee reports to a Manager. PostgreSQL can plan this graph relationship through the underlying relational tables. A graph relationship over relational data REPORTS_TO Employee Manager

PostgreSQL describes a property graph as a read-only view over relational tables. Graph patterns are processed through the same planning and execution infrastructure as ordinary SQL, and the PostgreSQL 19 release notes say they are internally written as relational queries.

That means familiar engineering still matters: indexes, statistics, join selectivity, row counts, and the shape of the generated plan. Use EXPLAIN and EXPLAIN ANALYZE. Graph syntax does not make an inefficient physical design disappear.


PostgreSQL SQL/PGQ Versus Neo4j

PostgreSQL and Neo4j overlap at the query-model level, but their storage models and target workloads remain different.

PostgreSQL graph abstraction compared with Neo4j graph storage Two vertical stacks are compared. PostgreSQL translates graph patterns through SQL into relational tables. Neo4j runs Cypher through a graph runtime over graph-native nodes and relationships. Same graph-shaped question, different storage architecture POSTGRESQL 19 SQL/PGQ NEO4J GRAPH_TABLE PATTERN RELATIONAL QUERY PLAN TABLES + INDEXESGraph is a logical view CYPHER GRAPH RUNTIME NODES + RELATIONSHIPSGraph is the storage model

PostgreSQL keeps rows in relational tables and exposes selected tables as vertices and edges. Neo4j is designed around the management, storage, and traversal of nodes and relationships. Its operations documentation explicitly describes native graph processing and storage.

For a bounded application question such as Employee -> Manager -> Department, PostgreSQL SQL/PGQ can be a very natural fit. Deep and unpredictable traversal is a different workload:

Traversal depth grows with every hop Read left to right across seven connected vertices from A through G. Traversal depth grows with every hop A B C D E F G
  • Who can be reached within six social connections?
  • What is the shortest path between two accounts?
  • Which circular money-transfer paths cross devices, IP addresses, merchants, and accounts?

Those questions benefit from a graph-native storage model, mature path operations, and graph analytics. PostgreSQL 19 does not turn the relational engine into that architecture.

AreaPostgreSQL 19 SQL/PGQNeo4j
Primary storageRelational tablesNative graph
Graph modelLogical property graph over tablesNative property graph
Existing PostgreSQL dataUsed directlyUsually needs loading/synchronization
SQL supportNativeNot the primary query model
Graph query languageSQL/PGQCypher
Fixed relationship traversalGood fitGood fit
Deep graph traversalNot its primary architectureCore use case
Shortest-path workloadsInitial PG19 support is limitedMature graph capability
Graph analyticsLimited compared with graph platformsStrong ecosystem
Existing OLTP applicationExcellent fitUsually additional architecture
Additional system beside existing PostgreSQLNoYes
Relationship-oriented developer experienceMuch improvedNative from the beginning

Where PostgreSQL SQL/PGQ Fits

SQL/PGQ is especially attractive when:

  • PostgreSQL is already the source of truth.
  • The domain contains named relationships such as REPORTS_TO, OWNS, MEMBER_OF, or DEPENDS_ON.
  • Most traversals have a known and relatively small number of hops.
  • The same application still needs transactions, aggregation, filtering, reporting, and ordinary relational joins.

In that middle ground, adding another database, a change-data-capture pipeline, operational monitoring, and consistency handling may provide little value.

One database, two query models Read top to bottom. The application uses PostgreSQL, which supports relational SQL and SQL/PGQ over the same relational tables. One database, two query models Application PostgreSQL Relational SQL SQL/PGQ Same relational tables

A dedicated graph database remains compelling when the graph itself is the primary data model: social networks, fraud investigation, knowledge graphs, network topology, recommendation systems, and other workloads centered on deep traversal or graph algorithms.


SQL and Graph Queries Can Work Together

GRAPH_TABLE produces a tabular result. PostgreSQL can join that result to ordinary tables, filter it, aggregate it, and order it like another FROM source.

Graph traversal flows into relational SQL Read left to right. Graph traversal enters GRAPH_TABLE, produces rows for normal relational SQL, and continues into GROUP BY, JOIN, or ORDER BY. Graph and relational operations stay in one SQL pipeline Graphtraversal GRAPH_TABLE Relationalrows GROUP BYJOINORDER BY GRAPH_TABLE returns ordinary rows to the surrounding SQL query.

This may be the feature’s most practical strength. A query can use a graph pattern where relationships are central, then return to relational SQL for the rest of the workload. It is not a choice between two separate query worlds.


Production Considerations

Treat a property graph definition as a schema contract. Labels, properties, edge direction, source keys, and destination keys become part of the application’s domain-facing query model.

  • Expose properties deliberately. PostgreSQL exposes all columns as properties by default. For a stable domain model, prefer explicit PROPERTIES (...) lists or NO PROPERTIES for edges that carry no data.
  • Do not treat the graph as a security boundary. A user querying the graph also needs the relevant privileges on the property graph and its underlying relations.
  • Review schema evolution. Renaming or removing referenced columns, tables, labels, or properties can break graph queries and application expectations.
  • Index the traversal directions you use. Graph syntax still executes over relational storage.
CREATE INDEX idx_employees_manager
ON employees(manager_id);

CREATE INDEX idx_employees_department
ON employees(department_id);
SQL

The property and privilege behavior is documented in PostgreSQL’s CREATE PROPERTY GRAPH reference.


Final Takeaway

PostgreSQL 19 SQL/PGQ does not replace relational SQL, and it does not turn PostgreSQL into Neo4j. It adds a standardized graph-query layer over relational data that already exists.

Use it when named relationships and bounded traversal are central to a question, but PostgreSQL remains the natural system of record. Continue using ordinary joins for ordinary relational work. Consider a native graph database when deep paths, shortest-path queries, highly connected datasets, and graph analytics define the workload.

The real improvement is not a new storage engine. It is the ability to query the meaning of relationships without repeatedly expressing every relationship as column equality.

For more practical backend and architecture deep dives, browse the complete article archive.

Share
Prabhat Kashyap

Prabhat Kashyap

Senior Technical Architect at HCLTech · working with Leonteq Security AG

I have 10+ years of experience building distributed systems and fintech platforms. I write about practical, non-obvious engineering details that official documentation often skips.

Newsletter

Get new engineering deep dives

Scala, JVM, AI, and backend systems. I send practical articles when there is something worth reading.

Join the newsletter

Engineering deep dives on Scala, Java, Rust, and AI Systems. Written by a senior engineer who builds real fintech systems.

© 2026 prabhat.dev