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 relationshipFor example:
Customer
|
| placed
v
Order
|
| contains
v
ProductA vertex can have properties:
Customer
----------------
id
name
emailAn edge can also contain properties:
Customer → Order
----------------
order_date
statusPostgreSQL 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
ProductThe 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
productAn edge can also have a label:
has_placed
containsThis 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 ProductsThe 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 QueryFor 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_idThe 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
partnersThey could potentially share a logical label such as:
personThis 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.

Join the conversation! Your thoughts help the community grow.