PostgreSQL  

PostgreSQL 19 SQL/PGQ: Building Property Graph Queries with .NET

Relational databases are excellent at representing structured data with tables, relationships, constraints, and indexes. However, some applications naturally deal with connected data.

Examples include:

  • Social networks

  • Recommendation systems

  • Fraud detection

  • Dependency analysis

  • Organization hierarchies

  • Supply-chain relationships

  • Product and customer connections

Traditionally, developers have modeled these relationships using tables and SQL joins. PostgreSQL 19 introduces support for SQL/PGQ (Property Graph Queries), allowing relational data to be exposed as a property graph and queried using graph-pattern syntax. PostgreSQL keeps the underlying data in regular tables rather than converting the database into a separate native graph-storage engine.

For .NET developers, this means graph-style queries can be integrated into existing PostgreSQL applications while continuing to use familiar relational tables and Npgsql-based data access.

What Is a Property Graph?

A property graph represents data using two fundamental concepts:

Vertex
  |
  +-- Represents an entity

Edge
  |
  +-- Represents a relationship

For example:

Customer
   |
   | placed
   v
Order
   |
   | contains
   v
Product

A vertex can have properties:

Customer
----------------
id
name
email

An edge can also contain properties:

Customer → Order
----------------
order_date
status

PostgreSQL 19 implements SQL/PGQ by defining a property graph over relational tables. The graph is logical rather than a separate physical graph store.

Relational Model vs Property Graph

Consider these tables:

CREATE TABLE customers
(
    customer_id integer PRIMARY KEY,
    name text NOT NULL
);

CREATE TABLE orders
(
    order_id integer PRIMARY KEY,
    customer_id integer NOT NULL,
    order_date date NOT NULL
);

CREATE TABLE products
(
    product_id integer PRIMARY KEY,
    name text NOT NULL
);

CREATE TABLE order_items
(
    order_id integer NOT NULL,
    product_id integer NOT NULL
);

The relational model represents the relationships using foreign keys.

A graph representation can expose:

Customer
   |
   | has_placed
   v
Order
   |
   | contains
   v
Product

The underlying tables remain unchanged.

This is one of the most important characteristics of PostgreSQL's SQL/PGQ implementation: the property graph is a logical representation over relational data rather than a separately materialized graph database.

Creating a Property Graph

PostgreSQL 19 introduces CREATE PROPERTY GRAPH.

A simplified example is:

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

        order_products
            SOURCE orders
            DESTINATION products
            LABEL contains
    );

The exact edge tables and source/destination columns need to match the schema being modeled.

CREATE PROPERTY GRAPH defines the graph structure and does not physically materialize a new graph. PostgreSQL records the relationship between the graph and its underlying tables.

Why Labels Matter

Labels describe the type of graph element.

For example:

customer
order
product

An edge can also have a label:

has_placed
contains

This makes graph patterns easier to understand.

For example:

(customer)-[has_placed]->(order)

is more expressive than manually describing the same relationship through several joins.

PostgreSQL also allows multiple labels and property definitions for graph elements, subject to consistency rules.

Querying a Property Graph

PostgreSQL provides the GRAPH_TABLE construct for querying property graphs.

A simplified query is:

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

The graph pattern appears inside GRAPH_TABLE.

The result behaves like a table expression and can participate in the surrounding SQL query. PostgreSQL documents GRAPH_TABLE as the mechanism for executing graph-pattern queries over property graphs.

Filtering Graph Patterns

Graph queries become more useful when the pattern includes conditions.

For example:

SELECT customer_name
FROM GRAPH_TABLE (
    myshop
    MATCH (c IS customer)-[IS has_placed]->(o IS "order")
    WHERE o.order_date >= CURRENT_DATE - INTERVAL '30 days'
    COLUMNS (
        c.name AS customer_name
    )
);

This asks for customers connected to orders from the recent period.

The exact placement and supported syntax should be verified against the PostgreSQL 19 documentation for the query being written.

Why SQL/PGQ Can Be Useful

Traditional SQL joins are still appropriate for many relationships.

For example:

SELECT c.name
FROM customers c
JOIN orders o
    ON o.customer_id = c.customer_id;

This is simple and readable.

Graph patterns become particularly interesting when relationships become more complex:

Customer
   ↓
Order
   ↓
Product
   ↓
Category
   ↓
Other Products

The graph model allows developers to describe connected paths rather than manually constructing every relationship as a series of joins.

Graph Queries and Existing Relational SQL

One of the useful aspects of PostgreSQL's implementation is that graph and relational queries can be combined.

Conceptually:

Relational Tables
       +
Property Graph
       ↓
PostgreSQL Query

For example, a graph result can be used with other relational operations:

SELECT *
FROM GRAPH_TABLE (
    myshop
    MATCH (c IS customer)-[IS has_placed]->(o IS "order")
    COLUMNS (
        c.customer_id AS customer_id,
        c.name AS customer_name
    )
) AS graph_result
WHERE customer_id > 100;

