diff --git a/src/backend/parser/parse_rpr.c b/src/backend/parser/parse_rpr.c
index 62746fc33bd..a8c7b03d53b 100644
--- a/src/backend/parser/parse_rpr.c
+++ b/src/backend/parser/parse_rpr.c
@@ -56,6 +56,7 @@ typedef struct
 	ParseState *pstate;
 	List	  **targetlist;
 	List	   *groupExprs;		/* expressions GROUP BY computes */
+	bool		hasJoinRTEs;	/* true if pstate->p_rtable has a join RTE */
 } DefinePlantCtx;
 
 /* Forward declarations */
@@ -63,6 +64,7 @@ static void validateRPRPatternVarCount(ParseState *pstate, RPRPatternNode *node,
 									   List **varNames);
 static List *transformDefineClause(ParseState *pstate, WindowDef *windef,
 								   List **targetlist, List *groupClause);
+static Node *define_flatten_join_alias(ParseState *pstate, Node *node);
 static bool define_plant_walker(Node *node, void *context);
 static bool define_walker(Node *node, void *context);
 static bool rpr_frame_is_supported(int frameOptions);
@@ -254,6 +256,7 @@ transformDefineClause(ParseState *pstate, WindowDef *windef,
 	List	   *defineClause = NIL;
 	List	   *patternVarNames = NIL;
 	List	   *groupExprs = NIL;
+	bool		hasJoinRTEs = false;
 
 	/*
 	 * Collect what GROUP BY computes, so that the planting below can stop at
@@ -267,6 +270,24 @@ transformDefineClause(ParseState *pstate, WindowDef *windef,
 		groupExprs = lappend(groupExprs, tle->expr);
 	}
 
+	/*
+	 * A join's merged column can reach GROUP BY spelled either as the join's
+	 * own name or as that name's expansion.  define_plant_walker() only
+	 * matches a subexpression GROUP BY computes when both sides are spelled
+	 * alike, so flatten join alias Vars out of groupExprs here, once, for
+	 * every DEFINE variable below to reuse.
+	 */
+	foreach_node(RangeTblEntry, rte, pstate->p_rtable)
+	{
+		if (rte->rtekind == RTE_JOIN)
+		{
+			hasJoinRTEs = true;
+			break;
+		}
+	}
+	if (hasJoinRTEs && groupExprs != NIL)
+		groupExprs = (List *) define_flatten_join_alias(pstate, (Node *) groupExprs);
+
 	/*
 	 * The grammar builds an RPCommonSyntax only for a window specification
 	 * that carries DEFINE, so the list is never empty here.
@@ -393,6 +414,7 @@ transformDefineClause(ParseState *pstate, WindowDef *windef,
 		ctx.pstate = pstate;
 		ctx.targetlist = targetlist;
 		ctx.groupExprs = groupExprs;
+		ctx.hasJoinRTEs = hasJoinRTEs;
 		(void) define_plant_walker(expr, &ctx);
 	}
 	pstate->p_rpr_pattern_vars = NIL;
@@ -416,6 +438,28 @@ transformDefineClause(ParseState *pstate, WindowDef *windef,
 	return defineClause;
 }
 
+/*
+ * define_flatten_join_alias
+ *		Flatten join alias Vars out of a DEFINE-related expression, so a
+ *		join's merged column and its expanded spelling compare equal.
+ *
+ * No Query exists yet to hand flatten_join_alias_for_parser() at this point
+ * in parsing, so a stand-in carrying just the fields it reads is enough --
+ * hasSubLinks included only for shape, since DEFINE cannot contain one.
+ */
+static Node *
+define_flatten_join_alias(ParseState *pstate, Node *node)
+{
+	Query		stub;
+
+	memset(&stub, 0, sizeof(stub));
+	stub.type = T_Query;
+	stub.rtable = pstate->p_rtable;
+	stub.hasSubLinks = pstate->p_hasSubLinks;
+
+	return flatten_join_alias_for_parser(&stub, node, 0);
+}
+
 /*
  * define_plant_walker
  *		Plant in the target list what a DEFINE expression reads.
@@ -428,14 +472,19 @@ static bool
 define_plant_walker(Node *node, void *context)
 {
 	DefinePlantCtx *ctx = (DefinePlantCtx *) context;
+	Node	   *cmpnode = node;
 
 	if (node == NULL)
 		return false;
 
+	/* Compare in the same flattened shape groupExprs was put in above. */
+	if (ctx->hasJoinRTEs)
+		cmpnode = define_flatten_join_alias(ctx->pstate, node);
+
 	/* A subexpression GROUP BY computes needs nothing planted for it. */
 	foreach_ptr(Node, gexpr, ctx->groupExprs)
 	{
-		if (equal(node, gexpr))
+		if (equal(cmpnode, gexpr))
 			return false;
 	}
 
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index ef6c05be399..3000498b3c2 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -447,6 +447,7 @@ static void append_pattern_quantifier(StringInfo buf, RPRPatternNode *node);
 static void get_rule_pattern_node(RPRPatternNode *node, deparse_context *context);
 static void get_rule_pattern(RPRPatternNode *rpPattern, deparse_context *context);
 static void get_rule_define(List *defineClause, deparse_context *context);
+static Node *collapse_define_join_vars_mutator(Node *node, List *rtable);
 static void get_rule_windowclause(Query *query, deparse_context *context);
 static void get_rule_windowspec(WindowClause *wc, List *targetList,
 								deparse_context *context);
@@ -5829,15 +5830,23 @@ get_query_def(Query *query, StringInfo buf, List *parentnamespace,
 		/*
 		 * A row pattern DEFINE clause carries GROUP Vars of its own; expand
 		 * them, or the deparsed text would name the grouping step rather than
-		 * the expression the user wrote, and the view would not re-parse.
+		 * the expression the user wrote.  For a FULL JOIN's USING column,
+		 * that expansion is a CoalesceExpr DEFINE cannot print (XIV-5 forbids
+		 * qualifiers) -- fold it back into a plain join-column reference; see
+		 * collapse_define_join_vars_mutator().
 		 */
 		foreach(lc, query->windowClause)
 		{
 			WindowClause *wc = lfirst_node(WindowClause, lc);
 
 			if (wc->defineClause != NIL)
+			{
 				wc->defineClause = (List *)
 					flatten_group_exprs(NULL, query, (Node *) wc->defineClause);
+				wc->defineClause = (List *)
+					collapse_define_join_vars_mutator((Node *) wc->defineClause,
+													  query->rtable);
+			}
 		}
 	}
 
@@ -7096,6 +7105,49 @@ get_rule_define(List *defineClause, deparse_context *context)
 	context->inRPRDefine = save_inrprdefine;
 }
 
