diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index a6c6de78f1..cc2b957956 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -2075,9 +2075,10 @@ show_plan_tlist(PlanState *planstate, List *ancestors, ExplainState *es)
 		return;
 
 	/* Set up deparsing context */
-	context = set_deparse_context_planstate(es->deparse_cxt,
-											(Node *) planstate,
-											ancestors);
+	context = set_deparse_context_plan(es->deparse_cxt,
+									   (Node *) planstate,
+									   planstate->plan,
+									   ancestors);
 	useprefix = list_length(es->rtable) > 1;
 
 	/* Deparse each result column (we now include resjunk ones) */
@@ -2106,9 +2107,10 @@ show_expression(Node *node, const char *qlabel,
 	char	   *exprstr;
 
 	/* Set up deparsing context */
-	context = set_deparse_context_planstate(es->deparse_cxt,
-											(Node *) planstate,
-											ancestors);
+	context = set_deparse_context_plan(es->deparse_cxt,
+									   (Node *) planstate,
+									   planstate->plan,
+									   ancestors);
 
 	/* Deparse the expression */
 	exprstr = deparse_expression(node, context, useprefix, false);
@@ -2232,9 +2234,10 @@ show_grouping_sets(PlanState *planstate, Agg *agg,
 	ListCell   *lc;
 
 	/* Set up deparsing context */
-	context = set_deparse_context_planstate(es->deparse_cxt,
-											(Node *) planstate,
-											ancestors);
+	context = set_deparse_context_plan(es->deparse_cxt,
+									   (Node *) planstate,
+									   planstate->plan,
+									   ancestors);
 	useprefix = (list_length(es->rtable) > 1 || es->verbose);
 
 	ExplainOpenGroup("Grouping Sets", "Grouping Sets", false, es);
@@ -2371,9 +2374,10 @@ show_sort_group_keys(PlanState *planstate, const char *qlabel,
 	initStringInfo(&sortkeybuf);
 
 	/* Set up deparsing context */
-	context = set_deparse_context_planstate(es->deparse_cxt,
-											(Node *) planstate,
-											ancestors);
+	context = set_deparse_context_plan(es->deparse_cxt,
+									   (Node *) planstate,
+									   planstate->plan,
+									   ancestors);
 	useprefix = (list_length(es->rtable) > 1 || es->verbose);
 
 	for (keyno = 0; keyno < nkeys; keyno++)
@@ -2479,9 +2483,10 @@ show_tablesample(TableSampleClause *tsc, PlanState *planstate,
 	ListCell   *lc;
 
 	/* Set up deparsing context */
-	context = set_deparse_context_planstate(es->deparse_cxt,
-											(Node *) planstate,
-											ancestors);
+	context = set_deparse_context_plan(es->deparse_cxt,
+									   (Node *) planstate,
+									   planstate->plan,
+									   ancestors);
 	useprefix = list_length(es->rtable) > 1;
 
 	/* Get the tablesample method name */
diff --git a/src/backend/executor/nodeAppend.c b/src/backend/executor/nodeAppend.c
index f3be2429db..26a4409fef 100644
--- a/src/backend/executor/nodeAppend.c
+++ b/src/backend/executor/nodeAppend.c
@@ -78,7 +78,6 @@ struct ParallelAppendState
 };
 
 #define INVALID_SUBPLAN_INDEX		-1
-#define NO_MATCHING_SUBPLANS		-2
 
 static TupleTableSlot *ExecAppend(PlanState *pstate);
 static bool choose_next_subplan_locally(AppendState *node);
@@ -142,23 +141,6 @@ ExecInitAppend(Append *node, EState *estate, int eflags)
 			validsubplans = ExecFindInitialMatchingSubPlans(prunestate,
 															list_length(node->appendplans));
 
-			/*
-			 * The case where no subplans survive pruning must be handled
-			 * specially.  The problem here is that code in explain.c requires
-			 * an Append to have at least one subplan in order for it to
-			 * properly determine the Vars in that subplan's targetlist.  We
-			 * sidestep this issue by just initializing the first subplan and
-			 * setting as_whichplan to NO_MATCHING_SUBPLANS to indicate that
-			 * we don't really need to scan any subnodes.
-			 */
-			if (bms_is_empty(validsubplans))
-			{
-				appendstate->as_whichplan = NO_MATCHING_SUBPLANS;
-
-				/* Mark the first as valid so that it's initialized below */
-				validsubplans = bms_make_singleton(0);
-			}
-
 			nplans = bms_num_members(validsubplans);
 		}
 		else
@@ -170,14 +152,12 @@ ExecInitAppend(Append *node, EState *estate, int eflags)
 		}
 
 		/*
-		 * If no runtime pruning is required, we can fill as_valid_subplans
-		 * immediately, preventing later calls to ExecFindMatchingSubPlans.
+		 * If there's at least one valid plan and no run-time pruning is
+		 * required, we can fill as_valid_subplans immediately, preventing
+		 * later calls to ExecFindMatchingSubPlans.
 		 */
-		if (!prunestate->do_exec_prune)
-		{
-			Assert(nplans > 0);
+		if (nplans > 0 && !prunestate->do_exec_prune)
 			appendstate->as_valid_subplans = bms_add_range(NULL, 0, nplans - 1);
-		}
 	}
 	else
 	{
@@ -202,8 +182,11 @@ ExecInitAppend(Append *node, EState *estate, int eflags)
 	appendstate->ps.resultopsset = true;
 	appendstate->ps.resultopsfixed = false;
 
-	appendplanstates = (PlanState **) palloc(nplans *
-											 sizeof(PlanState *));
+	if (nplans > 0)
+		appendplanstates = (PlanState **) palloc(nplans *
+												 sizeof(PlanState *));
+	else
+		appendplanstates = NULL;
 
 	/*
 	 * call ExecInitNode on each of the valid plans to be executed and save
@@ -260,6 +243,10 @@ ExecAppend(PlanState *pstate)
 
 	if (node->as_whichplan < 0)
 	{
+		/* Nothing to do if there are no subplans */
+		if (node->as_nplans == 0)
+			return ExecClearTuple(node->ps.ps_ResultTupleSlot);
+
 		/*
 		 * If no subplan has been chosen, we must choose one before
 		 * proceeding.
@@ -267,10 +254,6 @@ ExecAppend(PlanState *pstate)
 		if (node->as_whichplan == INVALID_SUBPLAN_INDEX &&
 			!node->choose_next_subplan(node))
 			return ExecClearTuple(node->ps.ps_ResultTupleSlot);
-
-		/* Nothing to do if there are no matching subplans */
-		else if (node->as_whichplan == NO_MATCHING_SUBPLANS)
-			return ExecClearTuple(node->ps.ps_ResultTupleSlot);
 	}
 
 	for (;;)