This allows teams to adopt graph querying without abandoning their existing relational SQL model.

PostgreSQL's documentation specifically notes that relational and graph queries use the same query planning and execution infrastructure and can be mixed within queries.

Using SQL/PGQ From .NET

For a .NET application, Npgsql can execute PostgreSQL SQL just as it does other queries.

A simple example is:

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

await using var command =
    new NpgsqlCommand(sql, connection);

await using var reader =
    await command.ExecuteReaderAsync(
        cancellationToken);

while (await reader.ReadAsync(cancellationToken))
{
    var customerName =
        reader.GetString(0);

    Console.WriteLine(customerName);
}

The .NET application does not need a separate graph database client for this scenario.

The application can continue using its existing PostgreSQL connection infrastructure.

Using Parameters From .NET

Graph queries can still require application-supplied values.

For example:

const string sql = """
    SELECT customer_name
    FROM GRAPH_TABLE (
        myshop
        MATCH (c IS customer)-[IS has_placed]->(o IS "order")
        WHERE o.order_date >= @startDate
        COLUMNS (
            c.name AS customer_name
        )
    );
    """;

await using var command =
    new NpgsqlCommand(sql, connection);

command.Parameters.AddWithValue(
    "startDate",
    startDate);

Application values should continue to use parameters rather than being concatenated into SQL.

SQL/PGQ does not change standard SQL security practices.

Designing Tables for Graph Queries

A useful graph model starts with a good relational schema.

For example:

customers
    |
    +-- customer_id

orders
    |
    +-- order_id
    +-- customer_id

products
    |
    +-- product_id

order_items
    |
    +-- order_id
    +-- product_id

The relationships should be clear before creating the property graph.

Primary keys are particularly important because PostgreSQL uses them as the default key for property graph elements when an explicit KEY is not supplied.

Explicit Graph Keys

PostgreSQL allows graph definitions to specify keys explicitly.

Conceptually:

CREATE PROPERTY GRAPH myshop
    VERTEX TABLES (
        customers
            KEY (customer_id)
            LABEL customer
    );

This makes the identity of the graph element explicit.

When designing a property graph, verify that the selected key uniquely identifies the underlying element.

Property Mapping

PostgreSQL can expose table columns as graph properties.

For example:

CREATE PROPERTY GRAPH myshop
    VERTEX TABLES (
        customers
            LABEL customer
            PROPERTIES (
                name,
                email
            )
    );

This allows the graph representation to expose only the properties required by the graph model.

That can be useful when the underlying table contains columns that should not automatically become graph properties.

PostgreSQL supports explicit property expressions and aliases as part of property graph definitions.

Multiple Labels

A graph element can have more than one label.

For example, different tables could represent people:

customers
employees
partners

They could potentially share a logical label such as:

person

This allows a graph pattern to work with a broader logical category.

PostgreSQL requires properties with the same name across relevant labels to maintain consistent definitions and data types.

This is an important schema-design consideration.

SQL/PGQ vs Traditional Joins

AreaTraditional SQL JoinsSQL/PGQ
Data storageRelational tablesRelational tables
Relationship representationForeign keys / join conditionsGraph edges
Query styleJOINGraph pattern matching
Multi-hop relationshipsMultiple joinsGraph paths
Existing SQL compatibilityExcellentDesigned to integrate with SQL
Separate graph databaseNot requiredNot required
Best fitConventional relational queriesConnected-data patterns

This is not a case where SQL/PGQ automatically replaces joins.

For straightforward relationships, normal SQL may remain clearer.

The value of SQL/PGQ increases when the problem is naturally expressed as connected graph patterns.

SQL/PGQ vs a Native Graph Database

PostgreSQL's approach also differs from a native graph database.

FeaturePostgreSQL SQL/PGQNative Graph Database
Primary storage modelRelationalGraph-oriented
Existing relational schemaReusedUsually modeled separately
SQL integrationNativeDepends on database
Existing PostgreSQL ecosystemStrongSeparate platform
Graph query modelSQL/PGQGraph-specific
Data duplicationCan avoid separate graph copyDepends on architecture

For teams already using PostgreSQL, SQL/PGQ can be attractive because graph querying can be introduced over existing relational data.

It does not mean every graph workload will perform better than a specialized graph database.

Performance Considerations

Do not assume that graph syntax automatically makes a query faster.

PostgreSQL still needs to execute the underlying operations.

Benchmark graph queries against equivalent relational queries.

For example:

EXPLAIN ANALYZE
SELECT ...

can be used to inspect the execution plan of a comparable relational query.

For graph queries, evaluate the generated execution plan and actual workload behavior rather than comparing the visual simplicity of the query.

Important factors include:

  • Table size

  • Join relationships

  • Indexes

  • Data distribution

  • Query depth

  • Filtering

  • Cardinality

  • Result size

Indexing Still Matters

Suppose an edge table contains:

CREATE INDEX idx_order_items_product
ON order_items(product_id);

and:

CREATE INDEX idx_order_items_order
ON order_items(order_id);

These indexes can support relationship traversal through the underlying relational structures.

A graph abstraction does not remove the need to design the underlying relational schema and indexes carefully.

Security and Permissions

Property graphs do not bypass PostgreSQL permissions.

PostgreSQL documentation states that access to the underlying relations used by GRAPH_TABLE is determined by the privileges of the user executing the query.

This matters for .NET applications.

If the application role can access:

customers
orders
employees

through the underlying relations, those permissions remain relevant when the graph is queried.

A property graph should therefore be incorporated into the existing database security model rather than treated as an independent authorization boundary.

Updating a Property Graph Definition

The graph itself is a logical definition.

PostgreSQL provides ALTER PROPERTY GRAPH for changing its structure, including adding or dropping vertex or edge tables and modifying labels and properties.

For example:

ALTER PROPERTY GRAPH myshop
ADD VERTEX TABLES (
    employees LABEL employee
);

The exact change should be tested against the consistency requirements of the existing graph.

Schema changes should be managed through the same migration process used for other database objects.

Common Mistakes

Treating SQL/PGQ as a Replacement for SQL

Traditional SQL remains appropriate for many workloads.

Assuming Graph Queries Are Automatically Faster

Graph syntax changes how relationships are expressed. It does not guarantee better performance.

Ignoring the Relational Schema

The graph is built over underlying tables, so keys, indexes, constraints, and relationships still matter.

Exposing Every Column as a Property

Explicit property definitions can prevent unnecessary data exposure.

Ignoring Database Permissions

Graph queries still depend on access to the underlying relations.

Using Production Data Without Benchmarking

Connected-data workloads can behave very differently as data volume and relationship density increase.

Troubleshooting SQL/PGQ Queries

Check the PostgreSQL Version

SQL/PGQ support is introduced with PostgreSQL 19.

Verify the server version before debugging syntax:

SELECT version();

Check the Graph Definition

Inspect the property graph and confirm that the expected vertex and edge tables are included.

Check Labels

Make sure the labels referenced by MATCH correspond to the labels defined on the graph.

Check Keys

Verify that graph element keys correctly identify rows.

Check Permissions

The executing database role needs appropriate access to the underlying relations.

Check the Generated Workload

If a graph query is slow, inspect the execution plan and underlying relational access rather than assuming the graph definition itself is the problem.

Best Practices

  1. Start with a clear relational schema.

  2. Use primary keys or explicit graph keys consistently.

  3. Define meaningful vertex and edge labels.

  4. Expose only the properties required by the graph workload.

  5. Use SQL/PGQ where graph patterns improve query clarity.

  6. Keep ordinary SQL for straightforward relational operations.

  7. Benchmark graph queries against equivalent relational queries.

  8. Index the underlying relationship columns appropriately.

  9. Apply normal PostgreSQL permission controls.

  10. Execute SQL/PGQ from .NET using parameterized commands.

  11. Test graph definitions through database migrations.

  12. Monitor query plans as graph workloads grow.

Advantages and Disadvantages

Advantages

  • Adds standardized SQL/PGQ property graph support to PostgreSQL.

  • Allows graph queries over existing relational tables.

  • Avoids requiring a separate graph database for every connected-data use case.

  • Integrates graph queries with PostgreSQL SQL.

  • Can be consumed from existing .NET/Npgsql applications.

  • Supports logical graph definitions without physically duplicating the data.

Disadvantages

  • SQL/PGQ is new to PostgreSQL 19 and requires teams to learn another query model.

  • Graph syntax does not automatically improve query performance.

  • Complex graph workloads may still require careful relational indexing.

  • The underlying relational schema remains important.

  • Specialized graph databases may still be more appropriate for some workloads.

  • PostgreSQL 19-specific functionality requires an environment that supports the feature.

Conclusion

PostgreSQL 19's SQL/PGQ support gives developers another way to work with highly connected data without automatically moving that data into a separate graph database.

The key architecture is:

Existing Relational Tables
          |
          v
   Property Graph
          |
          v
      GRAPH_TABLE
          |
          v
     SQL/PGQ Query
          |
          v
      .NET / Npgsql

For .NET teams, this is particularly interesting because the application can continue using PostgreSQL and Npgsql while introducing graph-style querying where it provides a clearer representation of connected data.

The important decision is not whether graph queries are newer or more sophisticated than joins. It is whether the application's data and query patterns actually benefit from graph-style traversal.

For straightforward relationships, traditional SQL remains a strong choice. For multi-hop connected-data problems, SQL/PGQ can provide a more natural query model while keeping the underlying data inside PostgreSQL.

PostgreSQL 19's implementation is based on the SQL/PGQ standard and integrates property graphs with the database's existing relational query infrastructure, making it a feature worth evaluating for applications that already have a PostgreSQL relational foundation.