+/*
+ * collapse_define_join_vars_mutator
+ *		Fold a subexpression that exactly matches some join RTE's merged
+ *		column definition back into a plain reference to that join column;
+ *		see the caller for why.
+ */
+static Node *
+collapse_define_join_vars_mutator(Node *node, List *rtable)
+{
+	ListCell   *lc;
+	int			varno;
+
+	if (node == NULL)
+		return NULL;
+
+	varno = 0;
+	foreach(lc, rtable)
+	{
+		RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc);
+		ListCell   *lc2;
+		int			attno;
+
+		varno++;
+		if (rte->rtekind != RTE_JOIN)
+			continue;
+
+		attno = 0;
+		foreach(lc2, rte->joinaliasvars)
+		{
+			Node	   *aliasvar = (Node *) lfirst(lc2);
+
+			attno++;
+			if (aliasvar != NULL && equal(node, aliasvar))
+				return (Node *) makeVar(varno, attno,
+										exprType(node), exprTypmod(node),
+										exprCollation(node), 0);
+		}
+	}
+
+	return expression_tree_mutator(node, collapse_define_join_vars_mutator,
+								   rtable);
+}
+
 /*
  * Display a WINDOW clause.
  *
diff --git a/src/test/regress/expected/rpr_base.out b/src/test/regress/expected/rpr_base.out
index 539a4fb4bc7..9d44cc8545e 100644
--- a/src/test/regress/expected/rpr_base.out
+++ b/src/test/regress/expected/rpr_base.out
@@ -9530,6 +9530,87 @@ WINDOW w AS (
    1
 (1 row)
 
+-- GROUP BY spelled as the merged column's COALESCE expansion, rather than
+-- the join's own name, while DEFINE reads that same column: the two spellings
+-- must compare equal despite the different tree shapes.
+SELECT id + 1 AS b, count(*) OVER w AS cnt
+FROM rpr_grp FULL JOIN rpr_sort USING (id)
+GROUP BY COALESCE(rpr_grp.id, rpr_sort.id) + 1
+WINDOW w AS (
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    PATTERN (A) DEFINE A AS (id + 1) > 0)
+ORDER BY 1;
+ b | cnt 
+---+-----
+ 2 |   1
+ 3 |   1
+ 4 |   1
+ 5 |   1
+ 6 |   1
+ 7 |   1
+(6 rows)
+
+-- Same construct as a view: the DEFINE clause must deparse to the plain
+-- join column, not the two-sided COALESCE GROUP BY computed, or the printed
+-- text would not re-parse.
+CREATE VIEW rpr_fjcoal_v AS
+SELECT COALESCE(rpr_grp.id, rpr_sort.id) + 1 AS idp1, count(*) OVER w AS cnt
+FROM rpr_grp FULL JOIN rpr_sort USING (id)
+GROUP BY COALESCE(rpr_grp.id, rpr_sort.id) + 1
+WINDOW w AS (
+    ORDER BY COALESCE(rpr_grp.id, rpr_sort.id) + 1
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    PATTERN (A+)
+    DEFINE A AS id + 1 > 0);
+SELECT pg_get_viewdef('rpr_fjcoal_v'::regclass, true);
+                                                  pg_get_viewdef                                                  
+------------------------------------------------------------------------------------------------------------------
+  SELECT COALESCE(rpr_grp.id, rpr_sort.id) + 1 AS idp1,                                                          +
+     count(*) OVER w AS cnt                                                                                      +
+    FROM rpr_grp                                                                                                 +
+      FULL JOIN rpr_sort USING (id)                                                                              +
+   GROUP BY (COALESCE(rpr_grp.id, rpr_sort.id) + 1)                                                              +
+   WINDOW w AS (ORDER BY (COALESCE(rpr_grp.id, rpr_sort.id) + 1) ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING+
+   AFTER MATCH SKIP PAST LAST ROW                                                                                +
+   INITIAL                                                                                                       +
+   PATTERN (a+)                                                                                                  +
+   DEFINE                                                                                                        +
+   a AS (id + 1) > 0);
+(1 row)
+
+SELECT * FROM rpr_fjcoal_v ORDER BY 1;
+ idp1 | cnt 
+------+-----
+    2 |   6
+    3 |   0
+    4 |   0
+    5 |   0
+    6 |   0
+    7 |   0
+(6 rows)
+
+-- The deparsed definition re-parses into an identical view.
+CREATE VIEW rpr_fjcoal_v2 AS
+ SELECT COALESCE(rpr_grp.id, rpr_sort.id) + 1 AS idp1,
+    count(*) OVER w AS cnt
+   FROM rpr_grp
+     FULL JOIN rpr_sort USING (id)
+   GROUP BY (COALESCE(rpr_grp.id, rpr_sort.id) + 1)
+   WINDOW w AS (ORDER BY (COALESCE(rpr_grp.id, rpr_sort.id) + 1) ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+   AFTER MATCH SKIP PAST LAST ROW
+   INITIAL
+   PATTERN (a+)
+   DEFINE
+   a AS (id + 1) > 0);
+SELECT pg_get_viewdef('rpr_fjcoal_v2'::regclass, true) =
+      pg_get_viewdef('rpr_fjcoal_v'::regclass, true) AS same_definition;
+ same_definition 
+-----------------
+ t
+(1 row)
+
+DROP VIEW rpr_fjcoal_v2;
+DROP VIEW rpr_fjcoal_v;
 DROP TABLE rpr_grp;
 DROP TABLE rpr_sort;
 -- SQL function inlining: $1 in DEFINE must be substituted by
diff --git a/src/test/regress/sql/rpr_base.sql b/src/test/regress/sql/rpr_base.sql
index db6afdcc257..ac046740ac2 100644
--- a/src/test/regress/sql/rpr_base.sql
+++ b/src/test/regress/sql/rpr_base.sql
@@ -5778,6 +5778,53 @@ WINDOW w AS (
     PATTERN (A)
     DEFINE A AS true);
 
+-- GROUP BY spelled as the merged column's COALESCE expansion, rather than
+-- the join's own name, while DEFINE reads that same column: the two spellings
+-- must compare equal despite the different tree shapes.
+SELECT id + 1 AS b, count(*) OVER w AS cnt
+FROM rpr_grp FULL JOIN rpr_sort USING (id)
+GROUP BY COALESCE(rpr_grp.id, rpr_sort.id) + 1
+WINDOW w AS (
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    PATTERN (A) DEFINE A AS (id + 1) > 0)
+ORDER BY 1;
+
+-- Same construct as a view: the DEFINE clause must deparse to the plain
+-- join column, not the two-sided COALESCE GROUP BY computed, or the printed
+-- text would not re-parse.
+CREATE VIEW rpr_fjcoal_v AS
+SELECT COALESCE(rpr_grp.id, rpr_sort.id) + 1 AS idp1, count(*) OVER w AS cnt
+FROM rpr_grp FULL JOIN rpr_sort USING (id)
+GROUP BY COALESCE(rpr_grp.id, rpr_sort.id) + 1
+WINDOW w AS (
+    ORDER BY COALESCE(rpr_grp.id, rpr_sort.id) + 1
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    PATTERN (A+)
+    DEFINE A AS id + 1 > 0);
+
+SELECT pg_get_viewdef('rpr_fjcoal_v'::regclass, true);
+SELECT * FROM rpr_fjcoal_v ORDER BY 1;
+
+-- The deparsed definition re-parses into an identical view.
+CREATE VIEW rpr_fjcoal_v2 AS
+ SELECT COALESCE(rpr_grp.id, rpr_sort.id) + 1 AS idp1,
+    count(*) OVER w AS cnt
+   FROM rpr_grp
+     FULL JOIN rpr_sort USING (id)
+   GROUP BY (COALESCE(rpr_grp.id, rpr_sort.id) + 1)
+   WINDOW w AS (ORDER BY (COALESCE(rpr_grp.id, rpr_sort.id) + 1) ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+   AFTER MATCH SKIP PAST LAST ROW
+   INITIAL
+   PATTERN (a+)
+   DEFINE
+   a AS (id + 1) > 0);
+
+SELECT pg_get_viewdef('rpr_fjcoal_v2'::regclass, true) =
+      pg_get_viewdef('rpr_fjcoal_v'::regclass, true) AS same_definition;
+
+DROP VIEW rpr_fjcoal_v2;
+DROP VIEW rpr_fjcoal_v;
+
 DROP TABLE rpr_grp;
 
 DROP TABLE rpr_sort;