@@ -465,7 +448,7 @@ choose_next_subplan_locally(AppendState *node)
 	int			nextplan;
 
 	/* We should never be called when there are no subplans */
-	Assert(whichplan != NO_MATCHING_SUBPLANS);
+	Assert(node->as_nplans > 0);
 
 	/*
 	 * If first call then have the bms member function choose the first valid
@@ -516,7 +499,7 @@ choose_next_subplan_for_leader(AppendState *node)
 	Assert(ScanDirectionIsForward(node->ps.state->es_direction));
 
 	/* We should never be called when there are no subplans */
-	Assert(node->as_whichplan != NO_MATCHING_SUBPLANS);
+	Assert(node->as_nplans > 0);
 
 	LWLockAcquire(&pstate->pa_lock, LW_EXCLUSIVE);
 
@@ -597,7 +580,7 @@ choose_next_subplan_for_worker(AppendState *node)
 	Assert(ScanDirectionIsForward(node->ps.state->es_direction));
 
 	/* We should never be called when there are no subplans */
-	Assert(node->as_whichplan != NO_MATCHING_SUBPLANS);
+	Assert(node->as_nplans > 0);
 
 	LWLockAcquire(&pstate->pa_lock, LW_EXCLUSIVE);
 
diff --git a/src/backend/executor/nodeMergeAppend.c b/src/backend/executor/nodeMergeAppend.c
index 7ba53ba185..c08c4f38ff 100644
--- a/src/backend/executor/nodeMergeAppend.c
+++ b/src/backend/executor/nodeMergeAppend.c
@@ -81,7 +81,6 @@ ExecInitMergeAppend(MergeAppend *node, EState *estate, int eflags)
 	mergestate->ps.plan = (Plan *) node;
 	mergestate->ps.state = estate;
 	mergestate->ps.ExecProcNode = ExecMergeAppend;
-	mergestate->ms_noopscan = false;
 
 	/* If run-time partition pruning is enabled, then set that up now */
 	if (node->part_prune_info != NULL)
@@ -102,23 +101,6 @@ ExecInitMergeAppend(MergeAppend *node, EState *estate, int eflags)
 			validsubplans = ExecFindInitialMatchingSubPlans(prunestate,
 															list_length(node->mergeplans));
 
-			/*
-			 * The case where no subplans survive pruning must be handled
-			 * specially.  The problem here is that code in explain.c requires
-			 * a MergeAppend to have at least one subplan in order for it to
-			 * properly determine the Vars in that subplan's targetlist.  We
-			 * sidestep this issue by just initializing the first subplan and
-			 * setting ms_noopscan to true to indicate that we don't really
-			 * need to scan any subnodes.
-			 */
-			if (bms_is_empty(validsubplans))
-			{
-				mergestate->ms_noopscan = true;
-
-				/* Mark the first as valid so that it's initialized below */
-				validsubplans = bms_make_singleton(0);
-			}
-
 			nplans = bms_num_members(validsubplans);
 		}
 		else
@@ -133,11 +115,8 @@ ExecInitMergeAppend(MergeAppend *node, EState *estate, int eflags)
 		 * If no runtime pruning is required, we can fill ms_valid_subplans
 		 * immediately, preventing later calls to ExecFindMatchingSubPlans.
 		 */
-		if (!prunestate->do_exec_prune)
-		{
-			Assert(nplans > 0);
+		if (nplans > 0 && !prunestate->do_exec_prune)
 			mergestate->ms_valid_subplans = bms_add_range(NULL, 0, nplans - 1);
-		}
 	}
 	else
 	{
@@ -153,7 +132,11 @@ ExecInitMergeAppend(MergeAppend *node, EState *estate, int eflags)
 		mergestate->ms_prune_state = NULL;
 	}
 
-	mergeplanstates = (PlanState **) palloc(nplans * sizeof(PlanState *));
+	if (nplans > 0)
+		mergeplanstates = (PlanState **) palloc(nplans * sizeof(PlanState *));
+	else
+		mergeplanstates = NULL;
+
 	mergestate->mergeplans = mergeplanstates;
 	mergestate->ms_nplans = nplans;
 
