Re: pg_plan_advice
Ajay Pal <ajay.pal.k@gmail.com>
Thank you Robert for clarification. On Tue, Jan 27, 2026 at 6:02 PM Robert Haas <robertmhaas@gmail.com> wrote: > > On Tue, Jan 27, 2026 at 2:49 AM Ajay Pal <ajay.pal.k@gmail.com> wrote: > > #1 Grouped Hash Join, This forces the join of dim1 and dim2 to happen > > first, and then places that resulting set on the inner side of a Hash > > Join against fact. > > but the planner partially matches the generated advice. > > > > -- We want (dim1 JOIN dim2) to be the inner side of a Hash Join > > SET LOCAL pg_plan_advice.advice = 'HASH_JOIN((dim1 dim2))'; > > > > postgres=*# EXPLAIN (COSTS OFF, PLAN_ADVICE) > > SELECT * FROM fact > > JOIN dim1 ON fact.d1_id = dim1.id > > JOIN dim2 ON fact.d2_id = dim2.id; > > QUERY PLAN > > ----------------------------------------------------------- > > Nested Loop > > Disabled: true > > -> Nested Loop > > Disabled: true > > -> Seq Scan on fact > > -> Index Scan using dim1_pkey on dim1 > > Index Cond: (id = fact.d1_id) > > -> Index Scan using dim2_pkey on dim2 > > Index Cond: (id = fact.d2_id) > > Supplied Plan Advice: > > HASH_JOIN((dim1 dim2)) /* partially matched */ > > Generated Plan Advice: > > JOIN_ORDER(fact dim1 dim2) > > NESTED_LOOP_PLAIN(dim1 dim2) > > SEQ_SCAN(fact) > > INDEX_SCAN(dim1 public.dim1_pkey dim2 public.dim2_pkey) > > NO_GATHER(fact dim1 dim2) > > (17 rows) > > Thanks for the report, but this is actually correct behavior. There's > no join clause between dim1 and dim2, so the planner doesn't consider > a dim1-dim2 join. This is a good example of the phenomenon described > in the documentation: you can't force the planner to create an > arbitrary plan that it wouldn't otherwise have considered. I might > tweak the documentation wording a little to try to mention that this > is another way "partially matched" can happen, but there's no bug > here. > > > #2 Multiple Instances of Same Table in Subqueries, here target the > > second instance of dim1 inside the subquery 'sq'. both seq_scan and > > index_scan advices are not matching. > > > > SET LOCAL pg_plan_advice.advice = 'SEQ_SCAN(dim1#2@sq) > > INDEX_SCAN(dim1@sq dim1_pkey)'; > > > > postgres=*# EXPLAIN (COSTS OFF, PLAN_ADVICE) > > SELECT * FROM fact > > JOIN ( > > SELECT a.id FROM dim1 a > > JOIN dim1 b ON a.id = b.id > > OFFSET 0 > > ) sq ON fact.d1_id = sq.id; > > QUERY PLAN > > --------------------------------------------------- > > Hash Join > > Hash Cond: (fact.d1_id = b.id) > > -> Seq Scan on fact > > -> Hash > > -> Seq Scan on dim1 b > > Supplied Plan Advice: > > SEQ_SCAN(dim1#2@sq) /* not matched */ > > INDEX_SCAN(dim1@sq dim1_pkey) /* not matched */ > > Generated Plan Advice: > > JOIN_ORDER(fact sq) > > HASH_JOIN(sq) > > SEQ_SCAN(b@sq fact) > > NO_GATHER(fact b@sq) > > (13 rows) > > I'm not sure what why you expected this to work. You can see what the > correct relation identifiers are from the generated plan advice, and > you've used something else, so it doesn't match. It's documented in > both the SGML documentation and the README that relation identifiers > are based on the relation alias, not the relation name. > > In general, this seems like a good to reiterate that this is first and > foremost a plan stability feature. More than anything, these examples > show that if you try to write your own plan advice from scratch to > force a novel plan that the planner has never produced itself, you may > not have much luck. If you do want to try to produce a novel plan, you > should at least look at the generated plan advice and adapt it instead > of starting from scratch. And if you find, when trying to produce a > novel plan, that it doesn't work, you need to consider the possibility > that this is because the optimizer did not ever consider that plan, > and that is why pg_plan_advice is unable to induce the planner to > prefer it. That's not to say there can't be any remaining bugs in > pg_plan_advice; there probably are. But it also is absolutely not a > "write your own plan and do anything you like" feature. > > -- > Robert Haas > EDB: http://www.enterprisedb.com
Commits
GET /api/v1/messages/:b64id/commits
the thread's linked commits as JSON, with link sources.
API reference →
-
pg_plan_advice: Fix another unique-semijoin bug.
- 4321dcad475b 19 (unreleased) landed
-
pg_plan_advice: Export feedback-related definitions.
- c644aca24089 19 (unreleased) landed
-
pg_plan_advice: Fix a bug when a subquery is pruned away entirely.
- 0f93ebb3112d 19 (unreleased) landed
-
pg_plan_advice: Add alternatives test to Makefile.
- 1faf9dfa4796 19 (unreleased) landed
-
pg_plan_advice: Handle non-repeatable TABLESAMPLE scans.
- 3311ccc3d24b 19 (unreleased) landed
-
pg_stash_advice: Allow stashed advice to be persisted to disk.
- c10edb102ada 19 (unreleased) landed
-
Add pg_stash_advice contrib module.
- e8ec19aa321a 19 (unreleased) landed
-
pg_plan_advice: Avoid assertion failure with partitionwise aggregate.
- e2ee95233cab 19 (unreleased) landed
-
pg_plan_advice: Invent DO_NOT_SCAN(relation_identifier).
- 6455e55b0da4 19 (unreleased) landed
-
Add an alternative_plan_name field to PlannerInfo.
- 26255a320733 19 (unreleased) landed
-
pg_plan_advice: Refactor to invent pgpa_planner_info
- 5dcb15e89af2 19 (unreleased) landed
-
Respect disabled_nodes in fix_alternative_subplan.
- 47c110f77e75 19 (unreleased) landed
-
get_memoize_path: Don't exit quickly when PGS_NESTLOOP_PLAIN is unset.
- dc47beacaa0b 19 (unreleased) landed
-
test_plan_advice: Set TAP test priority 50 in meson.build.
- 12444183e401 19 (unreleased) landed
-
pg_plan_advice: Avoid a crash under GEQO.
- 01b02c0ecad1 19 (unreleased) landed
-
Test pg_plan_advice using a new test_plan_advice module.
- e0e4c132ef2b 19 (unreleased) landed
-
pg_plan_advice: Always install pg_plan_advice.h, and in the right place
- 59dcc19b397f 19 (unreleased) landed
-
pg_plan_advice: Fix failures to accept identifier keywords.
- 5e72ce2467c1 19 (unreleased) landed
-
Add pg_plan_advice contrib module.
- 5883ff30b02c 19 (unreleased) landed
-
Allow extensions to mark an individual index as disabled.
- 0fbfd37cefb7 19 (unreleased) landed
-
Replace get_relation_info_hook with build_simple_rel_hook.
- 91f33a2ae92a 19 (unreleased) landed
-
Store information about Append node consolidation in the final plan.
- 7358abcc6076 19 (unreleased) landed
-
Store information about elided nodes in the final plan.
- 0d4391b265f8 19 (unreleased) landed
-
Store information about range-table flattening in the final plan.
- adbad833f3d9 19 (unreleased) landed
-
Pass cursorOptions to planner_setup_hook.
- 0f4c8d33d49d 19 (unreleased) landed
-
Fix PGS_CONSIDER_NONPARTIAL interaction with Materialize nodes.
- cbdf93d47122 19 (unreleased) landed
-
Fix mistakes in commit 4020b370f214315b8c10430301898ac21658143f
- 71c1136989b3 19 (unreleased) landed
-
Allow for plugin control over path generation strategies.
- 4020b370f214 19 (unreleased) landed
-
Update some comments for fasthash
- 7892e2592471 19 (unreleased) landed
-
Allow passing a pointer to GetNamedDSMSegment()'s init callback.
- 48d4a1423d2e 19 (unreleased) cited
-
Don't reset the pathlist of partitioned joinrels.
- 014f9a831a32 19 (unreleased) cited
-
Treat number of disabled nodes in a path as a separate cost metric.
- e22253467942 18.0 cited