<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/plan-advice-in-postgresql-19-grktnne2f" -->

---
title: Plan Advice in PostgreSQL 19 | daily.dev
description: PostgreSQL 19 introduces two new contrib modules, pg_plan_advice and pg_stash_advice, that let developers capture a query plan as a compact string of decisions...
canonical: https://daily.dev/posts/plan-advice-in-postgresql-19-grktnne2f
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: Plan Advice in PostgreSQL 19 | daily.dev
og:description: PostgreSQL 19 introduces two new contrib modules, pg_plan_advice and pg_stash_advice, that let developers capture a query plan as a compact string of decisions...
og:url: https://daily.dev/posts/plan-advice-in-postgresql-19-grktnne2f
og:image: https://api.daily.dev/og/posts/GrktnnE2F.png
og:image:alt: Plan Advice in 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.

# Plan Advice in PostgreSQL 19

**[Planet PostgreSQL](https://daily.dev/sources/planet-postgresql)** · 13 min read · 0 upvotes · 0 comments

## Summary

PostgreSQL 19 introduces two new contrib modules, pg_plan_advice and pg_stash_advice, that let developers capture a query plan as a compact string of decisions (join order, join method, scan method, parallelism) and force the planner to reproduce it later. Unlike raw EXPLAIN output, advice strings omit costs and timings, making them stable across statistics changes and diffable across plan versions or PostgreSQL upgrades. pg_stash_advice maps query ids from pg_stat_statements to advice strings in shared memory, applying them transparently to matching queries without any application change. The companion tool sqlfmt can generate and diff the same advice format from plain EXPLAIN output on any PostgreSQL version today. The piece walks through examples using an F1 database, shows how advice fails gracefully when a plan option is disabled (annotated as matched, failed), and warns that pinning plans removes the planner's ability to adapt as data changes, so advice should be trimmed to only the decisions that matter and applied per-query rather than cluster-wide.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://postgr.es/p/9uC>

## Questions this post answers

### What does pg_plan_advice in PostgreSQL 19 actually do?

pg_plan_advice is a PostgreSQL 19 contrib module that reads a query plan back out as a compact string describing four decisions: join order, join method, scan method per table, and whether to run in parallel. It omits costs, row estimates, and timings so the same plan produces identical advice text even after statistics change. The string can be fed back via the pg_plan_advice.advice setting to force the planner to reproduce that plan.

_Developers pinning down flaky PostgreSQL query plans can track releases like this one on daily.dev._

### How can I compare two PostgreSQL query plans without noise from cost and timing differences?

Use sqlfmt, a standalone tool that extracts the same four-line plan advice format (join order, join method, scan method, parallelism) from ordinary EXPLAIN output on any PostgreSQL version, then diff two advice files with 'sqlfmt explain diff'. Because advice omits costs and timings, identical plans produce identical output and only genuine structural differences show up, unlike raw EXPLAIN diffs where every line differs due to cost noise.

_Teams validating query plans before a PostgreSQL upgrade can follow tooling changes like this via daily.dev._

### What happens if PostgreSQL plan advice asks for a join method that is disabled?

The planner reports the advice as matched but failed rather than silently ignoring it or crashing. For example, forcing HASH_JOIN while enable_hashjoin is off produces the annotation 'matched, failed' in the Supplied Plan Advice block, meaning the advice was understood and applied to the right part of the query, but the planner could not honor it because that plan type was unavailable. Advice only constrains choices the planner would otherwise consider; it cannot resurrect disabled plan types.

_Engineers relying on plan hints to hold a fix in place can watch for edge cases like this through daily.dev._

## Similar posts on daily.dev

- [Looking Forward to Postgres 19: Query Hints](https://daily.dev/posts/looking-forward-to-postgres-19-query-hints-ubjiqehqi) · Planet PostgreSQL · 0 upvotes · 0 comments
- [advice contrib module. – select \* from depesz;](https://daily.dev/posts/advice-contrib-module-select-from-depesz--jy7lidjez) · depesz · 0 upvotes · 0 comments

---

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

[View this post on daily.dev](https://daily.dev/posts/plan-advice-in-postgresql-19-grktnne2f)

```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":"Plan Advice in PostgreSQL 19","url":"https://daily.dev/posts/plan-advice-in-postgresql-19-grktnne2f","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/plan-advice-in-postgresql-19-grktnne2f"},"datePublished":"2026-09-15T16:50:01.965Z","dateModified":"2026-09-17T23:36:17.458Z","description":"PostgreSQL 19 introduces two new contrib modules, pg_plan_advice and pg_stash_advice, that let developers capture a query plan as a compact string of decisions...","image":"https://media.daily.dev/image/upload/s--VDukGCjf--/f_auto/v1722860399/public/Placeholder%2002","thumbnailUrl":"https://media.daily.dev/image/upload/s--VDukGCjf--/f_auto/v1722860399/public/Placeholder%2002","isAccessibleForFree":true,"articleSection":"Planet 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":"Planet PostgreSQL","logo":"https://media.daily.dev/image/upload/logos/placeholder.jpg","url":"https://daily.dev/sources/planet-postgresql"},"commentCount":0,"discussionUrl":"https://daily.dev/posts/plan-advice-in-postgresql-19-grktnne2f","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":0},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":0}],"keywords":"database,postgresql","timeRequired":"PT13M"}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"Planet PostgreSQL","item":"https://daily.dev/sources/planet-postgresql"},{"@type":"ListItem","position":3,"name":"Plan Advice in PostgreSQL 19"}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/plan-advice-in-postgresql-19-grktnne2f#faq","mainEntity":[{"@type":"Question","name":"What does pg_plan_advice in PostgreSQL 19 actually do?","acceptedAnswer":{"@type":"Answer","text":"pg_plan_advice is a PostgreSQL 19 contrib module that reads a query plan back out as a compact string describing four decisions: join order, join method, scan method per table, and whether to run in parallel. It omits costs, row estimates, and timings so the same plan produces identical advice text even after statistics change. The string can be fed back via the pg_plan_advice.advice setting to force the planner to reproduce that plan. Developers pinning down flaky PostgreSQL query plans can track releases like this one on daily.dev."}},{"@type":"Question","name":"How can I compare two PostgreSQL query plans without noise from cost and timing differences?","acceptedAnswer":{"@type":"Answer","text":"Use sqlfmt, a standalone tool that extracts the same four-line plan advice format (join order, join method, scan method, parallelism) from ordinary EXPLAIN output on any PostgreSQL version, then diff two advice files with 'sqlfmt explain diff'. Because advice omits costs and timings, identical plans produce identical output and only genuine structural differences show up, unlike raw EXPLAIN diffs where every line differs due to cost noise. Teams validating query plans before a PostgreSQL upgrade can follow tooling changes like this via daily.dev."}},{"@type":"Question","name":"What happens if PostgreSQL plan advice asks for a join method that is disabled?","acceptedAnswer":{"@type":"Answer","text":"The planner reports the advice as matched but failed rather than silently ignoring it or crashing. For example, forcing HASH_JOIN while enable_hashjoin is off produces the annotation 'matched, failed' in the Supplied Plan Advice block, meaning the advice was understood and applied to the right part of the query, but the planner could not honor it because that plan type was unavailable. Advice only constrains choices the planner would otherwise consider; it cannot resurrect disabled plan types. Engineers relying on plan hints to hold a fix in place can watch for edge cases like this through daily.dev."}}]}
```