@@ -244,7 +227,7 @@ ExecMergeAppend(PlanState *pstate)
 	if (!node->ms_initialized)
 	{
 		/* Nothing to do if all subplans were pruned */
-		if (node->ms_noopscan)
+		if (node->ms_nplans == 0)
 			return ExecClearTuple(node->ps.ps_ResultTupleSlot);
 
 		/*
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 0c7a533e69..712ff3a9a6 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -146,7 +146,13 @@ typedef struct
  * state nodes, as well as their plan nodes' targetlists, and the index tlist
  * if the current plan node might contain INDEX_VAR Vars.  (These fields could
  * be derived on-the-fly from the current PlanState, but it seems notationally
- * clearer to set them up as separate fields.)
+ * clearer to set them up as separate fields.).  Additionally we store the Plan
+ * nodes corisponding to each PlanState node.  It might appear like we could
+ * just obtain these from looking at the PlanStates->plan field, but in some
+ * cases a PlanState can be NULL when a Plan node was not initialized during
+ * executor startup.  This can happen for things such as run-time partition
+ * pruning.  Storing these allows us to still resolve Vars for Plan nodes that
+ * were never initialized.
  */
 typedef struct
 {
@@ -158,6 +164,10 @@ typedef struct
 	bool		unique_using;	/* Are we making USING names globally unique */
 	List	   *using_names;	/* List of assigned names for USING columns */
 	/* Remaining fields are used only when deparsing a Plan tree: */
+	Plan	   *plan;		/* immediate parent of current expression */
+	Plan	   *outer_plan;		/* outer subplan, or NULL if none */
+	Plan	   *inner_plan;		/* inner subplan, or NULL if none */
+	List	   *ancestor_plans;	/* ancestors of plan */
 	PlanState  *planstate;		/* immediate parent of current expression */
 	List	   *ancestors;		/* ancestors of planstate */
 	PlanState  *outer_planstate;	/* outer subplan state, or NULL if none */
@@ -358,12 +368,14 @@ static void identify_join_columns(JoinExpr *j, RangeTblEntry *jrte,
 static void flatten_join_using_qual(Node *qual,
 						List **leftvars, List **rightvars);
 static char *get_rtable_name(int rtindex, deparse_context *context);
-static void set_deparse_planstate(deparse_namespace *dpns, PlanState *ps);
+static void set_deparse_plan(deparse_namespace *dpns, PlanState *ps,
+				 Plan *plan);
 static void push_child_plan(deparse_namespace *dpns, PlanState *ps,
-				deparse_namespace *save_dpns);
+				Plan *plan, deparse_namespace *save_dpns);
 static void pop_child_plan(deparse_namespace *dpns,
 			   deparse_namespace *save_dpns);
 static void push_ancestor_plan(deparse_namespace *dpns, ListCell *ancestor_cell,
+				   ListCell *ancestor_plan_cell,
 				   deparse_namespace *save_dpns);
 static void pop_ancestor_plan(deparse_namespace *dpns,
 				  deparse_namespace *save_dpns);
@@ -410,7 +422,8 @@ static void resolve_special_varno(Node *node, deparse_context *context,
 					  void *private,
 					  void (*callback) (Node *, deparse_context *, void *));
 static Node *find_param_referent(Param *param, deparse_context *context,
-					deparse_namespace **dpns_p, ListCell **ancestor_cell_p);
+					deparse_namespace **dpns_p, ListCell **ancestor_cell_p,
+					ListCell **ancestor_plan_cell_p);
 static void get_parameter(Param *param, deparse_context *context);
 static const char *get_simple_binary_op_name(OpExpr *expr);
 static bool isSimpleNode(Node *node, Node *parentNode, int prettyFlags);
@@ -3285,7 +3298,7 @@ deparse_context_for(const char *aliasname, Oid relid)
  * this is rather expensive if the rangetable is large, and it'll be the same
  * for every expression in the Plan tree; so we do it just once and re-use
  * the result of this function for each expression.  (Note that the result
- * is not usable until set_deparse_context_planstate() is applied to it.)
+ * is not usable until set_deparse_context_plan() is applied to it.)
  *
  * In addition to the plan's rangetable list, pass the per-RTE alias names
  * assigned by a previous call to select_rtable_names_for_explain.
@@ -3314,7 +3327,7 @@ deparse_context_for_plan_rtable(List *rtable, List *rtable_names)
 }
 
 /*
- * set_deparse_context_planstate	- Specify Plan node containing expression
+ * set_deparse_context_plan	- Specify Plan node containing expression
  *
  * When deparsing an expression in a Plan tree, we might have to resolve
  * OUTER_VAR, INNER_VAR, or INDEX_VAR references.  To do this, the caller must
@@ -3342,19 +3355,32 @@ deparse_context_for_plan_rtable(List *rtable, List *rtable_names)
  * The result is the same List passed in; this is a notational convenience.
  */
 List *
-set_deparse_context_planstate(List *dpcontext,
-							  Node *planstate, List *ancestors)
+set_deparse_context_plan(List *dpcontext,
+						 Node *planstate, Plan *plan, List *ancestors)
 {
 	deparse_namespace *dpns;
+	ListCell	   *lc;
 
 	/* Should always have one-entry namespace list for Plan deparsing */
 	Assert(list_length(dpcontext) == 1);
 	dpns = (deparse_namespace *) linitial(dpcontext);
 
 	/* Set our attention on the specific plan node passed in */
-	set_deparse_planstate(dpns, (PlanState *) planstate);
+	set_deparse_plan(dpns, (PlanState *) planstate, plan);
 	dpns->ancestors = ancestors;
 
+	if (dpns->ancestor_plans != NIL)
+	{
+		list_free(dpns->ancestor_plans);
+		dpns->ancestor_plans = NIL;
+	}
+
+	foreach(lc, ancestors)
+	{
+		PlanState *ps = lfirst(lc);
+		dpns->ancestor_plans = lappend(dpns->ancestor_plans, ps->plan);
+	}
+
 	return dpcontext;
 }
 
