<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/how-sql-pgq-rewrites-to-joins-on-postgresql-19-ktufwgfrw" -->

---
title: How SQL/PGQ Rewrites to Joins on PostgreSQL 19 | daily.dev
description: PostgreSQL 19's SQL/PGQ GRAPH_TABLE syntax is a parser-level rewriter that compiles graph queries into ordinary SQL joins against underlying tables. The...
canonical: https://daily.dev/posts/how-sql-pgq-rewrites-to-joins-on-postgresql-19-ktufwgfrw
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: How SQL/PGQ Rewrites to Joins on PostgreSQL 19 | daily.dev
og:description: PostgreSQL 19's SQL/PGQ GRAPH_TABLE syntax is a parser-level rewriter that compiles graph queries into ordinary SQL joins against underlying tables. The...
og:url: https://daily.dev/posts/how-sql-pgq-rewrites-to-joins-on-postgresql-19-ktufwgfrw
og:image: https://api.daily.dev/og/posts/kTufWgFRW.png
og:image:alt: How SQL/PGQ Rewrites to Joins on PostgreSQL 19
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# How SQL/PGQ Rewrites to Joins on PostgreSQL 19

**[CYBERTEC PostgreSQL](https://daily.dev/sources/cybertec_postgresql)** · 8 min read · 1 upvotes · 0 comments

## Summary

PostgreSQL 19's SQL/PGQ GRAPH_TABLE syntax is a parser-level rewriter that compiles graph queries into ordinary SQL joins against underlying tables. The regular query optimizer handles planning, meaning standard indexing rules apply. Forward traversal benefits from primary key indexes on source columns, while reverse traversal requires an explicit index on the destination column — adding one can yield ~20x speedup. Two-hop and multi-hop patterns expand into nested loop joins. Current limitations include no variable-length path patterns or shortest-path support, which can be worked around with WITH RECURSIVE. CREATE PROPERTY GRAPH is metadata-only, requiring no data migration on existing schemas.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://www.cybertec-postgresql.com/en/how-sql-pgq-rewrites-to-joins-on-postgresql-19>

## Questions this post answers

### How does PostgreSQL 19's GRAPH_TABLE syntax get executed under the hood?

GRAPH_TABLE is a parser-level rewriter that converts graph pattern queries into ordinary SELECT statements with joins over the underlying vertex and edge tables, using the mappings defined in the CREATE PROPERTY GRAPH statement. The regular query planner then optimizes these joins using the same cost model and statistics as any other SQL query, so EXPLAIN shows a standard join tree rather than a graph-specific plan node.

_Developers adopting PostgreSQL 19's graph features can track how query planning works on daily.dev._

### Why is reverse traversal slow in a PostgreSQL SQL/PGQ graph query and how do I fix it?

Reverse traversal (finding incoming edges) is slow because the edge table's primary key, typically on (source, destination), only indexes the source column, forcing a sequential scan when filtering by destination. Adding a dedicated index on the destination column fixes this: in one measured case, query time dropped from 3.1 ms to 0.16 ms and buffer hits fell from 287 to 29, roughly a 20x speedup.

_Anyone tuning graph-style queries in postgresql can compare indexing fixes like this on daily.dev._

### Does PostgreSQL 19's SQL/PGQ support variable-length paths or shortest path queries?

No, PostgreSQL 19's initial GRAPH_TABLE implementation does not support quantified (variable-length) patterns, shortest path, or multi-pattern MATCH clauses. The documented workaround for variable-length paths is a WITH RECURSIVE common table expression, and multi-pattern queries can be handled by joining two separate GRAPH_TABLE invocations on projected vertex IDs.

_Teams evaluating graph query features in postgresql can weigh these limitations before migrating on daily.dev._

## Similar posts on daily.dev

- [SQL/PGQ in PostgreSQL 19: Graph Queries Without the Graph Database](https://daily.dev/posts/sql-pgq-in-postgresql-19-graph-queries-without-the-graph-database-jjw0uthjc) · Planet PostgreSQL · 14 upvotes · 2 comments
- [Handling graphs with SQL/PGQ in PostgreSQL](https://daily.dev/posts/handling-graphs-with-sql-pgq-in-postgresql-nqopnkk2x) · CYBERTEC PostgreSQL · 3 upvotes · 0 comments
- [PostgreSQL: Documentation: 19: 5.15. Property Graphs](https://daily.dev/posts/postgresql-documentation-19-5-15-property-graphs-js6ov6326) · Lobsters · 1 upvotes · 0 comments

---

Tags: [#architecture](https://daily.dev/tags/architecture), [#postgresql](https://daily.dev/tags/postgresql)

[View this post on daily.dev](https://daily.dev/posts/how-sql-pgq-rewrites-to-joins-on-postgresql-19-ktufwgfrw)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"TechArticle","headline":"How SQL/PGQ Rewrites to Joins on PostgreSQL 19","url":"https://daily.dev/posts/how-sql-pgq-rewrites-to-joins-on-postgresql-19-ktufwgfrw","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/how-sql-pgq-rewrites-to-joins-on-postgresql-19-ktufwgfrw"},"datePublished":"2026-07-14T07:53:40.533Z","dateModified":"2026-09-14T06:36:37.479Z","description":"PostgreSQL 19's SQL/PGQ GRAPH_TABLE syntax is a parser-level rewriter that compiles graph queries into ordinary SQL joins against underlying tables. The...","image":"https://media.daily.dev/image/upload/s--CxzD6vbw--/f_auto/v1722860399/public/Placeholder%2005","thumbnailUrl":"https://media.daily.dev/image/upload/s--CxzD6vbw--/f_auto/v1722860399/public/Placeholder%2005","isAccessibleForFree":true,"articleSection":"CYBERTEC PostgreSQL","inLanguage":"en","publisher":{"@type":"Organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180}},"author":{"@type":"Organization","name":"CYBERTEC PostgreSQL","logo":"https://media.daily.dev/image/upload/s--T-0b7qSy--/f_auto/v1720886268/logos/cybertec_postgresql","url":"https://daily.dev/sources/cybertec_postgresql"},"commentCount":0,"discussionUrl":"https://daily.dev/posts/how-sql-pgq-rewrites-to-joins-on-postgresql-19-ktufwgfrw","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":1},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":0}],"keywords":"architecture,postgresql","timeRequired":"PT8M"}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"CYBERTEC PostgreSQL","item":"https://daily.dev/sources/cybertec_postgresql"},{"@type":"ListItem","position":3,"name":"How SQL/PGQ Rewrites to Joins on PostgreSQL 19"}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/how-sql-pgq-rewrites-to-joins-on-postgresql-19-ktufwgfrw#faq","mainEntity":[{"@type":"Question","name":"How does PostgreSQL 19's GRAPH_TABLE syntax get executed under the hood?","acceptedAnswer":{"@type":"Answer","text":"GRAPH_TABLE is a parser-level rewriter that converts graph pattern queries into ordinary SELECT statements with joins over the underlying vertex and edge tables, using the mappings defined in the CREATE PROPERTY GRAPH statement. The regular query planner then optimizes these joins using the same cost model and statistics as any other SQL query, so EXPLAIN shows a standard join tree rather than a graph-specific plan node. Developers adopting PostgreSQL 19's graph features can track how query planning works on daily.dev."}},{"@type":"Question","name":"Why is reverse traversal slow in a PostgreSQL SQL/PGQ graph query and how do I fix it?","acceptedAnswer":{"@type":"Answer","text":"Reverse traversal (finding incoming edges) is slow because the edge table's primary key, typically on (source, destination), only indexes the source column, forcing a sequential scan when filtering by destination. Adding a dedicated index on the destination column fixes this: in one measured case, query time dropped from 3.1 ms to 0.16 ms and buffer hits fell from 287 to 29, roughly a 20x speedup. Anyone tuning graph-style queries in postgresql can compare indexing fixes like this on daily.dev."}},{"@type":"Question","name":"Does PostgreSQL 19's SQL/PGQ support variable-length paths or shortest path queries?","acceptedAnswer":{"@type":"Answer","text":"No, PostgreSQL 19's initial GRAPH_TABLE implementation does not support quantified (variable-length) patterns, shortest path, or multi-pattern MATCH clauses. The documented workaround for variable-length paths is a WITH RECURSIVE common table expression, and multi-pattern queries can be handled by joining two separate GRAPH_TABLE invocations on projected vertex IDs. Teams evaluating graph query features in postgresql can weigh these limitations before migrating on daily.dev."}}]}
```