@@ -4620,19 +4646,20 @@ get_rtable_name(int rtindex, deparse_context *context)
 }
 
 /*
- * set_deparse_planstate: set up deparse_namespace to parse subexpressions
+ * set_deparse_plan: set up deparse_namespace to parse subexpressions
  * of a given PlanState node
  *
- * This sets the planstate, outer_planstate, inner_planstate, outer_tlist,
- * inner_tlist, and index_tlist fields.  Caller is responsible for adjusting
- * the ancestors list if necessary.  Note that the rtable and ctes fields do
- * not need to change when shifting attention to different plan nodes in a
- * single plan tree.
+ * This sets the planstate, outer_planstate, inner_planstate, plan,
+ * outer_plan, inner_plan outer_tlist, inner_tlist, and index_tlist fields.
+ * Caller is responsible for adjusting the ancestors and ancestor_plans lists
+ * if necessary.  Note that the rtable and ctes fields do not need to change
+ * when shifting attention to different plan nodes in a single plan tree.
  */
 static void
-set_deparse_planstate(deparse_namespace *dpns, PlanState *ps)
+set_deparse_plan(deparse_namespace *dpns, PlanState *ps, Plan *plan)
 {
 	dpns->planstate = ps;
+	dpns->plan = plan;
 
 	/*
 	 * We special-case Append and MergeAppend to pretend that the first child
@@ -4642,17 +4669,60 @@ set_deparse_planstate(deparse_namespace *dpns, PlanState *ps)
 	 * first child plan is the OUTER referent; this is to support RETURNING
 	 * lists containing references to non-target relations.
 	 */
-	if (IsA(ps, AppendState))
-		dpns->outer_planstate = ((AppendState *) ps)->appendplans[0];
-	else if (IsA(ps, MergeAppendState))
-		dpns->outer_planstate = ((MergeAppendState *) ps)->mergeplans[0];
-	else if (IsA(ps, ModifyTableState))
-		dpns->outer_planstate = ((ModifyTableState *) ps)->mt_plans[0];
+	if (IsA(plan, Append))
+	{
+		AppendState *astate = (AppendState *) ps;
+
+		/*
+		 * It's possible that run-time partition pruning pruned every subplan
+		 * which means we'll have no subnodes in the AppendState.  If this
+		 * happens we'll use the first of the planned subnodes and set the
+		 * outer_planstate to NULL.
+		 */
+		if (astate->as_nplans > 0)
+		{
+			dpns->outer_planstate = astate->appendplans[0];
+			dpns->outer_plan = dpns->outer_planstate->plan;
+		}
+		else
+		{
+			dpns->outer_planstate = NULL;
+			dpns->outer_plan = (Plan *) linitial(((Append *) plan)->appendplans);
+		}
+	}
+	else if (IsA(plan, MergeAppend))
+	{
+		MergeAppendState *mastate = (MergeAppendState *) ps;
+
+		/*
+		 * Like the Append case above, use the first subplan from the plan if
+		 * run-time pruning pruned all of the subnodes.
+		 */
+		if (mastate->ms_nplans > 0)
+		{
+			dpns->outer_planstate = mastate->mergeplans[0];
+			dpns->outer_plan = dpns->outer_planstate->plan;
+		}
+		else
+		{
+			dpns->outer_planstate = NULL;
+			dpns->outer_plan = (Plan *) linitial(((MergeAppend *) plan)->mergeplans);
+		}
+	}
+	else if (IsA(plan, ModifyTable))
+	{
+		ModifyTableState *mtstate = (ModifyTableState *) ps;
+		dpns->outer_planstate = mtstate->mt_plans[0];
+		dpns->outer_plan = dpns->outer_planstate->plan;
+	}
 	else
-		dpns->outer_planstate = outerPlanState(ps);
+	{
+		dpns->outer_plan = outerPlan(plan);
+		dpns->outer_planstate = ps != NULL ? outerPlanState(ps) : NULL;
+	}
 
-	if (dpns->outer_planstate)
-		dpns->outer_tlist = dpns->outer_planstate->plan->targetlist;
+	if (dpns->outer_plan)
+		dpns->outer_tlist = dpns->outer_plan->targetlist;
 	else
 		dpns->outer_tlist = NIL;
 
@@ -4665,29 +4735,41 @@ set_deparse_planstate(deparse_namespace *dpns, PlanState *ps)
 	 * to reuse OUTER, it's used for RETURNING in some modify table cases,
 	 * although not INSERT .. CONFLICT).
 	 */
-	if (IsA(ps, SubqueryScanState))
-		dpns->inner_planstate = ((SubqueryScanState *) ps)->subplan;
-	else if (IsA(ps, CteScanState))
-		dpns->inner_planstate = ((CteScanState *) ps)->cteplanstate;
-	else if (IsA(ps, ModifyTableState))
+	if (IsA(plan, SubqueryScan))
+	{
+		dpns->inner_planstate = ((SubqueryScanState *)ps)->subplan;
+		dpns->inner_plan = dpns->inner_planstate->plan;
+	}
+	else if (IsA(plan, CteScan))
+	{
+		dpns->inner_planstate = ((CteScanState *)ps)->cteplanstate;
+		dpns->inner_plan = dpns->inner_planstate->plan;
+	}
+	else if (IsA(plan, ModifyTable))
+	{
+		dpns->inner_plan = plan;
 		dpns->inner_planstate = ps;
+	}
 	else
-		dpns->inner_planstate = innerPlanState(ps);
+	{
+		dpns->inner_plan = innerPlan(plan);
+		dpns->inner_planstate = ps != NULL ? innerPlanState(ps) : NULL;
+	}
 
-	if (IsA(ps, ModifyTableState))
-		dpns->inner_tlist = ((ModifyTableState *) ps)->mt_excludedtlist;
-	else if (dpns->inner_planstate)
-		dpns->inner_tlist = dpns->inner_planstate->plan->targetlist;
+	if (IsA(plan, ModifyTable))
+		dpns->inner_tlist = ((ModifyTable *) plan)->exclRelTlist;
+	else if (dpns->inner_plan)
+		dpns->inner_tlist = dpns->inner_plan->targetlist;
 	else
 		dpns->inner_tlist = NIL;
 
 	/* Set up referent for INDEX_VAR Vars, if needed */
-	if (IsA(ps->plan, IndexOnlyScan))
-		dpns->index_tlist = ((IndexOnlyScan *) ps->plan)->indextlist;
-	else if (IsA(ps->plan, ForeignScan))
-		dpns->index_tlist = ((ForeignScan *) ps->plan)->fdw_scan_tlist;
-	else if (IsA(ps->plan, CustomScan))
-		dpns->index_tlist = ((CustomScan *) ps->plan)->custom_scan_tlist;
+	if (IsA(plan, IndexOnlyScan))
+		dpns->index_tlist = ((IndexOnlyScan *) plan)->indextlist;
+	else if (IsA(plan, ForeignScan))
+		dpns->index_tlist = ((ForeignScan *) plan)->fdw_scan_tlist;
+	else if (IsA(plan, CustomScan))
+		dpns->index_tlist = ((CustomScan *) plan)->custom_scan_tlist;
 	else
 		dpns->index_tlist = NIL;
 }
@@ -4705,17 +4787,18 @@ set_deparse_planstate(deparse_namespace *dpns, PlanState *ps)
  * previous state for pop_child_plan.
  */
 static void
-push_child_plan(deparse_namespace *dpns, PlanState *ps,
+push_child_plan(deparse_namespace *dpns, PlanState *ps, Plan *plan,
 				deparse_namespace *save_dpns)
 {
 	/* Save state for restoration later */
 	*save_dpns = *dpns;
 
-	/* Link current plan node into ancestors list */
+	/* Link current plan node into ancestors and ancestor_plans lists */
 	dpns->ancestors = lcons(dpns->planstate, dpns->ancestors);
+	dpns->ancestor_plans = lcons(dpns->plan, dpns->ancestor_plans);
 
 	/* Set attention on selected child */
-	set_deparse_planstate(dpns, ps);
+	set_deparse_plan(dpns, ps, plan);
 }
 
 /*
@@ -4725,15 +4808,22 @@ static void
 pop_child_plan(deparse_namespace *dpns, deparse_namespace *save_dpns)
 {
 	List	   *ancestors;
+	List	   *ancestor_plans;
 
-	/* Get rid of ancestors list cell added by push_child_plan */
+	/* Get rid of ancestors list cells added by push_child_plan */
 	ancestors = list_delete_first(dpns->ancestors);
+	ancestor_plans = list_delete_first(dpns->ancestor_plans);
 
 	/* Restore fields changed by push_child_plan */
 	*dpns = *save_dpns;
 
-	/* Make sure dpns->ancestors is right (may be unnecessary) */
+	/*
+	 * Make sure dpns->ancestors and dpns->ancestor_plans is right (may be
+	 * unnecessary)
+	 */
 	dpns->ancestors = ancestors;
+	dpns->ancestor_plans = ancestor_plans;
+
 }
 
 /*
@@ -4753,10 +4843,12 @@ pop_child_plan(deparse_namespace *dpns, deparse_namespace *save_dpns)
  */
 static void
 push_ancestor_plan(deparse_namespace *dpns, ListCell *ancestor_cell,
-				   deparse_namespace *save_dpns)
+				   ListCell *ancestor_plan_cell, deparse_namespace *save_dpns)
 {
-	PlanState  *ps = (PlanState *) lfirst(ancestor_cell);
+	PlanState  *planstate = (PlanState *) lfirst(ancestor_cell);
+	Plan	   *plan = (Plan *) lfirst(ancestor_plan_cell);
 	List	   *ancestors;
+	List	   *ancestor_plans;
 
 	/* Save state for restoration later */
 	*save_dpns = *dpns;
@@ -4767,8 +4859,14 @@ push_ancestor_plan(deparse_namespace *dpns, ListCell *ancestor_cell,
 		ancestors = lappend(ancestors, lfirst(ancestor_cell));
 	dpns->ancestors = ancestors;
 
+	/* and same for the ancestor_plans list */
+	ancestor_plans = NIL;
+	while ((ancestor_plan_cell = lnext(ancestor_plan_cell)) != NULL)
+		ancestor_plans = lappend(ancestor_plans, lfirst(ancestor_plan_cell));
+	dpns->ancestor_plans = ancestor_plans;
+
 	/* Set attention on selected ancestor */
-	set_deparse_planstate(dpns, ps);
+	set_deparse_plan(dpns, planstate, plan);
 }
 
 /*
@@ -6702,7 +6800,7 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
 	 */
 	if ((rte->rtekind == RTE_SUBQUERY || rte->rtekind == RTE_CTE) &&
 		attnum > list_length(rte->eref->colnames) &&
-		dpns->inner_planstate)
+		dpns->inner_plan)
 	{
 		TargetEntry *tle;
 		deparse_namespace save_dpns;
@@ -6713,7 +6811,7 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
 				 var->varattno, rte->eref->aliasname);
 
 		Assert(netlevelsup == 0);
-		push_child_plan(dpns, dpns->inner_planstate, &save_dpns);
+		push_child_plan(dpns, dpns->inner_planstate, dpns->inner_plan, &save_dpns);
 
 		/*
 		 * Force parentheses because our caller probably assumed a Var is a
@@ -6863,7 +6961,7 @@ resolve_special_varno(Node *node, deparse_context *context, void *private,
 		if (!tle)
 			elog(ERROR, "bogus varattno for OUTER_VAR var: %d", var->varattno);
 
-		push_child_plan(dpns, dpns->outer_planstate, &save_dpns);
+		push_child_plan(dpns, dpns->outer_planstate, dpns->outer_plan, &save_dpns);
 		resolve_special_varno((Node *) tle->expr, context, private, callback);
 		pop_child_plan(dpns, &save_dpns);
 		return;
@@ -6877,7 +6975,7 @@ resolve_special_varno(Node *node, deparse_context *context, void *private,
 		if (!tle)
 			elog(ERROR, "bogus varattno for INNER_VAR var: %d", var->varattno);
 
-		push_child_plan(dpns, dpns->inner_planstate, &save_dpns);
+		push_child_plan(dpns, dpns->inner_planstate, dpns->inner_plan, &save_dpns);
 		resolve_special_varno((Node *) tle->expr, context, private, callback);
 		pop_child_plan(dpns, &save_dpns);
 		return;
@@ -6947,15 +7045,18 @@ get_name_for_var_field(Var *var, int fieldno,
 	{
 		Param	   *param = (Param *) var;
 		ListCell   *ancestor_cell;
+		ListCell   *ancestor_plan_cell;
 
-		expr = find_param_referent(param, context, &dpns, &ancestor_cell);
+		expr = find_param_referent(param, context, &dpns, &ancestor_cell,
+								   &ancestor_plan_cell);
 		if (expr)
 		{
 			/* Found a match, so recurse to decipher the field name */
 			deparse_namespace save_dpns;
 			const char *result;
 
-			push_ancestor_plan(dpns, ancestor_cell, &save_dpns);
+			push_ancestor_plan(dpns, ancestor_cell, ancestor_plan_cell,
+							   &save_dpns);
 			result = get_name_for_var_field((Var *) expr, fieldno,
 											0, context);
 			pop_ancestor_plan(dpns, &save_dpns);
@@ -7005,7 +7106,8 @@ get_name_for_var_field(Var *var, int fieldno,
 			elog(ERROR, "bogus varattno for OUTER_VAR var: %d", var->varattno);
 
 		Assert(netlevelsup == 0);
-		push_child_plan(dpns, dpns->outer_planstate, &save_dpns);
+		push_child_plan(dpns, dpns->outer_planstate, dpns->outer_plan,
+						&save_dpns);
 
 		result = get_name_for_var_field((Var *) tle->expr, fieldno,
 										levelsup, context);
@@ -7024,7 +7126,8 @@ get_name_for_var_field(Var *var, int fieldno,
 			elog(ERROR, "bogus varattno for INNER_VAR var: %d", var->varattno);
 
 		Assert(netlevelsup == 0);
-		push_child_plan(dpns, dpns->inner_planstate, &save_dpns);
+		push_child_plan(dpns, dpns->inner_planstate, dpns->inner_plan,
+						&save_dpns);
 
 		result = get_name_for_var_field((Var *) tle->expr, fieldno,
 										levelsup, context);
@@ -7134,7 +7237,7 @@ get_name_for_var_field(Var *var, int fieldno,
 					deparse_namespace save_dpns;
 					const char *result;
 
-					if (!dpns->inner_planstate)
+					if (!dpns->inner_plan)
 						elog(ERROR, "failed to find plan for subquery %s",
 							 rte->eref->aliasname);
 					tle = get_tle_by_resno(dpns->inner_tlist, attnum);
@@ -7142,7 +7245,8 @@ get_name_for_var_field(Var *var, int fieldno,
 						elog(ERROR, "bogus varattno for subquery var: %d",
 							 attnum);
 					Assert(netlevelsup == 0);
-					push_child_plan(dpns, dpns->inner_planstate, &save_dpns);
+					push_child_plan(dpns, dpns->inner_planstate,
+									dpns->inner_plan, &save_dpns);
 
 					result = get_name_for_var_field((Var *) tle->expr, fieldno,
 													levelsup, context);
@@ -7252,7 +7356,7 @@ get_name_for_var_field(Var *var, int fieldno,
 					deparse_namespace save_dpns;
 					const char *result;
 
-					if (!dpns->inner_planstate)
+					if (!dpns->inner_plan)
 						elog(ERROR, "failed to find plan for CTE %s",
 							 rte->eref->aliasname);
 					tle = get_tle_by_resno(dpns->inner_tlist, attnum);
@@ -7260,7 +7364,8 @@ get_name_for_var_field(Var *var, int fieldno,
 						elog(ERROR, "bogus varattno for subquery var: %d",
 							 attnum);
 					Assert(netlevelsup == 0);
-					push_child_plan(dpns, dpns->inner_planstate, &save_dpns);
+					push_child_plan(dpns, dpns->inner_planstate,
+									dpns->inner_plan, &save_dpns);
 
 					result = get_name_for_var_field((Var *) tle->expr, fieldno,
 													levelsup, context);
@@ -7286,13 +7391,14 @@ get_name_for_var_field(Var *var, int fieldno,
  * Try to find the referenced expression for a PARAM_EXEC Param that might
  * reference a parameter supplied by an upper NestLoop or SubPlan plan node.
  *
- * If successful, return the expression and set *dpns_p and *ancestor_cell_p
- * appropriately for calling push_ancestor_plan().  If no referent can be
- * found, return NULL.
+ * If successful, return the expression and set *dpns_p, *ancestor_cell_p
+ * and ancestor_plan_cell_p appropriately for calling push_ancestor_plan().
+ *  If no referent can be found, return NULL.
  */
 static Node *
 find_param_referent(Param *param, deparse_context *context,
-					deparse_namespace **dpns_p, ListCell **ancestor_cell_p)
+					deparse_namespace **dpns_p, ListCell **ancestor_cell_p,
+					ListCell **ancestor_plan_cell_p)
 {
 	/* Initialize output parameters to prevent compiler warnings */
 	*dpns_p = NULL;
@@ -7309,12 +7415,13 @@ find_param_referent(Param *param, deparse_context *context,
 		PlanState  *child_ps;
 		bool		in_same_plan_level;
 		ListCell   *lc;
+		ListCell   *lcp;
 
 		dpns = (deparse_namespace *) linitial(context->namespaces);
 		child_ps = dpns->planstate;
 		in_same_plan_level = true;
 
-		foreach(lc, dpns->ancestors)
+		forboth(lc, dpns->ancestors, lcp, dpns->ancestor_plans)
 		{
 			PlanState  *ps = (PlanState *) lfirst(lc);
 			ListCell   *lc2;
@@ -7339,6 +7446,7 @@ find_param_referent(Param *param, deparse_context *context,
 						/* Found a match, so return it */
 						*dpns_p = dpns;
 						*ancestor_cell_p = lc;
+						*ancestor_plan_cell_p = lcp;
 						return (Node *) nlp->paramval;
 					}
 				}
@@ -7368,6 +7476,7 @@ find_param_referent(Param *param, deparse_context *context,
 						/* Found a match, so return it */
 						*dpns_p = dpns;
 						*ancestor_cell_p = lc;
+						*ancestor_plan_cell_p = lcp;
 						return arg;
 					}
 				}
@@ -7416,6 +7525,7 @@ get_parameter(Param *param, deparse_context *context)
 	Node	   *expr;
 	deparse_namespace *dpns;
 	ListCell   *ancestor_cell;
+	ListCell   *ancestor_plan_cell;
 
 	/*
 	 * If it's a PARAM_EXEC parameter, try to locate the expression from which
@@ -7423,7 +7533,8 @@ get_parameter(Param *param, deparse_context *context)
 	 * an error, since the Param might well be a subplan output rather than an
 	 * input.
 	 */
-	expr = find_param_referent(param, context, &dpns, &ancestor_cell);
+	expr = find_param_referent(param, context, &dpns, &ancestor_cell,
+							   &ancestor_plan_cell);
 	if (expr)
 	{
 		/* Found a match, so print it */
@@ -7432,7 +7543,8 @@ get_parameter(Param *param, deparse_context *context)
 		bool		need_paren;
 
 		/* Switch attention to the ancestor plan node */
-		push_ancestor_plan(dpns, ancestor_cell, &save_dpns);
+		push_ancestor_plan(dpns, ancestor_cell, ancestor_plan_cell,
+						   &save_dpns);
 
 		/*
 		 * Force prefixing of Vars, since they won't belong to the relation
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index ff3328752e..67ce8e343b 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1185,8 +1185,6 @@ struct AppendState
  *		slots			current output tuple of each subplan
  *		heap			heap of active tuples
  *		initialized		true if we have fetched first tuple from each subplan
- *		noopscan		true if partition pruning proved that none of the
- *						mergeplans can contain a record to satisfy this query.
  *		prune_state		details required to allow partitions to be
  *						eliminated from the scan, or NULL if not possible.
  *		valid_subplans	for runtime pruning, valid mergeplans indexes to
@@ -1203,7 +1201,6 @@ typedef struct MergeAppendState
 	TupleTableSlot **ms_slots;	/* array of length ms_nplans */
 	struct binaryheap *ms_heap; /* binary heap of slot indices */
 	bool		ms_initialized; /* are subplans started? */
-	bool		ms_noopscan;
 	struct PartitionPruneState *ms_prune_state;
 	Bitmapset  *ms_valid_subplans;
 } MergeAppendState;
diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h
index 7c49e9d0a8..f4b846bf6d 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -29,8 +29,9 @@ extern char *deparse_expression(Node *expr, List *dpcontext,
 				   bool forceprefix, bool showimplicit);
 extern List *deparse_context_for(const char *aliasname, Oid relid);
 extern List *deparse_context_for_plan_rtable(List *rtable, List *rtable_names);
-extern List *set_deparse_context_planstate(List *dpcontext,
-							  Node *planstate, List *ancestors);
+extern List *set_deparse_context_plan(List *dpcontext,
+						 Node *planstatee, Plan *plan,
+						 List *ancestors);
 extern List *select_rtable_names_for_explain(List *rtable,
 								Bitmapset *rels_used);
 extern char *generate_collation_name(Oid collid);
diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out
index 0789b316eb..144e62b548 100644
--- a/src/test/regress/expected/partition_prune.out
+++ b/src/test/regress/expected/partition_prune.out
@@ -2058,20 +2058,17 @@ select explain_parallel_append('execute ab_q5 (2, 3, 3)');
 (19 rows)
 
 -- Try some params whose values do not belong to any partition.
--- We'll still get a single subplan in this case, but it should not be scanned.
 select explain_parallel_append('execute ab_q5 (33, 44, 55)');
-                            explain_parallel_append                            
--------------------------------------------------------------------------------
+                  explain_parallel_append                  
+-----------------------------------------------------------
  Finalize Aggregate (actual rows=1 loops=1)
    ->  Gather (actual rows=3 loops=1)
          Workers Planned: 2
          Workers Launched: 2
          ->  Partial Aggregate (actual rows=1 loops=3)
                ->  Parallel Append (actual rows=0 loops=N)
-                     Subplans Removed: 8
-                     ->  Parallel Seq Scan on ab_a1_b1 (never executed)
-                           Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3])))
-(9 rows)
+                     Subplans Removed: 9
+(7 rows)
 
 -- Test Parallel Append with PARAM_EXEC Params
 select explain_parallel_append('select count(*) from ab where (a = (select 1) or a = (select 3)) and b = 2');
@@ -2961,16 +2958,13 @@ explain (analyze, costs off, summary off, timing off)  execute q1 (2,2);
          Filter: (b = ANY (ARRAY[$1, $2]))
 (4 rows)
 
--- Try with no matching partitions. One subplan should remain in this case,
--- but it shouldn't be executed.
+-- Try with no matching partitions.
 explain (analyze, costs off, summary off, timing off)  execute q1 (0,0);
-                  QUERY PLAN                  
-----------------------------------------------
+           QUERY PLAN           
+--------------------------------
  Append (actual rows=0 loops=1)
-   Subplans Removed: 1
-   ->  Seq Scan on listp_1_1 (never executed)
-         Filter: (b = ANY (ARRAY[$1, $2]))
-(4 rows)
+   Subplans Removed: 2
+(2 rows)
 
 deallocate q1;
 -- Test more complex cases where a not-equal condition further eliminates partitions.
@@ -3011,15 +3005,12 @@ explain (analyze, costs off, summary off, timing off)  execute q1 (1,2,2,0);
 (4 rows)
 
 -- Both partitions allowed by IN clause, then both excluded again by <> clauses.
--- One subplan will remain in this case, but it should not be executed.
 explain (analyze, costs off, summary off, timing off)  execute q1 (1,2,2,1);
-                               QUERY PLAN                                
--------------------------------------------------------------------------
+           QUERY PLAN           
+--------------------------------
  Append (actual rows=0 loops=1)
-   Subplans Removed: 1
-   ->  Seq Scan on listp_1_1 (never executed)
-         Filter: ((b = ANY (ARRAY[$1, $2])) AND ($3 <> b) AND ($4 <> b))
-(4 rows)
+   Subplans Removed: 2
+(2 rows)
 
 -- Ensure Params that evaluate to NULL properly prune away all partitions
 explain (analyze, costs off, summary off, timing off)
@@ -3168,14 +3159,12 @@ execute mt_q1(25);
 
 -- Ensure MergeAppend behaves correctly when no subplans match
 explain (analyze, costs off, summary off, timing off) execute mt_q1(35);
-                               QUERY PLAN                               
-------------------------------------------------------------------------
+              QUERY PLAN              
+--------------------------------------
  Merge Append (actual rows=0 loops=1)
    Sort Key: ma_test_p1.b
-   Subplans Removed: 2
-   ->  Index Scan using ma_test_p1_b_idx on ma_test_p1 (never executed)
-         Filter: ((a >= $1) AND ((a % 10) = 5))
-(5 rows)
+   Subplans Removed: 3
+(3 rows)
 
 execute mt_q1(35);
  a 
diff --git a/src/test/regress/sql/partition_prune.sql b/src/test/regress/sql/partition_prune.sql
index c30e58eef7..f2912d9afe 100644
--- a/src/test/regress/sql/partition_prune.sql
+++ b/src/test/regress/sql/partition_prune.sql
@@ -485,7 +485,6 @@ select explain_parallel_append('execute ab_q5 (1, 1, 1)');
 select explain_parallel_append('execute ab_q5 (2, 3, 3)');
 
 -- Try some params whose values do not belong to any partition.
--- We'll still get a single subplan in this case, but it should not be scanned.
 select explain_parallel_append('execute ab_q5 (33, 44, 55)');
 
 -- Test Parallel Append with PARAM_EXEC Params
@@ -726,8 +725,7 @@ explain (analyze, costs off, summary off, timing off)  execute q1 (1,1);
 
 explain (analyze, costs off, summary off, timing off)  execute q1 (2,2);
 
--- Try with no matching partitions. One subplan should remain in this case,
--- but it shouldn't be executed.
+-- Try with no matching partitions.
 explain (analyze, costs off, summary off, timing off)  execute q1 (0,0);
 
 deallocate q1;
@@ -745,7 +743,6 @@ execute q1 (1,2,3,4);
 explain (analyze, costs off, summary off, timing off)  execute q1 (1,2,2,0);
 
 -- Both partitions allowed by IN clause, then both excluded again by <> clauses.
--- One subplan will remain in this case, but it should not be executed.
 explain (analyze, costs off, summary off, timing off)  execute q1 (1,2,2,1);
 
 -- Ensure Params that evaluate to NULL properly prune away all partitions
