New CORRESPONDING clause design

Started by Surafel Temsgenalmost 9 years ago38 messages
#1Surafel Temsgen
surafel3000@gmail.com

I am new here and I really want to contribute, I have read same resource
that help understanding database system and postgresql. I would like to
start implementing sql syntax corresponding by clause because I believe
implementing sql syntax gives an opportunity to familiarize many part of
postgresql source code. Previous implementation is here
</messages/by-id/CAJZSWkWN3YwQ01C3+cq0+eyZ1DmK=69_6vryrsVGMC=+fWrSZA@mail.gmail.com&gt;and
have an issue on explain query and break cases on unlabeled NULLs
To repeat what a corresponding by clause means
Corresponding clause either contains a BY(...) clause or not. If it
doesn't have a BY(...) clause the usage is as follows.

SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 4 b, 5 d, 6 c, 7 f;

with output:
b c
-----
2 3
4 6

i.e. matching column names are filtered and are only output from the
whole set operation clause.

If we introduce a BY(...) clause, then column names are further
intersected with that BY clause:

SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b) SELECT 4 b, 5 d, 6 c, 7 f;

with output:

b
--
2
4
My design is
*In parsing stage*
1. Check at least one common column name appear in queries
2. If corresponding column list is not specified, then make corresponding
list from common column name in queries target lists in the order
that those column names appear in first query
3. If corresponding column list is specified, then check that every column
name
in The corresponding column list appears in column names of both queries
*In planner*
1. Take projection columen name from corresponding list
2. Reorder left and right queries target lists according to corresponding
list
3. For each query, project columens as many as number of corresponding
columen.
Continue with normal set operation process

#2Merlin Moncure
mmoncure@gmail.com
In reply to: Surafel Temsgen (#1)
Re: New CORRESPONDING clause design

On Tue, Jan 17, 2017 at 12:37 AM, Surafel Temsgen <surafel3000@gmail.com> wrote:

I am new here and I really want to contribute, I have read same resource
that help understanding database system and postgresql. I would like to
start implementing sql syntax corresponding by clause because I believe
implementing sql syntax gives an opportunity to familiarize many part of
postgresql source code. Previous implementation is here and have an issue on
explain query and break cases on unlabeled NULLs
To repeat what a corresponding by clause means
Corresponding clause either contains a BY(...) clause or not. If it
doesn't have a BY(...) clause the usage is as follows.

This is great stuff. Does the syntax only apply to UNION? I would
imagine it would also apply to INTERSECT/EXCEPT? What about UNION
ALL?

merlin

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#3David Fetter
david@fetter.org
In reply to: Merlin Moncure (#2)
Re: New CORRESPONDING clause design

On Tue, Jan 17, 2017 at 08:20:25AM -0600, Merlin Moncure wrote:

On Tue, Jan 17, 2017 at 12:37 AM, Surafel Temsgen <surafel3000@gmail.com> wrote:

I am new here and I really want to contribute, I have read same resource
that help understanding database system and postgresql. I would like to
start implementing sql syntax corresponding by clause because I believe
implementing sql syntax gives an opportunity to familiarize many part of
postgresql source code. Previous implementation is here and have an issue on
explain query and break cases on unlabeled NULLs
To repeat what a corresponding by clause means
Corresponding clause either contains a BY(...) clause or not. If it
doesn't have a BY(...) clause the usage is as follows.

This is great stuff. Does the syntax only apply to UNION? I would
imagine it would also apply to INTERSECT/EXCEPT? What about UNION
ALL?

My draft working standard from 2011 says in 7IWD-02-Foundation section 7.13 <query expression>:

a) If CORRESPONDING is specified, then:
i) Within the columns of T1, equivalent <column name>s shall not be specified more than once
and within the columns of T2, equivalent <column name>s shall not be specified more than
once.
ii) At least one column of T1 shall have a <column name> that is the <column name> of some
column of T2.
iii) Case:
1) If <corresponding column list> is not specified, then let SL be a <select list> of those
<column name>s that are <column name>s of both T1 and T2 in the order that those
<column name>s appear in T1.
2) If <corresponding column list> is specified, then let SL be a <select list> of those <column
name>s explicitly appearing in the <corresponding column list> in the order that these
<column name>s appear in the <corresponding column list>. Every <column name> in
the <corresponding column list> shall be a <column name> of both T1 and T2.
iv) The <query term> or <query expression body> is equivalent to:
( SELECT SL FROM TN1 ) OP ( SELECT SL FROM TN2 )

Earlier, it defines ( UNION | EXCEPT ) [ ALL | DISTINCT ] to have a
meaning in this context, to wit:

<query expression body> ::=
<query term>
| <query expression body> UNION [ ALL | DISTINCT ]
[ <corresponding spec> ] <query term>
| <query expression body> EXCEPT [ ALL | DISTINCT ]
[ <corresponding spec> ] <query term>

Best,
David.
--
David Fetter <david(at)fetter(dot)org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david(dot)fetter(at)gmail(dot)com

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#4Tom Lane
tgl@sss.pgh.pa.us
In reply to: Surafel Temsgen (#1)
Re: New CORRESPONDING clause design

Surafel Temsgen <surafel3000@gmail.com> writes:

My design is
*In parsing stage*
1. Check at least one common column name appear in queries
2. If corresponding column list is not specified, then make corresponding
list from common column name in queries target lists in the order
that those column names appear in first query
3. If corresponding column list is specified, then check that every column
name
in The corresponding column list appears in column names of both queries
*In planner*
1. Take projection columen name from corresponding list
2. Reorder left and right queries target lists according to corresponding
list
3. For each query, project columens as many as number of corresponding
columen.

FWIW, I think you need to do most of that work in the parser, not the
planner. The parser certainly has to determine the actual output column
names and types of the setop construct, and we usually consider that the
parsing stage is expected to fully determine the semantics of the query.
So I'd envision the parser as emitting some explicit representation of
the column mapping for each side, rather than expecting the planner to
determine for itself what maps to what.

It probably does make sense for the actual implementation to involve
inserting projection nodes below the setop. I'm pretty sure the executor
code for setops expects to just pass input tuples through without
projection. You could imagine changing that, but it would add a penalty
for non-CORRESPONDING setops, which IMO would be the wrong tradeoff.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#5Surafel Temsgen
surafel3000@gmail.com
In reply to: Tom Lane (#4)
1 attachment(s)
Re: New CORRESPONDING clause design

Here is the implementation of the clause with the slight change, instead of
doing column mapping for each side of leaf Queries in planner I make the
projection nodes output to corresponding column lists only.

This patch compiles and tests successfully with master branch on
ubuntu-15.10-desktop-amd64.It also includes documentation and new
regression tests in union.sql.

Regards

Surafel Temesgen

On Tue, Jan 17, 2017 at 8:21 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Show quoted text

Surafel Temsgen <surafel3000@gmail.com> writes:

My design is
*In parsing stage*
1. Check at least one common column name appear in queries
2. If corresponding column list is not specified, then make corresponding
list from common column name in queries target lists in the order
that those column names appear in first query
3. If corresponding column list is specified, then check that every

column

name
in The corresponding column list appears in column names of both

queries

*In planner*
1. Take projection columen name from corresponding list
2. Reorder left and right queries target lists according to corresponding
list
3. For each query, project columens as many as number of corresponding
columen.

FWIW, I think you need to do most of that work in the parser, not the
planner. The parser certainly has to determine the actual output column
names and types of the setop construct, and we usually consider that the
parsing stage is expected to fully determine the semantics of the query.
So I'd envision the parser as emitting some explicit representation of
the column mapping for each side, rather than expecting the planner to
determine for itself what maps to what.

It probably does make sense for the actual implementation to involve
inserting projection nodes below the setop. I'm pretty sure the executor
code for setops expects to just pass input tuples through without
projection. You could imagine changing that, but it would add a penalty
for non-CORRESPONDING setops, which IMO would be the wrong tradeoff.

regards, tom lane

Attachments:

corresponding_clause_v1.patchapplication/octet-stream; name=corresponding_clause_v1.patchDownload
diff --git a/doc/src/sgml/queries.sgml b/doc/src/sgml/queries.sgml
index 30792f4..3f207e5 100644
--- a/doc/src/sgml/queries.sgml
+++ b/doc/src/sgml/queries.sgml
@@ -1601,6 +1601,9 @@ SELECT DISTINCT ON (<replaceable>expression</replaceable> <optional>, <replaceab
    <primary>EXCEPT</primary>
   </indexterm>
   <indexterm zone="queries-union">
+   <primary>CORRESPONDING</primary>
+  </indexterm>
+  <indexterm zone="queries-union">
    <primary>set union</primary>
   </indexterm>
   <indexterm zone="queries-union">
@@ -1617,9 +1620,9 @@ SELECT DISTINCT ON (<replaceable>expression</replaceable> <optional>, <replaceab
    The results of two queries can be combined using the set operations
    union, intersection, and difference.  The syntax is
 <synopsis>
-<replaceable>query1</replaceable> UNION <optional>ALL</optional> <replaceable>query2</replaceable>
-<replaceable>query1</replaceable> INTERSECT <optional>ALL</optional> <replaceable>query2</replaceable>
-<replaceable>query1</replaceable> EXCEPT <optional>ALL</optional> <replaceable>query2</replaceable>
+<replaceable>query1</replaceable> UNION <optional>ALL</optional> <optional>CORRESPONDING <optional>BY <replaceable>query2</replaceable>
+<replaceable>query1</replaceable> INTERSECT <optional>ALL</optional> <optional>CORRESPONDING <optional>BY <replaceable>query2</replaceable>
+<replaceable>query1</replaceable> EXCEPT <optional>ALL</optional> <optional>CORRESPONDING <optional>BY <replaceable>query2</replaceable>
 </synopsis>
    <replaceable>query1</replaceable> and
    <replaceable>query2</replaceable> are queries that can use any of
@@ -1659,11 +1662,22 @@ SELECT DISTINCT ON (<replaceable>expression</replaceable> <optional>, <replaceab
   </para>
 
   <para>
-   In order to calculate the union, intersection, or difference of two
-   queries, the two queries must be <quote>union compatible</quote>,
-   which means that they return the same number of columns and
-   the corresponding columns have compatible data types, as
-   described in <xref linkend="typeconv-union-case">.
+   <literal>EXCEPT</> returns all rows that are in the result of
+   <replaceable>query1</replaceable> but not in the result of
+   <replaceable>query2</replaceable>.  (This is sometimes called the
+   <firstterm>difference</> between two queries.)  Again, duplicates
+   are eliminated unless <literal>EXCEPT ALL</> is used.
+  </para>
+
+  <para>
+   <literal>CORRESPONDING</> returns all columns that are in both 
+   <replaceable>query1</> and <replaceable>query2</> with the same name.
+  </para>
+
+  <para>
+   <literal>CORRESPONDING BY</> returns all columns in the column list 
+   that are also in both <replaceable>query1</> and 
+   <replaceable>query2</> with the same name.
   </para>
  </sect1>
 
diff --git a/doc/src/sgml/sql.sgml b/doc/src/sgml/sql.sgml
index 57396d7..f98c22e 100644
--- a/doc/src/sgml/sql.sgml
+++ b/doc/src/sgml/sql.sgml
@@ -859,7 +859,7 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="PARAMETER">expression</replac
     [ WHERE <replaceable class="PARAMETER">condition</replaceable> ]
     [ GROUP BY <replaceable class="PARAMETER">expression</replaceable> [, ...] ]
     [ HAVING <replaceable class="PARAMETER">condition</replaceable> [, ...] ]
-    [ { UNION | INTERSECT | EXCEPT } [ ALL ] <replaceable class="PARAMETER">select</replaceable> ]
+    [ { UNION | INTERSECT | EXCEPT } [ ALL ] [ CORRESPONDING [ BY ( <replaceable class="PARAMETER">expression</replaceable> ) ] ] <replaceable class="PARAMETER">select</replaceable> ]
     [ ORDER BY <replaceable class="parameter">expression</replaceable> [ ASC | DESC | USING <replaceable class="parameter">operator</replaceable> ] [ NULLS { FIRST | LAST } ] [, ...] ]
     [ LIMIT { <replaceable class="PARAMETER">count</replaceable> | ALL } ]
     [ OFFSET <replaceable class="PARAMETER">start</replaceable> ]
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 30d733e..30874e0 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2870,6 +2870,7 @@ _copySelectStmt(const SelectStmt *from)
 	COPY_NODE_FIELD(withClause);
 	COPY_SCALAR_FIELD(op);
 	COPY_SCALAR_FIELD(all);
+	COPY_NODE_FIELD(correspondingClause);
 	COPY_NODE_FIELD(larg);
 	COPY_NODE_FIELD(rarg);
 
@@ -2885,6 +2886,7 @@ _copySetOperationStmt(const SetOperationStmt *from)
 	COPY_SCALAR_FIELD(all);
 	COPY_NODE_FIELD(larg);
 	COPY_NODE_FIELD(rarg);
+	COPY_NODE_FIELD(correspondingColumns);
 	COPY_NODE_FIELD(colTypes);
 	COPY_NODE_FIELD(colTypmods);
 	COPY_NODE_FIELD(colCollations);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 55c73b7..f375255 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1020,6 +1020,7 @@ _equalSelectStmt(const SelectStmt *a, const SelectStmt *b)
 	COMPARE_NODE_FIELD(withClause);
 	COMPARE_SCALAR_FIELD(op);
 	COMPARE_SCALAR_FIELD(all);
+	COMPARE_NODE_FIELD(correspondingClause);
 	COMPARE_NODE_FIELD(larg);
 	COMPARE_NODE_FIELD(rarg);
 
@@ -1033,6 +1034,7 @@ _equalSetOperationStmt(const SetOperationStmt *a, const SetOperationStmt *b)
 	COMPARE_SCALAR_FIELD(all);
 	COMPARE_NODE_FIELD(larg);
 	COMPARE_NODE_FIELD(rarg);
+	COMPARE_NODE_FIELD(correspondingColumns);
 	COMPARE_NODE_FIELD(colTypes);
 	COMPARE_NODE_FIELD(colTypmods);
 	COMPARE_NODE_FIELD(colCollations);
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 7586cce..4962845 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -610,3 +610,24 @@ makeGroupingSet(GroupingSetKind kind, List *content, int location)
 	n->location = location;
 	return n;
 }
+
+/*
+ * changeTargetEntry -
+ *	  change a TargetEntry node
+ */
+TargetEntry *
+changeTargetEntry(TargetEntry *target, AttrNumber resno, AttrNumber loc)
+{
+	TargetEntry *tle = makeNode(TargetEntry);
+
+	tle->expr = target->expr;
+	tle->resno = resno;
+	tle->resname = target->resname;
+	tle->ressortgroupref = 0;
+	tle->resorigtbl = InvalidOid;
+	tle->resorigcol = loc;
+
+	tle->resjunk = target->resjunk;
+
+	return tle;
+}
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index c2f03b7..ca39c1c 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -3411,6 +3411,8 @@ raw_expression_tree_walker(Node *node,
 					return true;
 				if (walker(stmt->lockingClause, context))
 					return true;
+				if (walker(stmt->correspondingClause, context))
+					return true;
 				if (walker(stmt->withClause, context))
 					return true;
 				if (walker(stmt->larg, context))
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 1560ac3..6e96018 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2526,6 +2526,7 @@ _outSelectStmt(StringInfo str, const SelectStmt *node)
 	WRITE_NODE_FIELD(withClause);
 	WRITE_ENUM_FIELD(op, SetOperation);
 	WRITE_BOOL_FIELD(all);
+	WRITE_NODE_FIELD(correspondingClause);
 	WRITE_NODE_FIELD(larg);
 	WRITE_NODE_FIELD(rarg);
 }
@@ -2833,6 +2834,7 @@ _outSetOperationStmt(StringInfo str, const SetOperationStmt *node)
 	WRITE_BOOL_FIELD(all);
 	WRITE_NODE_FIELD(larg);
 	WRITE_NODE_FIELD(rarg);
+	WRITE_NODE_FIELD(correspondingColumns);
 	WRITE_NODE_FIELD(colTypes);
 	WRITE_NODE_FIELD(colTypmods);
 	WRITE_NODE_FIELD(colCollations);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index dcfa6ee..94b724b 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -418,6 +418,7 @@ _readSetOperationStmt(void)
 	READ_BOOL_FIELD(all);
 	READ_NODE_FIELD(larg);
 	READ_NODE_FIELD(rarg);
+	READ_NODE_FIELD(correspondingColumns);
 	READ_NODE_FIELD(colTypes);
 	READ_NODE_FIELD(colTypmods);
 	READ_NODE_FIELD(colCollations);
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 06e843d..4626d4e 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -110,6 +110,7 @@ static Node *adjust_appendrel_attrs_mutator(Node *node,
 static Relids adjust_relid_set(Relids relids, Index oldrelid, Index newrelid);
 static List *adjust_inherited_tlist(List *tlist,
 					   AppendRelInfo *context);
+static List *make_corresponding_target(List *ltargetlist, List *rtargetlist);
 
 
 /*
@@ -187,6 +188,24 @@ plan_set_operations(PlannerInfo *root)
 									   leftmostQuery->targetList,
 									   &top_tlist);
 	}
+	/*
+	 * If corresponding column specified, we take column names from it.
+	 */
+	else if (topop->correspondingColumns != NIL )
+	{
+		/*
+		 * Recurse on setOperations tree to generate paths for set ops. The
+		 * final output path should have just the column types shown as the
+		 * output from the top-level node, plus possibly resjunk working
+		 * columns (we can rely on upper-level nodes to deal with that).
+		 */
+		path = recurse_set_operations((Node *) topop, root,
+									  topop->colTypes, topop->colCollations,
+									  true, -1,
+									  topop->correspondingColumns,
+									  &top_tlist,
+									  NULL);
+	}
 	else
 	{
 		/*
@@ -252,6 +271,8 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 					   List **pTargetList,
 					   double *pNumGroups)
 {
+	SetOperationStmt *topop = (SetOperationStmt *) root->parse->setOperations;
+
 	if (IsA(setOp, RangeTblRef))
 	{
 		RangeTblRef *rtr = (RangeTblRef *) setOp;
@@ -316,23 +337,45 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 		path = (Path *) create_subqueryscan_path(root, rel, subpath,
 												 NIL, NULL);
 
-		/*
-		 * Figure out the appropriate target list, and update the
-		 * SubqueryScanPath with the PathTarget form of that.
-		 */
-		tlist = generate_setop_tlist(colTypes, colCollations,
+		if (topop->correspondingColumns != NIL )
+		{
+			List *correspondingTarget;
+			/* make target list that only contains corresponding column from sub-queries list ito use it for projection */
+			correspondingTarget = make_corresponding_target(topop->correspondingColumns,
+					subroot->processed_tlist);
+			/*
+			 * Figure out the appropriate target list, and update the
+			 * SubqueryScanPath with the PathTarget form of that.
+			 */
+			tlist = generate_setop_tlist(colTypes, colCollations, flag,
+					rtr->rtindex, true, correspondingTarget, refnames_tlist);
+
+			path = apply_projection_to_path(root, rel, path,
+					create_pathtarget(root, tlist));
+
+			/* Return the fully-fledged tlist to caller, too */
+			*pTargetList = tlist;
+
+		}
+		else
+		{
+			/*
+			* Figure out the appropriate target list, and update the
+			* SubqueryScanPath with the PathTarget form of that.
+			*/
+			tlist = generate_setop_tlist(colTypes, colCollations,
 									 flag,
 									 rtr->rtindex,
 									 true,
 									 subroot->processed_tlist,
 									 refnames_tlist);
 
-		path = apply_projection_to_path(root, rel, path,
+			path = apply_projection_to_path(root, rel, path,
 										create_pathtarget(root, tlist));
 
-		/* Return the fully-fledged tlist to caller, too */
-		*pTargetList = tlist;
-
+			/* Return the fully-fledged tlist to caller, too */
+			*pTargetList = tlist;
+		}
 		/*
 		 * Estimate number of groups if caller wants it.  If the subquery used
 		 * grouping or aggregation, its output is probably mostly unique
@@ -1026,9 +1069,7 @@ generate_setop_tlist(List *colTypes, List *colCollations,
 
 		rtlc = lnext(rtlc);
 
-		Assert(inputtle->resno == resno);
-		Assert(reftle->resno == resno);
-		Assert(!inputtle->resjunk);
+		Assert(!reftle->resjunk);
 		Assert(!reftle->resjunk);
 
 		/*
@@ -2112,3 +2153,75 @@ adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node,
 	/* Now translate for this child */
 	return adjust_appendrel_attrs(root, node, appinfo);
 }
+
+/*
+ * generate target list from left target list with the order
+ * of right target list
+ */
+static List *make_corresponding_target(List *ltargetlist, List *rtargetlist)
+{
+	Index internal = 0;
+	ListCell *ltl;
+	ListCell *rtl;
+	int size;
+	int i;
+	List *matchingColumns = NIL;
+	TargetEntry *simple_te_array;
+
+	size = list_length(ltargetlist) + 1;
+
+	/* Use array to find the order of corresponding columen */
+	simple_te_array = (TargetEntry *) palloc0(size * sizeof(TargetEntry));
+	foreach(ltl, ltargetlist)
+	{
+
+		foreach(rtl, rtargetlist)
+		{
+
+			TargetEntry *ltle = (TargetEntry *) lfirst(ltl);
+			TargetEntry *rtle = (TargetEntry *) lfirst(rtl);
+
+			elog(DEBUG4, "%s", ltle->resname);
+
+			/* Names of the columns must be resolved before calling this method. */
+			Assert(ltle->resname != NULL);
+			Assert(rtle->resname != NULL);
+
+			/* If column names are the same, add it to array. */
+			if (strcmp(ltle->resname, rtle->resname) == 0)
+			{
+
+				simple_te_array[internal].xpr = rtle->xpr;
+				simple_te_array[internal].expr = rtle->expr;
+				simple_te_array[internal].resno = rtle->resno;
+				simple_te_array[internal].resname = rtle->resname;
+				simple_te_array[internal].ressortgroupref =
+						rtle->ressortgroupref;
+				simple_te_array[internal].resorigtbl = rtle->resorigtbl;
+				simple_te_array[internal].resorigcol = rtle->resorigcol;
+				simple_te_array[internal].resjunk = rtle->resjunk;
+				internal++;
+				continue;
+			}
+		}
+	}
+	/* traverse the array and make targetlist */
+	for (i = 0; i < internal; i++)
+	{
+
+		TargetEntry *tle = makeNode(TargetEntry);
+
+		tle->xpr = simple_te_array[i].xpr;
+		tle->expr = simple_te_array[i].expr;
+		tle->resno = simple_te_array[i].resno;
+		tle->resname = simple_te_array[i].resname;
+		tle->ressortgroupref = simple_te_array[i].ressortgroupref;
+		tle->resorigtbl = simple_te_array[i].resorigtbl;
+		tle->resorigcol = simple_te_array[i].resorigcol;
+		tle->resjunk = simple_te_array[i].resjunk;
+
+		matchingColumns = lappend(matchingColumns, tle);
+
+	}
+	return matchingColumns;
+}
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 0f7659b..c82cb32 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -76,6 +76,10 @@ static Query *transformCreateTableAsStmt(ParseState *pstate,
 						   CreateTableAsStmt *stmt);
 static void transformLockingClause(ParseState *pstate, Query *qry,
 					   LockingClause *lc, bool pushedDown);
+static List *determineMatchingColumns(List *ltargetlist, List *rtargetlist);
+static void *makeUnionDatatype(List *ltargetlist, List *rtargetlist,
+		SetOperationStmt *op, List **targetlist, ParseState *parentParseState,
+		const char *context);
 #ifdef RAW_EXPRESSION_COVERAGE_TEST
 static bool test_raw_expression_coverage(Node *node, void *context);
 #endif
@@ -1664,7 +1668,13 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->targetList = NIL;
 	targetvars = NIL;
 	targetnames = NIL;
-	left_tlist = list_head(leftmostQuery->targetList);
+	/* for corresponding clause limits top-level query targetlist to those
+	 * corresponding column list only
+	 */
+	if (sostmt->correspondingColumns != NIL )
+		left_tlist = list_head(sostmt->correspondingColumns);
+	else
+		left_tlist = list_head(leftmostQuery->targetList);
 
 	forthree(lct, sostmt->colTypes,
 			 lcm, sostmt->colTypmods,
@@ -1924,8 +1934,6 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 		SetOperationStmt *op = makeNode(SetOperationStmt);
 		List	   *ltargetlist;
 		List	   *rtargetlist;
-		ListCell   *ltl;
-		ListCell   *rtl;
 		const char *context;
 
 		context = (stmt->op == SETOP_UNION ? "UNION" :
@@ -1935,6 +1943,205 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 		op->op = stmt->op;
 		op->all = stmt->all;
 
+		/* If CORRESPONDING is specified, syntax and column name validities checked,
+		 * column filtering is done by a subquery later on.
+		 */
+		if (stmt->correspondingClause == NIL )
+		{
+			/* No CORRESPONDING clause, no operation needed for column filtering */
+			op->correspondingColumns = stmt->correspondingClause;
+		}
+		else if (linitial(stmt->correspondingClause) == NULL )
+		{
+			/* CORRESPONDING clause, find matching column names from both tables.
+			 * If there are none then it is a syntax error.
+			 */
+			Query *largQuery;
+			Query *rargQuery;
+			List *matchingColumns;
+			List *rightCorrespondingColumns;
+
+			/* Analyze left query to resolve column names. */
+			largQuery = parse_sub_analyze((Node *) stmt->larg, pstate, NULL, false, false );
+
+			/* Analyze right query to resolve column names. */
+			rargQuery = parse_sub_analyze((Node *) stmt->rarg, pstate, NULL, false, false );
+
+			/* Find matching columns from both queries. */
+			matchingColumns = determineMatchingColumns(largQuery->targetList,rargQuery->targetList);
+			/*
+			 * there may be out-of-order resnos in corresponding target list
+			 */
+			op->correspondingColumns = orderCorrespondingLsit(matchingColumns);
+
+			/* If matchingColumns is empty, there is an error.
+			 * At least one column in the select lists must have the same name.
+			 */
+			if (list_length(matchingColumns) == 0)
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR), errmsg("%s queries with a CORRESPONDING clause must have at least one column with the same name", context)));
+			}
+
+			/* matchingColumns contain target list as it appear in left query targetList
+			 * we need matching column as it appear in right query targetlist inorder
+			 * to make output column type for corresponding columns
+			 */
+			rightCorrespondingColumns = determineMatchingColumns(rargQuery->targetList, largQuery->targetList);
+
+			/* make union'd datatype of output column
+			 */
+			makeUnionDatatype(matchingColumns, rightCorrespondingColumns, op, targetlist, pstate,
+					context);
+		}
+		else
+		{
+			/*
+			 * CORRESPONDING BY clause, find matching column names from both tables
+			 * and intersect them with BY(...) column list. If there are none
+			 * then it is a syntax error.
+			 */
+
+			Query *largQuery;
+			Query *rargQuery;
+			List *matchingColumns;
+			List *matchingColumnsFiltered;
+			List *rightCorrespondingColumns;
+			ListCell *corrtl;
+			ListCell *mctl;
+
+			/* Analyze left query to resolve column names. */
+			largQuery = parse_sub_analyze((Node *) stmt->larg, pstate, NULL,false, false );
+
+			/* Analyze right query to resolve column names. */
+			rargQuery = parse_sub_analyze((Node *) stmt->rarg, pstate, NULL, false, false );
+
+			/*
+			 * Find matching columns from both queries.
+			 * In CORRESPONDING BY, column names will be removed from
+			 * matchingColumns if they are not in the BY clause.
+			 * All columns in the BY clause must be in matchingColumns,
+			 * otherwise raise syntax error in BY clause.
+			 */
+
+			matchingColumns = determineMatchingColumns(largQuery->targetList,rargQuery->targetList);
+
+			/*
+			 * Every column name in correspondingClause must be in matchingColumns,
+			 * otherwise it is a syntax error.
+			 */
+			foreach(corrtl, stmt->correspondingClause)
+			{
+				Node* corrtle = lfirst(corrtl);
+				if (IsA(corrtle, ColumnRef)
+						&& list_length(((ColumnRef *) corrtle)->fields)
+								== 1&&
+								IsA(linitial(((ColumnRef *) corrtle)->fields), String))
+				{
+					/* Get column name from correspondingClause. */
+					char *name =
+							strVal(linitial(((ColumnRef *) corrtle)->fields));
+					bool hasMatch = false;
+
+					foreach(mctl, matchingColumns)
+					{
+						TargetEntry *mctle = (TargetEntry *) lfirst(mctl);
+
+						Assert(mctle->resname != NULL);
+						Assert(name != NULL);
+
+						/* Compare correspondingClause column name with matchingColumns column names. */
+						if (strcmp(mctle->resname, name) == 0)
+						{
+							/* we have a match. */
+							hasMatch = true;
+							break;
+						}
+					}
+
+					if (!hasMatch)
+					{
+						/* CORRESPONDING BY clause contains a column name that is not in both tables. */
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR), errmsg("CORRESPONDING BY clause must only contain column names from both tables.")));
+					}
+
+				}
+				else
+				{
+					/* Only column names are supported, constants are syntax error in CORRESPONDING BY clause. */
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR), errmsg( "%s queries with CORRESPONDING BY clause must have only column names and not constants or ordinals in the column name list.", context)));
+				}
+			}
+
+			/* To preserve column ordering from correspondingClause and to remove
+			 * columns from matchingColumns if they are not in correspondingClause,
+			 * create a new list and finalize our column list for the
+			 * CORRESPONDING BY clause.
+			 */
+
+			matchingColumnsFiltered = NIL;
+
+			/* For each column in CORRESPONDING BY column list, check
+			 * column existence in matchingColumns.
+			 */
+			foreach(corrtl, stmt->correspondingClause)
+			{
+				Node* corrtle = lfirst(corrtl);
+
+				if (IsA(corrtle, ColumnRef)
+						&& list_length(((ColumnRef *) corrtle)->fields)
+								== 1&&
+								IsA(linitial(((ColumnRef *) corrtle)->fields), String))
+				{
+					char *name =
+							strVal(linitial(((ColumnRef *) corrtle)->fields));
+
+					foreach(mctl, matchingColumns)
+					{
+						TargetEntry *mctle = (TargetEntry *) lfirst(mctl);
+
+						Assert(mctle->resname != NULL);
+						Assert(name != NULL);
+
+						if (strcmp(mctle->resname, name) == 0)
+						{
+							/* we have a match.*/
+							matchingColumnsFiltered = lappend(
+									matchingColumnsFiltered, mctle);
+							break;
+						}
+					}
+				}
+			}
+
+			/*
+			 * If matchingColumnsFiltered is empty, there is a semantic error.
+			 * At least one column in the select lists must have the same name.
+			 */
+			if (list_length(matchingColumnsFiltered) == 0)
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR), errmsg("%s queries with CORRESPONDING BY clause must have at least one column name in BY clause and in both of the queries.", context)));
+			}
+			/*
+			 * there may be out-of-order resnos in corresponding target list
+			 */
+			op->correspondingColumns = orderCorrespondingLsit(matchingColumnsFiltered);
+
+			/*
+			 * matchingColumns contain target list as it appear in left query targetList
+			 * we need matching column as it appear in right query targetlist inorder to
+			 * make output column type for corresponding columns
+			 */
+			rightCorrespondingColumns = determineMatchingColumns(rargQuery->targetList,matchingColumnsFiltered);
+			/*
+			 * make union'd datatype of output columns
+			 */
+			makeUnionDatatype(matchingColumnsFiltered, rightCorrespondingColumns, op, targetlist, pstate,
+					context);
+		}
 		/*
 		 * Recursively transform the left child node.
 		 */
@@ -1971,165 +2178,206 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 						context),
 					 parser_errposition(pstate,
 										exprLocation((Node *) rtargetlist))));
+		if (op->correspondingColumns == NIL)
+			makeUnionDatatype(ltargetlist, rtargetlist, op, targetlist, pstate,
+					context);
 
-		if (targetlist)
-			*targetlist = NIL;
-		op->colTypes = NIL;
-		op->colTypmods = NIL;
-		op->colCollations = NIL;
-		op->groupClauses = NIL;
-		forboth(ltl, ltargetlist, rtl, rtargetlist)
+		return (Node *) op;
+	}
+}
+
+/*
+ * Processes targetlists of two queries for column equivalence to use
+ * with UNION/INTERSECT/EXCEPT CORRESPONDING.
+ */
+static List *
+determineMatchingColumns(List *ltargetlist, List *rtargetlist)
+{
+	List *matchingColumns = NIL;
+	ListCell *ltl;
+	ListCell *rtl;
+
+	foreach(ltl, ltargetlist)
+	{
+		foreach(rtl, rtargetlist)
 		{
 			TargetEntry *ltle = (TargetEntry *) lfirst(ltl);
 			TargetEntry *rtle = (TargetEntry *) lfirst(rtl);
-			Node	   *lcolnode = (Node *) ltle->expr;
-			Node	   *rcolnode = (Node *) rtle->expr;
-			Oid			lcoltype = exprType(lcolnode);
-			Oid			rcoltype = exprType(rcolnode);
-			int32		lcoltypmod = exprTypmod(lcolnode);
-			int32		rcoltypmod = exprTypmod(rcolnode);
-			Node	   *bestexpr;
-			int			bestlocation;
-			Oid			rescoltype;
-			int32		rescoltypmod;
-			Oid			rescolcoll;
-
-			/* select common type, same as CASE et al */
-			rescoltype = select_common_type(pstate,
-											list_make2(lcolnode, rcolnode),
-											context,
-											&bestexpr);
-			bestlocation = exprLocation(bestexpr);
-			/* if same type and same typmod, use typmod; else default */
-			if (lcoltype == rcoltype && lcoltypmod == rcoltypmod)
-				rescoltypmod = lcoltypmod;
-			else
-				rescoltypmod = -1;
-
-			/*
-			 * Verify the coercions are actually possible.  If not, we'd fail
-			 * later anyway, but we want to fail now while we have sufficient
-			 * context to produce an error cursor position.
-			 *
-			 * For all non-UNKNOWN-type cases, we verify coercibility but we
-			 * don't modify the child's expression, for fear of changing the
-			 * child query's semantics.
-			 *
-			 * If a child expression is an UNKNOWN-type Const or Param, we
-			 * want to replace it with the coerced expression.  This can only
-			 * happen when the child is a leaf set-op node.  It's safe to
-			 * replace the expression because if the child query's semantics
-			 * depended on the type of this output column, it'd have already
-			 * coerced the UNKNOWN to something else.  We want to do this
-			 * because (a) we want to verify that a Const is valid for the
-			 * target type, or resolve the actual type of an UNKNOWN Param,
-			 * and (b) we want to avoid unnecessary discrepancies between the
-			 * output type of the child query and the resolved target type.
-			 * Such a discrepancy would disable optimization in the planner.
-			 *
-			 * If it's some other UNKNOWN-type node, eg a Var, we do nothing
-			 * (knowing that coerce_to_common_type would fail).  The planner
-			 * is sometimes able to fold an UNKNOWN Var to a constant before
-			 * it has to coerce the type, so failing now would just break
-			 * cases that might work.
-			 */
-			if (lcoltype != UNKNOWNOID)
-				lcolnode = coerce_to_common_type(pstate, lcolnode,
-												 rescoltype, context);
-			else if (IsA(lcolnode, Const) ||
-					 IsA(lcolnode, Param))
-			{
-				lcolnode = coerce_to_common_type(pstate, lcolnode,
-												 rescoltype, context);
-				ltle->expr = (Expr *) lcolnode;
-			}
 
-			if (rcoltype != UNKNOWNOID)
-				rcolnode = coerce_to_common_type(pstate, rcolnode,
-												 rescoltype, context);
-			else if (IsA(rcolnode, Const) ||
-					 IsA(rcolnode, Param))
-			{
-				rcolnode = coerce_to_common_type(pstate, rcolnode,
-												 rescoltype, context);
-				rtle->expr = (Expr *) rcolnode;
-			}
+			elog(DEBUG4, "%s", ltle->resname);
 
-			/*
-			 * Select common collation.  A common collation is required for
-			 * all set operators except UNION ALL; see SQL:2008 7.13 <query
-			 * expression> Syntax Rule 15c.  (If we fail to identify a common
-			 * collation for a UNION ALL column, the curCollations element
-			 * will be set to InvalidOid, which may result in a runtime error
-			 * if something at a higher query level wants to use the column's
-			 * collation.)
-			 */
-			rescolcoll = select_common_collation(pstate,
-											  list_make2(lcolnode, rcolnode),
-										 (op->op == SETOP_UNION && op->all));
-
-			/* emit results */
-			op->colTypes = lappend_oid(op->colTypes, rescoltype);
-			op->colTypmods = lappend_int(op->colTypmods, rescoltypmod);
-			op->colCollations = lappend_oid(op->colCollations, rescolcoll);
-
-			/*
-			 * For all cases except UNION ALL, identify the grouping operators
-			 * (and, if available, sorting operators) that will be used to
-			 * eliminate duplicates.
-			 */
-			if (op->op != SETOP_UNION || !op->all)
-			{
-				SortGroupClause *grpcl = makeNode(SortGroupClause);
-				Oid			sortop;
-				Oid			eqop;
-				bool		hashable;
-				ParseCallbackState pcbstate;
-
-				setup_parser_errposition_callback(&pcbstate, pstate,
-												  bestlocation);
-
-				/* determine the eqop and optional sortop */
-				get_sort_group_operators(rescoltype,
-										 false, true, false,
-										 &sortop, &eqop, NULL,
-										 &hashable);
-
-				cancel_parser_errposition_callback(&pcbstate);
-
-				/* we don't have a tlist yet, so can't assign sortgrouprefs */
-				grpcl->tleSortGroupRef = 0;
-				grpcl->eqop = eqop;
-				grpcl->sortop = sortop;
-				grpcl->nulls_first = false;		/* OK with or without sortop */
-				grpcl->hashable = hashable;
-
-				op->groupClauses = lappend(op->groupClauses, grpcl);
-			}
+			/* Names of the columns must be resolved before calling this method. */
+			Assert(ltle->resname != NULL);
+			Assert(rtle->resname != NULL);
 
-			/*
-			 * Construct a dummy tlist entry to return.  We use a SetToDefault
-			 * node for the expression, since it carries exactly the fields
-			 * needed, but any other expression node type would do as well.
-			 */
-			if (targetlist)
+			/* If column names are the same, append it to the result. */
+			if (strcmp(ltle->resname, rtle->resname) == 0)
 			{
-				SetToDefault *rescolnode = makeNode(SetToDefault);
-				TargetEntry *restle;
-
-				rescolnode->typeId = rescoltype;
-				rescolnode->typeMod = rescoltypmod;
-				rescolnode->collation = rescolcoll;
-				rescolnode->location = bestlocation;
-				restle = makeTargetEntry((Expr *) rescolnode,
-										 0,		/* no need to set resno */
-										 NULL,
-										 false);
-				*targetlist = lappend(*targetlist, restle);
+				matchingColumns = lappend(matchingColumns, ltle);
+				continue;
 			}
 		}
+	}
+
+	return matchingColumns;
+}
 
-		return (Node *) op;
+/*
+ * process right and left target list to set up union'd datatype
+ */
+static void *
+makeUnionDatatype(List *ltargetlist, List *rtargetlist, SetOperationStmt *op,
+		List **targetlist, ParseState *pstate, const char *context)
+{
+	ListCell *ltl;
+	ListCell *rtl;
+	if (targetlist)
+		*targetlist = NIL;
+	op->colTypes = NIL;
+	op->colTypmods = NIL;
+	op->colCollations = NIL;
+	op->groupClauses = NIL;
+
+	forboth(ltl, ltargetlist, rtl, rtargetlist)
+	{
+		TargetEntry *ltle = (TargetEntry *) lfirst(ltl);
+		TargetEntry *rtle = (TargetEntry *) lfirst(rtl);
+		Node *lcolnode = (Node *) ltle->expr;
+		Node *rcolnode = (Node *) rtle->expr;
+		Oid lcoltype = exprType(lcolnode);
+		Oid rcoltype = exprType(rcolnode);
+		int32 lcoltypmod = exprTypmod(lcolnode);
+		int32 rcoltypmod = exprTypmod(rcolnode);
+		Node *bestexpr;
+		int bestlocation;
+		Oid rescoltype;
+		int32 rescoltypmod;
+		Oid rescolcoll;
+
+		/* select common type, same as CASE et al */
+		rescoltype = select_common_type(pstate, list_make2(lcolnode, rcolnode),
+				context, &bestexpr);
+		bestlocation = exprLocation(bestexpr);
+		/* if same type and same typmod, use typmod; else default */
+		if (lcoltype == rcoltype && lcoltypmod == rcoltypmod)
+			rescoltypmod = lcoltypmod;
+		else
+			rescoltypmod = -1;
+
+		/*
+		 * Verify the coercions are actually possible.  If not, we'd fail
+		 * later anyway, but we want to fail now while we have sufficient
+		 * context to produce an error cursor position.
+		 *
+		 * For all non-UNKNOWN-type cases, we verify coercibility but we
+		 * don't modify the child's expression, for fear of changing the
+		 * child query's semantics.
+		 *
+		 * If a child expression is an UNKNOWN-type Const or Param, we
+		 * want to replace it with the coerced expression.  This can only
+		 * happen when the child is a leaf set-op node.  It's safe to
+		 * replace the expression because if the child query's semantics
+		 * depended on the type of this output column, it'd have already
+		 * coerced the UNKNOWN to something else.  We want to do this
+		 * because (a) we want to verify that a Const is valid for the
+		 * target type, or resolve the actual type of an UNKNOWN Param,
+		 * and (b) we want to avoid unnecessary discrepancies between the
+		 * output type of the child query and the resolved target type.
+		 * Such a discrepancy would disable optimization in the planner.
+		 *
+		 * If it's some other UNKNOWN-type node, eg a Var, we do nothing
+		 * (knowing that coerce_to_common_type would fail).  The planner
+		 * is sometimes able to fold an UNKNOWN Var to a constant before
+		 * it has to coerce the type, so failing now would just break
+		 * cases that might work.
+		 */
+		if (lcoltype != UNKNOWNOID)
+			lcolnode = coerce_to_common_type(pstate, lcolnode, rescoltype,
+					context);
+		else if (IsA(lcolnode, Const) || IsA(lcolnode, Param))
+		{
+			lcolnode = coerce_to_common_type(pstate, lcolnode, rescoltype,
+					context);
+			ltle->expr = (Expr *) lcolnode;
+		}
+
+		if (rcoltype != UNKNOWNOID)
+			rcolnode = coerce_to_common_type(pstate, rcolnode, rescoltype,
+					context);
+		else if (IsA(rcolnode, Const) || IsA(rcolnode, Param))
+		{
+			rcolnode = coerce_to_common_type(pstate, rcolnode, rescoltype,
+					context);
+			rtle->expr = (Expr *) rcolnode;
+		}
+
+		/*
+		 * Select common collation.  A common collation is required for
+		 * all set operators except UNION ALL; see SQL:2008 7.13 <query
+		 * expression> Syntax Rule 15c.  (If we fail to identify a common
+		 * collation for a UNION ALL column, the curCollations element
+		 * will be set to InvalidOid, which may result in a runtime error
+		 * if something at a higher query level wants to use the column's
+		 * collation.)
+		 */
+		rescolcoll = select_common_collation(pstate,
+				list_make2(lcolnode, rcolnode),
+				(op->op == SETOP_UNION && op->all));
+
+		/* emit results */
+		op->colTypes = lappend_oid(op->colTypes, rescoltype);
+		op->colTypmods = lappend_int(op->colTypmods, rescoltypmod);
+		op->colCollations = lappend_oid(op->colCollations, rescolcoll);
+
+		/*
+		 * For all cases except UNION ALL, identify the grouping operators
+		 * (and, if available, sorting operators) that will be used to
+		 * eliminate duplicates.
+		 */
+		if (op->op != SETOP_UNION || !op->all)
+		{
+			SortGroupClause *grpcl = makeNode(SortGroupClause);
+			Oid sortop;
+			Oid eqop;
+			bool hashable;
+			ParseCallbackState pcbstate;
+
+			setup_parser_errposition_callback(&pcbstate, pstate, bestlocation);
+
+			/* determine the eqop and optional sortop */
+			get_sort_group_operators(rescoltype, false, true, false, &sortop,
+					&eqop, NULL, &hashable);
+
+			cancel_parser_errposition_callback(&pcbstate);
+
+			/* we don't have a tlist yet, so can't assign sortgrouprefs */
+			grpcl->tleSortGroupRef = 0;
+			grpcl->eqop = eqop;
+			grpcl->sortop = sortop;
+			grpcl->nulls_first = false; /* OK with or without sortop */
+			grpcl->hashable = hashable;
+
+			op->groupClauses = lappend(op->groupClauses, grpcl);
+		}
+
+		/*
+		 * Construct a dummy tlist entry to return.  We use a SetToDefault
+		 * node for the expression, since it carries exactly the fields
+		 * needed, but any other expression node type would do as well.
+		 */
+		if (targetlist)
+		{
+			SetToDefault *rescolnode = makeNode(SetToDefault);
+			TargetEntry *restle;
+
+			rescolnode->typeId = rescoltype;
+			rescolnode->typeMod = rescoltypmod;
+			rescolnode->collation = rescolcoll;
+			rescolnode->location = bestlocation;
+			restle = makeTargetEntry((Expr *) rescolnode, 0, /* no need to set resno */
+			NULL, false );
+			*targetlist = lappend(*targetlist, restle);
+		}
 	}
 }
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 174773b..22aa911 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -166,7 +166,7 @@ static void insertSelectOptions(SelectStmt *stmt,
 								Node *limitOffset, Node *limitCount,
 								WithClause *withClause,
 								core_yyscan_t yyscanner);
-static Node *makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg);
+static Node *makeSetOp(SetOperation op, bool all, List *correspondingClause, Node *larg, Node *rarg);
 static Node *doNegate(Node *n, int location);
 static void doNegateFloat(Value *v);
 static Node *makeAndExpr(Node *lexpr, Node *rexpr, int location);
@@ -394,7 +394,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				relation_expr_list dostmt_opt_list
 				transform_element_list transform_type_list
 				TriggerTransitions TriggerReferencing
-				publication_name_list
+				publication_name_list opt_corresponding_clause
 
 %type <list>	group_by_list
 %type <node>	group_by_item empty_grouping_set rollup_clause cube_clause
@@ -608,7 +608,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
 	CLUSTER COALESCE COLLATE COLLATION COLUMN COMMENT COMMENTS COMMIT
 	COMMITTED CONCURRENTLY CONFIGURATION CONFLICT CONNECTION CONSTRAINT
-	CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE
+	CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY CORRESPONDING COST CREATE
 	CROSS CSV CUBE CURRENT_P
 	CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA
 	CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
@@ -10808,20 +10808,26 @@ simple_select:
 					n->fromClause = list_make1($2);
 					$$ = (Node *)n;
 				}
-			| select_clause UNION all_or_distinct select_clause
+			| select_clause UNION all_or_distinct opt_corresponding_clause select_clause
 				{
-					$$ = makeSetOp(SETOP_UNION, $3, $1, $4);
+					$$ = makeSetOp(SETOP_UNION, $3, $4, $1, $5);
 				}
-			| select_clause INTERSECT all_or_distinct select_clause
+			| select_clause INTERSECT all_or_distinct opt_corresponding_clause select_clause
 				{
-					$$ = makeSetOp(SETOP_INTERSECT, $3, $1, $4);
+					$$ = makeSetOp(SETOP_INTERSECT, $3, $4, $1, $5);
 				}
-			| select_clause EXCEPT all_or_distinct select_clause
+			| select_clause EXCEPT all_or_distinct opt_corresponding_clause select_clause
 				{
-					$$ = makeSetOp(SETOP_EXCEPT, $3, $1, $4);
+					$$ = makeSetOp(SETOP_EXCEPT, $3, $4, $1, $5);
 				}
 		;
 
+opt_corresponding_clause:
+			CORRESPONDING BY '(' expr_list ')'		{ $$ = $4; }
+			| CORRESPONDING							{ $$ = list_make1(NIL); }
+			| /*EMPTY*/								{ $$ = NIL; }
+			;
+
 /*
  * SQL standard WITH clause looks like:
  *
@@ -14315,6 +14321,7 @@ unreserved_keyword:
 			| CONTINUE_P
 			| CONVERSION_P
 			| COPY
+			| CORRESPONDING
 			| COST
 			| CSV
 			| CUBE
@@ -15145,7 +15152,7 @@ insertSelectOptions(SelectStmt *stmt,
 }
 
 static Node *
-makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg)
+makeSetOp(SetOperation op, bool all, List *correspondingClause, Node *larg, Node *rarg)
 {
 	SelectStmt *n = makeNode(SelectStmt);
 
@@ -15153,6 +15160,7 @@ makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg)
 	n->all = all;
 	n->larg = (SelectStmt *) larg;
 	n->rarg = (SelectStmt *) rarg;
+	n->correspondingClause = correspondingClause;
 	return (Node *) n;
 }
 
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 2576e31..d45a88e 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1903,3 +1903,26 @@ FigureColnameInternal(Node *node, char **name)
 
 	return strength;
 }
+
+/*
+ * changeCorrespondingList()
+ * order target list resno .
+ */
+List *
+orderCorrespondingLsit(List *targetlist)
+{
+	List *p_target = NIL;
+	ListCell *o_target;
+	int pos = 1;
+	int loc = 1;
+
+	foreach(o_target, targetlist)
+	{
+		TargetEntry *tar = (TargetEntry *) lfirst(o_target);
+
+		p_target = lappend(p_target,
+				changeTargetEntry(tar, (AttrNumber) pos++, (AttrNumber) loc++));
+	}
+
+	return p_target;
+}
diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
index 8feec0b..ac23b1b 100644
--- a/src/backend/parser/parse_type.c
+++ b/src/backend/parser/parse_type.c
@@ -736,7 +736,8 @@ typeStringToTypeName(const char *str)
 		stmt->limitCount != NULL ||
 		stmt->lockingClause != NIL ||
 		stmt->withClause != NULL ||
-		stmt->op != SETOP_NONE)
+		stmt->op != SETOP_NONE ||
+		stmt->correspondingClause != NULL)
 		goto fail;
 	if (list_length(stmt->targetList) != 1)
 		goto fail;
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 1f4bad7..777c3e2 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -86,4 +86,6 @@ extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
 
 extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int location);
 
+extern TargetEntry *changeTargetEntry(TargetEntry *target, AttrNumber resno, AttrNumber loc);
+
 #endif   /* MAKEFUNC_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 07a8436..1dfff8f 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1420,7 +1420,9 @@ typedef struct SelectStmt
 	 * These fields are used only in "leaf" SelectStmts.
 	 */
 	List	   *distinctClause; /* NULL, list of DISTINCT ON exprs, or
-								 * lcons(NIL,NIL) for all (SELECT DISTINCT) */
+								* lcons(NIL,NIL) for all (SELECT DISTINCT) */
+	List	   *correspondingClause; /* NULL, list of CORRESPONDING BY exprs, or */
+								/* lcons(NIL, NIL) for CORRESPONDING */
 	IntoClause *intoClause;		/* target for SELECT INTO */
 	List	   *targetList;		/* the target list (of ResTarget) */
 	List	   *fromClause;		/* the FROM clause */
@@ -1486,8 +1488,9 @@ typedef struct SetOperationStmt
 	bool		all;			/* ALL specified? */
 	Node	   *larg;			/* left child */
 	Node	   *rarg;			/* right child */
-	/* Eventually add fields for CORRESPONDING spec here */
-
+	/* CORRESPONDING clause fields */
+	List	   *correspondingColumns;	/* NIL: No corresponding, else: CORRESPONDING or CORRESPONDIN
+						* BY matching columns. Not the original clause. */
 	/* Fields derived during parse analysis: */
 	List	   *colTypes;		/* OID list of output column type OIDs */
 	List	   *colTypmods;		/* integer list of output column typmods */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 985d650..77e0396 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -96,6 +96,7 @@ PG_KEYWORD("content", CONTENT_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("continue", CONTINUE_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("conversion", CONVERSION_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("copy", COPY, UNRESERVED_KEYWORD)
+PG_KEYWORD("corresponding", CORRESPONDING, UNRESERVED_KEYWORD)
 PG_KEYWORD("cost", COST, UNRESERVED_KEYWORD)
 PG_KEYWORD("create", CREATE, RESERVED_KEYWORD)
 PG_KEYWORD("cross", CROSS, TYPE_FUNC_NAME_KEYWORD)
diff --git a/src/include/parser/parse_target.h b/src/include/parser/parse_target.h
index d06a235..a4cfe31 100644
--- a/src/include/parser/parse_target.h
+++ b/src/include/parser/parse_target.h
@@ -42,5 +42,6 @@ extern TupleDesc expandRecordVariable(ParseState *pstate, Var *var,
 					 int levelsup);
 extern char *FigureColname(Node *node);
 extern char *FigureIndexColname(Node *node);
+extern List *orderCorrespondingLsit(List *targetlist);
 
 #endif   /* PARSE_TARGET_H */
diff --git a/src/test/regress/expected/union.out b/src/test/regress/expected/union.out
index 4d697ba..3d60b23 100644
--- a/src/test/regress/expected/union.out
+++ b/src/test/regress/expected/union.out
@@ -59,6 +59,198 @@ SELECT 1.1 AS two UNION SELECT 2.2 ORDER BY 1;
  2.2
 (2 rows)
 
+SELECT 1 AS two UNION CORRESPONDING SELECT 2 two;
+ two 
+-----
+   1
+   2
+(2 rows)
+
+SELECT 1 AS one UNION CORRESPONDING SELECT 1 one;
+ one 
+-----
+   1
+(1 row)
+
+SELECT 1 AS two UNION ALL CORRESPONDING SELECT 2 two;
+ two 
+-----
+   1
+   2
+(2 rows)
+
+SELECT 1 AS two UNION ALL CORRESPONDING SELECT 1 two;
+ two 
+-----
+   1
+   1
+(2 rows)
+
+SELECT 1 AS two UNION CORRESPONDING SELECT 2 two UNION CORRESPONDING SELECT 2 two;
+ two 
+-----
+   2
+   1
+(2 rows)
+
+SELECT 1 AS three UNION CORRESPONDING SELECT 2 three UNION ALL CORRESPONDING SELECT 2 three;
+ three 
+-------
+     1
+     2
+     2
+(3 rows)
+
+SELECT 1 AS three UNION CORRESPONDING SELECT 2 three UNION CORRESPONDING SELECT 3 three;
+ three 
+-------
+     3
+     2
+     1
+(3 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+SELECT 3 c, 2 b, 1 a UNION CORRESPONDING SELECT 6 c, 4 a, 5 b;
+ c | b | a 
+---+---+---
+ 3 | 2 | 1
+ 6 | 5 | 4
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a) SELECT 4 a, 5 b, 6 c;
+ a 
+---
+ 1
+ 4
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b) SELECT 4 a, 5 b, 6 c;
+ b 
+---
+ 2
+ 5
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(c) SELECT 4 a, 5 b, 6 c;
+ c 
+---
+ 3
+ 6
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b) SELECT 4 a, 5 b, 6 c;
+ a | b 
+---+---
+ 1 | 2
+ 4 | 5
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c) SELECT 4 a, 5 b, 6 c;
+ b | c 
+---+---
+ 2 | 3
+ 5 | 6
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, c) SELECT 4 a, 5 b, 6 c;
+ a | c 
+---+---
+ 1 | 3
+ 4 | 6
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 4 a, 5 b, 6 c;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+-- CORRESPONDING column ordering, left clause's column ordering must be preserved.
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+SELECT 2 b, 1 a, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+ b | a | c 
+---+---+---
+ 2 | 1 | 3
+ 5 | 4 | 6
+(2 rows)
+
+SELECT 1 a, 3 c, 2 b UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+ a | c | b 
+---+---+---
+ 1 | 3 | 2
+ 4 | 6 | 5
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 5 b, 6 c, 4 a;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+SELECT 2 b, 1 a, 3 c UNION CORRESPONDING SELECT 5 b, 6 c, 4 a;
+ b | a | c 
+---+---+---
+ 2 | 1 | 3
+ 5 | 4 | 6
+(2 rows)
+
+-- CORRESPONDING BY column ordering, BY clause column ordering must be preserved.
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 4 a, 5 b, 6 c;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c, a) SELECT 4 a, 5 b, 6 c;
+ b | c | a 
+---+---+---
+ 2 | 3 | 1
+ 5 | 6 | 4
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(c, a, b) SELECT 4 a, 5 b, 6 c;
+ c | a | b 
+---+---+---
+ 3 | 1 | 2
+ 6 | 4 | 5
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 5 b, 6 c, 4 a;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+SELECT 2 b, 3 c, 1 a UNION CORRESPONDING BY(b, c, a) SELECT 4 a, 5 b, 6 c;
+ b | c | a 
+---+---+---
+ 2 | 3 | 1
+ 5 | 6 | 4
+(2 rows)
+
+SELECT 3 c, 2 b, 1 a UNION CORRESPONDING BY(c, a, b) SELECT 4 a, 5 b, 6 c;
+ c | a | b 
+---+---+---
+ 3 | 1 | 2
+ 6 | 4 | 5
+(2 rows)
+
 -- Mixed types
 SELECT 1.1 AS two UNION SELECT 2 ORDER BY 1;
  two 
@@ -258,6 +450,97 @@ ORDER BY 1;
  hi de ho neighbor
 (5 rows)
 
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+         five          
+-----------------------
+ -1.2345678901234e+200
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+                     0
+(5 rows)
+
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING BY(five)
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+         five          
+-----------------------
+ -1.2345678901234e+200
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+                     0
+(5 rows)
+
+SELECT f1 AS ten FROM FLOAT8_TBL
+UNION ALL CORRESPONDING
+SELECT f1 AS ten FROM FLOAT8_TBL;
+          ten          
+-----------------------
+                     0
+                -34.84
+               -1004.3
+ -1.2345678901234e+200
+ -1.2345678901234e-200
+                     0
+                -34.84
+               -1004.3
+ -1.2345678901234e+200
+ -1.2345678901234e-200
+(10 rows)
+
+SELECT f1 AS nine FROM FLOAT8_TBL
+UNION CORRESPONDING
+SELECT f1 AS nine FROM INT4_TBL
+ORDER BY 1;
+         nine          
+-----------------------
+ -1.2345678901234e+200
+           -2147483647
+               -123456
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+                     0
+                123456
+            2147483647
+(9 rows)
+
+SELECT f1 AS ten FROM FLOAT8_TBL
+UNION ALL CORRESPONDING
+SELECT f1 AS ten FROM INT4_TBL;
+          ten          
+-----------------------
+                     0
+                -34.84
+               -1004.3
+ -1.2345678901234e+200
+ -1.2345678901234e-200
+                     0
+                123456
+               -123456
+            2147483647
+           -2147483647
+(10 rows)
+
+SELECT f1 AS five FROM FLOAT8_TBL
+  WHERE f1 BETWEEN -1e6 AND 1e6
+UNION CORRESPONDING
+SELECT f1 AS five FROM INT4_TBL
+  WHERE f1 BETWEEN 0 AND 1000000;
+         five          
+-----------------------
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+                     0
+                123456
+(5 rows)
+
 --
 -- INTERSECT and EXCEPT
 --
@@ -320,6 +603,63 @@ SELECT q1 FROM int8_tbl EXCEPT ALL SELECT DISTINCT q2 FROM int8_tbl ORDER BY 1;
 
 SELECT q1 FROM int8_tbl EXCEPT ALL SELECT q1 FROM int8_tbl FOR NO KEY UPDATE;
 ERROR:  FOR NO KEY UPDATE is not allowed with UNION/INTERSECT/EXCEPT
+SELECT q2 FROM int8_tbl INTERSECT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+        q2        
+------------------
+              123
+ 4567890123456789
+(2 rows)
+
+SELECT q2 FROM int8_tbl INTERSECT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+        q2        
+------------------
+              123
+ 4567890123456789
+ 4567890123456789
+(3 rows)
+
+SELECT q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+        q2         
+-------------------
+ -4567890123456789
+               456
+(2 rows)
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+        q2         
+-------------------
+ -4567890123456789
+               456
+(2 rows)
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q1 AS q2 FROM int8_tbl ORDER BY 1;
+        q2         
+-------------------
+ -4567890123456789
+               456
+  4567890123456789
+(3 rows)
+
+SELECT q1 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+ q1 
+----
+(0 rows)
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+        q1        
+------------------
+              123
+ 4567890123456789
+(2 rows)
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q2 AS q1 FROM int8_tbl;
+        q1        
+------------------
+              123
+ 4567890123456789
+ 4567890123456789
+(3 rows)
+
 --
 -- Mixed types
 --
@@ -338,6 +678,21 @@ SELECT f1 FROM float8_tbl EXCEPT SELECT f1 FROM int4_tbl ORDER BY 1;
  -1.2345678901234e-200
 (4 rows)
 
+SELECT f1 FROM float8_tbl INTERSECT CORRESPONDING SELECT f1 FROM int4_tbl;
+ f1 
+----
+  0
+(1 row)
+
+SELECT f1 FROM float8_tbl EXCEPT CORRESPONDING SELECT f1 FROM int4_tbl ORDER BY 1;
+          f1           
+-----------------------
+ -1.2345678901234e+200
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+(4 rows)
+
 --
 -- Operator precedence and (((((extra))))) parentheses
 --
@@ -424,6 +779,24 @@ SELECT q1 FROM int8_tbl EXCEPT (((SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1)))
  4567890123456789
 (2 rows)
 
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2,q1 FROM int8_tbl
+ORDER BY q2,q1;
+ q1 | q2 
+----+----
+(0 rows)
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q1) SELECT q2,q1 FROM int8_tbl
+ORDER BY q1;
+ q1 
+----
+(0 rows)
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q2) SELECT q2,q1 FROM int8_tbl
+ORDER BY q2;
+ q2 
+----
+(0 rows)
+
 --
 -- New syntaxes (7.1) permit new tests
 --
@@ -459,6 +832,22 @@ SELECT '3.4'::numeric UNION SELECT 'foo';
 ERROR:  invalid input syntax for type numeric: "foo"
 LINE 1: SELECT '3.4'::numeric UNION SELECT 'foo';
                                            ^
+SELECT '3.4'::numeric AS a UNION CORRESPONDING SELECT 'foo' AS a;
+ERROR:  invalid input syntax for type numeric: "foo"
+LINE 1: ...CT '3.4'::numeric AS a UNION CORRESPONDING SELECT 'foo' AS a...
+                                                             ^
+SELECT a.f1 FROM (SELECT 'test' AS f1 FROM varchar_tbl) a
+UNION CORRESPONDING
+SELECT b.f1 FROM (SELECT f1 FROM varchar_tbl) b
+ORDER BY 1;
+  f1  
+------
+ a
+ ab
+ abcd
+ test
+(4 rows)
+
 --
 -- Test that expression-index constraints can be pushed down through
 -- UNION or UNION ALL
diff --git a/src/test/regress/sql/union.sql b/src/test/regress/sql/union.sql
index 48e6850..bae0df2 100644
--- a/src/test/regress/sql/union.sql
+++ b/src/test/regress/sql/union.sql
@@ -20,6 +20,64 @@ SELECT 1 AS three UNION SELECT 2 UNION ALL SELECT 2 ORDER BY 1;
 
 SELECT 1.1 AS two UNION SELECT 2.2 ORDER BY 1;
 
+SELECT 1 AS two UNION CORRESPONDING SELECT 2 two;
+
+SELECT 1 AS one UNION CORRESPONDING SELECT 1 one;
+
+SELECT 1 AS two UNION ALL CORRESPONDING SELECT 2 two;
+
+SELECT 1 AS two UNION ALL CORRESPONDING SELECT 1 two;
+
+SELECT 1 AS two UNION CORRESPONDING SELECT 2 two UNION CORRESPONDING SELECT 2 two;
+
+SELECT 1 AS three UNION CORRESPONDING SELECT 2 three UNION ALL CORRESPONDING SELECT 2 three;
+
+SELECT 1 AS three UNION CORRESPONDING SELECT 2 three UNION CORRESPONDING SELECT 3 three;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+
+SELECT 3 c, 2 b, 1 a UNION CORRESPONDING SELECT 6 c, 4 a, 5 b;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(c) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, c) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 4 a, 5 b, 6 c;
+
+-- CORRESPONDING column ordering, left clause's column ordering must be preserved.
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+
+SELECT 2 b, 1 a, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 3 c, 2 b UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 5 b, 6 c, 4 a;
+
+SELECT 2 b, 1 a, 3 c UNION CORRESPONDING SELECT 5 b, 6 c, 4 a;
+
+-- CORRESPONDING BY column ordering, BY clause column ordering must be preserved.
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c, a) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(c, a, b) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 5 b, 6 c, 4 a;
+
+SELECT 2 b, 3 c, 1 a UNION CORRESPONDING BY(b, c, a) SELECT 4 a, 5 b, 6 c;
+
+SELECT 3 c, 2 b, 1 a UNION CORRESPONDING BY(c, a, b) SELECT 4 a, 5 b, 6 c;
+
 -- Mixed types
 
 SELECT 1.1 AS two UNION SELECT 2 ORDER BY 1;
@@ -90,6 +148,35 @@ UNION
 SELECT TRIM(TRAILING FROM f1) FROM CHAR_TBL
 ORDER BY 1;
 
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING BY(five)
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+
+SELECT f1 AS ten FROM FLOAT8_TBL
+UNION ALL CORRESPONDING
+SELECT f1 AS ten FROM FLOAT8_TBL;
+
+SELECT f1 AS nine FROM FLOAT8_TBL
+UNION CORRESPONDING
+SELECT f1 AS nine FROM INT4_TBL
+ORDER BY 1;
+
+SELECT f1 AS ten FROM FLOAT8_TBL
+UNION ALL CORRESPONDING
+SELECT f1 AS ten FROM INT4_TBL;
+
+SELECT f1 AS five FROM FLOAT8_TBL
+  WHERE f1 BETWEEN -1e6 AND 1e6
+UNION CORRESPONDING
+SELECT f1 AS five FROM INT4_TBL
+  WHERE f1 BETWEEN 0 AND 1000000;
+
 --
 -- INTERSECT and EXCEPT
 --
@@ -112,6 +199,22 @@ SELECT q1 FROM int8_tbl EXCEPT ALL SELECT DISTINCT q2 FROM int8_tbl ORDER BY 1;
 
 SELECT q1 FROM int8_tbl EXCEPT ALL SELECT q1 FROM int8_tbl FOR NO KEY UPDATE;
 
+SELECT q2 FROM int8_tbl INTERSECT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+
+SELECT q2 FROM int8_tbl INTERSECT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+
+SELECT q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q1 AS q2 FROM int8_tbl ORDER BY 1;
+
+SELECT q1 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q2 AS q1 FROM int8_tbl;
+
 --
 -- Mixed types
 --
@@ -120,6 +223,10 @@ SELECT f1 FROM float8_tbl INTERSECT SELECT f1 FROM int4_tbl ORDER BY 1;
 
 SELECT f1 FROM float8_tbl EXCEPT SELECT f1 FROM int4_tbl ORDER BY 1;
 
+SELECT f1 FROM float8_tbl INTERSECT CORRESPONDING SELECT f1 FROM int4_tbl;
+
+SELECT f1 FROM float8_tbl EXCEPT CORRESPONDING SELECT f1 FROM int4_tbl ORDER BY 1;
+
 --
 -- Operator precedence and (((((extra))))) parentheses
 --
@@ -150,6 +257,15 @@ SELECT q1 FROM int8_tbl EXCEPT SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1;
 -- But this should work:
 SELECT q1 FROM int8_tbl EXCEPT (((SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1))) ORDER BY 1;
 
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2,q1 FROM int8_tbl
+ORDER BY q2,q1;
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q1) SELECT q2,q1 FROM int8_tbl
+ORDER BY q1;
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q2) SELECT q2,q1 FROM int8_tbl
+ORDER BY q2;
+
 --
 -- New syntaxes (7.1) permit new tests
 --
@@ -170,6 +286,13 @@ ORDER BY 1;
 -- This should fail, but it should produce an error cursor
 SELECT '3.4'::numeric UNION SELECT 'foo';
 
+SELECT '3.4'::numeric AS a UNION CORRESPONDING SELECT 'foo' AS a;
+
+SELECT a.f1 FROM (SELECT 'test' AS f1 FROM varchar_tbl) a
+UNION CORRESPONDING
+SELECT b.f1 FROM (SELECT f1 FROM varchar_tbl) b
+ORDER BY 1;
+
 --
 -- Test that expression-index constraints can be pushed down through
 -- UNION or UNION ALL
#6Robert Haas
robertmhaas@gmail.com
In reply to: Surafel Temsgen (#5)
Re: New CORRESPONDING clause design

On Thu, Feb 16, 2017 at 8:28 PM, Surafel Temsgen <surafel3000@gmail.com> wrote:

Here is the implementation of the clause with the slight change, instead of
doing column mapping for each side of leaf Queries in planner I make the
projection nodes output to corresponding column lists only.

This patch compiles and tests successfully with master branch on
ubuntu-15.10-desktop-amd64.It also includes documentation and new regression
tests in union.sql.

You should probably add this to https://commitfest.postgresql.org/ so
that it doesn't get forgotten about.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#7Pavel Stehule
pavel.stehule@gmail.com
In reply to: Surafel Temsgen (#1)
Re: New CORRESPONDING clause design

Hi

I am sending a review of this interesting feature.

I found following issues, questions:

1. unclosed tags <optional> in documentation
2. bad name "changeTargetEntry" - should be makeTargetEntry?
3. Why you removed lot of asserts in prepunion.c? These asserts should be
valid still
4. make_coresponding_target has wrong formatting
5. error "%s queries with a CORRESPONDING clause must have at least one
column with the same name" has wrong formatting, you can show position
6. previous issue is repeated - look on formatting ereport function,
please, you can use DETAIL and HINT fields
7. corresponding clause should to contain column list (I am looking to
ANSI/SQL 99) - you are using expr_list, what has not sense and probably it
has impact on all implementation.
8. typo orderCorrespondingLsit(List *targetlist)
9. I miss more tests for CORRESPONDING BY
10. if I understand to this feature, this query should to work

postgres=# SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY (c,b) SELECT 4
a, 5 b, 6 c, 8 d;
ERROR: each UNION query must have the same number of columns
LINE 1: ...1 a, 2 b, 3 c UNION CORRESPONDING BY (c,b) SELECT 4 a, 5 b, ...

postgres=# create table t1(a int, b int, c int);
CREATE TABLE
Time: 63,260 ms
postgres=# create table t2(a int, b int);
CREATE TABLE
Time: 57,120 ms
postgres=# select * from t1 union corresponding select * from t2;
ERROR: each UNION query must have the same number of columns
LINE 1: select * from t1 union corresponding select * from t2;

If it is your first patch to Postgres, then it is perfect work!

The @7 is probably most significant - I dislike a expression list there.
name_list should be better there.

Regards

Pavel

#8Surafel Temesgen
surafel3000@gmail.com
In reply to: Pavel Stehule (#7)
1 attachment(s)
Re: New CORRESPONDING clause design

Hi ,

Here is a patch corrected as your feedback except missed tests case because
corresponding by clause is implemented on the top of set operation and you
can’t do that to set operation without corresponding by clause too

Eg

postgres=# SELECT 1 a, 2 b, 3 c UNION SELECT 4 a, 5 b, 6 c, 8 d;

ERROR: each UNION query must have the same number of columns

LINE 1: SELECT 1 a, 2 b, 3 c UNION SELECT 4 a, 5 b, 6 c, 8 d;

^

postgres=# create table t1(a int, b int, c int);

CREATE TABLE

postgres=# create table t2(a int, b int);

CREATE TABLE

postgres=# select * from t1 union select * from t2;

ERROR: each UNION query must have the same number of columns

LINE 1: select * from t1 union select * from t2;

Thanks

Surafel

On Tue, Mar 7, 2017 at 10:26 PM, Pavel Stehule <pavel.stehule@gmail.com>
wrote:

Show quoted text

Hi

I am sending a review of this interesting feature.

I found following issues, questions:

1. unclosed tags <optional> in documentation
2. bad name "changeTargetEntry" - should be makeTargetEntry?
3. Why you removed lot of asserts in prepunion.c? These asserts should be
valid still
4. make_coresponding_target has wrong formatting
5. error "%s queries with a CORRESPONDING clause must have at least one
column with the same name" has wrong formatting, you can show position
6. previous issue is repeated - look on formatting ereport function,
please, you can use DETAIL and HINT fields
7. corresponding clause should to contain column list (I am looking to
ANSI/SQL 99) - you are using expr_list, what has not sense and probably it
has impact on all implementation.
8. typo orderCorrespondingLsit(List *targetlist)
9. I miss more tests for CORRESPONDING BY
10. if I understand to this feature, this query should to work

postgres=# SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY (c,b) SELECT 4 a, 5 b, 6 c, 8 d;
ERROR: each UNION query must have the same number of columns
LINE 1: ...1 a, 2 b, 3 c UNION CORRESPONDING BY (c,b) SELECT 4 a, 5 b, ...

postgres=# create table t1(a int, b int, c int);
CREATE TABLE
Time: 63,260 ms
postgres=# create table t2(a int, b int);
CREATE TABLE
Time: 57,120 ms
postgres=# select * from t1 union corresponding select * from t2;
ERROR: each UNION query must have the same number of columns
LINE 1: select * from t1 union corresponding select * from t2;

If it is your first patch to Postgres, then it is perfect work!

The @7 is probably most significant - I dislike a expression list there.
name_list should be better there.

Regards

Pavel

Attachments:

corresponding_clause_v2.patchapplication/octet-stream; name=corresponding_clause_v2.patchDownload
diff --git a/doc/src/sgml/queries.sgml b/doc/src/sgml/queries.sgml
index 30792f4..c3cdee5 100644
--- a/doc/src/sgml/queries.sgml
+++ b/doc/src/sgml/queries.sgml
@@ -1601,6 +1601,9 @@ SELECT DISTINCT ON (<replaceable>expression</replaceable> <optional>, <replaceab
    <primary>EXCEPT</primary>
   </indexterm>
   <indexterm zone="queries-union">
+   <primary>CORRESPONDING</primary>
+  </indexterm>
+  <indexterm zone="queries-union">
    <primary>set union</primary>
   </indexterm>
   <indexterm zone="queries-union">
@@ -1617,9 +1620,9 @@ SELECT DISTINCT ON (<replaceable>expression</replaceable> <optional>, <replaceab
    The results of two queries can be combined using the set operations
    union, intersection, and difference.  The syntax is
 <synopsis>
-<replaceable>query1</replaceable> UNION <optional>ALL</optional> <replaceable>query2</replaceable>
-<replaceable>query1</replaceable> INTERSECT <optional>ALL</optional> <replaceable>query2</replaceable>
-<replaceable>query1</replaceable> EXCEPT <optional>ALL</optional> <replaceable>query2</replaceable>
+<replaceable>query1</replaceable> UNION <optional>ALL</optional> <optional>CORRESPONDING</optional> <optional>BY</optional> <replaceable>query2</replaceable>
+<replaceable>query1</replaceable> INTERSECT <optional>ALL</optional> <optional>CORRESPONDING</optional> <optional>BY</optional> <replaceable>query2</replaceable>
+<replaceable>query1</replaceable> EXCEPT <optional>ALL</optional> <optional>CORRESPONDING</optional> <optional>BY</optional> <replaceable>query2</replaceable>
 </synopsis>
    <replaceable>query1</replaceable> and
    <replaceable>query2</replaceable> are queries that can use any of
@@ -1659,11 +1662,22 @@ SELECT DISTINCT ON (<replaceable>expression</replaceable> <optional>, <replaceab
   </para>
 
   <para>
-   In order to calculate the union, intersection, or difference of two
-   queries, the two queries must be <quote>union compatible</quote>,
-   which means that they return the same number of columns and
-   the corresponding columns have compatible data types, as
-   described in <xref linkend="typeconv-union-case">.
+   <literal>EXCEPT</> returns all rows that are in the result of
+   <replaceable>query1</replaceable> but not in the result of
+   <replaceable>query2</replaceable>.  (This is sometimes called the
+   <firstterm>difference</> between two queries.)  Again, duplicates
+   are eliminated unless <literal>EXCEPT ALL</> is used.
+  </para>
+
+  <para>
+   <literal>CORRESPONDING</> returns all columns that are in both 
+   <replaceable>query1</> and <replaceable>query2</> with the same name.
+  </para>
+
+  <para>
+   <literal>CORRESPONDING BY</> returns all columns in the column list 
+   that are also in both <replaceable>query1</> and 
+   <replaceable>query2</> with the same name.
   </para>
  </sect1>
 
diff --git a/doc/src/sgml/sql.sgml b/doc/src/sgml/sql.sgml
index 57396d7..f98c22e 100644
--- a/doc/src/sgml/sql.sgml
+++ b/doc/src/sgml/sql.sgml
@@ -859,7 +859,7 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="PARAMETER">expression</replac
     [ WHERE <replaceable class="PARAMETER">condition</replaceable> ]
     [ GROUP BY <replaceable class="PARAMETER">expression</replaceable> [, ...] ]
     [ HAVING <replaceable class="PARAMETER">condition</replaceable> [, ...] ]
-    [ { UNION | INTERSECT | EXCEPT } [ ALL ] <replaceable class="PARAMETER">select</replaceable> ]
+    [ { UNION | INTERSECT | EXCEPT } [ ALL ] [ CORRESPONDING [ BY ( <replaceable class="PARAMETER">expression</replaceable> ) ] ] <replaceable class="PARAMETER">select</replaceable> ]
     [ ORDER BY <replaceable class="parameter">expression</replaceable> [ ASC | DESC | USING <replaceable class="parameter">operator</replaceable> ] [ NULLS { FIRST | LAST } ] [, ...] ]
     [ LIMIT { <replaceable class="PARAMETER">count</replaceable> | ALL } ]
     [ OFFSET <replaceable class="PARAMETER">start</replaceable> ]
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 30d733e..30874e0 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2870,6 +2870,7 @@ _copySelectStmt(const SelectStmt *from)
 	COPY_NODE_FIELD(withClause);
 	COPY_SCALAR_FIELD(op);
 	COPY_SCALAR_FIELD(all);
+	COPY_NODE_FIELD(correspondingClause);
 	COPY_NODE_FIELD(larg);
 	COPY_NODE_FIELD(rarg);
 
@@ -2885,6 +2886,7 @@ _copySetOperationStmt(const SetOperationStmt *from)
 	COPY_SCALAR_FIELD(all);
 	COPY_NODE_FIELD(larg);
 	COPY_NODE_FIELD(rarg);
+	COPY_NODE_FIELD(correspondingColumns);
 	COPY_NODE_FIELD(colTypes);
 	COPY_NODE_FIELD(colTypmods);
 	COPY_NODE_FIELD(colCollations);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 55c73b7..f375255 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1020,6 +1020,7 @@ _equalSelectStmt(const SelectStmt *a, const SelectStmt *b)
 	COMPARE_NODE_FIELD(withClause);
 	COMPARE_SCALAR_FIELD(op);
 	COMPARE_SCALAR_FIELD(all);
+	COMPARE_NODE_FIELD(correspondingClause);
 	COMPARE_NODE_FIELD(larg);
 	COMPARE_NODE_FIELD(rarg);
 
@@ -1033,6 +1034,7 @@ _equalSetOperationStmt(const SetOperationStmt *a, const SetOperationStmt *b)
 	COMPARE_SCALAR_FIELD(all);
 	COMPARE_NODE_FIELD(larg);
 	COMPARE_NODE_FIELD(rarg);
+	COMPARE_NODE_FIELD(correspondingColumns);
 	COMPARE_NODE_FIELD(colTypes);
 	COMPARE_NODE_FIELD(colTypmods);
 	COMPARE_NODE_FIELD(colCollations);
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index c2f03b7..ca39c1c 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -3411,6 +3411,8 @@ raw_expression_tree_walker(Node *node,
 					return true;
 				if (walker(stmt->lockingClause, context))
 					return true;
+				if (walker(stmt->correspondingClause, context))
+					return true;
 				if (walker(stmt->withClause, context))
 					return true;
 				if (walker(stmt->larg, context))
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 1560ac3..6e96018 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2526,6 +2526,7 @@ _outSelectStmt(StringInfo str, const SelectStmt *node)
 	WRITE_NODE_FIELD(withClause);
 	WRITE_ENUM_FIELD(op, SetOperation);
 	WRITE_BOOL_FIELD(all);
+	WRITE_NODE_FIELD(correspondingClause);
 	WRITE_NODE_FIELD(larg);
 	WRITE_NODE_FIELD(rarg);
 }
@@ -2833,6 +2834,7 @@ _outSetOperationStmt(StringInfo str, const SetOperationStmt *node)
 	WRITE_BOOL_FIELD(all);
 	WRITE_NODE_FIELD(larg);
 	WRITE_NODE_FIELD(rarg);
+	WRITE_NODE_FIELD(correspondingColumns);
 	WRITE_NODE_FIELD(colTypes);
 	WRITE_NODE_FIELD(colTypmods);
 	WRITE_NODE_FIELD(colCollations);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index dcfa6ee..94b724b 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -418,6 +418,7 @@ _readSetOperationStmt(void)
 	READ_BOOL_FIELD(all);
 	READ_NODE_FIELD(larg);
 	READ_NODE_FIELD(rarg);
+	READ_NODE_FIELD(correspondingColumns);
 	READ_NODE_FIELD(colTypes);
 	READ_NODE_FIELD(colTypmods);
 	READ_NODE_FIELD(colCollations);
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 06e843d..b183719 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -91,7 +91,8 @@ static List *generate_setop_tlist(List *colTypes, List *colCollations,
 					 Index varno,
 					 bool hack_constants,
 					 List *input_tlist,
-					 List *refnames_tlist);
+					 List *refnames_tlist,
+					 bool no_corresponding);
 static List *generate_append_tlist(List *colTypes, List *colCollations,
 					  bool flag,
 					  List *input_tlists,
@@ -110,6 +111,7 @@ static Node *adjust_appendrel_attrs_mutator(Node *node,
 static Relids adjust_relid_set(Relids relids, Index oldrelid, Index newrelid);
 static List *adjust_inherited_tlist(List *tlist,
 					   AppendRelInfo *context);
+static List *make_corresponding_target(List *corresponding_list, List *subroot_list);
 
 
 /*
@@ -187,6 +189,24 @@ plan_set_operations(PlannerInfo *root)
 									   leftmostQuery->targetList,
 									   &top_tlist);
 	}
+	/*
+	 * If corresponding column specified, we take column names from it.
+	 */
+	else if (topop->correspondingColumns != NIL )
+	{
+		/*
+		 * Recurse on setOperations tree to generate paths for set ops. The
+		 * final output path should have just the column types shown as the
+		 * output from the top-level node, plus possibly resjunk working
+		 * columns (we can rely on upper-level nodes to deal with that).
+		 */
+		path = recurse_set_operations((Node *) topop, root,
+									  topop->colTypes, topop->colCollations,
+									  true, -1,
+									  topop->correspondingColumns,
+									  &top_tlist,
+									  NULL);
+	}
 	else
 	{
 		/*
@@ -252,6 +272,8 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 					   List **pTargetList,
 					   double *pNumGroups)
 {
+	SetOperationStmt *topop = (SetOperationStmt *) root->parse->setOperations;
+
 	if (IsA(setOp, RangeTblRef))
 	{
 		RangeTblRef *rtr = (RangeTblRef *) setOp;
@@ -316,23 +338,45 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 		path = (Path *) create_subqueryscan_path(root, rel, subpath,
 												 NIL, NULL);
 
-		/*
-		 * Figure out the appropriate target list, and update the
-		 * SubqueryScanPath with the PathTarget form of that.
-		 */
-		tlist = generate_setop_tlist(colTypes, colCollations,
+		if (topop->correspondingColumns != NIL )
+		{
+			List *correspondingTarget;
+			/* make target list that only contains corresponding column from sub-queries list ito use it for projection */
+			correspondingTarget = make_corresponding_target(topop->correspondingColumns,
+					subroot->processed_tlist);
+			/*
+			 * Figure out the appropriate target list, and update the
+			 * SubqueryScanPath with the PathTarget form of that.
+			 */
+			tlist = generate_setop_tlist(colTypes, colCollations, flag,
+					rtr->rtindex, true, correspondingTarget, refnames_tlist, false);
+
+			path = apply_projection_to_path(root, rel, path,
+					create_pathtarget(root, tlist));
+
+			/* Return the fully-fledged tlist to caller, too */
+			*pTargetList = tlist;
+
+		}
+		else
+		{
+			/*
+			* Figure out the appropriate target list, and update the
+			* SubqueryScanPath with the PathTarget form of that.
+			*/
+			tlist = generate_setop_tlist(colTypes, colCollations,
 									 flag,
 									 rtr->rtindex,
 									 true,
 									 subroot->processed_tlist,
-									 refnames_tlist);
+									 refnames_tlist, true);
 
-		path = apply_projection_to_path(root, rel, path,
+			path = apply_projection_to_path(root, rel, path,
 										create_pathtarget(root, tlist));
 
-		/* Return the fully-fledged tlist to caller, too */
-		*pTargetList = tlist;
-
+			/* Return the fully-fledged tlist to caller, too */
+			*pTargetList = tlist;
+		}
 		/*
 		 * Estimate number of groups if caller wants it.  If the subquery used
 		 * grouping or aggregation, its output is probably mostly unique
@@ -392,7 +436,7 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 												0,
 												false,
 												*pTargetList,
-												refnames_tlist);
+												refnames_tlist, true);
 			path = apply_projection_to_path(root,
 											path->parent,
 											path,
@@ -1004,7 +1048,8 @@ generate_setop_tlist(List *colTypes, List *colCollations,
 					 Index varno,
 					 bool hack_constants,
 					 List *input_tlist,
-					 List *refnames_tlist)
+					 List *refnames_tlist,
+					 bool no_corresponding)
 {
 	List	   *tlist = NIL;
 	int			resno = 1;
@@ -1025,9 +1070,11 @@ generate_setop_tlist(List *colTypes, List *colCollations,
 		TargetEntry *reftle = (TargetEntry *) lfirst(rtlc);
 
 		rtlc = lnext(rtlc);
-
-		Assert(inputtle->resno == resno);
-		Assert(reftle->resno == resno);
+		if (no_corresponding)
+		{
+			Assert(inputtle->resno == resno);
+			Assert(reftle->resno == resno);
+		}
 		Assert(!inputtle->resjunk);
 		Assert(!reftle->resjunk);
 
@@ -2112,3 +2159,75 @@ adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node,
 	/* Now translate for this child */
 	return adjust_appendrel_attrs(root, node, appinfo);
 }
+
+/*
+ * generate target list from left target list with the order
+ * of right target list
+ */
+static List *make_corresponding_target(List *corresponding_list, List *subroot_list)
+{
+	Index internal = 0;
+	ListCell *ltl;
+	ListCell *rtl;
+	int size;
+	int i;
+	List *matchingColumns = NIL;
+	TargetEntry *simple_te_array;
+
+	size = list_length(corresponding_list) + 1;
+
+	/* Use array to find the order of corresponding columen */
+	simple_te_array = (TargetEntry *) palloc0(size * sizeof(TargetEntry));
+	foreach(ltl, corresponding_list)
+	{
+
+		foreach(rtl, subroot_list)
+		{
+
+			TargetEntry *ltle = (TargetEntry *) lfirst(ltl);
+			TargetEntry *rtle = (TargetEntry *) lfirst(rtl);
+
+			elog(DEBUG4, "%s", ltle->resname);
+
+			/* Names of the columns must be resolved before calling this method. */
+			Assert(ltle->resname != NULL);
+			Assert(rtle->resname != NULL);
+
+			/* If column names are the same, add it to array. */
+			if (strcmp(ltle->resname, rtle->resname) == 0)
+			{
+
+				simple_te_array[internal].xpr = rtle->xpr;
+				simple_te_array[internal].expr = rtle->expr;
+				simple_te_array[internal].resno = rtle->resno;
+				simple_te_array[internal].resname = rtle->resname;
+				simple_te_array[internal].ressortgroupref =
+						rtle->ressortgroupref;
+				simple_te_array[internal].resorigtbl = rtle->resorigtbl;
+				simple_te_array[internal].resorigcol = rtle->resorigcol;
+				simple_te_array[internal].resjunk = rtle->resjunk;
+				internal++;
+				continue;
+			}
+		}
+	}
+	/* traverse the array and make targetlist */
+	for (i = 0; i < internal; i++)
+	{
+
+		TargetEntry *tle = makeNode(TargetEntry);
+
+		tle->xpr = simple_te_array[i].xpr;
+		tle->expr = simple_te_array[i].expr;
+		tle->resno = simple_te_array[i].resno;
+		tle->resname = simple_te_array[i].resname;
+		tle->ressortgroupref = simple_te_array[i].ressortgroupref;
+		tle->resorigtbl = simple_te_array[i].resorigtbl;
+		tle->resorigcol = simple_te_array[i].resorigcol;
+		tle->resjunk = simple_te_array[i].resjunk;
+
+		matchingColumns = lappend(matchingColumns, tle);
+
+	}
+	return matchingColumns;
+}
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 0f7659b..bc19256 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -76,6 +76,10 @@ static Query *transformCreateTableAsStmt(ParseState *pstate,
 						   CreateTableAsStmt *stmt);
 static void transformLockingClause(ParseState *pstate, Query *qry,
 					   LockingClause *lc, bool pushedDown);
+static List *determineMatchingColumns(List *ltargetlist, List *rtargetlist);
+static void *makeUnionDatatype(List *ltargetlist, List *rtargetlist,
+		SetOperationStmt *op, List **targetlist, ParseState *parentParseState,
+		const char *context);
 #ifdef RAW_EXPRESSION_COVERAGE_TEST
 static bool test_raw_expression_coverage(Node *node, void *context);
 #endif
@@ -1664,7 +1668,13 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->targetList = NIL;
 	targetvars = NIL;
 	targetnames = NIL;
-	left_tlist = list_head(leftmostQuery->targetList);
+	/* for corresponding clause limits top-level query targetlist to those
+	 * corresponding column list only
+	 */
+	if (sostmt->correspondingColumns != NIL )
+		left_tlist = list_head(sostmt->correspondingColumns);
+	else
+		left_tlist = list_head(leftmostQuery->targetList);
 
 	forthree(lct, sostmt->colTypes,
 			 lcm, sostmt->colTypmods,
@@ -1924,8 +1934,6 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 		SetOperationStmt *op = makeNode(SetOperationStmt);
 		List	   *ltargetlist;
 		List	   *rtargetlist;
-		ListCell   *ltl;
-		ListCell   *rtl;
 		const char *context;
 
 		context = (stmt->op == SETOP_UNION ? "UNION" :
@@ -1935,6 +1943,190 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 		op->op = stmt->op;
 		op->all = stmt->all;
 
+		/* If CORRESPONDING is specified, syntax and column name validities checked,
+		 * column filtering is done by a subquery later on.
+		 */
+		if (stmt->correspondingClause == NIL )
+		{
+			/* No CORRESPONDING clause, no operation needed for column filtering */
+			op->correspondingColumns = stmt->correspondingClause;
+		}
+		else if (linitial(stmt->correspondingClause) == NULL )
+		{
+			/* CORRESPONDING clause, find matching column names from both tables.
+			 * If there are none then it is a syntax error.
+			 */
+			Query *largQuery;
+			Query *rargQuery;
+			List *matchingColumns;
+			List *rightCorrespondingColumns;
+
+			/* Analyze left query to resolve column names. */
+			largQuery = parse_sub_analyze((Node *) stmt->larg, pstate, NULL, false, false );
+
+			/* Analyze right query to resolve column names. */
+			rargQuery = parse_sub_analyze((Node *) stmt->rarg, pstate, NULL, false, false );
+
+			/* Find matching columns from both queries. */
+			matchingColumns = determineMatchingColumns(largQuery->targetList,rargQuery->targetList);
+			/*
+			 * there may be out-of-order resnos in corresponding target list
+			 */
+			op->correspondingColumns = orderCorrespondingList(matchingColumns);
+
+			/* If matchingColumns is empty, there is an error.
+			 * At least one column in the select lists must have the same name.
+			 */
+			if (list_length(matchingColumns) == 0)
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+								errmsg("%s queries with a CORRESPONDING clause must have at least one column with the same name", context),
+								parser_errposition(pstate, exprLocation((Node *) linitial(largQuery->targetList)))));
+			}
+
+			/* matchingColumns contain target list as it appear in left query targetList
+			 * we need matching column as it appear in right query targetlist inorder
+			 * to make output column type for corresponding columns
+			 */
+			rightCorrespondingColumns = determineMatchingColumns(rargQuery->targetList, largQuery->targetList);
+
+			/* make union'd datatype of output column
+			 */
+			makeUnionDatatype(matchingColumns, rightCorrespondingColumns, op, targetlist, pstate,
+					context);
+		}
+		else
+		{
+			/*
+			 * CORRESPONDING BY clause, find matching column names from both tables
+			 * and intersect them with BY(...) column list. If there are none
+			 * then it is a syntax error.
+			 */
+
+			Query *largQuery;
+			Query *rargQuery;
+			List *matchingColumns;
+			List *matchingColumnsFiltered;
+			List *rightCorrespondingColumns;
+			ListCell *corrtl;
+			ListCell *mctl;
+
+			/* Analyze left query to resolve column names. */
+			largQuery = parse_sub_analyze((Node *) stmt->larg, pstate, NULL,false, false );
+
+			/* Analyze right query to resolve column names. */
+			rargQuery = parse_sub_analyze((Node *) stmt->rarg, pstate, NULL, false, false );
+
+			/*
+			 * Find matching columns from both queries.
+			 * In CORRESPONDING BY, column names will be removed from
+			 * matchingColumns if they are not in the BY clause.
+			 * All columns in the BY clause must be in matchingColumns,
+			 * otherwise raise syntax error in BY clause.
+			 */
+
+			matchingColumns = determineMatchingColumns(largQuery->targetList,rargQuery->targetList);
+
+			/*
+			 * Every column name in correspondingClause must be in matchingColumns,
+			 * otherwise it is a syntax error.
+			 */foreach(corrtl, stmt->correspondingClause)
+			{
+				Value* corrtle = lfirst(corrtl);
+
+				/* Get column name from correspondingClause. */
+				char *name = strVal(corrtle);
+				bool hasMatch = false;
+
+				foreach(mctl, matchingColumns)
+				{
+					TargetEntry *mctle = (TargetEntry *) lfirst(mctl);
+
+					Assert(mctle->resname != NULL);
+					Assert(name != NULL);
+
+					/* Compare correspondingClause column name with matchingColumns column names. */
+					if (strcmp(mctle->resname, name) == 0)
+					{
+						/* we have a match. */
+						hasMatch = true;
+						break;
+					}
+				}
+
+				if (!hasMatch)
+				{
+					/* CORRESPONDING BY clause contains a column name that is not in both tables. */
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+									errmsg("%s queries with a CORRESPONDING BY clause must only contain column names from both tables.", context),
+									parser_errposition(pstate, exprLocation((Node *) linitial(largQuery->targetList)))));
+				}
+			}
+
+			/* To preserve column ordering from correspondingClause and to remove
+			 * columns from matchingColumns if they are not in correspondingClause,
+			 * create a new list and finalize our column list for the
+			 * CORRESPONDING BY clause.
+			 */
+
+			matchingColumnsFiltered = NIL;
+
+			/* For each column in CORRESPONDING BY column list, check
+			 * column existence in matchingColumns.
+			 */foreach(corrtl, stmt->correspondingClause)
+			{
+				Value* corrtle = lfirst(corrtl);
+
+				/* Get column name from correspondingClause. */
+				char *name = strVal(corrtle);
+
+				foreach(mctl, matchingColumns)
+				{
+					TargetEntry *mctle = (TargetEntry *) lfirst(mctl);
+
+					Assert(mctle->resname != NULL);
+					Assert(name != NULL);
+
+					if (strcmp(mctle->resname, name) == 0)
+					{
+						/* we have a match.*/
+						matchingColumnsFiltered = lappend(
+								matchingColumnsFiltered, mctle);
+						break;
+					}
+				}
+			}
+
+			/*
+			 * If matchingColumnsFiltered is empty, there is a semantic error.
+			 * At least one column in the select lists must have the same name.
+			 */
+			if (list_length(matchingColumnsFiltered) == 0)
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+								errmsg("%s queries with CORRESPONDING BY clause must have at least one column name in BY clause and in both of the queries.", context),
+								parser_errposition(pstate, exprLocation((Node *) linitial(largQuery->targetList)))));
+			}
+			/*
+			 * there may be out-of-order resnos in corresponding target list
+			 */
+			op->correspondingColumns = orderCorrespondingList(matchingColumnsFiltered);
+
+			/*
+			 * matchingColumns contain target list as it appear in left query targetList
+			 * we need matching column as it appear in right query targetlist inorder to
+			 * make output column type for corresponding columns
+			 */
+			rightCorrespondingColumns = determineMatchingColumns(rargQuery->targetList,matchingColumnsFiltered);
+			/*
+			 * make union'd datatype of output columns
+			 */
+			makeUnionDatatype(matchingColumnsFiltered, rightCorrespondingColumns, op, targetlist, pstate,
+					context);
+		}
 		/*
 		 * Recursively transform the left child node.
 		 */
@@ -1971,166 +2163,208 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 						context),
 					 parser_errposition(pstate,
 										exprLocation((Node *) rtargetlist))));
+		if (op->correspondingColumns == NIL)
+			makeUnionDatatype(ltargetlist, rtargetlist, op, targetlist, pstate,
+					context);
 
-		if (targetlist)
-			*targetlist = NIL;
-		op->colTypes = NIL;
-		op->colTypmods = NIL;
-		op->colCollations = NIL;
-		op->groupClauses = NIL;
-		forboth(ltl, ltargetlist, rtl, rtargetlist)
+		return (Node *) op;
+	}
+}
+
+/*
+ * Processes targetlists of two queries for column equivalence to use
+ * with UNION/INTERSECT/EXCEPT CORRESPONDING.
+ */
+static List *
+determineMatchingColumns(List *ltargetlist, List *rtargetlist)
+{
+	List *matchingColumns = NIL;
+	ListCell *ltl;
+	ListCell *rtl;
+
+	foreach(ltl, ltargetlist)
+	{
+		foreach(rtl, rtargetlist)
 		{
 			TargetEntry *ltle = (TargetEntry *) lfirst(ltl);
 			TargetEntry *rtle = (TargetEntry *) lfirst(rtl);
-			Node	   *lcolnode = (Node *) ltle->expr;
-			Node	   *rcolnode = (Node *) rtle->expr;
-			Oid			lcoltype = exprType(lcolnode);
-			Oid			rcoltype = exprType(rcolnode);
-			int32		lcoltypmod = exprTypmod(lcolnode);
-			int32		rcoltypmod = exprTypmod(rcolnode);
-			Node	   *bestexpr;
-			int			bestlocation;
-			Oid			rescoltype;
-			int32		rescoltypmod;
-			Oid			rescolcoll;
-
-			/* select common type, same as CASE et al */
-			rescoltype = select_common_type(pstate,
-											list_make2(lcolnode, rcolnode),
-											context,
-											&bestexpr);
-			bestlocation = exprLocation(bestexpr);
-			/* if same type and same typmod, use typmod; else default */
-			if (lcoltype == rcoltype && lcoltypmod == rcoltypmod)
-				rescoltypmod = lcoltypmod;
-			else
-				rescoltypmod = -1;
-
-			/*
-			 * Verify the coercions are actually possible.  If not, we'd fail
-			 * later anyway, but we want to fail now while we have sufficient
-			 * context to produce an error cursor position.
-			 *
-			 * For all non-UNKNOWN-type cases, we verify coercibility but we
-			 * don't modify the child's expression, for fear of changing the
-			 * child query's semantics.
-			 *
-			 * If a child expression is an UNKNOWN-type Const or Param, we
-			 * want to replace it with the coerced expression.  This can only
-			 * happen when the child is a leaf set-op node.  It's safe to
-			 * replace the expression because if the child query's semantics
-			 * depended on the type of this output column, it'd have already
-			 * coerced the UNKNOWN to something else.  We want to do this
-			 * because (a) we want to verify that a Const is valid for the
-			 * target type, or resolve the actual type of an UNKNOWN Param,
-			 * and (b) we want to avoid unnecessary discrepancies between the
-			 * output type of the child query and the resolved target type.
-			 * Such a discrepancy would disable optimization in the planner.
-			 *
-			 * If it's some other UNKNOWN-type node, eg a Var, we do nothing
-			 * (knowing that coerce_to_common_type would fail).  The planner
-			 * is sometimes able to fold an UNKNOWN Var to a constant before
-			 * it has to coerce the type, so failing now would just break
-			 * cases that might work.
-			 */
-			if (lcoltype != UNKNOWNOID)
-				lcolnode = coerce_to_common_type(pstate, lcolnode,
-												 rescoltype, context);
-			else if (IsA(lcolnode, Const) ||
-					 IsA(lcolnode, Param))
-			{
-				lcolnode = coerce_to_common_type(pstate, lcolnode,
-												 rescoltype, context);
-				ltle->expr = (Expr *) lcolnode;
-			}
 
-			if (rcoltype != UNKNOWNOID)
-				rcolnode = coerce_to_common_type(pstate, rcolnode,
-												 rescoltype, context);
-			else if (IsA(rcolnode, Const) ||
-					 IsA(rcolnode, Param))
-			{
-				rcolnode = coerce_to_common_type(pstate, rcolnode,
-												 rescoltype, context);
-				rtle->expr = (Expr *) rcolnode;
-			}
+			elog(DEBUG4, "%s", ltle->resname);
 
-			/*
-			 * Select common collation.  A common collation is required for
-			 * all set operators except UNION ALL; see SQL:2008 7.13 <query
-			 * expression> Syntax Rule 15c.  (If we fail to identify a common
-			 * collation for a UNION ALL column, the curCollations element
-			 * will be set to InvalidOid, which may result in a runtime error
-			 * if something at a higher query level wants to use the column's
-			 * collation.)
-			 */
-			rescolcoll = select_common_collation(pstate,
-											  list_make2(lcolnode, rcolnode),
-										 (op->op == SETOP_UNION && op->all));
-
-			/* emit results */
-			op->colTypes = lappend_oid(op->colTypes, rescoltype);
-			op->colTypmods = lappend_int(op->colTypmods, rescoltypmod);
-			op->colCollations = lappend_oid(op->colCollations, rescolcoll);
-
-			/*
-			 * For all cases except UNION ALL, identify the grouping operators
-			 * (and, if available, sorting operators) that will be used to
-			 * eliminate duplicates.
-			 */
-			if (op->op != SETOP_UNION || !op->all)
-			{
-				SortGroupClause *grpcl = makeNode(SortGroupClause);
-				Oid			sortop;
-				Oid			eqop;
-				bool		hashable;
-				ParseCallbackState pcbstate;
-
-				setup_parser_errposition_callback(&pcbstate, pstate,
-												  bestlocation);
-
-				/* determine the eqop and optional sortop */
-				get_sort_group_operators(rescoltype,
-										 false, true, false,
-										 &sortop, &eqop, NULL,
-										 &hashable);
-
-				cancel_parser_errposition_callback(&pcbstate);
-
-				/* we don't have a tlist yet, so can't assign sortgrouprefs */
-				grpcl->tleSortGroupRef = 0;
-				grpcl->eqop = eqop;
-				grpcl->sortop = sortop;
-				grpcl->nulls_first = false;		/* OK with or without sortop */
-				grpcl->hashable = hashable;
-
-				op->groupClauses = lappend(op->groupClauses, grpcl);
-			}
+			/* Names of the columns must be resolved before calling this method. */
+			Assert(ltle->resname != NULL);
+			Assert(rtle->resname != NULL);
 
-			/*
-			 * Construct a dummy tlist entry to return.  We use a SetToDefault
-			 * node for the expression, since it carries exactly the fields
-			 * needed, but any other expression node type would do as well.
-			 */
-			if (targetlist)
+			/* If column names are the same, append it to the result. */
+			if (strcmp(ltle->resname, rtle->resname) == 0)
 			{
-				SetToDefault *rescolnode = makeNode(SetToDefault);
-				TargetEntry *restle;
-
-				rescolnode->typeId = rescoltype;
-				rescolnode->typeMod = rescoltypmod;
-				rescolnode->collation = rescolcoll;
-				rescolnode->location = bestlocation;
-				restle = makeTargetEntry((Expr *) rescolnode,
-										 0,		/* no need to set resno */
-										 NULL,
-										 false);
-				*targetlist = lappend(*targetlist, restle);
+				matchingColumns = lappend(matchingColumns, ltle);
+				continue;
 			}
 		}
+	}
+
+	return matchingColumns;
+}
+
+/*
+ * process right and left target list to set up union'd datatype
+ */
+static void *
+makeUnionDatatype(List *ltargetlist, List *rtargetlist, SetOperationStmt *op,
+		List **targetlist, ParseState *pstate, const char *context)
+{
+	ListCell *ltl;
+	ListCell *rtl;
+	if (targetlist)
+		*targetlist = NIL;
+	op->colTypes = NIL;
+	op->colTypmods = NIL;
+	op->colCollations = NIL;
+	op->groupClauses = NIL;
+
+	forboth(ltl, ltargetlist, rtl, rtargetlist)
+	{
+		TargetEntry *ltle = (TargetEntry *) lfirst(ltl);
+		TargetEntry *rtle = (TargetEntry *) lfirst(rtl);
+		Node *lcolnode = (Node *) ltle->expr;
+		Node *rcolnode = (Node *) rtle->expr;
+		Oid lcoltype = exprType(lcolnode);
+		Oid rcoltype = exprType(rcolnode);
+		int32 lcoltypmod = exprTypmod(lcolnode);
+		int32 rcoltypmod = exprTypmod(rcolnode);
+		Node *bestexpr;
+		int bestlocation;
+		Oid rescoltype;
+		int32 rescoltypmod;
+		Oid rescolcoll;
+
+		/* select common type, same as CASE et al */
+		rescoltype = select_common_type(pstate, list_make2(lcolnode, rcolnode),
+				context, &bestexpr);
+		bestlocation = exprLocation(bestexpr);
+		/* if same type and same typmod, use typmod; else default */
+		if (lcoltype == rcoltype && lcoltypmod == rcoltypmod)
+			rescoltypmod = lcoltypmod;
+		else
+			rescoltypmod = -1;
+
+		/*
+		 * Verify the coercions are actually possible.  If not, we'd fail
+		 * later anyway, but we want to fail now while we have sufficient
+		 * context to produce an error cursor position.
+		 *
+		 * For all non-UNKNOWN-type cases, we verify coercibility but we
+		 * don't modify the child's expression, for fear of changing the
+		 * child query's semantics.
+		 *
+		 * If a child expression is an UNKNOWN-type Const or Param, we
+		 * want to replace it with the coerced expression.  This can only
+		 * happen when the child is a leaf set-op node.  It's safe to
+		 * replace the expression because if the child query's semantics
+		 * depended on the type of this output column, it'd have already
+		 * coerced the UNKNOWN to something else.  We want to do this
+		 * because (a) we want to verify that a Const is valid for the
+		 * target type, or resolve the actual type of an UNKNOWN Param,
+		 * and (b) we want to avoid unnecessary discrepancies between the
+		 * output type of the child query and the resolved target type.
+		 * Such a discrepancy would disable optimization in the planner.
+		 *
+		 * If it's some other UNKNOWN-type node, eg a Var, we do nothing
+		 * (knowing that coerce_to_common_type would fail).  The planner
+		 * is sometimes able to fold an UNKNOWN Var to a constant before
+		 * it has to coerce the type, so failing now would just break
+		 * cases that might work.
+		 */
+		if (lcoltype != UNKNOWNOID)
+			lcolnode = coerce_to_common_type(pstate, lcolnode, rescoltype,
+					context);
+		else if (IsA(lcolnode, Const) || IsA(lcolnode, Param))
+		{
+			lcolnode = coerce_to_common_type(pstate, lcolnode, rescoltype,
+					context);
+			ltle->expr = (Expr *) lcolnode;
+		}
 
-		return (Node *) op;
+		if (rcoltype != UNKNOWNOID)
+			rcolnode = coerce_to_common_type(pstate, rcolnode, rescoltype,
+					context);
+		else if (IsA(rcolnode, Const) || IsA(rcolnode, Param))
+		{
+			rcolnode = coerce_to_common_type(pstate, rcolnode, rescoltype,
+					context);
+			rtle->expr = (Expr *) rcolnode;
+		}
+
+		/*
+		 * Select common collation.  A common collation is required for
+		 * all set operators except UNION ALL; see SQL:2008 7.13 <query
+		 * expression> Syntax Rule 15c.  (If we fail to identify a common
+		 * collation for a UNION ALL column, the curCollations element
+		 * will be set to InvalidOid, which may result in a runtime error
+		 * if something at a higher query level wants to use the column's
+		 * collation.)
+		 */
+		rescolcoll = select_common_collation(pstate,
+				list_make2(lcolnode, rcolnode),
+				(op->op == SETOP_UNION && op->all));
+
+		/* emit results */
+		op->colTypes = lappend_oid(op->colTypes, rescoltype);
+		op->colTypmods = lappend_int(op->colTypmods, rescoltypmod);
+		op->colCollations = lappend_oid(op->colCollations, rescolcoll);
+
+		/*
+		 * For all cases except UNION ALL, identify the grouping operators
+		 * (and, if available, sorting operators) that will be used to
+		 * eliminate duplicates.
+		 */
+		if (op->op != SETOP_UNION || !op->all)
+		{
+			SortGroupClause *grpcl = makeNode(SortGroupClause);
+			Oid sortop;
+			Oid eqop;
+			bool hashable;
+			ParseCallbackState pcbstate;
+
+			setup_parser_errposition_callback(&pcbstate, pstate, bestlocation);
+
+			/* determine the eqop and optional sortop */
+			get_sort_group_operators(rescoltype, false, true, false, &sortop,
+					&eqop, NULL, &hashable);
+
+			cancel_parser_errposition_callback(&pcbstate);
+
+			/* we don't have a tlist yet, so can't assign sortgrouprefs */
+			grpcl->tleSortGroupRef = 0;
+			grpcl->eqop = eqop;
+			grpcl->sortop = sortop;
+			grpcl->nulls_first = false; /* OK with or without sortop */
+			grpcl->hashable = hashable;
+
+			op->groupClauses = lappend(op->groupClauses, grpcl);
+		}
+
+		/*
+		 * Construct a dummy tlist entry to return.  We use a SetToDefault
+		 * node for the expression, since it carries exactly the fields
+		 * needed, but any other expression node type would do as well.
+		 */
+		if (targetlist)
+		{
+			SetToDefault *rescolnode = makeNode(SetToDefault);
+			TargetEntry *restle;
+
+			rescolnode->typeId = rescoltype;
+			rescolnode->typeMod = rescoltypmod;
+			rescolnode->collation = rescolcoll;
+			rescolnode->location = bestlocation;
+			restle = makeTargetEntry((Expr *) rescolnode, 0, /* no need to set resno */
+			NULL, false );
+			*targetlist = lappend(*targetlist, restle);
+		}
 	}
+	return 0;
 }
 
 /*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 174773b..de76481 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -166,7 +166,7 @@ static void insertSelectOptions(SelectStmt *stmt,
 								Node *limitOffset, Node *limitCount,
 								WithClause *withClause,
 								core_yyscan_t yyscanner);
-static Node *makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg);
+static Node *makeSetOp(SetOperation op, bool all, List *correspondingClause, Node *larg, Node *rarg);
 static Node *doNegate(Node *n, int location);
 static void doNegateFloat(Value *v);
 static Node *makeAndExpr(Node *lexpr, Node *rexpr, int location);
@@ -394,7 +394,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				relation_expr_list dostmt_opt_list
 				transform_element_list transform_type_list
 				TriggerTransitions TriggerReferencing
-				publication_name_list
+				publication_name_list opt_corresponding_clause
 
 %type <list>	group_by_list
 %type <node>	group_by_item empty_grouping_set rollup_clause cube_clause
@@ -608,7 +608,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
 	CLUSTER COALESCE COLLATE COLLATION COLUMN COMMENT COMMENTS COMMIT
 	COMMITTED CONCURRENTLY CONFIGURATION CONFLICT CONNECTION CONSTRAINT
-	CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE
+	CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY CORRESPONDING COST CREATE
 	CROSS CSV CUBE CURRENT_P
 	CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA
 	CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
@@ -10808,20 +10808,26 @@ simple_select:
 					n->fromClause = list_make1($2);
 					$$ = (Node *)n;
 				}
-			| select_clause UNION all_or_distinct select_clause
+			| select_clause UNION all_or_distinct opt_corresponding_clause select_clause
 				{
-					$$ = makeSetOp(SETOP_UNION, $3, $1, $4);
+					$$ = makeSetOp(SETOP_UNION, $3, $4, $1, $5);
 				}
-			| select_clause INTERSECT all_or_distinct select_clause
+			| select_clause INTERSECT all_or_distinct opt_corresponding_clause select_clause
 				{
-					$$ = makeSetOp(SETOP_INTERSECT, $3, $1, $4);
+					$$ = makeSetOp(SETOP_INTERSECT, $3, $4, $1, $5);
 				}
-			| select_clause EXCEPT all_or_distinct select_clause
+			| select_clause EXCEPT all_or_distinct opt_corresponding_clause select_clause
 				{
-					$$ = makeSetOp(SETOP_EXCEPT, $3, $1, $4);
+					$$ = makeSetOp(SETOP_EXCEPT, $3, $4, $1, $5);
 				}
 		;
 
+opt_corresponding_clause:
+			CORRESPONDING BY '(' name_list ')'		{ $$ = $4; }
+			| CORRESPONDING							{ $$ = list_make1(NIL); }
+			| /*EMPTY*/								{ $$ = NIL; }
+			;
+
 /*
  * SQL standard WITH clause looks like:
  *
@@ -14315,6 +14321,7 @@ unreserved_keyword:
 			| CONTINUE_P
 			| CONVERSION_P
 			| COPY
+			| CORRESPONDING
 			| COST
 			| CSV
 			| CUBE
@@ -15145,7 +15152,7 @@ insertSelectOptions(SelectStmt *stmt,
 }
 
 static Node *
-makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg)
+makeSetOp(SetOperation op, bool all, List *correspondingClause, Node *larg, Node *rarg)
 {
 	SelectStmt *n = makeNode(SelectStmt);
 
@@ -15153,6 +15160,7 @@ makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg)
 	n->all = all;
 	n->larg = (SelectStmt *) larg;
 	n->rarg = (SelectStmt *) rarg;
+	n->correspondingClause = correspondingClause;
 	return (Node *) n;
 }
 
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 2576e31..291b66b 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1903,3 +1903,25 @@ FigureColnameInternal(Node *node, char **name)
 
 	return strength;
 }
+
+/*
+ * orderCorrespondingList()
+ * order target list resno .
+ */
+List *
+orderCorrespondingList(List *targetlist)
+{
+	List *p_target = NIL;
+	ListCell *o_target;
+	int pos = 1;
+
+	foreach(o_target, targetlist)
+	{
+		TargetEntry *tar = (TargetEntry *) lfirst(o_target);
+
+		p_target = lappend(p_target,
+				makeTargetEntry(tar->expr, (AttrNumber) pos++, tar->resname, false));
+	}
+
+	return p_target;
+}
diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
index 8feec0b..ac23b1b 100644
--- a/src/backend/parser/parse_type.c
+++ b/src/backend/parser/parse_type.c
@@ -736,7 +736,8 @@ typeStringToTypeName(const char *str)
 		stmt->limitCount != NULL ||
 		stmt->lockingClause != NIL ||
 		stmt->withClause != NULL ||
-		stmt->op != SETOP_NONE)
+		stmt->op != SETOP_NONE ||
+		stmt->correspondingClause != NULL)
 		goto fail;
 	if (list_length(stmt->targetList) != 1)
 		goto fail;
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 07a8436..1dfff8f 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1420,7 +1420,9 @@ typedef struct SelectStmt
 	 * These fields are used only in "leaf" SelectStmts.
 	 */
 	List	   *distinctClause; /* NULL, list of DISTINCT ON exprs, or
-								 * lcons(NIL,NIL) for all (SELECT DISTINCT) */
+								* lcons(NIL,NIL) for all (SELECT DISTINCT) */
+	List	   *correspondingClause; /* NULL, list of CORRESPONDING BY exprs, or */
+								/* lcons(NIL, NIL) for CORRESPONDING */
 	IntoClause *intoClause;		/* target for SELECT INTO */
 	List	   *targetList;		/* the target list (of ResTarget) */
 	List	   *fromClause;		/* the FROM clause */
@@ -1486,8 +1488,9 @@ typedef struct SetOperationStmt
 	bool		all;			/* ALL specified? */
 	Node	   *larg;			/* left child */
 	Node	   *rarg;			/* right child */
-	/* Eventually add fields for CORRESPONDING spec here */
-
+	/* CORRESPONDING clause fields */
+	List	   *correspondingColumns;	/* NIL: No corresponding, else: CORRESPONDING or CORRESPONDIN
+						* BY matching columns. Not the original clause. */
 	/* Fields derived during parse analysis: */
 	List	   *colTypes;		/* OID list of output column type OIDs */
 	List	   *colTypmods;		/* integer list of output column typmods */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 985d650..77e0396 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -96,6 +96,7 @@ PG_KEYWORD("content", CONTENT_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("continue", CONTINUE_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("conversion", CONVERSION_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("copy", COPY, UNRESERVED_KEYWORD)
+PG_KEYWORD("corresponding", CORRESPONDING, UNRESERVED_KEYWORD)
 PG_KEYWORD("cost", COST, UNRESERVED_KEYWORD)
 PG_KEYWORD("create", CREATE, RESERVED_KEYWORD)
 PG_KEYWORD("cross", CROSS, TYPE_FUNC_NAME_KEYWORD)
diff --git a/src/include/parser/parse_target.h b/src/include/parser/parse_target.h
index d06a235..32b9cfd 100644
--- a/src/include/parser/parse_target.h
+++ b/src/include/parser/parse_target.h
@@ -42,5 +42,6 @@ extern TupleDesc expandRecordVariable(ParseState *pstate, Var *var,
 					 int levelsup);
 extern char *FigureColname(Node *node);
 extern char *FigureIndexColname(Node *node);
+extern List *orderCorrespondingList(List *targetlist);
 
 #endif   /* PARSE_TARGET_H */
diff --git a/src/test/regress/expected/union.out b/src/test/regress/expected/union.out
index 4d697ba..3d60b23 100644
--- a/src/test/regress/expected/union.out
+++ b/src/test/regress/expected/union.out
@@ -59,6 +59,198 @@ SELECT 1.1 AS two UNION SELECT 2.2 ORDER BY 1;
  2.2
 (2 rows)
 
+SELECT 1 AS two UNION CORRESPONDING SELECT 2 two;
+ two 
+-----
+   1
+   2
+(2 rows)
+
+SELECT 1 AS one UNION CORRESPONDING SELECT 1 one;
+ one 
+-----
+   1
+(1 row)
+
+SELECT 1 AS two UNION ALL CORRESPONDING SELECT 2 two;
+ two 
+-----
+   1
+   2
+(2 rows)
+
+SELECT 1 AS two UNION ALL CORRESPONDING SELECT 1 two;
+ two 
+-----
+   1
+   1
+(2 rows)
+
+SELECT 1 AS two UNION CORRESPONDING SELECT 2 two UNION CORRESPONDING SELECT 2 two;
+ two 
+-----
+   2
+   1
+(2 rows)
+
+SELECT 1 AS three UNION CORRESPONDING SELECT 2 three UNION ALL CORRESPONDING SELECT 2 three;
+ three 
+-------
+     1
+     2
+     2
+(3 rows)
+
+SELECT 1 AS three UNION CORRESPONDING SELECT 2 three UNION CORRESPONDING SELECT 3 three;
+ three 
+-------
+     3
+     2
+     1
+(3 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+SELECT 3 c, 2 b, 1 a UNION CORRESPONDING SELECT 6 c, 4 a, 5 b;
+ c | b | a 
+---+---+---
+ 3 | 2 | 1
+ 6 | 5 | 4
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a) SELECT 4 a, 5 b, 6 c;
+ a 
+---
+ 1
+ 4
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b) SELECT 4 a, 5 b, 6 c;
+ b 
+---
+ 2
+ 5
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(c) SELECT 4 a, 5 b, 6 c;
+ c 
+---
+ 3
+ 6
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b) SELECT 4 a, 5 b, 6 c;
+ a | b 
+---+---
+ 1 | 2
+ 4 | 5
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c) SELECT 4 a, 5 b, 6 c;
+ b | c 
+---+---
+ 2 | 3
+ 5 | 6
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, c) SELECT 4 a, 5 b, 6 c;
+ a | c 
+---+---
+ 1 | 3
+ 4 | 6
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 4 a, 5 b, 6 c;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+-- CORRESPONDING column ordering, left clause's column ordering must be preserved.
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+SELECT 2 b, 1 a, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+ b | a | c 
+---+---+---
+ 2 | 1 | 3
+ 5 | 4 | 6
+(2 rows)
+
+SELECT 1 a, 3 c, 2 b UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+ a | c | b 
+---+---+---
+ 1 | 3 | 2
+ 4 | 6 | 5
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 5 b, 6 c, 4 a;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+SELECT 2 b, 1 a, 3 c UNION CORRESPONDING SELECT 5 b, 6 c, 4 a;
+ b | a | c 
+---+---+---
+ 2 | 1 | 3
+ 5 | 4 | 6
+(2 rows)
+
+-- CORRESPONDING BY column ordering, BY clause column ordering must be preserved.
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 4 a, 5 b, 6 c;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c, a) SELECT 4 a, 5 b, 6 c;
+ b | c | a 
+---+---+---
+ 2 | 3 | 1
+ 5 | 6 | 4
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(c, a, b) SELECT 4 a, 5 b, 6 c;
+ c | a | b 
+---+---+---
+ 3 | 1 | 2
+ 6 | 4 | 5
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 5 b, 6 c, 4 a;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+SELECT 2 b, 3 c, 1 a UNION CORRESPONDING BY(b, c, a) SELECT 4 a, 5 b, 6 c;
+ b | c | a 
+---+---+---
+ 2 | 3 | 1
+ 5 | 6 | 4
+(2 rows)
+
+SELECT 3 c, 2 b, 1 a UNION CORRESPONDING BY(c, a, b) SELECT 4 a, 5 b, 6 c;
+ c | a | b 
+---+---+---
+ 3 | 1 | 2
+ 6 | 4 | 5
+(2 rows)
+
 -- Mixed types
 SELECT 1.1 AS two UNION SELECT 2 ORDER BY 1;
  two 
@@ -258,6 +450,97 @@ ORDER BY 1;
  hi de ho neighbor
 (5 rows)
 
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+         five          
+-----------------------
+ -1.2345678901234e+200
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+                     0
+(5 rows)
+
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING BY(five)
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+         five          
+-----------------------
+ -1.2345678901234e+200
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+                     0
+(5 rows)
+
+SELECT f1 AS ten FROM FLOAT8_TBL
+UNION ALL CORRESPONDING
+SELECT f1 AS ten FROM FLOAT8_TBL;
+          ten          
+-----------------------
+                     0
+                -34.84
+               -1004.3
+ -1.2345678901234e+200
+ -1.2345678901234e-200
+                     0
+                -34.84
+               -1004.3
+ -1.2345678901234e+200
+ -1.2345678901234e-200
+(10 rows)
+
+SELECT f1 AS nine FROM FLOAT8_TBL
+UNION CORRESPONDING
+SELECT f1 AS nine FROM INT4_TBL
+ORDER BY 1;
+         nine          
+-----------------------
+ -1.2345678901234e+200
+           -2147483647
+               -123456
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+                     0
+                123456
+            2147483647
+(9 rows)
+
+SELECT f1 AS ten FROM FLOAT8_TBL
+UNION ALL CORRESPONDING
+SELECT f1 AS ten FROM INT4_TBL;
+          ten          
+-----------------------
+                     0
+                -34.84
+               -1004.3
+ -1.2345678901234e+200
+ -1.2345678901234e-200
+                     0
+                123456
+               -123456
+            2147483647
+           -2147483647
+(10 rows)
+
+SELECT f1 AS five FROM FLOAT8_TBL
+  WHERE f1 BETWEEN -1e6 AND 1e6
+UNION CORRESPONDING
+SELECT f1 AS five FROM INT4_TBL
+  WHERE f1 BETWEEN 0 AND 1000000;
+         five          
+-----------------------
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+                     0
+                123456
+(5 rows)
+
 --
 -- INTERSECT and EXCEPT
 --
@@ -320,6 +603,63 @@ SELECT q1 FROM int8_tbl EXCEPT ALL SELECT DISTINCT q2 FROM int8_tbl ORDER BY 1;
 
 SELECT q1 FROM int8_tbl EXCEPT ALL SELECT q1 FROM int8_tbl FOR NO KEY UPDATE;
 ERROR:  FOR NO KEY UPDATE is not allowed with UNION/INTERSECT/EXCEPT
+SELECT q2 FROM int8_tbl INTERSECT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+        q2        
+------------------
+              123
+ 4567890123456789
+(2 rows)
+
+SELECT q2 FROM int8_tbl INTERSECT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+        q2        
+------------------
+              123
+ 4567890123456789
+ 4567890123456789
+(3 rows)
+
+SELECT q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+        q2         
+-------------------
+ -4567890123456789
+               456
+(2 rows)
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+        q2         
+-------------------
+ -4567890123456789
+               456
+(2 rows)
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q1 AS q2 FROM int8_tbl ORDER BY 1;
+        q2         
+-------------------
+ -4567890123456789
+               456
+  4567890123456789
+(3 rows)
+
+SELECT q1 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+ q1 
+----
+(0 rows)
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+        q1        
+------------------
+              123
+ 4567890123456789
+(2 rows)
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q2 AS q1 FROM int8_tbl;
+        q1        
+------------------
+              123
+ 4567890123456789
+ 4567890123456789
+(3 rows)
+
 --
 -- Mixed types
 --
@@ -338,6 +678,21 @@ SELECT f1 FROM float8_tbl EXCEPT SELECT f1 FROM int4_tbl ORDER BY 1;
  -1.2345678901234e-200
 (4 rows)
 
+SELECT f1 FROM float8_tbl INTERSECT CORRESPONDING SELECT f1 FROM int4_tbl;
+ f1 
+----
+  0
+(1 row)
+
+SELECT f1 FROM float8_tbl EXCEPT CORRESPONDING SELECT f1 FROM int4_tbl ORDER BY 1;
+          f1           
+-----------------------
+ -1.2345678901234e+200
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+(4 rows)
+
 --
 -- Operator precedence and (((((extra))))) parentheses
 --
@@ -424,6 +779,24 @@ SELECT q1 FROM int8_tbl EXCEPT (((SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1)))
  4567890123456789
 (2 rows)
 
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2,q1 FROM int8_tbl
+ORDER BY q2,q1;
+ q1 | q2 
+----+----
+(0 rows)
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q1) SELECT q2,q1 FROM int8_tbl
+ORDER BY q1;
+ q1 
+----
+(0 rows)
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q2) SELECT q2,q1 FROM int8_tbl
+ORDER BY q2;
+ q2 
+----
+(0 rows)
+
 --
 -- New syntaxes (7.1) permit new tests
 --
@@ -459,6 +832,22 @@ SELECT '3.4'::numeric UNION SELECT 'foo';
 ERROR:  invalid input syntax for type numeric: "foo"
 LINE 1: SELECT '3.4'::numeric UNION SELECT 'foo';
                                            ^
+SELECT '3.4'::numeric AS a UNION CORRESPONDING SELECT 'foo' AS a;
+ERROR:  invalid input syntax for type numeric: "foo"
+LINE 1: ...CT '3.4'::numeric AS a UNION CORRESPONDING SELECT 'foo' AS a...
+                                                             ^
+SELECT a.f1 FROM (SELECT 'test' AS f1 FROM varchar_tbl) a
+UNION CORRESPONDING
+SELECT b.f1 FROM (SELECT f1 FROM varchar_tbl) b
+ORDER BY 1;
+  f1  
+------
+ a
+ ab
+ abcd
+ test
+(4 rows)
+
 --
 -- Test that expression-index constraints can be pushed down through
 -- UNION or UNION ALL
diff --git a/src/test/regress/sql/union.sql b/src/test/regress/sql/union.sql
index 48e6850..bae0df2 100644
--- a/src/test/regress/sql/union.sql
+++ b/src/test/regress/sql/union.sql
@@ -20,6 +20,64 @@ SELECT 1 AS three UNION SELECT 2 UNION ALL SELECT 2 ORDER BY 1;
 
 SELECT 1.1 AS two UNION SELECT 2.2 ORDER BY 1;
 
+SELECT 1 AS two UNION CORRESPONDING SELECT 2 two;
+
+SELECT 1 AS one UNION CORRESPONDING SELECT 1 one;
+
+SELECT 1 AS two UNION ALL CORRESPONDING SELECT 2 two;
+
+SELECT 1 AS two UNION ALL CORRESPONDING SELECT 1 two;
+
+SELECT 1 AS two UNION CORRESPONDING SELECT 2 two UNION CORRESPONDING SELECT 2 two;
+
+SELECT 1 AS three UNION CORRESPONDING SELECT 2 three UNION ALL CORRESPONDING SELECT 2 three;
+
+SELECT 1 AS three UNION CORRESPONDING SELECT 2 three UNION CORRESPONDING SELECT 3 three;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+
+SELECT 3 c, 2 b, 1 a UNION CORRESPONDING SELECT 6 c, 4 a, 5 b;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(c) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, c) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 4 a, 5 b, 6 c;
+
+-- CORRESPONDING column ordering, left clause's column ordering must be preserved.
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+
+SELECT 2 b, 1 a, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 3 c, 2 b UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 5 b, 6 c, 4 a;
+
+SELECT 2 b, 1 a, 3 c UNION CORRESPONDING SELECT 5 b, 6 c, 4 a;
+
+-- CORRESPONDING BY column ordering, BY clause column ordering must be preserved.
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c, a) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(c, a, b) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 5 b, 6 c, 4 a;
+
+SELECT 2 b, 3 c, 1 a UNION CORRESPONDING BY(b, c, a) SELECT 4 a, 5 b, 6 c;
+
+SELECT 3 c, 2 b, 1 a UNION CORRESPONDING BY(c, a, b) SELECT 4 a, 5 b, 6 c;
+
 -- Mixed types
 
 SELECT 1.1 AS two UNION SELECT 2 ORDER BY 1;
@@ -90,6 +148,35 @@ UNION
 SELECT TRIM(TRAILING FROM f1) FROM CHAR_TBL
 ORDER BY 1;
 
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING BY(five)
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+
+SELECT f1 AS ten FROM FLOAT8_TBL
+UNION ALL CORRESPONDING
+SELECT f1 AS ten FROM FLOAT8_TBL;
+
+SELECT f1 AS nine FROM FLOAT8_TBL
+UNION CORRESPONDING
+SELECT f1 AS nine FROM INT4_TBL
+ORDER BY 1;
+
+SELECT f1 AS ten FROM FLOAT8_TBL
+UNION ALL CORRESPONDING
+SELECT f1 AS ten FROM INT4_TBL;
+
+SELECT f1 AS five FROM FLOAT8_TBL
+  WHERE f1 BETWEEN -1e6 AND 1e6
+UNION CORRESPONDING
+SELECT f1 AS five FROM INT4_TBL
+  WHERE f1 BETWEEN 0 AND 1000000;
+
 --
 -- INTERSECT and EXCEPT
 --
@@ -112,6 +199,22 @@ SELECT q1 FROM int8_tbl EXCEPT ALL SELECT DISTINCT q2 FROM int8_tbl ORDER BY 1;
 
 SELECT q1 FROM int8_tbl EXCEPT ALL SELECT q1 FROM int8_tbl FOR NO KEY UPDATE;
 
+SELECT q2 FROM int8_tbl INTERSECT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+
+SELECT q2 FROM int8_tbl INTERSECT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+
+SELECT q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q1 AS q2 FROM int8_tbl ORDER BY 1;
+
+SELECT q1 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q2 AS q1 FROM int8_tbl;
+
 --
 -- Mixed types
 --
@@ -120,6 +223,10 @@ SELECT f1 FROM float8_tbl INTERSECT SELECT f1 FROM int4_tbl ORDER BY 1;
 
 SELECT f1 FROM float8_tbl EXCEPT SELECT f1 FROM int4_tbl ORDER BY 1;
 
+SELECT f1 FROM float8_tbl INTERSECT CORRESPONDING SELECT f1 FROM int4_tbl;
+
+SELECT f1 FROM float8_tbl EXCEPT CORRESPONDING SELECT f1 FROM int4_tbl ORDER BY 1;
+
 --
 -- Operator precedence and (((((extra))))) parentheses
 --
@@ -150,6 +257,15 @@ SELECT q1 FROM int8_tbl EXCEPT SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1;
 -- But this should work:
 SELECT q1 FROM int8_tbl EXCEPT (((SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1))) ORDER BY 1;
 
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2,q1 FROM int8_tbl
+ORDER BY q2,q1;
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q1) SELECT q2,q1 FROM int8_tbl
+ORDER BY q1;
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q2) SELECT q2,q1 FROM int8_tbl
+ORDER BY q2;
+
 --
 -- New syntaxes (7.1) permit new tests
 --
@@ -170,6 +286,13 @@ ORDER BY 1;
 -- This should fail, but it should produce an error cursor
 SELECT '3.4'::numeric UNION SELECT 'foo';
 
+SELECT '3.4'::numeric AS a UNION CORRESPONDING SELECT 'foo' AS a;
+
+SELECT a.f1 FROM (SELECT 'test' AS f1 FROM varchar_tbl) a
+UNION CORRESPONDING
+SELECT b.f1 FROM (SELECT f1 FROM varchar_tbl) b
+ORDER BY 1;
+
 --
 -- Test that expression-index constraints can be pushed down through
 -- UNION or UNION ALL
#9Pavel Stehule
pavel.stehule@gmail.com
In reply to: Surafel Temesgen (#8)
Re: New CORRESPONDING clause design

2017-03-09 13:18 GMT+01:00 Surafel Temesgen <surafel3000@gmail.com>:

Hi ,

Here is a patch corrected as your feedback except missed tests case
because corresponding by clause is implemented on the top of set operation
and you can’t do that to set operation without corresponding by clause too

I don't understand.

The following statement should to work

postgres=# select 10 as a, 20 as b union corresponding select 20 as a, 30
as b, 40 as c;

ERROR: each UNION query must have the same number of columns
LINE 1: ...elect 10 as a, 20 as b union corresponding select 20 as a, 3...

Corresponding clause should to work like projection filter.

Regards

Pavel

Show quoted text

Eg

postgres=# SELECT 1 a, 2 b, 3 c UNION SELECT 4 a, 5 b, 6 c, 8 d;

ERROR: each UNION query must have the same number of columns

LINE 1: SELECT 1 a, 2 b, 3 c UNION SELECT 4 a, 5 b, 6 c, 8 d;

^

postgres=# create table t1(a int, b int, c int);

CREATE TABLE

postgres=# create table t2(a int, b int);

CREATE TABLE

postgres=# select * from t1 union select * from t2;

ERROR: each UNION query must have the same number of columns

LINE 1: select * from t1 union select * from t2;

Thanks

Surafel

On Tue, Mar 7, 2017 at 10:26 PM, Pavel Stehule <pavel.stehule@gmail.com>
wrote:

Hi

I am sending a review of this interesting feature.

I found following issues, questions:

1. unclosed tags <optional> in documentation
2. bad name "changeTargetEntry" - should be makeTargetEntry?
3. Why you removed lot of asserts in prepunion.c? These asserts should be
valid still
4. make_coresponding_target has wrong formatting
5. error "%s queries with a CORRESPONDING clause must have at least one
column with the same name" has wrong formatting, you can show position
6. previous issue is repeated - look on formatting ereport function,
please, you can use DETAIL and HINT fields
7. corresponding clause should to contain column list (I am looking to
ANSI/SQL 99) - you are using expr_list, what has not sense and probably it
has impact on all implementation.
8. typo orderCorrespondingLsit(List *targetlist)
9. I miss more tests for CORRESPONDING BY
10. if I understand to this feature, this query should to work

postgres=# SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY (c,b) SELECT 4 a, 5 b, 6 c, 8 d;
ERROR: each UNION query must have the same number of columns
LINE 1: ...1 a, 2 b, 3 c UNION CORRESPONDING BY (c,b) SELECT 4 a, 5 b, ...

postgres=# create table t1(a int, b int, c int);
CREATE TABLE
Time: 63,260 ms
postgres=# create table t2(a int, b int);
CREATE TABLE
Time: 57,120 ms
postgres=# select * from t1 union corresponding select * from t2;
ERROR: each UNION query must have the same number of columns
LINE 1: select * from t1 union corresponding select * from t2;

If it is your first patch to Postgres, then it is perfect work!

The @7 is probably most significant - I dislike a expression list there.
name_list should be better there.

Regards

Pavel

#10Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#9)
Re: New CORRESPONDING clause design

hi

2017-03-09 17:19 GMT+01:00 Pavel Stehule <pavel.stehule@gmail.com>:

2017-03-09 13:18 GMT+01:00 Surafel Temesgen <surafel3000@gmail.com>:

Hi ,

Here is a patch corrected as your feedback except missed tests case
because corresponding by clause is implemented on the top of set operation
and you can’t do that to set operation without corresponding by clause too

I don't understand.

The following statement should to work

postgres=# select 10 as a, 20 as b union corresponding select 20 as a, 30
as b, 40 as c;

ERROR: each UNION query must have the same number of columns
LINE 1: ...elect 10 as a, 20 as b union corresponding select 20 as a, 3...

Corresponding clause should to work like projection filter.

I found a link to postgresql mailing list related to some older try to this
feature implementation

/messages/by-id/CAJZSWkX7C6Wmfo9Py4BaF8vHz_Ofko3AFSOsJPsb17rGmgBuDQ@mail.gmail.com

Show quoted text

Regards

Pavel

Eg

postgres=# SELECT 1 a, 2 b, 3 c UNION SELECT 4 a, 5 b, 6 c, 8 d;

ERROR: each UNION query must have the same number of columns

LINE 1: SELECT 1 a, 2 b, 3 c UNION SELECT 4 a, 5 b, 6 c, 8 d;

^

postgres=# create table t1(a int, b int, c int);

CREATE TABLE

postgres=# create table t2(a int, b int);

CREATE TABLE

postgres=# select * from t1 union select * from t2;

ERROR: each UNION query must have the same number of columns

LINE 1: select * from t1 union select * from t2;

Thanks

Surafel

On Tue, Mar 7, 2017 at 10:26 PM, Pavel Stehule <pavel.stehule@gmail.com>
wrote:

Hi

I am sending a review of this interesting feature.

I found following issues, questions:

1. unclosed tags <optional> in documentation
2. bad name "changeTargetEntry" - should be makeTargetEntry?
3. Why you removed lot of asserts in prepunion.c? These asserts should
be valid still
4. make_coresponding_target has wrong formatting
5. error "%s queries with a CORRESPONDING clause must have at least one
column with the same name" has wrong formatting, you can show position
6. previous issue is repeated - look on formatting ereport function,
please, you can use DETAIL and HINT fields
7. corresponding clause should to contain column list (I am looking to
ANSI/SQL 99) - you are using expr_list, what has not sense and probably it
has impact on all implementation.
8. typo orderCorrespondingLsit(List *targetlist)
9. I miss more tests for CORRESPONDING BY
10. if I understand to this feature, this query should to work

postgres=# SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY (c,b) SELECT 4 a, 5 b, 6 c, 8 d;
ERROR: each UNION query must have the same number of columns
LINE 1: ...1 a, 2 b, 3 c UNION CORRESPONDING BY (c,b) SELECT 4 a, 5 b, ...

postgres=# create table t1(a int, b int, c int);
CREATE TABLE
Time: 63,260 ms
postgres=# create table t2(a int, b int);
CREATE TABLE
Time: 57,120 ms
postgres=# select * from t1 union corresponding select * from t2;
ERROR: each UNION query must have the same number of columns
LINE 1: select * from t1 union corresponding select * from t2;

If it is your first patch to Postgres, then it is perfect work!

The @7 is probably most significant - I dislike a expression list there.
name_list should be better there.

Regards

Pavel

#11Surafel Temesgen
surafel3000@gmail.com
In reply to: Pavel Stehule (#10)
1 attachment(s)
Re: New CORRESPONDING clause design

Yes, you are correct it should to work on CORRESPONDING clause case. SQL
20nn standard draft only said each query to be of the same degree in a case
of set operation without corresponding clause. The attached patch is
corrected as such .I add those new test case to regression test too

Regards

Surafel

On Thu, Mar 9, 2017 at 9:49 PM, Pavel Stehule <pavel.stehule@gmail.com>
wrote:

Show quoted text

hi

2017-03-09 17:19 GMT+01:00 Pavel Stehule <pavel.stehule@gmail.com>:

2017-03-09 13:18 GMT+01:00 Surafel Temesgen <surafel3000@gmail.com>:

Hi ,

Here is a patch corrected as your feedback except missed tests case
because corresponding by clause is implemented on the top of set operation
and you can’t do that to set operation without corresponding by clause too

I don't understand.

The following statement should to work

postgres=# select 10 as a, 20 as b union corresponding select 20 as a, 30
as b, 40 as c;

ERROR: each UNION query must have the same number of columns
LINE 1: ...elect 10 as a, 20 as b union corresponding select 20 as a, 3...

Corresponding clause should to work like projection filter.

I found a link to postgresql mailing list related to some older try to
this feature implementation

/messages/by-id/CAJZSWkX7C6Wmfo9Py4BaF8vHz_
Ofko3AFSOsJPsb17rGmgBuDQ@mail.gmail.com

Regards

Pavel

Eg

postgres=# SELECT 1 a, 2 b, 3 c UNION SELECT 4 a, 5 b, 6 c, 8 d;

ERROR: each UNION query must have the same number of columns

LINE 1: SELECT 1 a, 2 b, 3 c UNION SELECT 4 a, 5 b, 6 c, 8 d;

^

postgres=# create table t1(a int, b int, c int);

CREATE TABLE

postgres=# create table t2(a int, b int);

CREATE TABLE

postgres=# select * from t1 union select * from t2;

ERROR: each UNION query must have the same number of columns

LINE 1: select * from t1 union select * from t2;

Thanks

Surafel

On Tue, Mar 7, 2017 at 10:26 PM, Pavel Stehule <pavel.stehule@gmail.com>
wrote:

Hi

I am sending a review of this interesting feature.

I found following issues, questions:

1. unclosed tags <optional> in documentation
2. bad name "changeTargetEntry" - should be makeTargetEntry?
3. Why you removed lot of asserts in prepunion.c? These asserts should
be valid still
4. make_coresponding_target has wrong formatting
5. error "%s queries with a CORRESPONDING clause must have at least one
column with the same name" has wrong formatting, you can show position
6. previous issue is repeated - look on formatting ereport function,
please, you can use DETAIL and HINT fields
7. corresponding clause should to contain column list (I am looking to
ANSI/SQL 99) - you are using expr_list, what has not sense and probably it
has impact on all implementation.
8. typo orderCorrespondingLsit(List *targetlist)
9. I miss more tests for CORRESPONDING BY
10. if I understand to this feature, this query should to work

postgres=# SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY (c,b) SELECT 4 a, 5 b, 6 c, 8 d;
ERROR: each UNION query must have the same number of columns
LINE 1: ...1 a, 2 b, 3 c UNION CORRESPONDING BY (c,b) SELECT 4 a, 5 b, ...

postgres=# create table t1(a int, b int, c int);
CREATE TABLE
Time: 63,260 ms
postgres=# create table t2(a int, b int);
CREATE TABLE
Time: 57,120 ms
postgres=# select * from t1 union corresponding select * from t2;
ERROR: each UNION query must have the same number of columns
LINE 1: select * from t1 union corresponding select * from t2;

If it is your first patch to Postgres, then it is perfect work!

The @7 is probably most significant - I dislike a expression list
there. name_list should be better there.

Regards

Pavel

Attachments:

corresponding_clause_v3.patchapplication/octet-stream; name=corresponding_clause_v3.patchDownload
diff --git a/doc/src/sgml/queries.sgml b/doc/src/sgml/queries.sgml
index 30792f4..c3cdee5 100644
--- a/doc/src/sgml/queries.sgml
+++ b/doc/src/sgml/queries.sgml
@@ -1601,6 +1601,9 @@ SELECT DISTINCT ON (<replaceable>expression</replaceable> <optional>, <replaceab
    <primary>EXCEPT</primary>
   </indexterm>
   <indexterm zone="queries-union">
+   <primary>CORRESPONDING</primary>
+  </indexterm>
+  <indexterm zone="queries-union">
    <primary>set union</primary>
   </indexterm>
   <indexterm zone="queries-union">
@@ -1617,9 +1620,9 @@ SELECT DISTINCT ON (<replaceable>expression</replaceable> <optional>, <replaceab
    The results of two queries can be combined using the set operations
    union, intersection, and difference.  The syntax is
 <synopsis>
-<replaceable>query1</replaceable> UNION <optional>ALL</optional> <replaceable>query2</replaceable>
-<replaceable>query1</replaceable> INTERSECT <optional>ALL</optional> <replaceable>query2</replaceable>
-<replaceable>query1</replaceable> EXCEPT <optional>ALL</optional> <replaceable>query2</replaceable>
+<replaceable>query1</replaceable> UNION <optional>ALL</optional> <optional>CORRESPONDING</optional> <optional>BY</optional> <replaceable>query2</replaceable>
+<replaceable>query1</replaceable> INTERSECT <optional>ALL</optional> <optional>CORRESPONDING</optional> <optional>BY</optional> <replaceable>query2</replaceable>
+<replaceable>query1</replaceable> EXCEPT <optional>ALL</optional> <optional>CORRESPONDING</optional> <optional>BY</optional> <replaceable>query2</replaceable>
 </synopsis>
    <replaceable>query1</replaceable> and
    <replaceable>query2</replaceable> are queries that can use any of
@@ -1659,11 +1662,22 @@ SELECT DISTINCT ON (<replaceable>expression</replaceable> <optional>, <replaceab
   </para>
 
   <para>
-   In order to calculate the union, intersection, or difference of two
-   queries, the two queries must be <quote>union compatible</quote>,
-   which means that they return the same number of columns and
-   the corresponding columns have compatible data types, as
-   described in <xref linkend="typeconv-union-case">.
+   <literal>EXCEPT</> returns all rows that are in the result of
+   <replaceable>query1</replaceable> but not in the result of
+   <replaceable>query2</replaceable>.  (This is sometimes called the
+   <firstterm>difference</> between two queries.)  Again, duplicates
+   are eliminated unless <literal>EXCEPT ALL</> is used.
+  </para>
+
+  <para>
+   <literal>CORRESPONDING</> returns all columns that are in both 
+   <replaceable>query1</> and <replaceable>query2</> with the same name.
+  </para>
+
+  <para>
+   <literal>CORRESPONDING BY</> returns all columns in the column list 
+   that are also in both <replaceable>query1</> and 
+   <replaceable>query2</> with the same name.
   </para>
  </sect1>
 
diff --git a/doc/src/sgml/sql.sgml b/doc/src/sgml/sql.sgml
index 57396d7..f98c22e 100644
--- a/doc/src/sgml/sql.sgml
+++ b/doc/src/sgml/sql.sgml
@@ -859,7 +859,7 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="PARAMETER">expression</replac
     [ WHERE <replaceable class="PARAMETER">condition</replaceable> ]
     [ GROUP BY <replaceable class="PARAMETER">expression</replaceable> [, ...] ]
     [ HAVING <replaceable class="PARAMETER">condition</replaceable> [, ...] ]
-    [ { UNION | INTERSECT | EXCEPT } [ ALL ] <replaceable class="PARAMETER">select</replaceable> ]
+    [ { UNION | INTERSECT | EXCEPT } [ ALL ] [ CORRESPONDING [ BY ( <replaceable class="PARAMETER">expression</replaceable> ) ] ] <replaceable class="PARAMETER">select</replaceable> ]
     [ ORDER BY <replaceable class="parameter">expression</replaceable> [ ASC | DESC | USING <replaceable class="parameter">operator</replaceable> ] [ NULLS { FIRST | LAST } ] [, ...] ]
     [ LIMIT { <replaceable class="PARAMETER">count</replaceable> | ALL } ]
     [ OFFSET <replaceable class="PARAMETER">start</replaceable> ]
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 30d733e..30874e0 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2870,6 +2870,7 @@ _copySelectStmt(const SelectStmt *from)
 	COPY_NODE_FIELD(withClause);
 	COPY_SCALAR_FIELD(op);
 	COPY_SCALAR_FIELD(all);
+	COPY_NODE_FIELD(correspondingClause);
 	COPY_NODE_FIELD(larg);
 	COPY_NODE_FIELD(rarg);
 
@@ -2885,6 +2886,7 @@ _copySetOperationStmt(const SetOperationStmt *from)
 	COPY_SCALAR_FIELD(all);
 	COPY_NODE_FIELD(larg);
 	COPY_NODE_FIELD(rarg);
+	COPY_NODE_FIELD(correspondingColumns);
 	COPY_NODE_FIELD(colTypes);
 	COPY_NODE_FIELD(colTypmods);
 	COPY_NODE_FIELD(colCollations);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 55c73b7..f375255 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1020,6 +1020,7 @@ _equalSelectStmt(const SelectStmt *a, const SelectStmt *b)
 	COMPARE_NODE_FIELD(withClause);
 	COMPARE_SCALAR_FIELD(op);
 	COMPARE_SCALAR_FIELD(all);
+	COMPARE_NODE_FIELD(correspondingClause);
 	COMPARE_NODE_FIELD(larg);
 	COMPARE_NODE_FIELD(rarg);
 
@@ -1033,6 +1034,7 @@ _equalSetOperationStmt(const SetOperationStmt *a, const SetOperationStmt *b)
 	COMPARE_SCALAR_FIELD(all);
 	COMPARE_NODE_FIELD(larg);
 	COMPARE_NODE_FIELD(rarg);
+	COMPARE_NODE_FIELD(correspondingColumns);
 	COMPARE_NODE_FIELD(colTypes);
 	COMPARE_NODE_FIELD(colTypmods);
 	COMPARE_NODE_FIELD(colCollations);
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index c2f03b7..ca39c1c 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -3411,6 +3411,8 @@ raw_expression_tree_walker(Node *node,
 					return true;
 				if (walker(stmt->lockingClause, context))
 					return true;
+				if (walker(stmt->correspondingClause, context))
+					return true;
 				if (walker(stmt->withClause, context))
 					return true;
 				if (walker(stmt->larg, context))
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 1560ac3..6e96018 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2526,6 +2526,7 @@ _outSelectStmt(StringInfo str, const SelectStmt *node)
 	WRITE_NODE_FIELD(withClause);
 	WRITE_ENUM_FIELD(op, SetOperation);
 	WRITE_BOOL_FIELD(all);
+	WRITE_NODE_FIELD(correspondingClause);
 	WRITE_NODE_FIELD(larg);
 	WRITE_NODE_FIELD(rarg);
 }
@@ -2833,6 +2834,7 @@ _outSetOperationStmt(StringInfo str, const SetOperationStmt *node)
 	WRITE_BOOL_FIELD(all);
 	WRITE_NODE_FIELD(larg);
 	WRITE_NODE_FIELD(rarg);
+	WRITE_NODE_FIELD(correspondingColumns);
 	WRITE_NODE_FIELD(colTypes);
 	WRITE_NODE_FIELD(colTypmods);
 	WRITE_NODE_FIELD(colCollations);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index dcfa6ee..94b724b 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -418,6 +418,7 @@ _readSetOperationStmt(void)
 	READ_BOOL_FIELD(all);
 	READ_NODE_FIELD(larg);
 	READ_NODE_FIELD(rarg);
+	READ_NODE_FIELD(correspondingColumns);
 	READ_NODE_FIELD(colTypes);
 	READ_NODE_FIELD(colTypmods);
 	READ_NODE_FIELD(colCollations);
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 06e843d..b183719 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -91,7 +91,8 @@ static List *generate_setop_tlist(List *colTypes, List *colCollations,
 					 Index varno,
 					 bool hack_constants,
 					 List *input_tlist,
-					 List *refnames_tlist);
+					 List *refnames_tlist,
+					 bool no_corresponding);
 static List *generate_append_tlist(List *colTypes, List *colCollations,
 					  bool flag,
 					  List *input_tlists,
@@ -110,6 +111,7 @@ static Node *adjust_appendrel_attrs_mutator(Node *node,
 static Relids adjust_relid_set(Relids relids, Index oldrelid, Index newrelid);
 static List *adjust_inherited_tlist(List *tlist,
 					   AppendRelInfo *context);
+static List *make_corresponding_target(List *corresponding_list, List *subroot_list);
 
 
 /*
@@ -187,6 +189,24 @@ plan_set_operations(PlannerInfo *root)
 									   leftmostQuery->targetList,
 									   &top_tlist);
 	}
+	/*
+	 * If corresponding column specified, we take column names from it.
+	 */
+	else if (topop->correspondingColumns != NIL )
+	{
+		/*
+		 * Recurse on setOperations tree to generate paths for set ops. The
+		 * final output path should have just the column types shown as the
+		 * output from the top-level node, plus possibly resjunk working
+		 * columns (we can rely on upper-level nodes to deal with that).
+		 */
+		path = recurse_set_operations((Node *) topop, root,
+									  topop->colTypes, topop->colCollations,
+									  true, -1,
+									  topop->correspondingColumns,
+									  &top_tlist,
+									  NULL);
+	}
 	else
 	{
 		/*
@@ -252,6 +272,8 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 					   List **pTargetList,
 					   double *pNumGroups)
 {
+	SetOperationStmt *topop = (SetOperationStmt *) root->parse->setOperations;
+
 	if (IsA(setOp, RangeTblRef))
 	{
 		RangeTblRef *rtr = (RangeTblRef *) setOp;
@@ -316,23 +338,45 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 		path = (Path *) create_subqueryscan_path(root, rel, subpath,
 												 NIL, NULL);
 
-		/*
-		 * Figure out the appropriate target list, and update the
-		 * SubqueryScanPath with the PathTarget form of that.
-		 */
-		tlist = generate_setop_tlist(colTypes, colCollations,
+		if (topop->correspondingColumns != NIL )
+		{
+			List *correspondingTarget;
+			/* make target list that only contains corresponding column from sub-queries list ito use it for projection */
+			correspondingTarget = make_corresponding_target(topop->correspondingColumns,
+					subroot->processed_tlist);
+			/*
+			 * Figure out the appropriate target list, and update the
+			 * SubqueryScanPath with the PathTarget form of that.
+			 */
+			tlist = generate_setop_tlist(colTypes, colCollations, flag,
+					rtr->rtindex, true, correspondingTarget, refnames_tlist, false);
+
+			path = apply_projection_to_path(root, rel, path,
+					create_pathtarget(root, tlist));
+
+			/* Return the fully-fledged tlist to caller, too */
+			*pTargetList = tlist;
+
+		}
+		else
+		{
+			/*
+			* Figure out the appropriate target list, and update the
+			* SubqueryScanPath with the PathTarget form of that.
+			*/
+			tlist = generate_setop_tlist(colTypes, colCollations,
 									 flag,
 									 rtr->rtindex,
 									 true,
 									 subroot->processed_tlist,
-									 refnames_tlist);
+									 refnames_tlist, true);
 
-		path = apply_projection_to_path(root, rel, path,
+			path = apply_projection_to_path(root, rel, path,
 										create_pathtarget(root, tlist));
 
-		/* Return the fully-fledged tlist to caller, too */
-		*pTargetList = tlist;
-
+			/* Return the fully-fledged tlist to caller, too */
+			*pTargetList = tlist;
+		}
 		/*
 		 * Estimate number of groups if caller wants it.  If the subquery used
 		 * grouping or aggregation, its output is probably mostly unique
@@ -392,7 +436,7 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 												0,
 												false,
 												*pTargetList,
-												refnames_tlist);
+												refnames_tlist, true);
 			path = apply_projection_to_path(root,
 											path->parent,
 											path,
@@ -1004,7 +1048,8 @@ generate_setop_tlist(List *colTypes, List *colCollations,
 					 Index varno,
 					 bool hack_constants,
 					 List *input_tlist,
-					 List *refnames_tlist)
+					 List *refnames_tlist,
+					 bool no_corresponding)
 {
 	List	   *tlist = NIL;
 	int			resno = 1;
@@ -1025,9 +1070,11 @@ generate_setop_tlist(List *colTypes, List *colCollations,
 		TargetEntry *reftle = (TargetEntry *) lfirst(rtlc);
 
 		rtlc = lnext(rtlc);
-
-		Assert(inputtle->resno == resno);
-		Assert(reftle->resno == resno);
+		if (no_corresponding)
+		{
+			Assert(inputtle->resno == resno);
+			Assert(reftle->resno == resno);
+		}
 		Assert(!inputtle->resjunk);
 		Assert(!reftle->resjunk);
 
@@ -2112,3 +2159,75 @@ adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node,
 	/* Now translate for this child */
 	return adjust_appendrel_attrs(root, node, appinfo);
 }
+
+/*
+ * generate target list from left target list with the order
+ * of right target list
+ */
+static List *make_corresponding_target(List *corresponding_list, List *subroot_list)
+{
+	Index internal = 0;
+	ListCell *ltl;
+	ListCell *rtl;
+	int size;
+	int i;
+	List *matchingColumns = NIL;
+	TargetEntry *simple_te_array;
+
+	size = list_length(corresponding_list) + 1;
+
+	/* Use array to find the order of corresponding columen */
+	simple_te_array = (TargetEntry *) palloc0(size * sizeof(TargetEntry));
+	foreach(ltl, corresponding_list)
+	{
+
+		foreach(rtl, subroot_list)
+		{
+
+			TargetEntry *ltle = (TargetEntry *) lfirst(ltl);
+			TargetEntry *rtle = (TargetEntry *) lfirst(rtl);
+
+			elog(DEBUG4, "%s", ltle->resname);
+
+			/* Names of the columns must be resolved before calling this method. */
+			Assert(ltle->resname != NULL);
+			Assert(rtle->resname != NULL);
+
+			/* If column names are the same, add it to array. */
+			if (strcmp(ltle->resname, rtle->resname) == 0)
+			{
+
+				simple_te_array[internal].xpr = rtle->xpr;
+				simple_te_array[internal].expr = rtle->expr;
+				simple_te_array[internal].resno = rtle->resno;
+				simple_te_array[internal].resname = rtle->resname;
+				simple_te_array[internal].ressortgroupref =
+						rtle->ressortgroupref;
+				simple_te_array[internal].resorigtbl = rtle->resorigtbl;
+				simple_te_array[internal].resorigcol = rtle->resorigcol;
+				simple_te_array[internal].resjunk = rtle->resjunk;
+				internal++;
+				continue;
+			}
+		}
+	}
+	/* traverse the array and make targetlist */
+	for (i = 0; i < internal; i++)
+	{
+
+		TargetEntry *tle = makeNode(TargetEntry);
+
+		tle->xpr = simple_te_array[i].xpr;
+		tle->expr = simple_te_array[i].expr;
+		tle->resno = simple_te_array[i].resno;
+		tle->resname = simple_te_array[i].resname;
+		tle->ressortgroupref = simple_te_array[i].ressortgroupref;
+		tle->resorigtbl = simple_te_array[i].resorigtbl;
+		tle->resorigcol = simple_te_array[i].resorigcol;
+		tle->resjunk = simple_te_array[i].resjunk;
+
+		matchingColumns = lappend(matchingColumns, tle);
+
+	}
+	return matchingColumns;
+}
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 0f7659b..48cf1dc 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -76,6 +76,10 @@ static Query *transformCreateTableAsStmt(ParseState *pstate,
 						   CreateTableAsStmt *stmt);
 static void transformLockingClause(ParseState *pstate, Query *qry,
 					   LockingClause *lc, bool pushedDown);
+static List *determineMatchingColumns(List *ltargetlist, List *rtargetlist);
+static void *makeUnionDatatype(List *ltargetlist, List *rtargetlist,
+		SetOperationStmt *op, List **targetlist, ParseState *parentParseState,
+		const char *context);
 #ifdef RAW_EXPRESSION_COVERAGE_TEST
 static bool test_raw_expression_coverage(Node *node, void *context);
 #endif
@@ -1664,7 +1668,13 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->targetList = NIL;
 	targetvars = NIL;
 	targetnames = NIL;
-	left_tlist = list_head(leftmostQuery->targetList);
+	/* for corresponding clause limits top-level query targetlist to those
+	 * corresponding column list only
+	 */
+	if (sostmt->correspondingColumns != NIL )
+		left_tlist = list_head(sostmt->correspondingColumns);
+	else
+		left_tlist = list_head(leftmostQuery->targetList);
 
 	forthree(lct, sostmt->colTypes,
 			 lcm, sostmt->colTypmods,
@@ -1924,8 +1934,6 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 		SetOperationStmt *op = makeNode(SetOperationStmt);
 		List	   *ltargetlist;
 		List	   *rtargetlist;
-		ListCell   *ltl;
-		ListCell   *rtl;
 		const char *context;
 
 		context = (stmt->op == SETOP_UNION ? "UNION" :
@@ -1935,6 +1943,190 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 		op->op = stmt->op;
 		op->all = stmt->all;
 
+		/* If CORRESPONDING is specified, syntax and column name validities checked,
+		 * column filtering is done by a subquery later on.
+		 */
+		if (stmt->correspondingClause == NIL )
+		{
+			/* No CORRESPONDING clause, no operation needed for column filtering */
+			op->correspondingColumns = stmt->correspondingClause;
+		}
+		else if (linitial(stmt->correspondingClause) == NULL )
+		{
+			/* CORRESPONDING clause, find matching column names from both tables.
+			 * If there are none then it is a syntax error.
+			 */
+			Query *largQuery;
+			Query *rargQuery;
+			List *matchingColumns;
+			List *rightCorrespondingColumns;
+
+			/* Analyze left query to resolve column names. */
+			largQuery = parse_sub_analyze((Node *) stmt->larg, pstate, NULL, false, false );
+
+			/* Analyze right query to resolve column names. */
+			rargQuery = parse_sub_analyze((Node *) stmt->rarg, pstate, NULL, false, false );
+
+			/* Find matching columns from both queries. */
+			matchingColumns = determineMatchingColumns(largQuery->targetList,rargQuery->targetList);
+			/*
+			 * there may be out-of-order resnos in corresponding target list
+			 */
+			op->correspondingColumns = orderCorrespondingList(matchingColumns);
+
+			/* If matchingColumns is empty, there is an error.
+			 * At least one column in the select lists must have the same name.
+			 */
+			if (list_length(matchingColumns) == 0)
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+								errmsg("%s queries with a CORRESPONDING clause must have at least one column with the same name", context),
+								parser_errposition(pstate, exprLocation((Node *) linitial(largQuery->targetList)))));
+			}
+
+			/* matchingColumns contain target list as it appear in left query targetList
+			 * we need matching column as it appear in right query targetlist inorder
+			 * to make output column type for corresponding columns
+			 */
+			rightCorrespondingColumns = determineMatchingColumns(rargQuery->targetList, largQuery->targetList);
+
+			/* make union'd datatype of output column
+			 */
+			makeUnionDatatype(matchingColumns, rightCorrespondingColumns, op, targetlist, pstate,
+					context);
+		}
+		else
+		{
+			/*
+			 * CORRESPONDING BY clause, find matching column names from both tables
+			 * and intersect them with BY(...) column list. If there are none
+			 * then it is a syntax error.
+			 */
+
+			Query *largQuery;
+			Query *rargQuery;
+			List *matchingColumns;
+			List *matchingColumnsFiltered;
+			List *rightCorrespondingColumns;
+			ListCell *corrtl;
+			ListCell *mctl;
+
+			/* Analyze left query to resolve column names. */
+			largQuery = parse_sub_analyze((Node *) stmt->larg, pstate, NULL,false, false );
+
+			/* Analyze right query to resolve column names. */
+			rargQuery = parse_sub_analyze((Node *) stmt->rarg, pstate, NULL, false, false );
+
+			/*
+			 * Find matching columns from both queries.
+			 * In CORRESPONDING BY, column names will be removed from
+			 * matchingColumns if they are not in the BY clause.
+			 * All columns in the BY clause must be in matchingColumns,
+			 * otherwise raise syntax error in BY clause.
+			 */
+
+			matchingColumns = determineMatchingColumns(largQuery->targetList,rargQuery->targetList);
+
+			/*
+			 * Every column name in correspondingClause must be in matchingColumns,
+			 * otherwise it is a syntax error.
+			 */foreach(corrtl, stmt->correspondingClause)
+			{
+				Value* corrtle = lfirst(corrtl);
+
+				/* Get column name from correspondingClause. */
+				char *name = strVal(corrtle);
+				bool hasMatch = false;
+
+				foreach(mctl, matchingColumns)
+				{
+					TargetEntry *mctle = (TargetEntry *) lfirst(mctl);
+
+					Assert(mctle->resname != NULL);
+					Assert(name != NULL);
+
+					/* Compare correspondingClause column name with matchingColumns column names. */
+					if (strcmp(mctle->resname, name) == 0)
+					{
+						/* we have a match. */
+						hasMatch = true;
+						break;
+					}
+				}
+
+				if (!hasMatch)
+				{
+					/* CORRESPONDING BY clause contains a column name that is not in both tables. */
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+									errmsg("%s queries with a CORRESPONDING BY clause must only contain column names from both tables.", context),
+									parser_errposition(pstate, exprLocation((Node *) linitial(largQuery->targetList)))));
+				}
+			}
+
+			/* To preserve column ordering from correspondingClause and to remove
+			 * columns from matchingColumns if they are not in correspondingClause,
+			 * create a new list and finalize our column list for the
+			 * CORRESPONDING BY clause.
+			 */
+
+			matchingColumnsFiltered = NIL;
+
+			/* For each column in CORRESPONDING BY column list, check
+			 * column existence in matchingColumns.
+			 */foreach(corrtl, stmt->correspondingClause)
+			{
+				Value* corrtle = lfirst(corrtl);
+
+				/* Get column name from correspondingClause. */
+				char *name = strVal(corrtle);
+
+				foreach(mctl, matchingColumns)
+				{
+					TargetEntry *mctle = (TargetEntry *) lfirst(mctl);
+
+					Assert(mctle->resname != NULL);
+					Assert(name != NULL);
+
+					if (strcmp(mctle->resname, name) == 0)
+					{
+						/* we have a match.*/
+						matchingColumnsFiltered = lappend(
+								matchingColumnsFiltered, mctle);
+						break;
+					}
+				}
+			}
+
+			/*
+			 * If matchingColumnsFiltered is empty, there is a semantic error.
+			 * At least one column in the select lists must have the same name.
+			 */
+			if (list_length(matchingColumnsFiltered) == 0)
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+								errmsg("%s queries with CORRESPONDING BY clause must have at least one column name in BY clause and in both of the queries.", context),
+								parser_errposition(pstate, exprLocation((Node *) linitial(largQuery->targetList)))));
+			}
+			/*
+			 * there may be out-of-order resnos in corresponding target list
+			 */
+			op->correspondingColumns = orderCorrespondingList(matchingColumnsFiltered);
+
+			/*
+			 * matchingColumns contain target list as it appear in left query targetList
+			 * we need matching column as it appear in right query targetlist inorder to
+			 * make output column type for corresponding columns
+			 */
+			rightCorrespondingColumns = determineMatchingColumns(rargQuery->targetList,matchingColumnsFiltered);
+			/*
+			 * make union'd datatype of output columns
+			 */
+			makeUnionDatatype(matchingColumnsFiltered, rightCorrespondingColumns, op, targetlist, pstate,
+					context);
+		}
 		/*
 		 * Recursively transform the left child node.
 		 */
@@ -1960,177 +2152,217 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 											 false,
 											 &rtargetlist);
 
-		/*
-		 * Verify that the two children have the same number of non-junk
-		 * columns, and determine the types of the merged output columns.
-		 */
-		if (list_length(ltargetlist) != list_length(rtargetlist))
-			ereport(ERROR,
-					(errcode(ERRCODE_SYNTAX_ERROR),
-				 errmsg("each %s query must have the same number of columns",
-						context),
-					 parser_errposition(pstate,
-										exprLocation((Node *) rtargetlist))));
+		if (op->correspondingColumns == NIL )
+		{
+			makeUnionDatatype(ltargetlist, rtargetlist, op, targetlist, pstate,
+					context);
+			/*
+			 * Verify that the two children have the same number of non-junk
+			 * columns, and determine the types of the merged output columns.
+			 */
+			if (list_length(ltargetlist) != list_length(rtargetlist))
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR), errmsg("each %s query must have the same number of columns", context), parser_errposition(pstate, exprLocation((Node *) rtargetlist))));
+		}
 
-		if (targetlist)
-			*targetlist = NIL;
-		op->colTypes = NIL;
-		op->colTypmods = NIL;
-		op->colCollations = NIL;
-		op->groupClauses = NIL;
-		forboth(ltl, ltargetlist, rtl, rtargetlist)
+		return (Node *) op;
+	}
+}
+
+/*
+ * Processes targetlists of two queries for column equivalence to use
+ * with UNION/INTERSECT/EXCEPT CORRESPONDING.
+ */
+static List *
+determineMatchingColumns(List *ltargetlist, List *rtargetlist)
+{
+	List *matchingColumns = NIL;
+	ListCell *ltl;
+	ListCell *rtl;
+
+	foreach(ltl, ltargetlist)
+	{
+		foreach(rtl, rtargetlist)
 		{
 			TargetEntry *ltle = (TargetEntry *) lfirst(ltl);
 			TargetEntry *rtle = (TargetEntry *) lfirst(rtl);
-			Node	   *lcolnode = (Node *) ltle->expr;
-			Node	   *rcolnode = (Node *) rtle->expr;
-			Oid			lcoltype = exprType(lcolnode);
-			Oid			rcoltype = exprType(rcolnode);
-			int32		lcoltypmod = exprTypmod(lcolnode);
-			int32		rcoltypmod = exprTypmod(rcolnode);
-			Node	   *bestexpr;
-			int			bestlocation;
-			Oid			rescoltype;
-			int32		rescoltypmod;
-			Oid			rescolcoll;
-
-			/* select common type, same as CASE et al */
-			rescoltype = select_common_type(pstate,
-											list_make2(lcolnode, rcolnode),
-											context,
-											&bestexpr);
-			bestlocation = exprLocation(bestexpr);
-			/* if same type and same typmod, use typmod; else default */
-			if (lcoltype == rcoltype && lcoltypmod == rcoltypmod)
-				rescoltypmod = lcoltypmod;
-			else
-				rescoltypmod = -1;
-
-			/*
-			 * Verify the coercions are actually possible.  If not, we'd fail
-			 * later anyway, but we want to fail now while we have sufficient
-			 * context to produce an error cursor position.
-			 *
-			 * For all non-UNKNOWN-type cases, we verify coercibility but we
-			 * don't modify the child's expression, for fear of changing the
-			 * child query's semantics.
-			 *
-			 * If a child expression is an UNKNOWN-type Const or Param, we
-			 * want to replace it with the coerced expression.  This can only
-			 * happen when the child is a leaf set-op node.  It's safe to
-			 * replace the expression because if the child query's semantics
-			 * depended on the type of this output column, it'd have already
-			 * coerced the UNKNOWN to something else.  We want to do this
-			 * because (a) we want to verify that a Const is valid for the
-			 * target type, or resolve the actual type of an UNKNOWN Param,
-			 * and (b) we want to avoid unnecessary discrepancies between the
-			 * output type of the child query and the resolved target type.
-			 * Such a discrepancy would disable optimization in the planner.
-			 *
-			 * If it's some other UNKNOWN-type node, eg a Var, we do nothing
-			 * (knowing that coerce_to_common_type would fail).  The planner
-			 * is sometimes able to fold an UNKNOWN Var to a constant before
-			 * it has to coerce the type, so failing now would just break
-			 * cases that might work.
-			 */
-			if (lcoltype != UNKNOWNOID)
-				lcolnode = coerce_to_common_type(pstate, lcolnode,
-												 rescoltype, context);
-			else if (IsA(lcolnode, Const) ||
-					 IsA(lcolnode, Param))
-			{
-				lcolnode = coerce_to_common_type(pstate, lcolnode,
-												 rescoltype, context);
-				ltle->expr = (Expr *) lcolnode;
-			}
 
-			if (rcoltype != UNKNOWNOID)
-				rcolnode = coerce_to_common_type(pstate, rcolnode,
-												 rescoltype, context);
-			else if (IsA(rcolnode, Const) ||
-					 IsA(rcolnode, Param))
-			{
-				rcolnode = coerce_to_common_type(pstate, rcolnode,
-												 rescoltype, context);
-				rtle->expr = (Expr *) rcolnode;
-			}
+			elog(DEBUG4, "%s", ltle->resname);
 
-			/*
-			 * Select common collation.  A common collation is required for
-			 * all set operators except UNION ALL; see SQL:2008 7.13 <query
-			 * expression> Syntax Rule 15c.  (If we fail to identify a common
-			 * collation for a UNION ALL column, the curCollations element
-			 * will be set to InvalidOid, which may result in a runtime error
-			 * if something at a higher query level wants to use the column's
-			 * collation.)
-			 */
-			rescolcoll = select_common_collation(pstate,
-											  list_make2(lcolnode, rcolnode),
-										 (op->op == SETOP_UNION && op->all));
-
-			/* emit results */
-			op->colTypes = lappend_oid(op->colTypes, rescoltype);
-			op->colTypmods = lappend_int(op->colTypmods, rescoltypmod);
-			op->colCollations = lappend_oid(op->colCollations, rescolcoll);
-
-			/*
-			 * For all cases except UNION ALL, identify the grouping operators
-			 * (and, if available, sorting operators) that will be used to
-			 * eliminate duplicates.
-			 */
-			if (op->op != SETOP_UNION || !op->all)
-			{
-				SortGroupClause *grpcl = makeNode(SortGroupClause);
-				Oid			sortop;
-				Oid			eqop;
-				bool		hashable;
-				ParseCallbackState pcbstate;
-
-				setup_parser_errposition_callback(&pcbstate, pstate,
-												  bestlocation);
-
-				/* determine the eqop and optional sortop */
-				get_sort_group_operators(rescoltype,
-										 false, true, false,
-										 &sortop, &eqop, NULL,
-										 &hashable);
-
-				cancel_parser_errposition_callback(&pcbstate);
-
-				/* we don't have a tlist yet, so can't assign sortgrouprefs */
-				grpcl->tleSortGroupRef = 0;
-				grpcl->eqop = eqop;
-				grpcl->sortop = sortop;
-				grpcl->nulls_first = false;		/* OK with or without sortop */
-				grpcl->hashable = hashable;
-
-				op->groupClauses = lappend(op->groupClauses, grpcl);
-			}
+			/* Names of the columns must be resolved before calling this method. */
+			Assert(ltle->resname != NULL);
+			Assert(rtle->resname != NULL);
 
-			/*
-			 * Construct a dummy tlist entry to return.  We use a SetToDefault
-			 * node for the expression, since it carries exactly the fields
-			 * needed, but any other expression node type would do as well.
-			 */
-			if (targetlist)
+			/* If column names are the same, append it to the result. */
+			if (strcmp(ltle->resname, rtle->resname) == 0)
 			{
-				SetToDefault *rescolnode = makeNode(SetToDefault);
-				TargetEntry *restle;
-
-				rescolnode->typeId = rescoltype;
-				rescolnode->typeMod = rescoltypmod;
-				rescolnode->collation = rescolcoll;
-				rescolnode->location = bestlocation;
-				restle = makeTargetEntry((Expr *) rescolnode,
-										 0,		/* no need to set resno */
-										 NULL,
-										 false);
-				*targetlist = lappend(*targetlist, restle);
+				matchingColumns = lappend(matchingColumns, ltle);
+				continue;
 			}
 		}
+	}
+
+	return matchingColumns;
+}
+
+/*
+ * process right and left target list to set up union'd datatype
+ */
+static void *
+makeUnionDatatype(List *ltargetlist, List *rtargetlist, SetOperationStmt *op,
+		List **targetlist, ParseState *pstate, const char *context)
+{
+	ListCell *ltl;
+	ListCell *rtl;
+	if (targetlist)
+		*targetlist = NIL;
+	op->colTypes = NIL;
+	op->colTypmods = NIL;
+	op->colCollations = NIL;
+	op->groupClauses = NIL;
+
+	forboth(ltl, ltargetlist, rtl, rtargetlist)
+	{
+		TargetEntry *ltle = (TargetEntry *) lfirst(ltl);
+		TargetEntry *rtle = (TargetEntry *) lfirst(rtl);
+		Node *lcolnode = (Node *) ltle->expr;
+		Node *rcolnode = (Node *) rtle->expr;
+		Oid lcoltype = exprType(lcolnode);
+		Oid rcoltype = exprType(rcolnode);
+		int32 lcoltypmod = exprTypmod(lcolnode);
+		int32 rcoltypmod = exprTypmod(rcolnode);
+		Node *bestexpr;
+		int bestlocation;
+		Oid rescoltype;
+		int32 rescoltypmod;
+		Oid rescolcoll;
+
+		/* select common type, same as CASE et al */
+		rescoltype = select_common_type(pstate, list_make2(lcolnode, rcolnode),
+				context, &bestexpr);
+		bestlocation = exprLocation(bestexpr);
+		/* if same type and same typmod, use typmod; else default */
+		if (lcoltype == rcoltype && lcoltypmod == rcoltypmod)
+			rescoltypmod = lcoltypmod;
+		else
+			rescoltypmod = -1;
+
+		/*
+		 * Verify the coercions are actually possible.  If not, we'd fail
+		 * later anyway, but we want to fail now while we have sufficient
+		 * context to produce an error cursor position.
+		 *
+		 * For all non-UNKNOWN-type cases, we verify coercibility but we
+		 * don't modify the child's expression, for fear of changing the
+		 * child query's semantics.
+		 *
+		 * If a child expression is an UNKNOWN-type Const or Param, we
+		 * want to replace it with the coerced expression.  This can only
+		 * happen when the child is a leaf set-op node.  It's safe to
+		 * replace the expression because if the child query's semantics
+		 * depended on the type of this output column, it'd have already
+		 * coerced the UNKNOWN to something else.  We want to do this
+		 * because (a) we want to verify that a Const is valid for the
+		 * target type, or resolve the actual type of an UNKNOWN Param,
+		 * and (b) we want to avoid unnecessary discrepancies between the
+		 * output type of the child query and the resolved target type.
+		 * Such a discrepancy would disable optimization in the planner.
+		 *
+		 * If it's some other UNKNOWN-type node, eg a Var, we do nothing
+		 * (knowing that coerce_to_common_type would fail).  The planner
+		 * is sometimes able to fold an UNKNOWN Var to a constant before
+		 * it has to coerce the type, so failing now would just break
+		 * cases that might work.
+		 */
+		if (lcoltype != UNKNOWNOID)
+			lcolnode = coerce_to_common_type(pstate, lcolnode, rescoltype,
+					context);
+		else if (IsA(lcolnode, Const) || IsA(lcolnode, Param))
+		{
+			lcolnode = coerce_to_common_type(pstate, lcolnode, rescoltype,
+					context);
+			ltle->expr = (Expr *) lcolnode;
+		}
 
-		return (Node *) op;
+		if (rcoltype != UNKNOWNOID)
+			rcolnode = coerce_to_common_type(pstate, rcolnode, rescoltype,
+					context);
+		else if (IsA(rcolnode, Const) || IsA(rcolnode, Param))
+		{
+			rcolnode = coerce_to_common_type(pstate, rcolnode, rescoltype,
+					context);
+			rtle->expr = (Expr *) rcolnode;
+		}
+
+		/*
+		 * Select common collation.  A common collation is required for
+		 * all set operators except UNION ALL; see SQL:2008 7.13 <query
+		 * expression> Syntax Rule 15c.  (If we fail to identify a common
+		 * collation for a UNION ALL column, the curCollations element
+		 * will be set to InvalidOid, which may result in a runtime error
+		 * if something at a higher query level wants to use the column's
+		 * collation.)
+		 */
+		rescolcoll = select_common_collation(pstate,
+				list_make2(lcolnode, rcolnode),
+				(op->op == SETOP_UNION && op->all));
+
+		/* emit results */
+		op->colTypes = lappend_oid(op->colTypes, rescoltype);
+		op->colTypmods = lappend_int(op->colTypmods, rescoltypmod);
+		op->colCollations = lappend_oid(op->colCollations, rescolcoll);
+
+		/*
+		 * For all cases except UNION ALL, identify the grouping operators
+		 * (and, if available, sorting operators) that will be used to
+		 * eliminate duplicates.
+		 */
+		if (op->op != SETOP_UNION || !op->all)
+		{
+			SortGroupClause *grpcl = makeNode(SortGroupClause);
+			Oid sortop;
+			Oid eqop;
+			bool hashable;
+			ParseCallbackState pcbstate;
+
+			setup_parser_errposition_callback(&pcbstate, pstate, bestlocation);
+
+			/* determine the eqop and optional sortop */
+			get_sort_group_operators(rescoltype, false, true, false, &sortop,
+					&eqop, NULL, &hashable);
+
+			cancel_parser_errposition_callback(&pcbstate);
+
+			/* we don't have a tlist yet, so can't assign sortgrouprefs */
+			grpcl->tleSortGroupRef = 0;
+			grpcl->eqop = eqop;
+			grpcl->sortop = sortop;
+			grpcl->nulls_first = false; /* OK with or without sortop */
+			grpcl->hashable = hashable;
+
+			op->groupClauses = lappend(op->groupClauses, grpcl);
+		}
+
+		/*
+		 * Construct a dummy tlist entry to return.  We use a SetToDefault
+		 * node for the expression, since it carries exactly the fields
+		 * needed, but any other expression node type would do as well.
+		 */
+		if (targetlist)
+		{
+			SetToDefault *rescolnode = makeNode(SetToDefault);
+			TargetEntry *restle;
+
+			rescolnode->typeId = rescoltype;
+			rescolnode->typeMod = rescoltypmod;
+			rescolnode->collation = rescolcoll;
+			rescolnode->location = bestlocation;
+			restle = makeTargetEntry((Expr *) rescolnode, 0, /* no need to set resno */
+			NULL, false );
+			*targetlist = lappend(*targetlist, restle);
+		}
 	}
+	return 0;
 }
 
 /*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 174773b..de76481 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -166,7 +166,7 @@ static void insertSelectOptions(SelectStmt *stmt,
 								Node *limitOffset, Node *limitCount,
 								WithClause *withClause,
 								core_yyscan_t yyscanner);
-static Node *makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg);
+static Node *makeSetOp(SetOperation op, bool all, List *correspondingClause, Node *larg, Node *rarg);
 static Node *doNegate(Node *n, int location);
 static void doNegateFloat(Value *v);
 static Node *makeAndExpr(Node *lexpr, Node *rexpr, int location);
@@ -394,7 +394,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				relation_expr_list dostmt_opt_list
 				transform_element_list transform_type_list
 				TriggerTransitions TriggerReferencing
-				publication_name_list
+				publication_name_list opt_corresponding_clause
 
 %type <list>	group_by_list
 %type <node>	group_by_item empty_grouping_set rollup_clause cube_clause
@@ -608,7 +608,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
 	CLUSTER COALESCE COLLATE COLLATION COLUMN COMMENT COMMENTS COMMIT
 	COMMITTED CONCURRENTLY CONFIGURATION CONFLICT CONNECTION CONSTRAINT
-	CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE
+	CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY CORRESPONDING COST CREATE
 	CROSS CSV CUBE CURRENT_P
 	CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA
 	CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
@@ -10808,20 +10808,26 @@ simple_select:
 					n->fromClause = list_make1($2);
 					$$ = (Node *)n;
 				}
-			| select_clause UNION all_or_distinct select_clause
+			| select_clause UNION all_or_distinct opt_corresponding_clause select_clause
 				{
-					$$ = makeSetOp(SETOP_UNION, $3, $1, $4);
+					$$ = makeSetOp(SETOP_UNION, $3, $4, $1, $5);
 				}
-			| select_clause INTERSECT all_or_distinct select_clause
+			| select_clause INTERSECT all_or_distinct opt_corresponding_clause select_clause
 				{
-					$$ = makeSetOp(SETOP_INTERSECT, $3, $1, $4);
+					$$ = makeSetOp(SETOP_INTERSECT, $3, $4, $1, $5);
 				}
-			| select_clause EXCEPT all_or_distinct select_clause
+			| select_clause EXCEPT all_or_distinct opt_corresponding_clause select_clause
 				{
-					$$ = makeSetOp(SETOP_EXCEPT, $3, $1, $4);
+					$$ = makeSetOp(SETOP_EXCEPT, $3, $4, $1, $5);
 				}
 		;
 
+opt_corresponding_clause:
+			CORRESPONDING BY '(' name_list ')'		{ $$ = $4; }
+			| CORRESPONDING							{ $$ = list_make1(NIL); }
+			| /*EMPTY*/								{ $$ = NIL; }
+			;
+
 /*
  * SQL standard WITH clause looks like:
  *
@@ -14315,6 +14321,7 @@ unreserved_keyword:
 			| CONTINUE_P
 			| CONVERSION_P
 			| COPY
+			| CORRESPONDING
 			| COST
 			| CSV
 			| CUBE
@@ -15145,7 +15152,7 @@ insertSelectOptions(SelectStmt *stmt,
 }
 
 static Node *
-makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg)
+makeSetOp(SetOperation op, bool all, List *correspondingClause, Node *larg, Node *rarg)
 {
 	SelectStmt *n = makeNode(SelectStmt);
 
@@ -15153,6 +15160,7 @@ makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg)
 	n->all = all;
 	n->larg = (SelectStmt *) larg;
 	n->rarg = (SelectStmt *) rarg;
+	n->correspondingClause = correspondingClause;
 	return (Node *) n;
 }
 
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 2576e31..291b66b 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1903,3 +1903,25 @@ FigureColnameInternal(Node *node, char **name)
 
 	return strength;
 }
+
+/*
+ * orderCorrespondingList()
+ * order target list resno .
+ */
+List *
+orderCorrespondingList(List *targetlist)
+{
+	List *p_target = NIL;
+	ListCell *o_target;
+	int pos = 1;
+
+	foreach(o_target, targetlist)
+	{
+		TargetEntry *tar = (TargetEntry *) lfirst(o_target);
+
+		p_target = lappend(p_target,
+				makeTargetEntry(tar->expr, (AttrNumber) pos++, tar->resname, false));
+	}
+
+	return p_target;
+}
diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
index 8feec0b..ac23b1b 100644
--- a/src/backend/parser/parse_type.c
+++ b/src/backend/parser/parse_type.c
@@ -736,7 +736,8 @@ typeStringToTypeName(const char *str)
 		stmt->limitCount != NULL ||
 		stmt->lockingClause != NIL ||
 		stmt->withClause != NULL ||
-		stmt->op != SETOP_NONE)
+		stmt->op != SETOP_NONE ||
+		stmt->correspondingClause != NULL)
 		goto fail;
 	if (list_length(stmt->targetList) != 1)
 		goto fail;
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 07a8436..1dfff8f 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1420,7 +1420,9 @@ typedef struct SelectStmt
 	 * These fields are used only in "leaf" SelectStmts.
 	 */
 	List	   *distinctClause; /* NULL, list of DISTINCT ON exprs, or
-								 * lcons(NIL,NIL) for all (SELECT DISTINCT) */
+								* lcons(NIL,NIL) for all (SELECT DISTINCT) */
+	List	   *correspondingClause; /* NULL, list of CORRESPONDING BY exprs, or */
+								/* lcons(NIL, NIL) for CORRESPONDING */
 	IntoClause *intoClause;		/* target for SELECT INTO */
 	List	   *targetList;		/* the target list (of ResTarget) */
 	List	   *fromClause;		/* the FROM clause */
@@ -1486,8 +1488,9 @@ typedef struct SetOperationStmt
 	bool		all;			/* ALL specified? */
 	Node	   *larg;			/* left child */
 	Node	   *rarg;			/* right child */
-	/* Eventually add fields for CORRESPONDING spec here */
-
+	/* CORRESPONDING clause fields */
+	List	   *correspondingColumns;	/* NIL: No corresponding, else: CORRESPONDING or CORRESPONDIN
+						* BY matching columns. Not the original clause. */
 	/* Fields derived during parse analysis: */
 	List	   *colTypes;		/* OID list of output column type OIDs */
 	List	   *colTypmods;		/* integer list of output column typmods */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 985d650..77e0396 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -96,6 +96,7 @@ PG_KEYWORD("content", CONTENT_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("continue", CONTINUE_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("conversion", CONVERSION_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("copy", COPY, UNRESERVED_KEYWORD)
+PG_KEYWORD("corresponding", CORRESPONDING, UNRESERVED_KEYWORD)
 PG_KEYWORD("cost", COST, UNRESERVED_KEYWORD)
 PG_KEYWORD("create", CREATE, RESERVED_KEYWORD)
 PG_KEYWORD("cross", CROSS, TYPE_FUNC_NAME_KEYWORD)
diff --git a/src/include/parser/parse_target.h b/src/include/parser/parse_target.h
index d06a235..32b9cfd 100644
--- a/src/include/parser/parse_target.h
+++ b/src/include/parser/parse_target.h
@@ -42,5 +42,6 @@ extern TupleDesc expandRecordVariable(ParseState *pstate, Var *var,
 					 int levelsup);
 extern char *FigureColname(Node *node);
 extern char *FigureIndexColname(Node *node);
+extern List *orderCorrespondingList(List *targetlist);
 
 #endif   /* PARSE_TARGET_H */
diff --git a/src/test/regress/expected/union.out b/src/test/regress/expected/union.out
index 4d697ba..474558c 100644
--- a/src/test/regress/expected/union.out
+++ b/src/test/regress/expected/union.out
@@ -59,6 +59,205 @@ SELECT 1.1 AS two UNION SELECT 2.2 ORDER BY 1;
  2.2
 (2 rows)
 
+SELECT 1 AS two UNION CORRESPONDING SELECT 2 two;
+ two 
+-----
+   1
+   2
+(2 rows)
+
+SELECT 1 AS one UNION CORRESPONDING SELECT 1 one;
+ one 
+-----
+   1
+(1 row)
+
+SELECT 1 AS two UNION ALL CORRESPONDING SELECT 2 two;
+ two 
+-----
+   1
+   2
+(2 rows)
+
+SELECT 1 AS two UNION ALL CORRESPONDING SELECT 1 two;
+ two 
+-----
+   1
+   1
+(2 rows)
+
+SELECT 1 AS two UNION CORRESPONDING SELECT 2 two UNION CORRESPONDING SELECT 2 two;
+ two 
+-----
+   2
+   1
+(2 rows)
+
+SELECT 1 AS three UNION CORRESPONDING SELECT 2 three UNION ALL CORRESPONDING SELECT 2 three;
+ three 
+-------
+     1
+     2
+     2
+(3 rows)
+
+SELECT 1 AS three UNION CORRESPONDING SELECT 2 three UNION CORRESPONDING SELECT 3 three;
+ three 
+-------
+     3
+     2
+     1
+(3 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+SELECT 3 c, 2 b, 1 a UNION CORRESPONDING SELECT 6 c, 4 a, 5 b;
+ c | b | a 
+---+---+---
+ 3 | 2 | 1
+ 6 | 5 | 4
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a) SELECT 4 a, 5 b, 6 c;
+ a 
+---
+ 1
+ 4
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b) SELECT 4 a, 5 b, 6 c;
+ b 
+---
+ 2
+ 5
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(c) SELECT 4 a, 5 b, 6 c;
+ c 
+---
+ 3
+ 6
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b) SELECT 4 a, 5 b, 6 c;
+ a | b 
+---+---
+ 1 | 2
+ 4 | 5
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c) SELECT 4 a, 5 b, 6 c;
+ b | c 
+---+---
+ 2 | 3
+ 5 | 6
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, c) SELECT 4 a, 5 b, 6 c;
+ a | c 
+---+---
+ 1 | 3
+ 4 | 6
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 4 a, 5 b, 6 c;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY (c,b) SELECT 4 a, 5 b, 6 c, 8 d;
+ c | b 
+---+---
+ 3 | 2
+ 6 | 5
+(2 rows)
+
+-- CORRESPONDING column ordering, left clause's column ordering must be preserved.
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+SELECT 2 b, 1 a, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+ b | a | c 
+---+---+---
+ 2 | 1 | 3
+ 5 | 4 | 6
+(2 rows)
+
+SELECT 1 a, 3 c, 2 b UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+ a | c | b 
+---+---+---
+ 1 | 3 | 2
+ 4 | 6 | 5
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 5 b, 6 c, 4 a;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+SELECT 2 b, 1 a, 3 c UNION CORRESPONDING SELECT 5 b, 6 c, 4 a;
+ b | a | c 
+---+---+---
+ 2 | 1 | 3
+ 5 | 4 | 6
+(2 rows)
+
+-- CORRESPONDING BY column ordering, BY clause column ordering must be preserved.
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 4 a, 5 b, 6 c;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c, a) SELECT 4 a, 5 b, 6 c;
+ b | c | a 
+---+---+---
+ 2 | 3 | 1
+ 5 | 6 | 4
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(c, a, b) SELECT 4 a, 5 b, 6 c;
+ c | a | b 
+---+---+---
+ 3 | 1 | 2
+ 6 | 4 | 5
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 5 b, 6 c, 4 a;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+SELECT 2 b, 3 c, 1 a UNION CORRESPONDING BY(b, c, a) SELECT 4 a, 5 b, 6 c;
+ b | c | a 
+---+---+---
+ 2 | 3 | 1
+ 5 | 6 | 4
+(2 rows)
+
+SELECT 3 c, 2 b, 1 a UNION CORRESPONDING BY(c, a, b) SELECT 4 a, 5 b, 6 c;
+ c | a | b 
+---+---+---
+ 3 | 1 | 2
+ 6 | 4 | 5
+(2 rows)
+
 -- Mixed types
 SELECT 1.1 AS two UNION SELECT 2 ORDER BY 1;
  two 
@@ -258,6 +457,108 @@ ORDER BY 1;
  hi de ho neighbor
 (5 rows)
 
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+         five          
+-----------------------
+ -1.2345678901234e+200
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+                     0
+(5 rows)
+
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING BY(five)
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+         five          
+-----------------------
+ -1.2345678901234e+200
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+                     0
+(5 rows)
+
+SELECT f1 AS ten FROM FLOAT8_TBL
+UNION ALL CORRESPONDING
+SELECT f1 AS ten FROM FLOAT8_TBL;
+          ten          
+-----------------------
+                     0
+                -34.84
+               -1004.3
+ -1.2345678901234e+200
+ -1.2345678901234e-200
+                     0
+                -34.84
+               -1004.3
+ -1.2345678901234e+200
+ -1.2345678901234e-200
+(10 rows)
+
+SELECT f1 AS nine FROM FLOAT8_TBL
+UNION CORRESPONDING
+SELECT f1 AS nine FROM INT4_TBL
+ORDER BY 1;
+         nine          
+-----------------------
+ -1.2345678901234e+200
+           -2147483647
+               -123456
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+                     0
+                123456
+            2147483647
+(9 rows)
+
+SELECT f1 AS ten FROM FLOAT8_TBL
+UNION ALL CORRESPONDING
+SELECT f1 AS ten FROM INT4_TBL;
+          ten          
+-----------------------
+                     0
+                -34.84
+               -1004.3
+ -1.2345678901234e+200
+ -1.2345678901234e-200
+                     0
+                123456
+               -123456
+            2147483647
+           -2147483647
+(10 rows)
+
+SELECT f1 AS five FROM FLOAT8_TBL
+  WHERE f1 BETWEEN -1e6 AND 1e6
+UNION CORRESPONDING
+SELECT f1 AS five FROM INT4_TBL
+  WHERE f1 BETWEEN 0 AND 1000000;
+         five          
+-----------------------
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+                     0
+                123456
+(5 rows)
+
+SELECT * FROM FLOAT8_TBL 
+UNION corresponding SELECT * FROM FLOAT8_TBL;
+          f1           
+-----------------------
+                     0
+ -1.2345678901234e-200
+                -34.84
+ -1.2345678901234e+200
+               -1004.3
+(5 rows)
+
 --
 -- INTERSECT and EXCEPT
 --
@@ -320,6 +621,63 @@ SELECT q1 FROM int8_tbl EXCEPT ALL SELECT DISTINCT q2 FROM int8_tbl ORDER BY 1;
 
 SELECT q1 FROM int8_tbl EXCEPT ALL SELECT q1 FROM int8_tbl FOR NO KEY UPDATE;
 ERROR:  FOR NO KEY UPDATE is not allowed with UNION/INTERSECT/EXCEPT
+SELECT q2 FROM int8_tbl INTERSECT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+        q2        
+------------------
+              123
+ 4567890123456789
+(2 rows)
+
+SELECT q2 FROM int8_tbl INTERSECT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+        q2        
+------------------
+              123
+ 4567890123456789
+ 4567890123456789
+(3 rows)
+
+SELECT q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+        q2         
+-------------------
+ -4567890123456789
+               456
+(2 rows)
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+        q2         
+-------------------
+ -4567890123456789
+               456
+(2 rows)
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q1 AS q2 FROM int8_tbl ORDER BY 1;
+        q2         
+-------------------
+ -4567890123456789
+               456
+  4567890123456789
+(3 rows)
+
+SELECT q1 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+ q1 
+----
+(0 rows)
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+        q1        
+------------------
+              123
+ 4567890123456789
+(2 rows)
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q2 AS q1 FROM int8_tbl;
+        q1        
+------------------
+              123
+ 4567890123456789
+ 4567890123456789
+(3 rows)
+
 --
 -- Mixed types
 --
@@ -338,6 +696,21 @@ SELECT f1 FROM float8_tbl EXCEPT SELECT f1 FROM int4_tbl ORDER BY 1;
  -1.2345678901234e-200
 (4 rows)
 
+SELECT f1 FROM float8_tbl INTERSECT CORRESPONDING SELECT f1 FROM int4_tbl;
+ f1 
+----
+  0
+(1 row)
+
+SELECT f1 FROM float8_tbl EXCEPT CORRESPONDING SELECT f1 FROM int4_tbl ORDER BY 1;
+          f1           
+-----------------------
+ -1.2345678901234e+200
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+(4 rows)
+
 --
 -- Operator precedence and (((((extra))))) parentheses
 --
@@ -424,6 +797,24 @@ SELECT q1 FROM int8_tbl EXCEPT (((SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1)))
  4567890123456789
 (2 rows)
 
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2,q1 FROM int8_tbl
+ORDER BY q2,q1;
+ q1 | q2 
+----+----
+(0 rows)
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q1) SELECT q2,q1 FROM int8_tbl
+ORDER BY q1;
+ q1 
+----
+(0 rows)
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q2) SELECT q2,q1 FROM int8_tbl
+ORDER BY q2;
+ q2 
+----
+(0 rows)
+
 --
 -- New syntaxes (7.1) permit new tests
 --
@@ -459,6 +850,22 @@ SELECT '3.4'::numeric UNION SELECT 'foo';
 ERROR:  invalid input syntax for type numeric: "foo"
 LINE 1: SELECT '3.4'::numeric UNION SELECT 'foo';
                                            ^
+SELECT '3.4'::numeric AS a UNION CORRESPONDING SELECT 'foo' AS a;
+ERROR:  invalid input syntax for type numeric: "foo"
+LINE 1: ...CT '3.4'::numeric AS a UNION CORRESPONDING SELECT 'foo' AS a...
+                                                             ^
+SELECT a.f1 FROM (SELECT 'test' AS f1 FROM varchar_tbl) a
+UNION CORRESPONDING
+SELECT b.f1 FROM (SELECT f1 FROM varchar_tbl) b
+ORDER BY 1;
+  f1  
+------
+ a
+ ab
+ abcd
+ test
+(4 rows)
+
 --
 -- Test that expression-index constraints can be pushed down through
 -- UNION or UNION ALL
diff --git a/src/test/regress/sql/union.sql b/src/test/regress/sql/union.sql
index 48e6850..ddd08b1 100644
--- a/src/test/regress/sql/union.sql
+++ b/src/test/regress/sql/union.sql
@@ -20,6 +20,66 @@ SELECT 1 AS three UNION SELECT 2 UNION ALL SELECT 2 ORDER BY 1;
 
 SELECT 1.1 AS two UNION SELECT 2.2 ORDER BY 1;
 
+SELECT 1 AS two UNION CORRESPONDING SELECT 2 two;
+
+SELECT 1 AS one UNION CORRESPONDING SELECT 1 one;
+
+SELECT 1 AS two UNION ALL CORRESPONDING SELECT 2 two;
+
+SELECT 1 AS two UNION ALL CORRESPONDING SELECT 1 two;
+
+SELECT 1 AS two UNION CORRESPONDING SELECT 2 two UNION CORRESPONDING SELECT 2 two;
+
+SELECT 1 AS three UNION CORRESPONDING SELECT 2 three UNION ALL CORRESPONDING SELECT 2 three;
+
+SELECT 1 AS three UNION CORRESPONDING SELECT 2 three UNION CORRESPONDING SELECT 3 three;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+
+SELECT 3 c, 2 b, 1 a UNION CORRESPONDING SELECT 6 c, 4 a, 5 b;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(c) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, c) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY (c,b) SELECT 4 a, 5 b, 6 c, 8 d;
+
+-- CORRESPONDING column ordering, left clause's column ordering must be preserved.
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+
+SELECT 2 b, 1 a, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 3 c, 2 b UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 5 b, 6 c, 4 a;
+
+SELECT 2 b, 1 a, 3 c UNION CORRESPONDING SELECT 5 b, 6 c, 4 a;
+
+-- CORRESPONDING BY column ordering, BY clause column ordering must be preserved.
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c, a) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(c, a, b) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 5 b, 6 c, 4 a;
+
+SELECT 2 b, 3 c, 1 a UNION CORRESPONDING BY(b, c, a) SELECT 4 a, 5 b, 6 c;
+
+SELECT 3 c, 2 b, 1 a UNION CORRESPONDING BY(c, a, b) SELECT 4 a, 5 b, 6 c;
+
 -- Mixed types
 
 SELECT 1.1 AS two UNION SELECT 2 ORDER BY 1;
@@ -90,6 +150,38 @@ UNION
 SELECT TRIM(TRAILING FROM f1) FROM CHAR_TBL
 ORDER BY 1;
 
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING BY(five)
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+
+SELECT f1 AS ten FROM FLOAT8_TBL
+UNION ALL CORRESPONDING
+SELECT f1 AS ten FROM FLOAT8_TBL;
+
+SELECT f1 AS nine FROM FLOAT8_TBL
+UNION CORRESPONDING
+SELECT f1 AS nine FROM INT4_TBL
+ORDER BY 1;
+
+SELECT f1 AS ten FROM FLOAT8_TBL
+UNION ALL CORRESPONDING
+SELECT f1 AS ten FROM INT4_TBL;
+
+SELECT f1 AS five FROM FLOAT8_TBL
+  WHERE f1 BETWEEN -1e6 AND 1e6
+UNION CORRESPONDING
+SELECT f1 AS five FROM INT4_TBL
+  WHERE f1 BETWEEN 0 AND 1000000;
+
+SELECT * FROM FLOAT8_TBL 
+UNION corresponding SELECT * FROM FLOAT8_TBL;
+
 --
 -- INTERSECT and EXCEPT
 --
@@ -112,6 +204,22 @@ SELECT q1 FROM int8_tbl EXCEPT ALL SELECT DISTINCT q2 FROM int8_tbl ORDER BY 1;
 
 SELECT q1 FROM int8_tbl EXCEPT ALL SELECT q1 FROM int8_tbl FOR NO KEY UPDATE;
 
+SELECT q2 FROM int8_tbl INTERSECT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+
+SELECT q2 FROM int8_tbl INTERSECT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+
+SELECT q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q1 AS q2 FROM int8_tbl ORDER BY 1;
+
+SELECT q1 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q2 AS q1 FROM int8_tbl;
+
 --
 -- Mixed types
 --
@@ -120,6 +228,10 @@ SELECT f1 FROM float8_tbl INTERSECT SELECT f1 FROM int4_tbl ORDER BY 1;
 
 SELECT f1 FROM float8_tbl EXCEPT SELECT f1 FROM int4_tbl ORDER BY 1;
 
+SELECT f1 FROM float8_tbl INTERSECT CORRESPONDING SELECT f1 FROM int4_tbl;
+
+SELECT f1 FROM float8_tbl EXCEPT CORRESPONDING SELECT f1 FROM int4_tbl ORDER BY 1;
+
 --
 -- Operator precedence and (((((extra))))) parentheses
 --
@@ -150,6 +262,15 @@ SELECT q1 FROM int8_tbl EXCEPT SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1;
 -- But this should work:
 SELECT q1 FROM int8_tbl EXCEPT (((SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1))) ORDER BY 1;
 
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2,q1 FROM int8_tbl
+ORDER BY q2,q1;
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q1) SELECT q2,q1 FROM int8_tbl
+ORDER BY q1;
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q2) SELECT q2,q1 FROM int8_tbl
+ORDER BY q2;
+
 --
 -- New syntaxes (7.1) permit new tests
 --
@@ -170,6 +291,13 @@ ORDER BY 1;
 -- This should fail, but it should produce an error cursor
 SELECT '3.4'::numeric UNION SELECT 'foo';
 
+SELECT '3.4'::numeric AS a UNION CORRESPONDING SELECT 'foo' AS a;
+
+SELECT a.f1 FROM (SELECT 'test' AS f1 FROM varchar_tbl) a
+UNION CORRESPONDING
+SELECT b.f1 FROM (SELECT f1 FROM varchar_tbl) b
+ORDER BY 1;
+
 --
 -- Test that expression-index constraints can be pushed down through
 -- UNION or UNION ALL
#12Pavel Stehule
pavel.stehule@gmail.com
In reply to: Surafel Temesgen (#11)
Re: New CORRESPONDING clause design

2017-03-10 10:13 GMT+01:00 Surafel Temesgen <surafel3000@gmail.com>:

Yes, you are correct it should to work on CORRESPONDING clause case. SQL
20nn standard draft only said each query to be of the same degree in a case
of set operation without corresponding clause. The attached patch is
corrected as such .I add those new test case to regression test too

Thank you - I will recheck it.

Regards

Pavel

Show quoted text

Regards

Surafel

On Thu, Mar 9, 2017 at 9:49 PM, Pavel Stehule <pavel.stehule@gmail.com>
wrote:

hi

2017-03-09 17:19 GMT+01:00 Pavel Stehule <pavel.stehule@gmail.com>:

2017-03-09 13:18 GMT+01:00 Surafel Temesgen <surafel3000@gmail.com>:

Hi ,

Here is a patch corrected as your feedback except missed tests case
because corresponding by clause is implemented on the top of set operation
and you can’t do that to set operation without corresponding by clause too

I don't understand.

The following statement should to work

postgres=# select 10 as a, 20 as b union corresponding select 20 as a,
30 as b, 40 as c;

ERROR: each UNION query must have the same number of columns
LINE 1: ...elect 10 as a, 20 as b union corresponding select 20 as a, 3...

Corresponding clause should to work like projection filter.

I found a link to postgresql mailing list related to some older try to
this feature implementation

/messages/by-id/CAJZSWkX7C6Wmfo9Py4Ba
F8vHz_Ofko3AFSOsJPsb17rGmgBuDQ@mail.gmail.com

Regards

Pavel

Eg

postgres=# SELECT 1 a, 2 b, 3 c UNION SELECT 4 a, 5 b, 6 c, 8 d;

ERROR: each UNION query must have the same number of columns

LINE 1: SELECT 1 a, 2 b, 3 c UNION SELECT 4 a, 5 b, 6 c, 8 d;

^

postgres=# create table t1(a int, b int, c int);

CREATE TABLE

postgres=# create table t2(a int, b int);

CREATE TABLE

postgres=# select * from t1 union select * from t2;

ERROR: each UNION query must have the same number of columns

LINE 1: select * from t1 union select * from t2;

Thanks

Surafel

On Tue, Mar 7, 2017 at 10:26 PM, Pavel Stehule <pavel.stehule@gmail.com

wrote:

Hi

I am sending a review of this interesting feature.

I found following issues, questions:

1. unclosed tags <optional> in documentation
2. bad name "changeTargetEntry" - should be makeTargetEntry?
3. Why you removed lot of asserts in prepunion.c? These asserts should
be valid still
4. make_coresponding_target has wrong formatting
5. error "%s queries with a CORRESPONDING clause must have at least
one column with the same name" has wrong formatting, you can show position
6. previous issue is repeated - look on formatting ereport function,
please, you can use DETAIL and HINT fields
7. corresponding clause should to contain column list (I am looking to
ANSI/SQL 99) - you are using expr_list, what has not sense and probably it
has impact on all implementation.
8. typo orderCorrespondingLsit(List *targetlist)
9. I miss more tests for CORRESPONDING BY
10. if I understand to this feature, this query should to work

postgres=# SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY (c,b) SELECT 4 a, 5 b, 6 c, 8 d;
ERROR: each UNION query must have the same number of columns
LINE 1: ...1 a, 2 b, 3 c UNION CORRESPONDING BY (c,b) SELECT 4 a, 5 b, ...

postgres=# create table t1(a int, b int, c int);
CREATE TABLE
Time: 63,260 ms
postgres=# create table t2(a int, b int);
CREATE TABLE
Time: 57,120 ms
postgres=# select * from t1 union corresponding select * from t2;
ERROR: each UNION query must have the same number of columns
LINE 1: select * from t1 union corresponding select * from t2;

If it is your first patch to Postgres, then it is perfect work!

The @7 is probably most significant - I dislike a expression list
there. name_list should be better there.

Regards

Pavel

#13Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#12)
Re: New CORRESPONDING clause design

Hi

2017-03-10 12:55 GMT+01:00 Pavel Stehule <pavel.stehule@gmail.com>:

2017-03-10 10:13 GMT+01:00 Surafel Temesgen <surafel3000@gmail.com>:

Yes, you are correct it should to work on CORRESPONDING clause case. SQL
20nn standard draft only said each query to be of the same degree in a case
of set operation without corresponding clause. The attached patch is
corrected as such .I add those new test case to regression test too

Thank you - I will recheck it.

Fast check - it looks well

Regards

Pavel

#14Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#13)
1 attachment(s)
Re: New CORRESPONDING clause design

Hi

2017-03-10 13:49 GMT+01:00 Pavel Stehule <pavel.stehule@gmail.com>:

Hi

2017-03-10 12:55 GMT+01:00 Pavel Stehule <pavel.stehule@gmail.com>:

2017-03-10 10:13 GMT+01:00 Surafel Temesgen <surafel3000@gmail.com>:

Yes, you are correct it should to work on CORRESPONDING clause case. SQL
20nn standard draft only said each query to be of the same degree in a case
of set operation without corresponding clause. The attached patch is
corrected as such .I add those new test case to regression test too

Thank you - I will recheck it.

Fast check - it looks well

I am sending minor update - cleaning formatting and white spaces, error
messages + few more tests

It is working very well.

Maybe correspondingClause needs own node type with attached location. Then
context can be much better positioned.

Regards

Pavel

Show quoted text

Regards

Pavel

Attachments:

corresponding_clause_v4.patchtext/x-patch; charset=US-ASCII; name=corresponding_clause_v4.patchDownload
diff --git a/doc/src/sgml/queries.sgml b/doc/src/sgml/queries.sgml
index 30792f45f1..c3cdee54ad 100644
--- a/doc/src/sgml/queries.sgml
+++ b/doc/src/sgml/queries.sgml
@@ -1601,6 +1601,9 @@ SELECT DISTINCT ON (<replaceable>expression</replaceable> <optional>, <replaceab
    <primary>EXCEPT</primary>
   </indexterm>
   <indexterm zone="queries-union">
+   <primary>CORRESPONDING</primary>
+  </indexterm>
+  <indexterm zone="queries-union">
    <primary>set union</primary>
   </indexterm>
   <indexterm zone="queries-union">
@@ -1617,9 +1620,9 @@ SELECT DISTINCT ON (<replaceable>expression</replaceable> <optional>, <replaceab
    The results of two queries can be combined using the set operations
    union, intersection, and difference.  The syntax is
 <synopsis>
-<replaceable>query1</replaceable> UNION <optional>ALL</optional> <replaceable>query2</replaceable>
-<replaceable>query1</replaceable> INTERSECT <optional>ALL</optional> <replaceable>query2</replaceable>
-<replaceable>query1</replaceable> EXCEPT <optional>ALL</optional> <replaceable>query2</replaceable>
+<replaceable>query1</replaceable> UNION <optional>ALL</optional> <optional>CORRESPONDING</optional> <optional>BY</optional> <replaceable>query2</replaceable>
+<replaceable>query1</replaceable> INTERSECT <optional>ALL</optional> <optional>CORRESPONDING</optional> <optional>BY</optional> <replaceable>query2</replaceable>
+<replaceable>query1</replaceable> EXCEPT <optional>ALL</optional> <optional>CORRESPONDING</optional> <optional>BY</optional> <replaceable>query2</replaceable>
 </synopsis>
    <replaceable>query1</replaceable> and
    <replaceable>query2</replaceable> are queries that can use any of
@@ -1659,11 +1662,22 @@ SELECT DISTINCT ON (<replaceable>expression</replaceable> <optional>, <replaceab
   </para>
 
   <para>
-   In order to calculate the union, intersection, or difference of two
-   queries, the two queries must be <quote>union compatible</quote>,
-   which means that they return the same number of columns and
-   the corresponding columns have compatible data types, as
-   described in <xref linkend="typeconv-union-case">.
+   <literal>EXCEPT</> returns all rows that are in the result of
+   <replaceable>query1</replaceable> but not in the result of
+   <replaceable>query2</replaceable>.  (This is sometimes called the
+   <firstterm>difference</> between two queries.)  Again, duplicates
+   are eliminated unless <literal>EXCEPT ALL</> is used.
+  </para>
+
+  <para>
+   <literal>CORRESPONDING</> returns all columns that are in both 
+   <replaceable>query1</> and <replaceable>query2</> with the same name.
+  </para>
+
+  <para>
+   <literal>CORRESPONDING BY</> returns all columns in the column list 
+   that are also in both <replaceable>query1</> and 
+   <replaceable>query2</> with the same name.
   </para>
  </sect1>
 
diff --git a/doc/src/sgml/sql.sgml b/doc/src/sgml/sql.sgml
index 57396d7c24..f98c22e696 100644
--- a/doc/src/sgml/sql.sgml
+++ b/doc/src/sgml/sql.sgml
@@ -859,7 +859,7 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="PARAMETER">expression</replac
     [ WHERE <replaceable class="PARAMETER">condition</replaceable> ]
     [ GROUP BY <replaceable class="PARAMETER">expression</replaceable> [, ...] ]
     [ HAVING <replaceable class="PARAMETER">condition</replaceable> [, ...] ]
-    [ { UNION | INTERSECT | EXCEPT } [ ALL ] <replaceable class="PARAMETER">select</replaceable> ]
+    [ { UNION | INTERSECT | EXCEPT } [ ALL ] [ CORRESPONDING [ BY ( <replaceable class="PARAMETER">expression</replaceable> ) ] ] <replaceable class="PARAMETER">select</replaceable> ]
     [ ORDER BY <replaceable class="parameter">expression</replaceable> [ ASC | DESC | USING <replaceable class="parameter">operator</replaceable> ] [ NULLS { FIRST | LAST } ] [, ...] ]
     [ LIMIT { <replaceable class="PARAMETER">count</replaceable> | ALL } ]
     [ OFFSET <replaceable class="PARAMETER">start</replaceable> ]
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index ac8e50ef1d..71e06e5c2e 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2952,6 +2952,7 @@ _copySelectStmt(const SelectStmt *from)
 	COPY_NODE_FIELD(withClause);
 	COPY_SCALAR_FIELD(op);
 	COPY_SCALAR_FIELD(all);
+	COPY_NODE_FIELD(correspondingClause);
 	COPY_NODE_FIELD(larg);
 	COPY_NODE_FIELD(rarg);
 
@@ -2967,6 +2968,7 @@ _copySetOperationStmt(const SetOperationStmt *from)
 	COPY_SCALAR_FIELD(all);
 	COPY_NODE_FIELD(larg);
 	COPY_NODE_FIELD(rarg);
+	COPY_NODE_FIELD(correspondingColumns);
 	COPY_NODE_FIELD(colTypes);
 	COPY_NODE_FIELD(colTypmods);
 	COPY_NODE_FIELD(colCollations);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 54e9c983a0..77bedc4b23 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1041,6 +1041,7 @@ _equalSelectStmt(const SelectStmt *a, const SelectStmt *b)
 	COMPARE_NODE_FIELD(withClause);
 	COMPARE_SCALAR_FIELD(op);
 	COMPARE_SCALAR_FIELD(all);
+	COMPARE_NODE_FIELD(correspondingClause);
 	COMPARE_NODE_FIELD(larg);
 	COMPARE_NODE_FIELD(rarg);
 
@@ -1054,6 +1055,7 @@ _equalSetOperationStmt(const SetOperationStmt *a, const SetOperationStmt *b)
 	COMPARE_SCALAR_FIELD(all);
 	COMPARE_NODE_FIELD(larg);
 	COMPARE_NODE_FIELD(rarg);
+	COMPARE_NODE_FIELD(correspondingColumns);
 	COMPARE_NODE_FIELD(colTypes);
 	COMPARE_NODE_FIELD(colTypmods);
 	COMPARE_NODE_FIELD(colCollations);
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 6e52eb7231..7102ea96c2 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -3444,6 +3444,8 @@ raw_expression_tree_walker(Node *node,
 					return true;
 				if (walker(stmt->lockingClause, context))
 					return true;
+				if (walker(stmt->correspondingClause, context))
+					return true;
 				if (walker(stmt->withClause, context))
 					return true;
 				if (walker(stmt->larg, context))
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 825a7b283a..31138be625 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2559,6 +2559,7 @@ _outSelectStmt(StringInfo str, const SelectStmt *node)
 	WRITE_NODE_FIELD(withClause);
 	WRITE_ENUM_FIELD(op, SetOperation);
 	WRITE_BOOL_FIELD(all);
+	WRITE_NODE_FIELD(correspondingClause);
 	WRITE_NODE_FIELD(larg);
 	WRITE_NODE_FIELD(rarg);
 }
@@ -2866,6 +2867,7 @@ _outSetOperationStmt(StringInfo str, const SetOperationStmt *node)
 	WRITE_BOOL_FIELD(all);
 	WRITE_NODE_FIELD(larg);
 	WRITE_NODE_FIELD(rarg);
+	WRITE_NODE_FIELD(correspondingColumns);
 	WRITE_NODE_FIELD(colTypes);
 	WRITE_NODE_FIELD(colTypmods);
 	WRITE_NODE_FIELD(colCollations);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 8f39d93a12..b8bf58f70f 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -416,6 +416,7 @@ _readSetOperationStmt(void)
 	READ_BOOL_FIELD(all);
 	READ_NODE_FIELD(larg);
 	READ_NODE_FIELD(rarg);
+	READ_NODE_FIELD(correspondingColumns);
 	READ_NODE_FIELD(colTypes);
 	READ_NODE_FIELD(colTypmods);
 	READ_NODE_FIELD(colCollations);
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 1389db18ba..5be3fcf021 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -91,7 +91,8 @@ static List *generate_setop_tlist(List *colTypes, List *colCollations,
 					 Index varno,
 					 bool hack_constants,
 					 List *input_tlist,
-					 List *refnames_tlist);
+					 List *refnames_tlist,
+					 bool no_corresponding);
 static List *generate_append_tlist(List *colTypes, List *colCollations,
 					  bool flag,
 					  List *input_tlists,
@@ -110,6 +111,7 @@ static Node *adjust_appendrel_attrs_mutator(Node *node,
 static Relids adjust_relid_set(Relids relids, Index oldrelid, Index newrelid);
 static List *adjust_inherited_tlist(List *tlist,
 					   AppendRelInfo *context);
+static List *make_corresponding_target(List *corresponding_list, List *subroot_list);
 
 
 /*
@@ -187,6 +189,24 @@ plan_set_operations(PlannerInfo *root)
 									   leftmostQuery->targetList,
 									   &top_tlist);
 	}
+	/*
+	 * If corresponding column specified, we take column names from it.
+	 */
+	else if (topop->correspondingColumns != NIL )
+	{
+		/*
+		 * Recurse on setOperations tree to generate paths for set ops. The
+		 * final output path should have just the column types shown as the
+		 * output from the top-level node, plus possibly resjunk working
+		 * columns (we can rely on upper-level nodes to deal with that).
+		 */
+		path = recurse_set_operations((Node *) topop, root,
+									  topop->colTypes, topop->colCollations,
+									  true, -1,
+									  topop->correspondingColumns,
+									  &top_tlist,
+									  NULL);
+	}
 	else
 	{
 		/*
@@ -252,6 +272,8 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 					   List **pTargetList,
 					   double *pNumGroups)
 {
+	SetOperationStmt *topop = (SetOperationStmt *) root->parse->setOperations;
+
 	if (IsA(setOp, RangeTblRef))
 	{
 		RangeTblRef *rtr = (RangeTblRef *) setOp;
@@ -316,23 +338,45 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 		path = (Path *) create_subqueryscan_path(root, rel, subpath,
 												 NIL, NULL);
 
-		/*
-		 * Figure out the appropriate target list, and update the
-		 * SubqueryScanPath with the PathTarget form of that.
-		 */
-		tlist = generate_setop_tlist(colTypes, colCollations,
+		if (topop->correspondingColumns != NIL )
+		{
+			List *correspondingTarget;
+			/* make target list that only contains corresponding column from sub-queries list ito use it for projection */
+			correspondingTarget = make_corresponding_target(topop->correspondingColumns,
+					subroot->processed_tlist);
+			/*
+			 * Figure out the appropriate target list, and update the
+			 * SubqueryScanPath with the PathTarget form of that.
+			 */
+			tlist = generate_setop_tlist(colTypes, colCollations, flag,
+					rtr->rtindex, true, correspondingTarget, refnames_tlist, false);
+
+			path = apply_projection_to_path(root, rel, path,
+					create_pathtarget(root, tlist));
+
+			/* Return the fully-fledged tlist to caller, too */
+			*pTargetList = tlist;
+
+		}
+		else
+		{
+			/*
+			* Figure out the appropriate target list, and update the
+			* SubqueryScanPath with the PathTarget form of that.
+			*/
+			tlist = generate_setop_tlist(colTypes, colCollations,
 									 flag,
 									 rtr->rtindex,
 									 true,
 									 subroot->processed_tlist,
-									 refnames_tlist);
+									 refnames_tlist, true);
 
-		path = apply_projection_to_path(root, rel, path,
+			path = apply_projection_to_path(root, rel, path,
 										create_pathtarget(root, tlist));
 
-		/* Return the fully-fledged tlist to caller, too */
-		*pTargetList = tlist;
-
+			/* Return the fully-fledged tlist to caller, too */
+			*pTargetList = tlist;
+		}
 		/*
 		 * Estimate number of groups if caller wants it.  If the subquery used
 		 * grouping or aggregation, its output is probably mostly unique
@@ -392,7 +436,7 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 												0,
 												false,
 												*pTargetList,
-												refnames_tlist);
+												refnames_tlist, true);
 			path = apply_projection_to_path(root,
 											path->parent,
 											path,
@@ -1004,7 +1048,8 @@ generate_setop_tlist(List *colTypes, List *colCollations,
 					 Index varno,
 					 bool hack_constants,
 					 List *input_tlist,
-					 List *refnames_tlist)
+					 List *refnames_tlist,
+					 bool no_corresponding)
 {
 	List	   *tlist = NIL;
 	int			resno = 1;
@@ -1025,9 +1070,11 @@ generate_setop_tlist(List *colTypes, List *colCollations,
 		TargetEntry *reftle = (TargetEntry *) lfirst(rtlc);
 
 		rtlc = lnext(rtlc);
-
-		Assert(inputtle->resno == resno);
-		Assert(reftle->resno == resno);
+		if (no_corresponding)
+		{
+			Assert(inputtle->resno == resno);
+			Assert(reftle->resno == resno);
+		}
 		Assert(!inputtle->resjunk);
 		Assert(!reftle->resjunk);
 
@@ -2111,3 +2158,72 @@ adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node,
 	/* Now translate for this child */
 	return adjust_appendrel_attrs(root, node, appinfo);
 }
+
+/*
+ * generate target list from left target list with the order
+ * of right target list
+ */
+static List *
+make_corresponding_target(List *corresponding_list, List *subroot_list)
+{
+	Index internal = 0;
+	ListCell   *ltl;
+	ListCell   *rtl;
+	int			size;
+	int			i;
+	List *matchingColumns = NIL;
+	TargetEntry *simple_te_array;
+
+	size = list_length(corresponding_list) + 1;
+
+	/* Use array to find the order of corresponding columen */
+	simple_te_array = (TargetEntry *) palloc0(size * sizeof(TargetEntry));
+	foreach(ltl, corresponding_list)
+	{
+		foreach(rtl, subroot_list)
+		{
+			TargetEntry *ltle = (TargetEntry *) lfirst(ltl);
+			TargetEntry *rtle = (TargetEntry *) lfirst(rtl);
+
+			elog(DEBUG4, "%s", ltle->resname);
+
+			/* Names of the columns must be resolved before calling this method. */
+			Assert(ltle->resname != NULL);
+			Assert(rtle->resname != NULL);
+
+			/* If column names are the same, add it to array. */
+			if (strcmp(ltle->resname, rtle->resname) == 0)
+			{
+				simple_te_array[internal].xpr = rtle->xpr;
+				simple_te_array[internal].expr = rtle->expr;
+				simple_te_array[internal].resno = rtle->resno;
+				simple_te_array[internal].resname = rtle->resname;
+				simple_te_array[internal].ressortgroupref =
+						rtle->ressortgroupref;
+				simple_te_array[internal].resorigtbl = rtle->resorigtbl;
+				simple_te_array[internal].resorigcol = rtle->resorigcol;
+				simple_te_array[internal].resjunk = rtle->resjunk;
+				internal++;
+				continue;
+			}
+		}
+	}
+	/* traverse the array and make targetlist */
+	for (i = 0; i < internal; i++)
+	{
+		TargetEntry *tle = makeNode(TargetEntry);
+
+		tle->xpr = simple_te_array[i].xpr;
+		tle->expr = simple_te_array[i].expr;
+		tle->resno = simple_te_array[i].resno;
+		tle->resname = simple_te_array[i].resname;
+		tle->ressortgroupref = simple_te_array[i].ressortgroupref;
+		tle->resorigtbl = simple_te_array[i].resorigtbl;
+		tle->resorigcol = simple_te_array[i].resorigcol;
+		tle->resjunk = simple_te_array[i].resjunk;
+
+		matchingColumns = lappend(matchingColumns, tle);
+
+	}
+	return matchingColumns;
+}
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 3571e50aea..b714c099c2 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -76,6 +76,10 @@ static Query *transformCreateTableAsStmt(ParseState *pstate,
 						   CreateTableAsStmt *stmt);
 static void transformLockingClause(ParseState *pstate, Query *qry,
 					   LockingClause *lc, bool pushedDown);
+static List *determineMatchingColumns(List *ltargetlist, List *rtargetlist);
+static void *makeUnionDatatype(List *ltargetlist, List *rtargetlist,
+		SetOperationStmt *op, List **targetlist, ParseState *parentParseState,
+		const char *context);
 #ifdef RAW_EXPRESSION_COVERAGE_TEST
 static bool test_raw_expression_coverage(Node *node, void *context);
 #endif
@@ -1661,7 +1665,13 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->targetList = NIL;
 	targetvars = NIL;
 	targetnames = NIL;
-	left_tlist = list_head(leftmostQuery->targetList);
+	/* for corresponding clause limits top-level query targetlist to those
+	 * corresponding column list only
+	 */
+	if (sostmt->correspondingColumns != NIL )
+		left_tlist = list_head(sostmt->correspondingColumns);
+	else
+		left_tlist = list_head(leftmostQuery->targetList);
 
 	forthree(lct, sostmt->colTypes,
 			 lcm, sostmt->colTypmods,
@@ -1921,8 +1931,6 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 		SetOperationStmt *op = makeNode(SetOperationStmt);
 		List	   *ltargetlist;
 		List	   *rtargetlist;
-		ListCell   *ltl;
-		ListCell   *rtl;
 		const char *context;
 
 		context = (stmt->op == SETOP_UNION ? "UNION" :
@@ -1932,6 +1940,190 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 		op->op = stmt->op;
 		op->all = stmt->all;
 
+		/* If CORRESPONDING is specified, syntax and column name validities checked,
+		 * column filtering is done by a subquery later on.
+		 */
+		if (stmt->correspondingClause == NIL )
+		{
+			/* No CORRESPONDING clause, no operation needed for column filtering */
+			op->correspondingColumns = stmt->correspondingClause;
+		}
+		else if (linitial(stmt->correspondingClause) == NULL )
+		{
+			/* CORRESPONDING clause, find matching column names from both tables.
+			 * If there are none then it is a syntax error.
+			 */
+			Query *largQuery;
+			Query *rargQuery;
+			List *matchingColumns;
+			List *rightCorrespondingColumns;
+
+			/* Analyze left query to resolve column names. */
+			largQuery = parse_sub_analyze((Node *) stmt->larg, pstate, NULL, false, false );
+
+			/* Analyze right query to resolve column names. */
+			rargQuery = parse_sub_analyze((Node *) stmt->rarg, pstate, NULL, false, false );
+
+			/* Find matching columns from both queries. */
+			matchingColumns = determineMatchingColumns(largQuery->targetList,rargQuery->targetList);
+			/*
+			 * there may be out-of-order resnos in corresponding target list
+			 */
+			op->correspondingColumns = orderCorrespondingList(matchingColumns);
+
+			/* If matchingColumns is empty, there is an error.
+			 * At least one column in the select lists must have the same name.
+			 */
+			if (list_length(matchingColumns) == 0)
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("there are not any corresponding name"),
+						 errhint("%s queries with a CORRESPONDING clause must have at least one column with the same name",
+								 context),
+						 parser_errposition(pstate,
+											exprLocation((Node *) linitial(largQuery->targetList)))));
+			}
+
+			/* matchingColumns contain target list as it appear in left query targetList
+			 * we need matching column as it appear in right query targetlist inorder
+			 * to make output column type for corresponding columns
+			 */
+			rightCorrespondingColumns = determineMatchingColumns(rargQuery->targetList, largQuery->targetList);
+
+			/* make union'd datatype of output column
+			 */
+			makeUnionDatatype(matchingColumns, rightCorrespondingColumns, op, targetlist, pstate,
+					context);
+		}
+		else
+		{
+			/*
+			 * CORRESPONDING BY clause, find matching column names from both tables
+			 * and intersect them with BY(...) column list. If there are none
+			 * then it is a syntax error.
+			 */
+			Query *largQuery;
+			Query *rargQuery;
+			List *matchingColumns;
+			List *matchingColumnsFiltered;
+			List *rightCorrespondingColumns;
+			ListCell *corrtl;
+			ListCell *mctl;
+
+			/* Analyze left query to resolve column names. */
+			largQuery = parse_sub_analyze((Node *) stmt->larg, pstate, NULL,false, false );
+
+			/* Analyze right query to resolve column names. */
+			rargQuery = parse_sub_analyze((Node *) stmt->rarg, pstate, NULL, false, false );
+
+			/*
+			 * Find matching columns from both queries.
+			 * In CORRESPONDING BY, column names will be removed from
+			 * matchingColumns if they are not in the BY clause.
+			 * All columns in the BY clause must be in matchingColumns,
+			 * otherwise raise syntax error in BY clause.
+			 */
+			matchingColumns = determineMatchingColumns(largQuery->targetList,rargQuery->targetList);
+
+			/*
+			 * Every column name in correspondingClause must be in matchingColumns,
+			 * otherwise it is a syntax error.
+			 */
+			foreach(corrtl, stmt->correspondingClause)
+			{
+				Value* corrtle = lfirst(corrtl);
+
+				/* Get column name from correspondingClause. */
+				char *name = strVal(corrtle);
+				bool hasMatch = false;
+
+				foreach(mctl, matchingColumns)
+				{
+					TargetEntry *mctle = (TargetEntry *) lfirst(mctl);
+
+					Assert(mctle->resname != NULL);
+					Assert(name != NULL);
+
+					/* Compare correspondingClause column name with matchingColumns column names. */
+					if (strcmp(mctle->resname, name) == 0)
+					{
+						/* we have a match. */
+						hasMatch = true;
+						break;
+					}
+				}
+
+				if (!hasMatch)
+				{
+					/* CORRESPONDING BY clause contains a column name that is not in both tables. */
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("column name \"%s\" cannot be used in corresponding by clause", name),
+							 errhint("%s queries with a CORRESPONDING BY clause must only contain column names from both tables.",
+									 context),
+							 parser_errposition(pstate,
+												exprLocation((Node *) linitial(largQuery->targetList)))));
+				}
+			}
+
+			/* To preserve column ordering from correspondingClause and to remove
+			 * columns from matchingColumns if they are not in correspondingClause,
+			 * create a new list and finalize our column list for the
+			 * CORRESPONDING BY clause.
+			 */
+			matchingColumnsFiltered = NIL;
+
+			/* For each column in CORRESPONDING BY column list, check
+			 * column existence in matchingColumns.
+			 */
+			foreach(corrtl, stmt->correspondingClause)
+			{
+				Value* corrtle = lfirst(corrtl);
+
+				/* Get column name from correspondingClause. */
+				char *name = strVal(corrtle);
+
+				foreach(mctl, matchingColumns)
+				{
+					TargetEntry *mctle = (TargetEntry *) lfirst(mctl);
+
+					Assert(mctle->resname != NULL);
+					Assert(name != NULL);
+
+					if (strcmp(mctle->resname, name) == 0)
+					{
+						/* we have a match.*/
+						matchingColumnsFiltered = lappend(
+								matchingColumnsFiltered, mctle);
+						break;
+					}
+				}
+			}
+
+			/*
+			 * If matchingColumnsFiltered is empty, there is a semantic error.
+			 * At least one column in the select lists must have the same name.
+			 */
+			Assert(list_length(matchingColumnsFiltered) > 0);
+
+			/*
+			 * there may be out-of-order resnos in corresponding target list
+			 */
+			op->correspondingColumns = orderCorrespondingList(matchingColumnsFiltered);
+
+			/*
+			 * matchingColumns contain target list as it appear in left query targetList
+			 * we need matching column as it appear in right query targetlist inorder to
+			 * make output column type for corresponding columns
+			 */
+			rightCorrespondingColumns = determineMatchingColumns(rargQuery->targetList,matchingColumnsFiltered);
+			/*
+			 * make union'd datatype of output columns
+			 */
+			makeUnionDatatype(matchingColumnsFiltered, rightCorrespondingColumns, op, targetlist, pstate,
+					context);
+		}
 		/*
 		 * Recursively transform the left child node.
 		 */
@@ -1957,177 +2149,220 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 											 false,
 											 &rtargetlist);
 
-		/*
-		 * Verify that the two children have the same number of non-junk
-		 * columns, and determine the types of the merged output columns.
-		 */
-		if (list_length(ltargetlist) != list_length(rtargetlist))
-			ereport(ERROR,
-					(errcode(ERRCODE_SYNTAX_ERROR),
-				 errmsg("each %s query must have the same number of columns",
-						context),
-					 parser_errposition(pstate,
-										exprLocation((Node *) rtargetlist))));
+		if (op->correspondingColumns == NIL )
+		{
+			makeUnionDatatype(ltargetlist, rtargetlist, op, targetlist, pstate,
+					context);
+			/*
+			 * Verify that the two children have the same number of non-junk
+			 * columns, and determine the types of the merged output columns.
+			 */
+			if (list_length(ltargetlist) != list_length(rtargetlist))
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("each %s query must have the same number of columns", context),
+						 parser_errposition(pstate,
+											exprLocation((Node *) rtargetlist))));
+		}
 
-		if (targetlist)
-			*targetlist = NIL;
-		op->colTypes = NIL;
-		op->colTypmods = NIL;
-		op->colCollations = NIL;
-		op->groupClauses = NIL;
-		forboth(ltl, ltargetlist, rtl, rtargetlist)
+		return (Node *) op;
+	}
+}
+
+/*
+ * Processes targetlists of two queries for column equivalence to use
+ * with UNION/INTERSECT/EXCEPT CORRESPONDING.
+ */
+static List *
+determineMatchingColumns(List *ltargetlist, List *rtargetlist)
+{
+	List *matchingColumns = NIL;
+	ListCell *ltl;
+	ListCell *rtl;
+
+	foreach(ltl, ltargetlist)
+	{
+		foreach(rtl, rtargetlist)
 		{
 			TargetEntry *ltle = (TargetEntry *) lfirst(ltl);
 			TargetEntry *rtle = (TargetEntry *) lfirst(rtl);
-			Node	   *lcolnode = (Node *) ltle->expr;
-			Node	   *rcolnode = (Node *) rtle->expr;
-			Oid			lcoltype = exprType(lcolnode);
-			Oid			rcoltype = exprType(rcolnode);
-			int32		lcoltypmod = exprTypmod(lcolnode);
-			int32		rcoltypmod = exprTypmod(rcolnode);
-			Node	   *bestexpr;
-			int			bestlocation;
-			Oid			rescoltype;
-			int32		rescoltypmod;
-			Oid			rescolcoll;
-
-			/* select common type, same as CASE et al */
-			rescoltype = select_common_type(pstate,
-											list_make2(lcolnode, rcolnode),
-											context,
-											&bestexpr);
-			bestlocation = exprLocation(bestexpr);
-			/* if same type and same typmod, use typmod; else default */
-			if (lcoltype == rcoltype && lcoltypmod == rcoltypmod)
-				rescoltypmod = lcoltypmod;
-			else
-				rescoltypmod = -1;
 
-			/*
-			 * Verify the coercions are actually possible.  If not, we'd fail
-			 * later anyway, but we want to fail now while we have sufficient
-			 * context to produce an error cursor position.
-			 *
-			 * For all non-UNKNOWN-type cases, we verify coercibility but we
-			 * don't modify the child's expression, for fear of changing the
-			 * child query's semantics.
-			 *
-			 * If a child expression is an UNKNOWN-type Const or Param, we
-			 * want to replace it with the coerced expression.  This can only
-			 * happen when the child is a leaf set-op node.  It's safe to
-			 * replace the expression because if the child query's semantics
-			 * depended on the type of this output column, it'd have already
-			 * coerced the UNKNOWN to something else.  We want to do this
-			 * because (a) we want to verify that a Const is valid for the
-			 * target type, or resolve the actual type of an UNKNOWN Param,
-			 * and (b) we want to avoid unnecessary discrepancies between the
-			 * output type of the child query and the resolved target type.
-			 * Such a discrepancy would disable optimization in the planner.
-			 *
-			 * If it's some other UNKNOWN-type node, eg a Var, we do nothing
-			 * (knowing that coerce_to_common_type would fail).  The planner
-			 * is sometimes able to fold an UNKNOWN Var to a constant before
-			 * it has to coerce the type, so failing now would just break
-			 * cases that might work.
-			 */
-			if (lcoltype != UNKNOWNOID)
-				lcolnode = coerce_to_common_type(pstate, lcolnode,
-												 rescoltype, context);
-			else if (IsA(lcolnode, Const) ||
-					 IsA(lcolnode, Param))
-			{
-				lcolnode = coerce_to_common_type(pstate, lcolnode,
-												 rescoltype, context);
-				ltle->expr = (Expr *) lcolnode;
-			}
+			elog(DEBUG4, "%s", ltle->resname);
+
+			/* Names of the columns must be resolved before calling this method. */
+			Assert(ltle->resname != NULL);
+			Assert(rtle->resname != NULL);
 
-			if (rcoltype != UNKNOWNOID)
-				rcolnode = coerce_to_common_type(pstate, rcolnode,
-												 rescoltype, context);
-			else if (IsA(rcolnode, Const) ||
-					 IsA(rcolnode, Param))
+			/* If column names are the same, append it to the result. */
+			if (strcmp(ltle->resname, rtle->resname) == 0)
 			{
-				rcolnode = coerce_to_common_type(pstate, rcolnode,
-												 rescoltype, context);
-				rtle->expr = (Expr *) rcolnode;
+				matchingColumns = lappend(matchingColumns, ltle);
+				continue;
 			}
+		}
+	}
 
-			/*
-			 * Select common collation.  A common collation is required for
-			 * all set operators except UNION ALL; see SQL:2008 7.13 <query
-			 * expression> Syntax Rule 15c.  (If we fail to identify a common
-			 * collation for a UNION ALL column, the curCollations element
-			 * will be set to InvalidOid, which may result in a runtime error
-			 * if something at a higher query level wants to use the column's
-			 * collation.)
-			 */
-			rescolcoll = select_common_collation(pstate,
-											  list_make2(lcolnode, rcolnode),
-										 (op->op == SETOP_UNION && op->all));
+	return matchingColumns;
+}
 
-			/* emit results */
-			op->colTypes = lappend_oid(op->colTypes, rescoltype);
-			op->colTypmods = lappend_int(op->colTypmods, rescoltypmod);
-			op->colCollations = lappend_oid(op->colCollations, rescolcoll);
+/*
+ * process right and left target list to set up union'd datatype
+ */
+static void *
+makeUnionDatatype(List *ltargetlist, List *rtargetlist, SetOperationStmt *op,
+		List **targetlist, ParseState *pstate, const char *context)
+{
+	ListCell *ltl;
+	ListCell *rtl;
+	if (targetlist)
+		*targetlist = NIL;
+	op->colTypes = NIL;
+	op->colTypmods = NIL;
+	op->colCollations = NIL;
+	op->groupClauses = NIL;
+
+	forboth(ltl, ltargetlist, rtl, rtargetlist)
+	{
+		TargetEntry *ltle = (TargetEntry *) lfirst(ltl);
+		TargetEntry *rtle = (TargetEntry *) lfirst(rtl);
+		Node *lcolnode = (Node *) ltle->expr;
+		Node *rcolnode = (Node *) rtle->expr;
+		Oid lcoltype = exprType(lcolnode);
+		Oid rcoltype = exprType(rcolnode);
+		int32 lcoltypmod = exprTypmod(lcolnode);
+		int32 rcoltypmod = exprTypmod(rcolnode);
+		Node *bestexpr;
+		int bestlocation;
+		Oid rescoltype;
+		int32 rescoltypmod;
+		Oid rescolcoll;
+
+		/* select common type, same as CASE et al */
+		rescoltype = select_common_type(pstate, list_make2(lcolnode, rcolnode),
+				context, &bestexpr);
+		bestlocation = exprLocation(bestexpr);
+		/* if same type and same typmod, use typmod; else default */
+		if (lcoltype == rcoltype && lcoltypmod == rcoltypmod)
+			rescoltypmod = lcoltypmod;
+		else
+			rescoltypmod = -1;
 
-			/*
-			 * For all cases except UNION ALL, identify the grouping operators
-			 * (and, if available, sorting operators) that will be used to
-			 * eliminate duplicates.
-			 */
-			if (op->op != SETOP_UNION || !op->all)
-			{
-				SortGroupClause *grpcl = makeNode(SortGroupClause);
-				Oid			sortop;
-				Oid			eqop;
-				bool		hashable;
-				ParseCallbackState pcbstate;
-
-				setup_parser_errposition_callback(&pcbstate, pstate,
-												  bestlocation);
-
-				/* determine the eqop and optional sortop */
-				get_sort_group_operators(rescoltype,
-										 false, true, false,
-										 &sortop, &eqop, NULL,
-										 &hashable);
-
-				cancel_parser_errposition_callback(&pcbstate);
-
-				/* we don't have a tlist yet, so can't assign sortgrouprefs */
-				grpcl->tleSortGroupRef = 0;
-				grpcl->eqop = eqop;
-				grpcl->sortop = sortop;
-				grpcl->nulls_first = false;		/* OK with or without sortop */
-				grpcl->hashable = hashable;
-
-				op->groupClauses = lappend(op->groupClauses, grpcl);
-			}
+		/*
+		 * Verify the coercions are actually possible.  If not, we'd fail
+		 * later anyway, but we want to fail now while we have sufficient
+		 * context to produce an error cursor position.
+		 *
+		 * For all non-UNKNOWN-type cases, we verify coercibility but we
+		 * don't modify the child's expression, for fear of changing the
+		 * child query's semantics.
+		 *
+		 * If a child expression is an UNKNOWN-type Const or Param, we
+		 * want to replace it with the coerced expression.  This can only
+		 * happen when the child is a leaf set-op node.  It's safe to
+		 * replace the expression because if the child query's semantics
+		 * depended on the type of this output column, it'd have already
+		 * coerced the UNKNOWN to something else.  We want to do this
+		 * because (a) we want to verify that a Const is valid for the
+		 * target type, or resolve the actual type of an UNKNOWN Param,
+		 * and (b) we want to avoid unnecessary discrepancies between the
+		 * output type of the child query and the resolved target type.
+		 * Such a discrepancy would disable optimization in the planner.
+		 *
+		 * If it's some other UNKNOWN-type node, eg a Var, we do nothing
+		 * (knowing that coerce_to_common_type would fail).  The planner
+		 * is sometimes able to fold an UNKNOWN Var to a constant before
+		 * it has to coerce the type, so failing now would just break
+		 * cases that might work.
+		 */
+		if (lcoltype != UNKNOWNOID)
+			lcolnode = coerce_to_common_type(pstate, lcolnode, rescoltype,
+					context);
+		else if (IsA(lcolnode, Const) || IsA(lcolnode, Param))
+		{
+			lcolnode = coerce_to_common_type(pstate, lcolnode, rescoltype,
+					context);
+			ltle->expr = (Expr *) lcolnode;
+		}
 
-			/*
-			 * Construct a dummy tlist entry to return.  We use a SetToDefault
-			 * node for the expression, since it carries exactly the fields
-			 * needed, but any other expression node type would do as well.
-			 */
-			if (targetlist)
-			{
-				SetToDefault *rescolnode = makeNode(SetToDefault);
-				TargetEntry *restle;
-
-				rescolnode->typeId = rescoltype;
-				rescolnode->typeMod = rescoltypmod;
-				rescolnode->collation = rescolcoll;
-				rescolnode->location = bestlocation;
-				restle = makeTargetEntry((Expr *) rescolnode,
-										 0,		/* no need to set resno */
-										 NULL,
-										 false);
-				*targetlist = lappend(*targetlist, restle);
-			}
+		if (rcoltype != UNKNOWNOID)
+			rcolnode = coerce_to_common_type(pstate, rcolnode, rescoltype,
+					context);
+		else if (IsA(rcolnode, Const) || IsA(rcolnode, Param))
+		{
+			rcolnode = coerce_to_common_type(pstate, rcolnode, rescoltype,
+					context);
+			rtle->expr = (Expr *) rcolnode;
 		}
 
-		return (Node *) op;
+		/*
+		 * Select common collation.  A common collation is required for
+		 * all set operators except UNION ALL; see SQL:2008 7.13 <query
+		 * expression> Syntax Rule 15c.  (If we fail to identify a common
+		 * collation for a UNION ALL column, the curCollations element
+		 * will be set to InvalidOid, which may result in a runtime error
+		 * if something at a higher query level wants to use the column's
+		 * collation.)
+		 */
+		rescolcoll = select_common_collation(pstate,
+				list_make2(lcolnode, rcolnode),
+				(op->op == SETOP_UNION && op->all));
+
+		/* emit results */
+		op->colTypes = lappend_oid(op->colTypes, rescoltype);
+		op->colTypmods = lappend_int(op->colTypmods, rescoltypmod);
+		op->colCollations = lappend_oid(op->colCollations, rescolcoll);
+
+		/*
+		 * For all cases except UNION ALL, identify the grouping operators
+		 * (and, if available, sorting operators) that will be used to
+		 * eliminate duplicates.
+		 */
+		if (op->op != SETOP_UNION || !op->all)
+		{
+			SortGroupClause *grpcl = makeNode(SortGroupClause);
+			Oid sortop;
+			Oid eqop;
+			bool hashable;
+			ParseCallbackState pcbstate;
+
+			setup_parser_errposition_callback(&pcbstate, pstate, bestlocation);
+
+			/* determine the eqop and optional sortop */
+			get_sort_group_operators(rescoltype, false, true, false, &sortop,
+					&eqop, NULL, &hashable);
+
+			cancel_parser_errposition_callback(&pcbstate);
+
+			/* we don't have a tlist yet, so can't assign sortgrouprefs */
+			grpcl->tleSortGroupRef = 0;
+			grpcl->eqop = eqop;
+			grpcl->sortop = sortop;
+			grpcl->nulls_first = false; /* OK with or without sortop */
+			grpcl->hashable = hashable;
+
+			op->groupClauses = lappend(op->groupClauses, grpcl);
+		}
+
+		/*
+		 * Construct a dummy tlist entry to return.  We use a SetToDefault
+		 * node for the expression, since it carries exactly the fields
+		 * needed, but any other expression node type would do as well.
+		 */
+		if (targetlist)
+		{
+			SetToDefault *rescolnode = makeNode(SetToDefault);
+			TargetEntry *restle;
+
+			rescolnode->typeId = rescoltype;
+			rescolnode->typeMod = rescoltypmod;
+			rescolnode->collation = rescolcoll;
+			rescolnode->location = bestlocation;
+			restle = makeTargetEntry((Expr *) rescolnode, 0, /* no need to set resno */
+			NULL, false );
+			*targetlist = lappend(*targetlist, restle);
+		}
 	}
+	return 0;
 }
 
 /*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index e7acc2d9a2..c04aa75507 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -166,7 +166,7 @@ static void insertSelectOptions(SelectStmt *stmt,
 								Node *limitOffset, Node *limitCount,
 								WithClause *withClause,
 								core_yyscan_t yyscanner);
-static Node *makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg);
+static Node *makeSetOp(SetOperation op, bool all, List *correspondingClause, Node *larg, Node *rarg);
 static Node *doNegate(Node *n, int location);
 static void doNegateFloat(Value *v);
 static Node *makeAndExpr(Node *lexpr, Node *rexpr, int location);
@@ -394,7 +394,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				relation_expr_list dostmt_opt_list
 				transform_element_list transform_type_list
 				TriggerTransitions TriggerReferencing
-				publication_name_list
+				publication_name_list opt_corresponding_clause
 
 %type <list>	group_by_list
 %type <node>	group_by_item empty_grouping_set rollup_clause cube_clause
@@ -614,7 +614,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
 	CLUSTER COALESCE COLLATE COLLATION COLUMN COLUMNS COMMENT COMMENTS COMMIT
 	COMMITTED CONCURRENTLY CONFIGURATION CONFLICT CONNECTION CONSTRAINT
-	CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE
+	CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY CORRESPONDING COST CREATE
 	CROSS CSV CUBE CURRENT_P
 	CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA
 	CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
@@ -10725,20 +10725,26 @@ simple_select:
 					n->fromClause = list_make1($2);
 					$$ = (Node *)n;
 				}
-			| select_clause UNION all_or_distinct select_clause
+			| select_clause UNION all_or_distinct opt_corresponding_clause select_clause
 				{
-					$$ = makeSetOp(SETOP_UNION, $3, $1, $4);
+					$$ = makeSetOp(SETOP_UNION, $3, $4, $1, $5);
 				}
-			| select_clause INTERSECT all_or_distinct select_clause
+			| select_clause INTERSECT all_or_distinct opt_corresponding_clause select_clause
 				{
-					$$ = makeSetOp(SETOP_INTERSECT, $3, $1, $4);
+					$$ = makeSetOp(SETOP_INTERSECT, $3, $4, $1, $5);
 				}
-			| select_clause EXCEPT all_or_distinct select_clause
+			| select_clause EXCEPT all_or_distinct opt_corresponding_clause select_clause
 				{
-					$$ = makeSetOp(SETOP_EXCEPT, $3, $1, $4);
+					$$ = makeSetOp(SETOP_EXCEPT, $3, $4, $1, $5);
 				}
 		;
 
+opt_corresponding_clause:
+			CORRESPONDING BY '(' name_list ')'		{ $$ = $4; }
+			| CORRESPONDING							{ $$ = list_make1(NIL); }
+			| /*EMPTY*/								{ $$ = NIL; }
+			;
+
 /*
  * SQL standard WITH clause looks like:
  *
@@ -14396,6 +14402,7 @@ unreserved_keyword:
 			| CONTINUE_P
 			| CONVERSION_P
 			| COPY
+			| CORRESPONDING
 			| COST
 			| CSV
 			| CUBE
@@ -15228,7 +15235,7 @@ insertSelectOptions(SelectStmt *stmt,
 }
 
 static Node *
-makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg)
+makeSetOp(SetOperation op, bool all, List *correspondingClause, Node *larg, Node *rarg)
 {
 	SelectStmt *n = makeNode(SelectStmt);
 
@@ -15236,6 +15243,7 @@ makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg)
 	n->all = all;
 	n->larg = (SelectStmt *) larg;
 	n->rarg = (SelectStmt *) rarg;
+	n->correspondingClause = correspondingClause;
 	return (Node *) n;
 }
 
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 3b84140a9b..90eb4e48aa 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1910,3 +1910,25 @@ FigureColnameInternal(Node *node, char **name)
 
 	return strength;
 }
+
+/*
+ * orderCorrespondingList()
+ * order target list resno .
+ */
+List *
+orderCorrespondingList(List *targetlist)
+{
+	List *p_target = NIL;
+	ListCell *o_target;
+	int pos = 1;
+
+	foreach(o_target, targetlist)
+	{
+		TargetEntry *tar = (TargetEntry *) lfirst(o_target);
+
+		p_target = lappend(p_target,
+				makeTargetEntry(tar->expr, (AttrNumber) pos++, tar->resname, false));
+	}
+
+	return p_target;
+}
diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
index 0d7a2b1e1b..b553d847d8 100644
--- a/src/backend/parser/parse_type.c
+++ b/src/backend/parser/parse_type.c
@@ -735,7 +735,8 @@ typeStringToTypeName(const char *str)
 		stmt->limitCount != NULL ||
 		stmt->lockingClause != NIL ||
 		stmt->withClause != NULL ||
-		stmt->op != SETOP_NONE)
+		stmt->op != SETOP_NONE ||
+		stmt->correspondingClause != NULL)
 		goto fail;
 	if (list_length(stmt->targetList) != 1)
 		goto fail;
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index a44d2178e1..fa2c57f043 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1459,7 +1459,9 @@ typedef struct SelectStmt
 	 * These fields are used only in "leaf" SelectStmts.
 	 */
 	List	   *distinctClause; /* NULL, list of DISTINCT ON exprs, or
-								 * lcons(NIL,NIL) for all (SELECT DISTINCT) */
+								* lcons(NIL,NIL) for all (SELECT DISTINCT) */
+	List	   *correspondingClause; /* NULL, list of CORRESPONDING BY exprs, or */
+								/* lcons(NIL, NIL) for CORRESPONDING */
 	IntoClause *intoClause;		/* target for SELECT INTO */
 	List	   *targetList;		/* the target list (of ResTarget) */
 	List	   *fromClause;		/* the FROM clause */
@@ -1525,8 +1527,9 @@ typedef struct SetOperationStmt
 	bool		all;			/* ALL specified? */
 	Node	   *larg;			/* left child */
 	Node	   *rarg;			/* right child */
-	/* Eventually add fields for CORRESPONDING spec here */
-
+	/* CORRESPONDING clause fields */
+	List	   *correspondingColumns;	/* NIL: No corresponding, else: CORRESPONDING or CORRESPONDIN
+						* BY matching columns. Not the original clause. */
 	/* Fields derived during parse analysis: */
 	List	   *colTypes;		/* OID list of output column type OIDs */
 	List	   *colTypmods;		/* integer list of output column typmods */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 28c4dab258..36ada7928a 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -97,6 +97,7 @@ PG_KEYWORD("content", CONTENT_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("continue", CONTINUE_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("conversion", CONVERSION_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("copy", COPY, UNRESERVED_KEYWORD)
+PG_KEYWORD("corresponding", CORRESPONDING, UNRESERVED_KEYWORD)
 PG_KEYWORD("cost", COST, UNRESERVED_KEYWORD)
 PG_KEYWORD("create", CREATE, RESERVED_KEYWORD)
 PG_KEYWORD("cross", CROSS, TYPE_FUNC_NAME_KEYWORD)
diff --git a/src/include/parser/parse_target.h b/src/include/parser/parse_target.h
index d06a235df0..32b9cfda17 100644
--- a/src/include/parser/parse_target.h
+++ b/src/include/parser/parse_target.h
@@ -42,5 +42,6 @@ extern TupleDesc expandRecordVariable(ParseState *pstate, Var *var,
 					 int levelsup);
 extern char *FigureColname(Node *node);
 extern char *FigureIndexColname(Node *node);
+extern List *orderCorrespondingList(List *targetlist);
 
 #endif   /* PARSE_TARGET_H */
diff --git a/src/test/regress/expected/union.out b/src/test/regress/expected/union.out
index 4d697bada7..e54956fba6 100644
--- a/src/test/regress/expected/union.out
+++ b/src/test/regress/expected/union.out
@@ -59,6 +59,221 @@ SELECT 1.1 AS two UNION SELECT 2.2 ORDER BY 1;
  2.2
 (2 rows)
 
+SELECT 1 AS two UNION CORRESPONDING SELECT 2 two;
+ two 
+-----
+   1
+   2
+(2 rows)
+
+SELECT 1 AS one UNION CORRESPONDING SELECT 1 one;
+ one 
+-----
+   1
+(1 row)
+
+SELECT 1 AS two UNION ALL CORRESPONDING SELECT 2 two;
+ two 
+-----
+   1
+   2
+(2 rows)
+
+SELECT 1 AS two UNION ALL CORRESPONDING SELECT 1 two;
+ two 
+-----
+   1
+   1
+(2 rows)
+
+SELECT 1 AS two UNION CORRESPONDING SELECT 2 two UNION CORRESPONDING SELECT 2 two;
+ two 
+-----
+   2
+   1
+(2 rows)
+
+SELECT 1 AS three UNION CORRESPONDING SELECT 2 three UNION ALL CORRESPONDING SELECT 2 three;
+ three 
+-------
+     1
+     2
+     2
+(3 rows)
+
+SELECT 1 AS three UNION CORRESPONDING SELECT 2 three UNION CORRESPONDING SELECT 3 three;
+ three 
+-------
+     3
+     2
+     1
+(3 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+SELECT 3 c, 2 b, 1 a UNION CORRESPONDING SELECT 6 c, 4 a, 5 b;
+ c | b | a 
+---+---+---
+ 3 | 2 | 1
+ 6 | 5 | 4
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a) SELECT 4 a, 5 b, 6 c;
+ a 
+---
+ 1
+ 4
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b) SELECT 4 a, 5 b, 6 c;
+ b 
+---
+ 2
+ 5
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(c) SELECT 4 a, 5 b, 6 c;
+ c 
+---
+ 3
+ 6
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b) SELECT 4 a, 5 b, 6 c;
+ a | b 
+---+---
+ 1 | 2
+ 4 | 5
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c) SELECT 4 a, 5 b, 6 c;
+ b | c 
+---+---
+ 2 | 3
+ 5 | 6
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, c) SELECT 4 a, 5 b, 6 c;
+ a | c 
+---+---
+ 1 | 3
+ 4 | 6
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 4 a, 5 b, 6 c;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY (c,b) SELECT 4 a, 5 b, 6 c, 8 d;
+ c | b 
+---+---
+ 3 | 2
+ 6 | 5
+(2 rows)
+
+-- CORRESPONDING column ordering, left clause's column ordering must be preserved.
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+SELECT 2 b, 1 a, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+ b | a | c 
+---+---+---
+ 2 | 1 | 3
+ 5 | 4 | 6
+(2 rows)
+
+SELECT 1 a, 3 c, 2 b UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+ a | c | b 
+---+---+---
+ 1 | 3 | 2
+ 4 | 6 | 5
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 5 b, 6 c, 4 a;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+SELECT 2 b, 1 a, 3 c UNION CORRESPONDING SELECT 5 b, 6 c, 4 a;
+ b | a | c 
+---+---+---
+ 2 | 1 | 3
+ 5 | 4 | 6
+(2 rows)
+
+-- CORRESPONDING BY column ordering, BY clause column ordering must be preserved.
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 4 a, 5 b, 6 c;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c, a) SELECT 4 a, 5 b, 6 c;
+ b | c | a 
+---+---+---
+ 2 | 3 | 1
+ 5 | 6 | 4
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(c, a, b) SELECT 4 a, 5 b, 6 c;
+ c | a | b 
+---+---+---
+ 3 | 1 | 2
+ 6 | 4 | 5
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 5 b, 6 c, 4 a;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+SELECT 2 b, 3 c, 1 a UNION CORRESPONDING BY(b, c, a) SELECT 4 a, 5 b, 6 c;
+ b | c | a 
+---+---+---
+ 2 | 3 | 1
+ 5 | 6 | 4
+(2 rows)
+
+SELECT 3 c, 2 b, 1 a UNION CORRESPONDING BY(c, a, b) SELECT 4 a, 5 b, 6 c;
+ c | a | b 
+---+---+---
+ 3 | 1 | 2
+ 6 | 4 | 5
+(2 rows)
+
+-- should to fail
+SELECT 10 a, 20 b UNION ALL CORRESPONDING SELECT 10 c, 20 d;
+ERROR:  there are not any corresponding name
+LINE 1: SELECT 10 a, 20 b UNION ALL CORRESPONDING SELECT 10 c, 20 d;
+               ^
+HINT:  UNION queries with a CORRESPONDING clause must have at least one column with the same name
+SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (a,b) SELECT 10 c, 20 d;
+ERROR:  column name "a" cannot be used in corresponding by clause
+LINE 1: SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (a,b) SELECT 10...
+               ^
+HINT:  UNION queries with a CORRESPONDING BY clause must only contain column names from both tables.
+SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (x) SELECT 10 c, 20 d;
+ERROR:  column name "x" cannot be used in corresponding by clause
+LINE 1: SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (x) SELECT 10 c...
+               ^
+HINT:  UNION queries with a CORRESPONDING BY clause must only contain column names from both tables.
 -- Mixed types
 SELECT 1.1 AS two UNION SELECT 2 ORDER BY 1;
  two 
@@ -258,6 +473,108 @@ ORDER BY 1;
  hi de ho neighbor
 (5 rows)
 
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+         five          
+-----------------------
+ -1.2345678901234e+200
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+                     0
+(5 rows)
+
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING BY(five)
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+         five          
+-----------------------
+ -1.2345678901234e+200
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+                     0
+(5 rows)
+
+SELECT f1 AS ten FROM FLOAT8_TBL
+UNION ALL CORRESPONDING
+SELECT f1 AS ten FROM FLOAT8_TBL;
+          ten          
+-----------------------
+                     0
+                -34.84
+               -1004.3
+ -1.2345678901234e+200
+ -1.2345678901234e-200
+                     0
+                -34.84
+               -1004.3
+ -1.2345678901234e+200
+ -1.2345678901234e-200
+(10 rows)
+
+SELECT f1 AS nine FROM FLOAT8_TBL
+UNION CORRESPONDING
+SELECT f1 AS nine FROM INT4_TBL
+ORDER BY 1;
+         nine          
+-----------------------
+ -1.2345678901234e+200
+           -2147483647
+               -123456
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+                     0
+                123456
+            2147483647
+(9 rows)
+
+SELECT f1 AS ten FROM FLOAT8_TBL
+UNION ALL CORRESPONDING
+SELECT f1 AS ten FROM INT4_TBL;
+          ten          
+-----------------------
+                     0
+                -34.84
+               -1004.3
+ -1.2345678901234e+200
+ -1.2345678901234e-200
+                     0
+                123456
+               -123456
+            2147483647
+           -2147483647
+(10 rows)
+
+SELECT f1 AS five FROM FLOAT8_TBL
+  WHERE f1 BETWEEN -1e6 AND 1e6
+UNION CORRESPONDING
+SELECT f1 AS five FROM INT4_TBL
+  WHERE f1 BETWEEN 0 AND 1000000;
+         five          
+-----------------------
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+                     0
+                123456
+(5 rows)
+
+SELECT * FROM FLOAT8_TBL
+UNION corresponding SELECT * FROM FLOAT8_TBL;
+          f1           
+-----------------------
+                     0
+ -1.2345678901234e-200
+                -34.84
+ -1.2345678901234e+200
+               -1004.3
+(5 rows)
+
 --
 -- INTERSECT and EXCEPT
 --
@@ -320,6 +637,63 @@ SELECT q1 FROM int8_tbl EXCEPT ALL SELECT DISTINCT q2 FROM int8_tbl ORDER BY 1;
 
 SELECT q1 FROM int8_tbl EXCEPT ALL SELECT q1 FROM int8_tbl FOR NO KEY UPDATE;
 ERROR:  FOR NO KEY UPDATE is not allowed with UNION/INTERSECT/EXCEPT
+SELECT q2 FROM int8_tbl INTERSECT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+        q2        
+------------------
+              123
+ 4567890123456789
+(2 rows)
+
+SELECT q2 FROM int8_tbl INTERSECT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+        q2        
+------------------
+              123
+ 4567890123456789
+ 4567890123456789
+(3 rows)
+
+SELECT q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+        q2         
+-------------------
+ -4567890123456789
+               456
+(2 rows)
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+        q2         
+-------------------
+ -4567890123456789
+               456
+(2 rows)
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q1 AS q2 FROM int8_tbl ORDER BY 1;
+        q2         
+-------------------
+ -4567890123456789
+               456
+  4567890123456789
+(3 rows)
+
+SELECT q1 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+ q1 
+----
+(0 rows)
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+        q1        
+------------------
+              123
+ 4567890123456789
+(2 rows)
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q2 AS q1 FROM int8_tbl;
+        q1        
+------------------
+              123
+ 4567890123456789
+ 4567890123456789
+(3 rows)
+
 --
 -- Mixed types
 --
@@ -338,6 +712,21 @@ SELECT f1 FROM float8_tbl EXCEPT SELECT f1 FROM int4_tbl ORDER BY 1;
  -1.2345678901234e-200
 (4 rows)
 
+SELECT f1 FROM float8_tbl INTERSECT CORRESPONDING SELECT f1 FROM int4_tbl;
+ f1 
+----
+  0
+(1 row)
+
+SELECT f1 FROM float8_tbl EXCEPT CORRESPONDING SELECT f1 FROM int4_tbl ORDER BY 1;
+          f1           
+-----------------------
+ -1.2345678901234e+200
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+(4 rows)
+
 --
 -- Operator precedence and (((((extra))))) parentheses
 --
@@ -424,6 +813,24 @@ SELECT q1 FROM int8_tbl EXCEPT (((SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1)))
  4567890123456789
 (2 rows)
 
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2,q1 FROM int8_tbl
+ORDER BY q2,q1;
+ q1 | q2 
+----+----
+(0 rows)
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q1) SELECT q2,q1 FROM int8_tbl
+ORDER BY q1;
+ q1 
+----
+(0 rows)
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q2) SELECT q2,q1 FROM int8_tbl
+ORDER BY q2;
+ q2 
+----
+(0 rows)
+
 --
 -- New syntaxes (7.1) permit new tests
 --
@@ -459,6 +866,22 @@ SELECT '3.4'::numeric UNION SELECT 'foo';
 ERROR:  invalid input syntax for type numeric: "foo"
 LINE 1: SELECT '3.4'::numeric UNION SELECT 'foo';
                                            ^
+SELECT '3.4'::numeric AS a UNION CORRESPONDING SELECT 'foo' AS a;
+ERROR:  invalid input syntax for type numeric: "foo"
+LINE 1: ...CT '3.4'::numeric AS a UNION CORRESPONDING SELECT 'foo' AS a...
+                                                             ^
+SELECT a.f1 FROM (SELECT 'test' AS f1 FROM varchar_tbl) a
+UNION CORRESPONDING
+SELECT b.f1 FROM (SELECT f1 FROM varchar_tbl) b
+ORDER BY 1;
+  f1  
+------
+ a
+ ab
+ abcd
+ test
+(4 rows)
+
 --
 -- Test that expression-index constraints can be pushed down through
 -- UNION or UNION ALL
diff --git a/src/test/regress/sql/union.sql b/src/test/regress/sql/union.sql
index 48e6850798..b0f3152ef1 100644
--- a/src/test/regress/sql/union.sql
+++ b/src/test/regress/sql/union.sql
@@ -20,6 +20,74 @@ SELECT 1 AS three UNION SELECT 2 UNION ALL SELECT 2 ORDER BY 1;
 
 SELECT 1.1 AS two UNION SELECT 2.2 ORDER BY 1;
 
+SELECT 1 AS two UNION CORRESPONDING SELECT 2 two;
+
+SELECT 1 AS one UNION CORRESPONDING SELECT 1 one;
+
+SELECT 1 AS two UNION ALL CORRESPONDING SELECT 2 two;
+
+SELECT 1 AS two UNION ALL CORRESPONDING SELECT 1 two;
+
+SELECT 1 AS two UNION CORRESPONDING SELECT 2 two UNION CORRESPONDING SELECT 2 two;
+
+SELECT 1 AS three UNION CORRESPONDING SELECT 2 three UNION ALL CORRESPONDING SELECT 2 three;
+
+SELECT 1 AS three UNION CORRESPONDING SELECT 2 three UNION CORRESPONDING SELECT 3 three;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+
+SELECT 3 c, 2 b, 1 a UNION CORRESPONDING SELECT 6 c, 4 a, 5 b;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(c) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, c) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY (c,b) SELECT 4 a, 5 b, 6 c, 8 d;
+
+-- CORRESPONDING column ordering, left clause's column ordering must be preserved.
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+
+SELECT 2 b, 1 a, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 3 c, 2 b UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 5 b, 6 c, 4 a;
+
+SELECT 2 b, 1 a, 3 c UNION CORRESPONDING SELECT 5 b, 6 c, 4 a;
+
+-- CORRESPONDING BY column ordering, BY clause column ordering must be preserved.
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c, a) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(c, a, b) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 5 b, 6 c, 4 a;
+
+SELECT 2 b, 3 c, 1 a UNION CORRESPONDING BY(b, c, a) SELECT 4 a, 5 b, 6 c;
+
+SELECT 3 c, 2 b, 1 a UNION CORRESPONDING BY(c, a, b) SELECT 4 a, 5 b, 6 c;
+
+
+-- should to fail
+SELECT 10 a, 20 b UNION ALL CORRESPONDING SELECT 10 c, 20 d;
+
+SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (a,b) SELECT 10 c, 20 d;
+
+SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (x) SELECT 10 c, 20 d;
+
 -- Mixed types
 
 SELECT 1.1 AS two UNION SELECT 2 ORDER BY 1;
@@ -90,6 +158,38 @@ UNION
 SELECT TRIM(TRAILING FROM f1) FROM CHAR_TBL
 ORDER BY 1;
 
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING BY(five)
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+
+SELECT f1 AS ten FROM FLOAT8_TBL
+UNION ALL CORRESPONDING
+SELECT f1 AS ten FROM FLOAT8_TBL;
+
+SELECT f1 AS nine FROM FLOAT8_TBL
+UNION CORRESPONDING
+SELECT f1 AS nine FROM INT4_TBL
+ORDER BY 1;
+
+SELECT f1 AS ten FROM FLOAT8_TBL
+UNION ALL CORRESPONDING
+SELECT f1 AS ten FROM INT4_TBL;
+
+SELECT f1 AS five FROM FLOAT8_TBL
+  WHERE f1 BETWEEN -1e6 AND 1e6
+UNION CORRESPONDING
+SELECT f1 AS five FROM INT4_TBL
+  WHERE f1 BETWEEN 0 AND 1000000;
+
+SELECT * FROM FLOAT8_TBL
+UNION corresponding SELECT * FROM FLOAT8_TBL;
+
 --
 -- INTERSECT and EXCEPT
 --
@@ -112,6 +212,22 @@ SELECT q1 FROM int8_tbl EXCEPT ALL SELECT DISTINCT q2 FROM int8_tbl ORDER BY 1;
 
 SELECT q1 FROM int8_tbl EXCEPT ALL SELECT q1 FROM int8_tbl FOR NO KEY UPDATE;
 
+SELECT q2 FROM int8_tbl INTERSECT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+
+SELECT q2 FROM int8_tbl INTERSECT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+
+SELECT q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q1 AS q2 FROM int8_tbl ORDER BY 1;
+
+SELECT q1 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q2 AS q1 FROM int8_tbl;
+
 --
 -- Mixed types
 --
@@ -120,6 +236,10 @@ SELECT f1 FROM float8_tbl INTERSECT SELECT f1 FROM int4_tbl ORDER BY 1;
 
 SELECT f1 FROM float8_tbl EXCEPT SELECT f1 FROM int4_tbl ORDER BY 1;
 
+SELECT f1 FROM float8_tbl INTERSECT CORRESPONDING SELECT f1 FROM int4_tbl;
+
+SELECT f1 FROM float8_tbl EXCEPT CORRESPONDING SELECT f1 FROM int4_tbl ORDER BY 1;
+
 --
 -- Operator precedence and (((((extra))))) parentheses
 --
@@ -150,6 +270,15 @@ SELECT q1 FROM int8_tbl EXCEPT SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1;
 -- But this should work:
 SELECT q1 FROM int8_tbl EXCEPT (((SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1))) ORDER BY 1;
 
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2,q1 FROM int8_tbl
+ORDER BY q2,q1;
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q1) SELECT q2,q1 FROM int8_tbl
+ORDER BY q1;
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q2) SELECT q2,q1 FROM int8_tbl
+ORDER BY q2;
+
 --
 -- New syntaxes (7.1) permit new tests
 --
@@ -170,6 +299,13 @@ ORDER BY 1;
 -- This should fail, but it should produce an error cursor
 SELECT '3.4'::numeric UNION SELECT 'foo';
 
+SELECT '3.4'::numeric AS a UNION CORRESPONDING SELECT 'foo' AS a;
+
+SELECT a.f1 FROM (SELECT 'test' AS f1 FROM varchar_tbl) a
+UNION CORRESPONDING
+SELECT b.f1 FROM (SELECT f1 FROM varchar_tbl) b
+ORDER BY 1;
+
 --
 -- Test that expression-index constraints can be pushed down through
 -- UNION or UNION ALL
#15Surafel Temesgen
surafel3000@gmail.com
In reply to: Pavel Stehule (#14)
Re: New CORRESPONDING clause design

On Sat, Mar 11, 2017 at 9:01 AM, Pavel Stehule <pavel.stehule@gmail.com>
wrote:

I am sending minor update - cleaning formatting and white spaces, error
messages + few more tests

Thank you very much for your help

Maybe correspondingClause needs own node type with attached location. Then
context can be much better positioned.

I think we can solve it by using your option or using expr_list for
corresponding column and check the syntax manually.

In my opinion, the last option eliminate the introduction of new node for
only the sake of error position.

What did you think about the second option?

Regards

Surafel

#16Pavel Stehule
pavel.stehule@gmail.com
In reply to: Surafel Temesgen (#15)
Re: New CORRESPONDING clause design

2017-03-13 14:13 GMT+01:00 Surafel Temesgen <surafel3000@gmail.com>:

On Sat, Mar 11, 2017 at 9:01 AM, Pavel Stehule <pavel.stehule@gmail.com>
wrote:

I am sending minor update - cleaning formatting and white spaces, error
messages + few more tests

Thank you very much for your help

Maybe correspondingClause needs own node type with attached location.
Then context can be much better positioned.

I think we can solve it by using your option or using expr_list for
corresponding column and check the syntax manually.

In my opinion, the last option eliminate the introduction of new node for
only the sake of error position.

What did you think about the second option?

I don't like it too much - using expr only for location is too misuse.

Some errors are related to just CORRESPONDING without any columns. So using
expr doesn't help here. So parse node CORRESPONDING can solve both issues.

Regards

Pavel

Show quoted text

Regards

Surafel

#17Surafel Temesgen
surafel3000@gmail.com
In reply to: Pavel Stehule (#16)
1 attachment(s)
Re: New CORRESPONDING clause design

hi

Some errors are related to just CORRESPONDING without any columns. So using

expr doesn't help here. So parse node CORRESPONDING can solve both issues.

In current implementation pointing to a node means pointing to a node’s
first element so I don’t think we can be able to point to CORRESPONDING
without any columns

I find out that there is already a node prepare for the case called A_Const.
The attached patch use that node

Regards
Surafel

Attachments:

corresponding_clause_v5.patchapplication/octet-stream; name=corresponding_clause_v5.patchDownload
diff --git a/doc/src/sgml/queries.sgml b/doc/src/sgml/queries.sgml
index 30792f4..c3cdee5 100644
--- a/doc/src/sgml/queries.sgml
+++ b/doc/src/sgml/queries.sgml
@@ -1601,6 +1601,9 @@ SELECT DISTINCT ON (<replaceable>expression</replaceable> <optional>, <replaceab
    <primary>EXCEPT</primary>
   </indexterm>
   <indexterm zone="queries-union">
+   <primary>CORRESPONDING</primary>
+  </indexterm>
+  <indexterm zone="queries-union">
    <primary>set union</primary>
   </indexterm>
   <indexterm zone="queries-union">
@@ -1617,9 +1620,9 @@ SELECT DISTINCT ON (<replaceable>expression</replaceable> <optional>, <replaceab
    The results of two queries can be combined using the set operations
    union, intersection, and difference.  The syntax is
 <synopsis>
-<replaceable>query1</replaceable> UNION <optional>ALL</optional> <replaceable>query2</replaceable>
-<replaceable>query1</replaceable> INTERSECT <optional>ALL</optional> <replaceable>query2</replaceable>
-<replaceable>query1</replaceable> EXCEPT <optional>ALL</optional> <replaceable>query2</replaceable>
+<replaceable>query1</replaceable> UNION <optional>ALL</optional> <optional>CORRESPONDING</optional> <optional>BY</optional> <replaceable>query2</replaceable>
+<replaceable>query1</replaceable> INTERSECT <optional>ALL</optional> <optional>CORRESPONDING</optional> <optional>BY</optional> <replaceable>query2</replaceable>
+<replaceable>query1</replaceable> EXCEPT <optional>ALL</optional> <optional>CORRESPONDING</optional> <optional>BY</optional> <replaceable>query2</replaceable>
 </synopsis>
    <replaceable>query1</replaceable> and
    <replaceable>query2</replaceable> are queries that can use any of
@@ -1659,11 +1662,22 @@ SELECT DISTINCT ON (<replaceable>expression</replaceable> <optional>, <replaceab
   </para>
 
   <para>
-   In order to calculate the union, intersection, or difference of two
-   queries, the two queries must be <quote>union compatible</quote>,
-   which means that they return the same number of columns and
-   the corresponding columns have compatible data types, as
-   described in <xref linkend="typeconv-union-case">.
+   <literal>EXCEPT</> returns all rows that are in the result of
+   <replaceable>query1</replaceable> but not in the result of
+   <replaceable>query2</replaceable>.  (This is sometimes called the
+   <firstterm>difference</> between two queries.)  Again, duplicates
+   are eliminated unless <literal>EXCEPT ALL</> is used.
+  </para>
+
+  <para>
+   <literal>CORRESPONDING</> returns all columns that are in both 
+   <replaceable>query1</> and <replaceable>query2</> with the same name.
+  </para>
+
+  <para>
+   <literal>CORRESPONDING BY</> returns all columns in the column list 
+   that are also in both <replaceable>query1</> and 
+   <replaceable>query2</> with the same name.
   </para>
  </sect1>
 
diff --git a/doc/src/sgml/sql.sgml b/doc/src/sgml/sql.sgml
index 57396d7..f98c22e 100644
--- a/doc/src/sgml/sql.sgml
+++ b/doc/src/sgml/sql.sgml
@@ -859,7 +859,7 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="PARAMETER">expression</replac
     [ WHERE <replaceable class="PARAMETER">condition</replaceable> ]
     [ GROUP BY <replaceable class="PARAMETER">expression</replaceable> [, ...] ]
     [ HAVING <replaceable class="PARAMETER">condition</replaceable> [, ...] ]
-    [ { UNION | INTERSECT | EXCEPT } [ ALL ] <replaceable class="PARAMETER">select</replaceable> ]
+    [ { UNION | INTERSECT | EXCEPT } [ ALL ] [ CORRESPONDING [ BY ( <replaceable class="PARAMETER">expression</replaceable> ) ] ] <replaceable class="PARAMETER">select</replaceable> ]
     [ ORDER BY <replaceable class="parameter">expression</replaceable> [ ASC | DESC | USING <replaceable class="parameter">operator</replaceable> ] [ NULLS { FIRST | LAST } ] [, ...] ]
     [ LIMIT { <replaceable class="PARAMETER">count</replaceable> | ALL } ]
     [ OFFSET <replaceable class="PARAMETER">start</replaceable> ]
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 30d733e..30874e0 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2870,6 +2870,7 @@ _copySelectStmt(const SelectStmt *from)
 	COPY_NODE_FIELD(withClause);
 	COPY_SCALAR_FIELD(op);
 	COPY_SCALAR_FIELD(all);
+	COPY_NODE_FIELD(correspondingClause);
 	COPY_NODE_FIELD(larg);
 	COPY_NODE_FIELD(rarg);
 
@@ -2885,6 +2886,7 @@ _copySetOperationStmt(const SetOperationStmt *from)
 	COPY_SCALAR_FIELD(all);
 	COPY_NODE_FIELD(larg);
 	COPY_NODE_FIELD(rarg);
+	COPY_NODE_FIELD(correspondingColumns);
 	COPY_NODE_FIELD(colTypes);
 	COPY_NODE_FIELD(colTypmods);
 	COPY_NODE_FIELD(colCollations);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 55c73b7..f375255 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1020,6 +1020,7 @@ _equalSelectStmt(const SelectStmt *a, const SelectStmt *b)
 	COMPARE_NODE_FIELD(withClause);
 	COMPARE_SCALAR_FIELD(op);
 	COMPARE_SCALAR_FIELD(all);
+	COMPARE_NODE_FIELD(correspondingClause);
 	COMPARE_NODE_FIELD(larg);
 	COMPARE_NODE_FIELD(rarg);
 
@@ -1033,6 +1034,7 @@ _equalSetOperationStmt(const SetOperationStmt *a, const SetOperationStmt *b)
 	COMPARE_SCALAR_FIELD(all);
 	COMPARE_NODE_FIELD(larg);
 	COMPARE_NODE_FIELD(rarg);
+	COMPARE_NODE_FIELD(correspondingColumns);
 	COMPARE_NODE_FIELD(colTypes);
 	COMPARE_NODE_FIELD(colTypmods);
 	COMPARE_NODE_FIELD(colCollations);
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index c2f03b7..ca39c1c 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -3411,6 +3411,8 @@ raw_expression_tree_walker(Node *node,
 					return true;
 				if (walker(stmt->lockingClause, context))
 					return true;
+				if (walker(stmt->correspondingClause, context))
+					return true;
 				if (walker(stmt->withClause, context))
 					return true;
 				if (walker(stmt->larg, context))
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 1560ac3..6e96018 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2526,6 +2526,7 @@ _outSelectStmt(StringInfo str, const SelectStmt *node)
 	WRITE_NODE_FIELD(withClause);
 	WRITE_ENUM_FIELD(op, SetOperation);
 	WRITE_BOOL_FIELD(all);
+	WRITE_NODE_FIELD(correspondingClause);
 	WRITE_NODE_FIELD(larg);
 	WRITE_NODE_FIELD(rarg);
 }
@@ -2833,6 +2834,7 @@ _outSetOperationStmt(StringInfo str, const SetOperationStmt *node)
 	WRITE_BOOL_FIELD(all);
 	WRITE_NODE_FIELD(larg);
 	WRITE_NODE_FIELD(rarg);
+	WRITE_NODE_FIELD(correspondingColumns);
 	WRITE_NODE_FIELD(colTypes);
 	WRITE_NODE_FIELD(colTypmods);
 	WRITE_NODE_FIELD(colCollations);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index dcfa6ee..94b724b 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -418,6 +418,7 @@ _readSetOperationStmt(void)
 	READ_BOOL_FIELD(all);
 	READ_NODE_FIELD(larg);
 	READ_NODE_FIELD(rarg);
+	READ_NODE_FIELD(correspondingColumns);
 	READ_NODE_FIELD(colTypes);
 	READ_NODE_FIELD(colTypmods);
 	READ_NODE_FIELD(colCollations);
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 06e843d..abd3103 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -91,7 +91,8 @@ static List *generate_setop_tlist(List *colTypes, List *colCollations,
 					 Index varno,
 					 bool hack_constants,
 					 List *input_tlist,
-					 List *refnames_tlist);
+					 List *refnames_tlist,
+					 bool no_corresponding);
 static List *generate_append_tlist(List *colTypes, List *colCollations,
 					  bool flag,
 					  List *input_tlists,
@@ -110,6 +111,7 @@ static Node *adjust_appendrel_attrs_mutator(Node *node,
 static Relids adjust_relid_set(Relids relids, Index oldrelid, Index newrelid);
 static List *adjust_inherited_tlist(List *tlist,
 					   AppendRelInfo *context);
+static List *make_corresponding_target(List *corresponding_list, List *subroot_list);
 
 
 /*
@@ -187,6 +189,24 @@ plan_set_operations(PlannerInfo *root)
 									   leftmostQuery->targetList,
 									   &top_tlist);
 	}
+	/*
+	 * If corresponding column specified, we take column names from it.
+	 */
+	else if (topop->correspondingColumns != NIL )
+	{
+		/*
+		 * Recurse on setOperations tree to generate paths for set ops. The
+		 * final output path should have just the column types shown as the
+		 * output from the top-level node, plus possibly resjunk working
+		 * columns (we can rely on upper-level nodes to deal with that).
+		 */
+		path = recurse_set_operations((Node *) topop, root,
+									  topop->colTypes, topop->colCollations,
+									  true, -1,
+									  topop->correspondingColumns,
+									  &top_tlist,
+									  NULL);
+	}
 	else
 	{
 		/*
@@ -252,6 +272,8 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 					   List **pTargetList,
 					   double *pNumGroups)
 {
+	SetOperationStmt *topop = (SetOperationStmt *) root->parse->setOperations;
+
 	if (IsA(setOp, RangeTblRef))
 	{
 		RangeTblRef *rtr = (RangeTblRef *) setOp;
@@ -316,23 +338,45 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 		path = (Path *) create_subqueryscan_path(root, rel, subpath,
 												 NIL, NULL);
 
-		/*
-		 * Figure out the appropriate target list, and update the
-		 * SubqueryScanPath with the PathTarget form of that.
-		 */
-		tlist = generate_setop_tlist(colTypes, colCollations,
+		if (topop->correspondingColumns != NIL )
+		{
+			List *correspondingTarget;
+			/* make target list that only contains corresponding column from sub-queries list ito use it for projection */
+			correspondingTarget = make_corresponding_target(topop->correspondingColumns,
+					subroot->processed_tlist);
+			/*
+			 * Figure out the appropriate target list, and update the
+			 * SubqueryScanPath with the PathTarget form of that.
+			 */
+			tlist = generate_setop_tlist(colTypes, colCollations, flag,
+					rtr->rtindex, true, correspondingTarget, refnames_tlist, false);
+
+			path = apply_projection_to_path(root, rel, path,
+					create_pathtarget(root, tlist));
+
+			/* Return the fully-fledged tlist to caller, too */
+			*pTargetList = tlist;
+
+		}
+		else
+		{
+			/*
+			* Figure out the appropriate target list, and update the
+			* SubqueryScanPath with the PathTarget form of that.
+			*/
+			tlist = generate_setop_tlist(colTypes, colCollations,
 									 flag,
 									 rtr->rtindex,
 									 true,
 									 subroot->processed_tlist,
-									 refnames_tlist);
+									 refnames_tlist, true);
 
-		path = apply_projection_to_path(root, rel, path,
+			path = apply_projection_to_path(root, rel, path,
 										create_pathtarget(root, tlist));
 
-		/* Return the fully-fledged tlist to caller, too */
-		*pTargetList = tlist;
-
+			/* Return the fully-fledged tlist to caller, too */
+			*pTargetList = tlist;
+		}
 		/*
 		 * Estimate number of groups if caller wants it.  If the subquery used
 		 * grouping or aggregation, its output is probably mostly unique
@@ -392,7 +436,7 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 												0,
 												false,
 												*pTargetList,
-												refnames_tlist);
+												refnames_tlist, true);
 			path = apply_projection_to_path(root,
 											path->parent,
 											path,
@@ -1004,7 +1048,8 @@ generate_setop_tlist(List *colTypes, List *colCollations,
 					 Index varno,
 					 bool hack_constants,
 					 List *input_tlist,
-					 List *refnames_tlist)
+					 List *refnames_tlist,
+					 bool no_corresponding)
 {
 	List	   *tlist = NIL;
 	int			resno = 1;
@@ -1025,9 +1070,11 @@ generate_setop_tlist(List *colTypes, List *colCollations,
 		TargetEntry *reftle = (TargetEntry *) lfirst(rtlc);
 
 		rtlc = lnext(rtlc);
-
-		Assert(inputtle->resno == resno);
-		Assert(reftle->resno == resno);
+		if (no_corresponding)
+		{
+			Assert(inputtle->resno == resno);
+			Assert(reftle->resno == resno);
+		}
 		Assert(!inputtle->resjunk);
 		Assert(!reftle->resjunk);
 
@@ -2112,3 +2159,72 @@ adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node,
 	/* Now translate for this child */
 	return adjust_appendrel_attrs(root, node, appinfo);
 }
+
+/*
+ * generate target list from left target list with the order
+ * of right target list
+ */
+static List *
+make_corresponding_target(List *corresponding_list, List *subroot_list)
+{
+	Index internal = 0;
+	ListCell   *ltl;
+	ListCell   *rtl;
+	int			size;
+	int			i;
+	List *matchingColumns = NIL;
+	TargetEntry *simple_te_array;
+
+	size = list_length(corresponding_list) + 1;
+
+	/* Use array to find the order of corresponding columen */
+	simple_te_array = (TargetEntry *) palloc0(size * sizeof(TargetEntry));
+	foreach(ltl, corresponding_list)
+	{
+		foreach(rtl, subroot_list)
+		{
+			TargetEntry *ltle = (TargetEntry *) lfirst(ltl);
+			TargetEntry *rtle = (TargetEntry *) lfirst(rtl);
+
+			elog(DEBUG4, "%s", ltle->resname);
+
+			/* Names of the columns must be resolved before calling this method. */
+			Assert(ltle->resname != NULL);
+			Assert(rtle->resname != NULL);
+
+			/* If column names are the same, add it to array. */
+			if (strcmp(ltle->resname, rtle->resname) == 0)
+			{
+				simple_te_array[internal].xpr = rtle->xpr;
+				simple_te_array[internal].expr = rtle->expr;
+				simple_te_array[internal].resno = rtle->resno;
+				simple_te_array[internal].resname = rtle->resname;
+				simple_te_array[internal].ressortgroupref =
+						rtle->ressortgroupref;
+				simple_te_array[internal].resorigtbl = rtle->resorigtbl;
+				simple_te_array[internal].resorigcol = rtle->resorigcol;
+				simple_te_array[internal].resjunk = rtle->resjunk;
+				internal++;
+				continue;
+			}
+		}
+	}
+	/* traverse the array and make targetlist */
+	for (i = 0; i < internal; i++)
+	{
+		TargetEntry *tle = makeNode(TargetEntry);
+
+		tle->xpr = simple_te_array[i].xpr;
+		tle->expr = simple_te_array[i].expr;
+		tle->resno = simple_te_array[i].resno;
+		tle->resname = simple_te_array[i].resname;
+		tle->ressortgroupref = simple_te_array[i].ressortgroupref;
+		tle->resorigtbl = simple_te_array[i].resorigtbl;
+		tle->resorigcol = simple_te_array[i].resorigcol;
+		tle->resjunk = simple_te_array[i].resjunk;
+
+		matchingColumns = lappend(matchingColumns, tle);
+
+	}
+	return matchingColumns;
+}
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 0f7659b..0b7deca 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -76,6 +76,10 @@ static Query *transformCreateTableAsStmt(ParseState *pstate,
 						   CreateTableAsStmt *stmt);
 static void transformLockingClause(ParseState *pstate, Query *qry,
 					   LockingClause *lc, bool pushedDown);
+static List *determineMatchingColumns(List *ltargetlist, List *rtargetlist);
+static void *makeUnionDatatype(List *ltargetlist, List *rtargetlist,
+		SetOperationStmt *op, List **targetlist, ParseState *parentParseState,
+		const char *context);
 #ifdef RAW_EXPRESSION_COVERAGE_TEST
 static bool test_raw_expression_coverage(Node *node, void *context);
 #endif
@@ -1664,7 +1668,13 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->targetList = NIL;
 	targetvars = NIL;
 	targetnames = NIL;
-	left_tlist = list_head(leftmostQuery->targetList);
+	/* for corresponding clause limits top-level query targetlist to those
+	 * corresponding column list only
+	 */
+	if (sostmt->correspondingColumns != NIL )
+		left_tlist = list_head(sostmt->correspondingColumns);
+	else
+		left_tlist = list_head(leftmostQuery->targetList);
 
 	forthree(lct, sostmt->colTypes,
 			 lcm, sostmt->colTypmods,
@@ -1924,8 +1934,6 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 		SetOperationStmt *op = makeNode(SetOperationStmt);
 		List	   *ltargetlist;
 		List	   *rtargetlist;
-		ListCell   *ltl;
-		ListCell   *rtl;
 		const char *context;
 
 		context = (stmt->op == SETOP_UNION ? "UNION" :
@@ -1935,6 +1943,191 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 		op->op = stmt->op;
 		op->all = stmt->all;
 
+		/* If CORRESPONDING is specified, syntax and column name validities checked,
+		 * column filtering is done by a subquery later on.
+		 */
+		if (stmt->correspondingClause == NIL )
+		{
+			/* No CORRESPONDING clause, no operation needed for column filtering */
+			op->correspondingColumns = stmt->correspondingClause;
+		}
+		else if (linitial(stmt->correspondingClause) == NULL )
+		{
+			/* CORRESPONDING clause, find matching column names from both tables.
+			 * If there are none then it is a syntax error.
+			 */
+			Query *largQuery;
+			Query *rargQuery;
+			List *matchingColumns;
+			List *rightCorrespondingColumns;
+
+			/* Analyze left query to resolve column names. */
+			largQuery = parse_sub_analyze((Node *) stmt->larg, pstate, NULL, false, false );
+
+			/* Analyze right query to resolve column names. */
+			rargQuery = parse_sub_analyze((Node *) stmt->rarg, pstate, NULL, false, false );
+
+			/* Find matching columns from both queries. */
+			matchingColumns = determineMatchingColumns(largQuery->targetList,rargQuery->targetList);
+			/*
+			 * there may be out-of-order resnos in corresponding target list
+			 */
+			op->correspondingColumns = orderCorrespondingList(matchingColumns);
+
+			/* If matchingColumns is empty, there is an error.
+			 * At least one column in the select lists must have the same name.
+			 */
+			if (list_length(matchingColumns) == 0)
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("there are not any corresponding name"),
+						 errhint("%s queries with a CORRESPONDING clause must have at least one column with the same name",
+								 context),
+						 parser_errposition(pstate,
+											exprLocation((Node *) linitial(largQuery->targetList)))));
+			}
+
+			/* matchingColumns contain target list as it appear in left query targetList
+			 * we need matching column as it appear in right query targetlist inorder
+			 * to make output column type for corresponding columns
+			 */
+			rightCorrespondingColumns = determineMatchingColumns(rargQuery->targetList, largQuery->targetList);
+
+			/* make union'd datatype of output column
+			 */
+			makeUnionDatatype(matchingColumns, rightCorrespondingColumns, op, targetlist, pstate,
+					context);
+		}
+		else
+		{
+			/*
+			 * CORRESPONDING BY clause, find matching column names from both tables
+			 * and intersect them with BY(...) column list. If there are none
+			 * then it is a syntax error.
+			 */
+			Query *largQuery;
+			Query *rargQuery;
+			List *matchingColumns;
+			List *matchingColumnsFiltered;
+			List *rightCorrespondingColumns;
+			ListCell *corrtl;
+			ListCell *mctl;
+
+			/* Analyze left query to resolve column names. */
+			largQuery = parse_sub_analyze((Node *) stmt->larg, pstate, NULL,false, false );
+
+			/* Analyze right query to resolve column names. */
+			rargQuery = parse_sub_analyze((Node *) stmt->rarg, pstate, NULL, false, false );
+
+			/*
+			 * Find matching columns from both queries.
+			 * In CORRESPONDING BY, column names will be removed from
+			 * matchingColumns if they are not in the BY clause.
+			 * All columns in the BY clause must be in matchingColumns,
+			 * otherwise raise syntax error in BY clause.
+			 */
+			matchingColumns = determineMatchingColumns(largQuery->targetList,rargQuery->targetList);
+
+			/*
+			 * Every column name in correspondingClause must be in matchingColumns,
+			 * otherwise it is a syntax error.
+			 */
+			foreach(corrtl, stmt->correspondingClause)
+			{
+				A_Const* corrtle = lfirst(corrtl);
+				Value* corrvalue = &corrtle->val;
+				/* Get column name from correspondingClause. */
+				char *name = strVal(corrvalue);
+				bool hasMatch = false;
+
+				foreach(mctl, matchingColumns)
+				{
+					TargetEntry *mctle = (TargetEntry *) lfirst(mctl);
+
+					Assert(mctle->resname != NULL);
+					Assert(name != NULL);
+
+					/* Compare correspondingClause column name with matchingColumns column names. */
+					if (strcmp(mctle->resname, name) == 0)
+					{
+						/* we have a match. */
+						hasMatch = true;
+						break;
+					}
+				}
+
+				if (!hasMatch)
+				{
+					/* CORRESPONDING BY clause contains a column name that is not in both tables. */
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("column name \"%s\" cannot be used in corresponding by clause", name),
+							 errhint("%s queries with a CORRESPONDING BY clause must only contain column names from both tables.",
+									 context),
+							 parser_errposition(pstate,
+												exprLocation((Node *) stmt->correspondingClause))));
+				}
+			}
+
+			/* To preserve column ordering from correspondingClause and to remove
+			 * columns from matchingColumns if they are not in correspondingClause,
+			 * create a new list and finalize our column list for the
+			 * CORRESPONDING BY clause.
+			 */
+			matchingColumnsFiltered = NIL;
+
+			/* For each column in CORRESPONDING BY column list, check
+			 * column existence in matchingColumns.
+			 */
+			foreach(corrtl, stmt->correspondingClause)
+			{
+				A_Const* corrtle = lfirst(corrtl);
+				Value* corrvalue = &corrtle->val;
+
+				/* Get column name from correspondingClause. */
+				char *name = strVal(corrvalue);
+
+				foreach(mctl, matchingColumns)
+				{
+					TargetEntry *mctle = (TargetEntry *) lfirst(mctl);
+
+					Assert(mctle->resname != NULL);
+					Assert(name != NULL);
+
+					if (strcmp(mctle->resname, name) == 0)
+					{
+						/* we have a match.*/
+						matchingColumnsFiltered = lappend(
+								matchingColumnsFiltered, mctle);
+						break;
+					}
+				}
+			}
+
+			/*
+			 * If matchingColumnsFiltered is empty, there is a semantic error.
+			 * At least one column in the select lists must have the same name.
+			 */
+			Assert(list_length(matchingColumnsFiltered) > 0);
+
+			/*
+			 * there may be out-of-order resnos in corresponding target list
+			 */
+			op->correspondingColumns = orderCorrespondingList(matchingColumnsFiltered);
+
+			/*
+			 * matchingColumns contain target list as it appear in left query targetList
+			 * we need matching column as it appear in right query targetlist inorder to
+			 * make output column type for corresponding columns
+			 */
+			rightCorrespondingColumns = determineMatchingColumns(rargQuery->targetList,matchingColumnsFiltered);
+			/*
+			 * make union'd datatype of output columns
+			 */
+			makeUnionDatatype(matchingColumnsFiltered, rightCorrespondingColumns, op, targetlist, pstate,
+					context);
+		}
 		/*
 		 * Recursively transform the left child node.
 		 */
@@ -1960,177 +2153,220 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 											 false,
 											 &rtargetlist);
 
-		/*
-		 * Verify that the two children have the same number of non-junk
-		 * columns, and determine the types of the merged output columns.
-		 */
-		if (list_length(ltargetlist) != list_length(rtargetlist))
-			ereport(ERROR,
-					(errcode(ERRCODE_SYNTAX_ERROR),
-				 errmsg("each %s query must have the same number of columns",
-						context),
-					 parser_errposition(pstate,
-										exprLocation((Node *) rtargetlist))));
+		if (op->correspondingColumns == NIL )
+		{
+			makeUnionDatatype(ltargetlist, rtargetlist, op, targetlist, pstate,
+					context);
+			/*
+			 * Verify that the two children have the same number of non-junk
+			 * columns, and determine the types of the merged output columns.
+			 */
+			if (list_length(ltargetlist) != list_length(rtargetlist))
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("each %s query must have the same number of columns", context),
+						 parser_errposition(pstate,
+											exprLocation((Node *) rtargetlist))));
+		}
 
-		if (targetlist)
-			*targetlist = NIL;
-		op->colTypes = NIL;
-		op->colTypmods = NIL;
-		op->colCollations = NIL;
-		op->groupClauses = NIL;
-		forboth(ltl, ltargetlist, rtl, rtargetlist)
+		return (Node *) op;
+	}
+}
+
+/*
+ * Processes targetlists of two queries for column equivalence to use
+ * with UNION/INTERSECT/EXCEPT CORRESPONDING.
+ */
+static List *
+determineMatchingColumns(List *ltargetlist, List *rtargetlist)
+{
+	List *matchingColumns = NIL;
+	ListCell *ltl;
+	ListCell *rtl;
+
+	foreach(ltl, ltargetlist)
+	{
+		foreach(rtl, rtargetlist)
 		{
 			TargetEntry *ltle = (TargetEntry *) lfirst(ltl);
 			TargetEntry *rtle = (TargetEntry *) lfirst(rtl);
-			Node	   *lcolnode = (Node *) ltle->expr;
-			Node	   *rcolnode = (Node *) rtle->expr;
-			Oid			lcoltype = exprType(lcolnode);
-			Oid			rcoltype = exprType(rcolnode);
-			int32		lcoltypmod = exprTypmod(lcolnode);
-			int32		rcoltypmod = exprTypmod(rcolnode);
-			Node	   *bestexpr;
-			int			bestlocation;
-			Oid			rescoltype;
-			int32		rescoltypmod;
-			Oid			rescolcoll;
-
-			/* select common type, same as CASE et al */
-			rescoltype = select_common_type(pstate,
-											list_make2(lcolnode, rcolnode),
-											context,
-											&bestexpr);
-			bestlocation = exprLocation(bestexpr);
-			/* if same type and same typmod, use typmod; else default */
-			if (lcoltype == rcoltype && lcoltypmod == rcoltypmod)
-				rescoltypmod = lcoltypmod;
-			else
-				rescoltypmod = -1;
-
-			/*
-			 * Verify the coercions are actually possible.  If not, we'd fail
-			 * later anyway, but we want to fail now while we have sufficient
-			 * context to produce an error cursor position.
-			 *
-			 * For all non-UNKNOWN-type cases, we verify coercibility but we
-			 * don't modify the child's expression, for fear of changing the
-			 * child query's semantics.
-			 *
-			 * If a child expression is an UNKNOWN-type Const or Param, we
-			 * want to replace it with the coerced expression.  This can only
-			 * happen when the child is a leaf set-op node.  It's safe to
-			 * replace the expression because if the child query's semantics
-			 * depended on the type of this output column, it'd have already
-			 * coerced the UNKNOWN to something else.  We want to do this
-			 * because (a) we want to verify that a Const is valid for the
-			 * target type, or resolve the actual type of an UNKNOWN Param,
-			 * and (b) we want to avoid unnecessary discrepancies between the
-			 * output type of the child query and the resolved target type.
-			 * Such a discrepancy would disable optimization in the planner.
-			 *
-			 * If it's some other UNKNOWN-type node, eg a Var, we do nothing
-			 * (knowing that coerce_to_common_type would fail).  The planner
-			 * is sometimes able to fold an UNKNOWN Var to a constant before
-			 * it has to coerce the type, so failing now would just break
-			 * cases that might work.
-			 */
-			if (lcoltype != UNKNOWNOID)
-				lcolnode = coerce_to_common_type(pstate, lcolnode,
-												 rescoltype, context);
-			else if (IsA(lcolnode, Const) ||
-					 IsA(lcolnode, Param))
-			{
-				lcolnode = coerce_to_common_type(pstate, lcolnode,
-												 rescoltype, context);
-				ltle->expr = (Expr *) lcolnode;
-			}
 
-			if (rcoltype != UNKNOWNOID)
-				rcolnode = coerce_to_common_type(pstate, rcolnode,
-												 rescoltype, context);
-			else if (IsA(rcolnode, Const) ||
-					 IsA(rcolnode, Param))
-			{
-				rcolnode = coerce_to_common_type(pstate, rcolnode,
-												 rescoltype, context);
-				rtle->expr = (Expr *) rcolnode;
-			}
+			elog(DEBUG4, "%s", ltle->resname);
 
-			/*
-			 * Select common collation.  A common collation is required for
-			 * all set operators except UNION ALL; see SQL:2008 7.13 <query
-			 * expression> Syntax Rule 15c.  (If we fail to identify a common
-			 * collation for a UNION ALL column, the curCollations element
-			 * will be set to InvalidOid, which may result in a runtime error
-			 * if something at a higher query level wants to use the column's
-			 * collation.)
-			 */
-			rescolcoll = select_common_collation(pstate,
-											  list_make2(lcolnode, rcolnode),
-										 (op->op == SETOP_UNION && op->all));
-
-			/* emit results */
-			op->colTypes = lappend_oid(op->colTypes, rescoltype);
-			op->colTypmods = lappend_int(op->colTypmods, rescoltypmod);
-			op->colCollations = lappend_oid(op->colCollations, rescolcoll);
-
-			/*
-			 * For all cases except UNION ALL, identify the grouping operators
-			 * (and, if available, sorting operators) that will be used to
-			 * eliminate duplicates.
-			 */
-			if (op->op != SETOP_UNION || !op->all)
-			{
-				SortGroupClause *grpcl = makeNode(SortGroupClause);
-				Oid			sortop;
-				Oid			eqop;
-				bool		hashable;
-				ParseCallbackState pcbstate;
-
-				setup_parser_errposition_callback(&pcbstate, pstate,
-												  bestlocation);
-
-				/* determine the eqop and optional sortop */
-				get_sort_group_operators(rescoltype,
-										 false, true, false,
-										 &sortop, &eqop, NULL,
-										 &hashable);
-
-				cancel_parser_errposition_callback(&pcbstate);
-
-				/* we don't have a tlist yet, so can't assign sortgrouprefs */
-				grpcl->tleSortGroupRef = 0;
-				grpcl->eqop = eqop;
-				grpcl->sortop = sortop;
-				grpcl->nulls_first = false;		/* OK with or without sortop */
-				grpcl->hashable = hashable;
-
-				op->groupClauses = lappend(op->groupClauses, grpcl);
-			}
+			/* Names of the columns must be resolved before calling this method. */
+			Assert(ltle->resname != NULL);
+			Assert(rtle->resname != NULL);
 
-			/*
-			 * Construct a dummy tlist entry to return.  We use a SetToDefault
-			 * node for the expression, since it carries exactly the fields
-			 * needed, but any other expression node type would do as well.
-			 */
-			if (targetlist)
+			/* If column names are the same, append it to the result. */
+			if (strcmp(ltle->resname, rtle->resname) == 0)
 			{
-				SetToDefault *rescolnode = makeNode(SetToDefault);
-				TargetEntry *restle;
-
-				rescolnode->typeId = rescoltype;
-				rescolnode->typeMod = rescoltypmod;
-				rescolnode->collation = rescolcoll;
-				rescolnode->location = bestlocation;
-				restle = makeTargetEntry((Expr *) rescolnode,
-										 0,		/* no need to set resno */
-										 NULL,
-										 false);
-				*targetlist = lappend(*targetlist, restle);
+				matchingColumns = lappend(matchingColumns, ltle);
+				continue;
 			}
 		}
+	}
+
+	return matchingColumns;
+}
+
+/*
+ * process right and left target list to set up union'd datatype
+ */
+static void *
+makeUnionDatatype(List *ltargetlist, List *rtargetlist, SetOperationStmt *op,
+		List **targetlist, ParseState *pstate, const char *context)
+{
+	ListCell *ltl;
+	ListCell *rtl;
+	if (targetlist)
+		*targetlist = NIL;
+	op->colTypes = NIL;
+	op->colTypmods = NIL;
+	op->colCollations = NIL;
+	op->groupClauses = NIL;
+
+	forboth(ltl, ltargetlist, rtl, rtargetlist)
+	{
+		TargetEntry *ltle = (TargetEntry *) lfirst(ltl);
+		TargetEntry *rtle = (TargetEntry *) lfirst(rtl);
+		Node *lcolnode = (Node *) ltle->expr;
+		Node *rcolnode = (Node *) rtle->expr;
+		Oid lcoltype = exprType(lcolnode);
+		Oid rcoltype = exprType(rcolnode);
+		int32 lcoltypmod = exprTypmod(lcolnode);
+		int32 rcoltypmod = exprTypmod(rcolnode);
+		Node *bestexpr;
+		int bestlocation;
+		Oid rescoltype;
+		int32 rescoltypmod;
+		Oid rescolcoll;
+
+		/* select common type, same as CASE et al */
+		rescoltype = select_common_type(pstate, list_make2(lcolnode, rcolnode),
+				context, &bestexpr);
+		bestlocation = exprLocation(bestexpr);
+		/* if same type and same typmod, use typmod; else default */
+		if (lcoltype == rcoltype && lcoltypmod == rcoltypmod)
+			rescoltypmod = lcoltypmod;
+		else
+			rescoltypmod = -1;
+
+		/*
+		 * Verify the coercions are actually possible.  If not, we'd fail
+		 * later anyway, but we want to fail now while we have sufficient
+		 * context to produce an error cursor position.
+		 *
+		 * For all non-UNKNOWN-type cases, we verify coercibility but we
+		 * don't modify the child's expression, for fear of changing the
+		 * child query's semantics.
+		 *
+		 * If a child expression is an UNKNOWN-type Const or Param, we
+		 * want to replace it with the coerced expression.  This can only
+		 * happen when the child is a leaf set-op node.  It's safe to
+		 * replace the expression because if the child query's semantics
+		 * depended on the type of this output column, it'd have already
+		 * coerced the UNKNOWN to something else.  We want to do this
+		 * because (a) we want to verify that a Const is valid for the
+		 * target type, or resolve the actual type of an UNKNOWN Param,
+		 * and (b) we want to avoid unnecessary discrepancies between the
+		 * output type of the child query and the resolved target type.
+		 * Such a discrepancy would disable optimization in the planner.
+		 *
+		 * If it's some other UNKNOWN-type node, eg a Var, we do nothing
+		 * (knowing that coerce_to_common_type would fail).  The planner
+		 * is sometimes able to fold an UNKNOWN Var to a constant before
+		 * it has to coerce the type, so failing now would just break
+		 * cases that might work.
+		 */
+		if (lcoltype != UNKNOWNOID)
+			lcolnode = coerce_to_common_type(pstate, lcolnode, rescoltype,
+					context);
+		else if (IsA(lcolnode, Const) || IsA(lcolnode, Param))
+		{
+			lcolnode = coerce_to_common_type(pstate, lcolnode, rescoltype,
+					context);
+			ltle->expr = (Expr *) lcolnode;
+		}
 
-		return (Node *) op;
+		if (rcoltype != UNKNOWNOID)
+			rcolnode = coerce_to_common_type(pstate, rcolnode, rescoltype,
+					context);
+		else if (IsA(rcolnode, Const) || IsA(rcolnode, Param))
+		{
+			rcolnode = coerce_to_common_type(pstate, rcolnode, rescoltype,
+					context);
+			rtle->expr = (Expr *) rcolnode;
+		}
+
+		/*
+		 * Select common collation.  A common collation is required for
+		 * all set operators except UNION ALL; see SQL:2008 7.13 <query
+		 * expression> Syntax Rule 15c.  (If we fail to identify a common
+		 * collation for a UNION ALL column, the curCollations element
+		 * will be set to InvalidOid, which may result in a runtime error
+		 * if something at a higher query level wants to use the column's
+		 * collation.)
+		 */
+		rescolcoll = select_common_collation(pstate,
+				list_make2(lcolnode, rcolnode),
+				(op->op == SETOP_UNION && op->all));
+
+		/* emit results */
+		op->colTypes = lappend_oid(op->colTypes, rescoltype);
+		op->colTypmods = lappend_int(op->colTypmods, rescoltypmod);
+		op->colCollations = lappend_oid(op->colCollations, rescolcoll);
+
+		/*
+		 * For all cases except UNION ALL, identify the grouping operators
+		 * (and, if available, sorting operators) that will be used to
+		 * eliminate duplicates.
+		 */
+		if (op->op != SETOP_UNION || !op->all)
+		{
+			SortGroupClause *grpcl = makeNode(SortGroupClause);
+			Oid sortop;
+			Oid eqop;
+			bool hashable;
+			ParseCallbackState pcbstate;
+
+			setup_parser_errposition_callback(&pcbstate, pstate, bestlocation);
+
+			/* determine the eqop and optional sortop */
+			get_sort_group_operators(rescoltype, false, true, false, &sortop,
+					&eqop, NULL, &hashable);
+
+			cancel_parser_errposition_callback(&pcbstate);
+
+			/* we don't have a tlist yet, so can't assign sortgrouprefs */
+			grpcl->tleSortGroupRef = 0;
+			grpcl->eqop = eqop;
+			grpcl->sortop = sortop;
+			grpcl->nulls_first = false; /* OK with or without sortop */
+			grpcl->hashable = hashable;
+
+			op->groupClauses = lappend(op->groupClauses, grpcl);
+		}
+
+		/*
+		 * Construct a dummy tlist entry to return.  We use a SetToDefault
+		 * node for the expression, since it carries exactly the fields
+		 * needed, but any other expression node type would do as well.
+		 */
+		if (targetlist)
+		{
+			SetToDefault *rescolnode = makeNode(SetToDefault);
+			TargetEntry *restle;
+
+			rescolnode->typeId = rescoltype;
+			rescolnode->typeMod = rescoltypmod;
+			rescolnode->collation = rescolcoll;
+			rescolnode->location = bestlocation;
+			restle = makeTargetEntry((Expr *) rescolnode, 0, /* no need to set resno */
+			NULL, false );
+			*targetlist = lappend(*targetlist, restle);
+		}
 	}
+	return 0;
 }
 
 /*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 174773b..8cbd0f8 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -166,7 +166,7 @@ static void insertSelectOptions(SelectStmt *stmt,
 								Node *limitOffset, Node *limitCount,
 								WithClause *withClause,
 								core_yyscan_t yyscanner);
-static Node *makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg);
+static Node *makeSetOp(SetOperation op, bool all, List *correspondingClause, Node *larg, Node *rarg);
 static Node *doNegate(Node *n, int location);
 static void doNegateFloat(Value *v);
 static Node *makeAndExpr(Node *lexpr, Node *rexpr, int location);
@@ -394,7 +394,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				relation_expr_list dostmt_opt_list
 				transform_element_list transform_type_list
 				TriggerTransitions TriggerReferencing
-				publication_name_list
+				publication_name_list opt_corresponding_clause corresponding_list
 
 %type <list>	group_by_list
 %type <node>	group_by_item empty_grouping_set rollup_clause cube_clause
@@ -608,7 +608,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
 	CLUSTER COALESCE COLLATE COLLATION COLUMN COMMENT COMMENTS COMMIT
 	COMMITTED CONCURRENTLY CONFIGURATION CONFLICT CONNECTION CONSTRAINT
-	CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE
+	CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY CORRESPONDING COST CREATE
 	CROSS CSV CUBE CURRENT_P
 	CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA
 	CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
@@ -10808,20 +10808,26 @@ simple_select:
 					n->fromClause = list_make1($2);
 					$$ = (Node *)n;
 				}
-			| select_clause UNION all_or_distinct select_clause
+			| select_clause UNION all_or_distinct opt_corresponding_clause select_clause
 				{
-					$$ = makeSetOp(SETOP_UNION, $3, $1, $4);
+					$$ = makeSetOp(SETOP_UNION, $3, $4, $1, $5);
 				}
-			| select_clause INTERSECT all_or_distinct select_clause
+			| select_clause INTERSECT all_or_distinct opt_corresponding_clause select_clause
 				{
-					$$ = makeSetOp(SETOP_INTERSECT, $3, $1, $4);
+					$$ = makeSetOp(SETOP_INTERSECT, $3, $4, $1, $5);
 				}
-			| select_clause EXCEPT all_or_distinct select_clause
+			| select_clause EXCEPT all_or_distinct opt_corresponding_clause select_clause
 				{
-					$$ = makeSetOp(SETOP_EXCEPT, $3, $1, $4);
+					$$ = makeSetOp(SETOP_EXCEPT, $3, $4, $1, $5);
 				}
 		;
 
+opt_corresponding_clause:
+			CORRESPONDING BY '(' corresponding_list ')'		{ $$ = $4; }
+			| CORRESPONDING							{ $$ = list_make1(NIL); }
+			| /*EMPTY*/								{ $$ = NIL; }
+			;
+
 /*
  * SQL standard WITH clause looks like:
  *
@@ -14000,6 +14006,13 @@ name_list:	name
 		;
 
 
+corresponding_list:	ColId
+					{ $$ = list_make1(makeStringConst($1, @1)); }
+			| corresponding_list ',' ColId
+					{ $$ = lappend($1, makeStringConst($3, @1)); }
+		;
+
+
 name:		ColId									{ $$ = $1; };
 
 database_name:
@@ -14315,6 +14328,7 @@ unreserved_keyword:
 			| CONTINUE_P
 			| CONVERSION_P
 			| COPY
+			| CORRESPONDING
 			| COST
 			| CSV
 			| CUBE
@@ -15145,7 +15159,7 @@ insertSelectOptions(SelectStmt *stmt,
 }
 
 static Node *
-makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg)
+makeSetOp(SetOperation op, bool all, List *correspondingClause, Node *larg, Node *rarg)
 {
 	SelectStmt *n = makeNode(SelectStmt);
 
@@ -15153,6 +15167,7 @@ makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg)
 	n->all = all;
 	n->larg = (SelectStmt *) larg;
 	n->rarg = (SelectStmt *) rarg;
+	n->correspondingClause = correspondingClause;
 	return (Node *) n;
 }
 
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 2576e31..291b66b 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1903,3 +1903,25 @@ FigureColnameInternal(Node *node, char **name)
 
 	return strength;
 }
+
+/*
+ * orderCorrespondingList()
+ * order target list resno .
+ */
+List *
+orderCorrespondingList(List *targetlist)
+{
+	List *p_target = NIL;
+	ListCell *o_target;
+	int pos = 1;
+
+	foreach(o_target, targetlist)
+	{
+		TargetEntry *tar = (TargetEntry *) lfirst(o_target);
+
+		p_target = lappend(p_target,
+				makeTargetEntry(tar->expr, (AttrNumber) pos++, tar->resname, false));
+	}
+
+	return p_target;
+}
diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
index 8feec0b..ac23b1b 100644
--- a/src/backend/parser/parse_type.c
+++ b/src/backend/parser/parse_type.c
@@ -736,7 +736,8 @@ typeStringToTypeName(const char *str)
 		stmt->limitCount != NULL ||
 		stmt->lockingClause != NIL ||
 		stmt->withClause != NULL ||
-		stmt->op != SETOP_NONE)
+		stmt->op != SETOP_NONE ||
+		stmt->correspondingClause != NULL)
 		goto fail;
 	if (list_length(stmt->targetList) != 1)
 		goto fail;
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 07a8436..1dfff8f 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1420,7 +1420,9 @@ typedef struct SelectStmt
 	 * These fields are used only in "leaf" SelectStmts.
 	 */
 	List	   *distinctClause; /* NULL, list of DISTINCT ON exprs, or
-								 * lcons(NIL,NIL) for all (SELECT DISTINCT) */
+								* lcons(NIL,NIL) for all (SELECT DISTINCT) */
+	List	   *correspondingClause; /* NULL, list of CORRESPONDING BY exprs, or */
+								/* lcons(NIL, NIL) for CORRESPONDING */
 	IntoClause *intoClause;		/* target for SELECT INTO */
 	List	   *targetList;		/* the target list (of ResTarget) */
 	List	   *fromClause;		/* the FROM clause */
@@ -1486,8 +1488,9 @@ typedef struct SetOperationStmt
 	bool		all;			/* ALL specified? */
 	Node	   *larg;			/* left child */
 	Node	   *rarg;			/* right child */
-	/* Eventually add fields for CORRESPONDING spec here */
-
+	/* CORRESPONDING clause fields */
+	List	   *correspondingColumns;	/* NIL: No corresponding, else: CORRESPONDING or CORRESPONDIN
+						* BY matching columns. Not the original clause. */
 	/* Fields derived during parse analysis: */
 	List	   *colTypes;		/* OID list of output column type OIDs */
 	List	   *colTypmods;		/* integer list of output column typmods */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 985d650..77e0396 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -96,6 +96,7 @@ PG_KEYWORD("content", CONTENT_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("continue", CONTINUE_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("conversion", CONVERSION_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("copy", COPY, UNRESERVED_KEYWORD)
+PG_KEYWORD("corresponding", CORRESPONDING, UNRESERVED_KEYWORD)
 PG_KEYWORD("cost", COST, UNRESERVED_KEYWORD)
 PG_KEYWORD("create", CREATE, RESERVED_KEYWORD)
 PG_KEYWORD("cross", CROSS, TYPE_FUNC_NAME_KEYWORD)
diff --git a/src/include/parser/parse_target.h b/src/include/parser/parse_target.h
index d06a235..32b9cfd 100644
--- a/src/include/parser/parse_target.h
+++ b/src/include/parser/parse_target.h
@@ -42,5 +42,6 @@ extern TupleDesc expandRecordVariable(ParseState *pstate, Var *var,
 					 int levelsup);
 extern char *FigureColname(Node *node);
 extern char *FigureIndexColname(Node *node);
+extern List *orderCorrespondingList(List *targetlist);
 
 #endif   /* PARSE_TARGET_H */
diff --git a/src/test/regress/expected/union.out b/src/test/regress/expected/union.out
index 4d697ba..e4ce1b9 100644
--- a/src/test/regress/expected/union.out
+++ b/src/test/regress/expected/union.out
@@ -59,6 +59,221 @@ SELECT 1.1 AS two UNION SELECT 2.2 ORDER BY 1;
  2.2
 (2 rows)
 
+SELECT 1 AS two UNION CORRESPONDING SELECT 2 two;
+ two 
+-----
+   1
+   2
+(2 rows)
+
+SELECT 1 AS one UNION CORRESPONDING SELECT 1 one;
+ one 
+-----
+   1
+(1 row)
+
+SELECT 1 AS two UNION ALL CORRESPONDING SELECT 2 two;
+ two 
+-----
+   1
+   2
+(2 rows)
+
+SELECT 1 AS two UNION ALL CORRESPONDING SELECT 1 two;
+ two 
+-----
+   1
+   1
+(2 rows)
+
+SELECT 1 AS two UNION CORRESPONDING SELECT 2 two UNION CORRESPONDING SELECT 2 two;
+ two 
+-----
+   2
+   1
+(2 rows)
+
+SELECT 1 AS three UNION CORRESPONDING SELECT 2 three UNION ALL CORRESPONDING SELECT 2 three;
+ three 
+-------
+     1
+     2
+     2
+(3 rows)
+
+SELECT 1 AS three UNION CORRESPONDING SELECT 2 three UNION CORRESPONDING SELECT 3 three;
+ three 
+-------
+     3
+     2
+     1
+(3 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+SELECT 3 c, 2 b, 1 a UNION CORRESPONDING SELECT 6 c, 4 a, 5 b;
+ c | b | a 
+---+---+---
+ 3 | 2 | 1
+ 6 | 5 | 4
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a) SELECT 4 a, 5 b, 6 c;
+ a 
+---
+ 1
+ 4
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b) SELECT 4 a, 5 b, 6 c;
+ b 
+---
+ 2
+ 5
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(c) SELECT 4 a, 5 b, 6 c;
+ c 
+---
+ 3
+ 6
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b) SELECT 4 a, 5 b, 6 c;
+ a | b 
+---+---
+ 1 | 2
+ 4 | 5
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c) SELECT 4 a, 5 b, 6 c;
+ b | c 
+---+---
+ 2 | 3
+ 5 | 6
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, c) SELECT 4 a, 5 b, 6 c;
+ a | c 
+---+---
+ 1 | 3
+ 4 | 6
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 4 a, 5 b, 6 c;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY (c,b) SELECT 4 a, 5 b, 6 c, 8 d;
+ c | b 
+---+---
+ 3 | 2
+ 6 | 5
+(2 rows)
+
+-- CORRESPONDING column ordering, left clause's column ordering must be preserved.
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+SELECT 2 b, 1 a, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+ b | a | c 
+---+---+---
+ 2 | 1 | 3
+ 5 | 4 | 6
+(2 rows)
+
+SELECT 1 a, 3 c, 2 b UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+ a | c | b 
+---+---+---
+ 1 | 3 | 2
+ 4 | 6 | 5
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 5 b, 6 c, 4 a;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+SELECT 2 b, 1 a, 3 c UNION CORRESPONDING SELECT 5 b, 6 c, 4 a;
+ b | a | c 
+---+---+---
+ 2 | 1 | 3
+ 5 | 4 | 6
+(2 rows)
+
+-- CORRESPONDING BY column ordering, BY clause column ordering must be preserved.
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 4 a, 5 b, 6 c;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c, a) SELECT 4 a, 5 b, 6 c;
+ b | c | a 
+---+---+---
+ 2 | 3 | 1
+ 5 | 6 | 4
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(c, a, b) SELECT 4 a, 5 b, 6 c;
+ c | a | b 
+---+---+---
+ 3 | 1 | 2
+ 6 | 4 | 5
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 5 b, 6 c, 4 a;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+SELECT 2 b, 3 c, 1 a UNION CORRESPONDING BY(b, c, a) SELECT 4 a, 5 b, 6 c;
+ b | c | a 
+---+---+---
+ 2 | 3 | 1
+ 5 | 6 | 4
+(2 rows)
+
+SELECT 3 c, 2 b, 1 a UNION CORRESPONDING BY(c, a, b) SELECT 4 a, 5 b, 6 c;
+ c | a | b 
+---+---+---
+ 3 | 1 | 2
+ 6 | 4 | 5
+(2 rows)
+
+-- should to fail
+SELECT 10 a, 20 b UNION ALL CORRESPONDING SELECT 10 c, 20 d;
+ERROR:  there are not any corresponding name
+LINE 1: SELECT 10 a, 20 b UNION ALL CORRESPONDING SELECT 10 c, 20 d;
+               ^
+HINT:  UNION queries with a CORRESPONDING clause must have at least one column with the same name
+SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (a,b) SELECT 10 c, 20 d;
+ERROR:  column name "a" cannot be used in corresponding by clause
+LINE 1: SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (a,b) SELECT 10...
+                                                      ^
+HINT:  UNION queries with a CORRESPONDING BY clause must only contain column names from both tables.
+SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (x) SELECT 10 c, 20 d;
+ERROR:  column name "x" cannot be used in corresponding by clause
+LINE 1: SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (x) SELECT 10 c...
+                                                      ^
+HINT:  UNION queries with a CORRESPONDING BY clause must only contain column names from both tables.
 -- Mixed types
 SELECT 1.1 AS two UNION SELECT 2 ORDER BY 1;
  two 
@@ -258,6 +473,108 @@ ORDER BY 1;
  hi de ho neighbor
 (5 rows)
 
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+         five          
+-----------------------
+ -1.2345678901234e+200
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+                     0
+(5 rows)
+
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING BY(five)
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+         five          
+-----------------------
+ -1.2345678901234e+200
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+                     0
+(5 rows)
+
+SELECT f1 AS ten FROM FLOAT8_TBL
+UNION ALL CORRESPONDING
+SELECT f1 AS ten FROM FLOAT8_TBL;
+          ten          
+-----------------------
+                     0
+                -34.84
+               -1004.3
+ -1.2345678901234e+200
+ -1.2345678901234e-200
+                     0
+                -34.84
+               -1004.3
+ -1.2345678901234e+200
+ -1.2345678901234e-200
+(10 rows)
+
+SELECT f1 AS nine FROM FLOAT8_TBL
+UNION CORRESPONDING
+SELECT f1 AS nine FROM INT4_TBL
+ORDER BY 1;
+         nine          
+-----------------------
+ -1.2345678901234e+200
+           -2147483647
+               -123456
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+                     0
+                123456
+            2147483647
+(9 rows)
+
+SELECT f1 AS ten FROM FLOAT8_TBL
+UNION ALL CORRESPONDING
+SELECT f1 AS ten FROM INT4_TBL;
+          ten          
+-----------------------
+                     0
+                -34.84
+               -1004.3
+ -1.2345678901234e+200
+ -1.2345678901234e-200
+                     0
+                123456
+               -123456
+            2147483647
+           -2147483647
+(10 rows)
+
+SELECT f1 AS five FROM FLOAT8_TBL
+  WHERE f1 BETWEEN -1e6 AND 1e6
+UNION CORRESPONDING
+SELECT f1 AS five FROM INT4_TBL
+  WHERE f1 BETWEEN 0 AND 1000000;
+         five          
+-----------------------
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+                     0
+                123456
+(5 rows)
+
+SELECT * FROM FLOAT8_TBL
+UNION corresponding SELECT * FROM FLOAT8_TBL;
+          f1           
+-----------------------
+                     0
+ -1.2345678901234e-200
+                -34.84
+ -1.2345678901234e+200
+               -1004.3
+(5 rows)
+
 --
 -- INTERSECT and EXCEPT
 --
@@ -320,6 +637,63 @@ SELECT q1 FROM int8_tbl EXCEPT ALL SELECT DISTINCT q2 FROM int8_tbl ORDER BY 1;
 
 SELECT q1 FROM int8_tbl EXCEPT ALL SELECT q1 FROM int8_tbl FOR NO KEY UPDATE;
 ERROR:  FOR NO KEY UPDATE is not allowed with UNION/INTERSECT/EXCEPT
+SELECT q2 FROM int8_tbl INTERSECT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+        q2        
+------------------
+              123
+ 4567890123456789
+(2 rows)
+
+SELECT q2 FROM int8_tbl INTERSECT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+        q2        
+------------------
+              123
+ 4567890123456789
+ 4567890123456789
+(3 rows)
+
+SELECT q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+        q2         
+-------------------
+ -4567890123456789
+               456
+(2 rows)
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+        q2         
+-------------------
+ -4567890123456789
+               456
+(2 rows)
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q1 AS q2 FROM int8_tbl ORDER BY 1;
+        q2         
+-------------------
+ -4567890123456789
+               456
+  4567890123456789
+(3 rows)
+
+SELECT q1 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+ q1 
+----
+(0 rows)
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+        q1        
+------------------
+              123
+ 4567890123456789
+(2 rows)
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q2 AS q1 FROM int8_tbl;
+        q1        
+------------------
+              123
+ 4567890123456789
+ 4567890123456789
+(3 rows)
+
 --
 -- Mixed types
 --
@@ -338,6 +712,21 @@ SELECT f1 FROM float8_tbl EXCEPT SELECT f1 FROM int4_tbl ORDER BY 1;
  -1.2345678901234e-200
 (4 rows)
 
+SELECT f1 FROM float8_tbl INTERSECT CORRESPONDING SELECT f1 FROM int4_tbl;
+ f1 
+----
+  0
+(1 row)
+
+SELECT f1 FROM float8_tbl EXCEPT CORRESPONDING SELECT f1 FROM int4_tbl ORDER BY 1;
+          f1           
+-----------------------
+ -1.2345678901234e+200
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+(4 rows)
+
 --
 -- Operator precedence and (((((extra))))) parentheses
 --
@@ -424,6 +813,24 @@ SELECT q1 FROM int8_tbl EXCEPT (((SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1)))
  4567890123456789
 (2 rows)
 
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2,q1 FROM int8_tbl
+ORDER BY q2,q1;
+ q1 | q2 
+----+----
+(0 rows)
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q1) SELECT q2,q1 FROM int8_tbl
+ORDER BY q1;
+ q1 
+----
+(0 rows)
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q2) SELECT q2,q1 FROM int8_tbl
+ORDER BY q2;
+ q2 
+----
+(0 rows)
+
 --
 -- New syntaxes (7.1) permit new tests
 --
@@ -459,6 +866,22 @@ SELECT '3.4'::numeric UNION SELECT 'foo';
 ERROR:  invalid input syntax for type numeric: "foo"
 LINE 1: SELECT '3.4'::numeric UNION SELECT 'foo';
                                            ^
+SELECT '3.4'::numeric AS a UNION CORRESPONDING SELECT 'foo' AS a;
+ERROR:  invalid input syntax for type numeric: "foo"
+LINE 1: ...CT '3.4'::numeric AS a UNION CORRESPONDING SELECT 'foo' AS a...
+                                                             ^
+SELECT a.f1 FROM (SELECT 'test' AS f1 FROM varchar_tbl) a
+UNION CORRESPONDING
+SELECT b.f1 FROM (SELECT f1 FROM varchar_tbl) b
+ORDER BY 1;
+  f1  
+------
+ a
+ ab
+ abcd
+ test
+(4 rows)
+
 --
 -- Test that expression-index constraints can be pushed down through
 -- UNION or UNION ALL
diff --git a/src/test/regress/sql/union.sql b/src/test/regress/sql/union.sql
index 48e6850..b0f3152 100644
--- a/src/test/regress/sql/union.sql
+++ b/src/test/regress/sql/union.sql
@@ -20,6 +20,74 @@ SELECT 1 AS three UNION SELECT 2 UNION ALL SELECT 2 ORDER BY 1;
 
 SELECT 1.1 AS two UNION SELECT 2.2 ORDER BY 1;
 
+SELECT 1 AS two UNION CORRESPONDING SELECT 2 two;
+
+SELECT 1 AS one UNION CORRESPONDING SELECT 1 one;
+
+SELECT 1 AS two UNION ALL CORRESPONDING SELECT 2 two;
+
+SELECT 1 AS two UNION ALL CORRESPONDING SELECT 1 two;
+
+SELECT 1 AS two UNION CORRESPONDING SELECT 2 two UNION CORRESPONDING SELECT 2 two;
+
+SELECT 1 AS three UNION CORRESPONDING SELECT 2 three UNION ALL CORRESPONDING SELECT 2 three;
+
+SELECT 1 AS three UNION CORRESPONDING SELECT 2 three UNION CORRESPONDING SELECT 3 three;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+
+SELECT 3 c, 2 b, 1 a UNION CORRESPONDING SELECT 6 c, 4 a, 5 b;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(c) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, c) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY (c,b) SELECT 4 a, 5 b, 6 c, 8 d;
+
+-- CORRESPONDING column ordering, left clause's column ordering must be preserved.
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+
+SELECT 2 b, 1 a, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 3 c, 2 b UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 5 b, 6 c, 4 a;
+
+SELECT 2 b, 1 a, 3 c UNION CORRESPONDING SELECT 5 b, 6 c, 4 a;
+
+-- CORRESPONDING BY column ordering, BY clause column ordering must be preserved.
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c, a) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(c, a, b) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 5 b, 6 c, 4 a;
+
+SELECT 2 b, 3 c, 1 a UNION CORRESPONDING BY(b, c, a) SELECT 4 a, 5 b, 6 c;
+
+SELECT 3 c, 2 b, 1 a UNION CORRESPONDING BY(c, a, b) SELECT 4 a, 5 b, 6 c;
+
+
+-- should to fail
+SELECT 10 a, 20 b UNION ALL CORRESPONDING SELECT 10 c, 20 d;
+
+SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (a,b) SELECT 10 c, 20 d;
+
+SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (x) SELECT 10 c, 20 d;
+
 -- Mixed types
 
 SELECT 1.1 AS two UNION SELECT 2 ORDER BY 1;
@@ -90,6 +158,38 @@ UNION
 SELECT TRIM(TRAILING FROM f1) FROM CHAR_TBL
 ORDER BY 1;
 
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING BY(five)
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+
+SELECT f1 AS ten FROM FLOAT8_TBL
+UNION ALL CORRESPONDING
+SELECT f1 AS ten FROM FLOAT8_TBL;
+
+SELECT f1 AS nine FROM FLOAT8_TBL
+UNION CORRESPONDING
+SELECT f1 AS nine FROM INT4_TBL
+ORDER BY 1;
+
+SELECT f1 AS ten FROM FLOAT8_TBL
+UNION ALL CORRESPONDING
+SELECT f1 AS ten FROM INT4_TBL;
+
+SELECT f1 AS five FROM FLOAT8_TBL
+  WHERE f1 BETWEEN -1e6 AND 1e6
+UNION CORRESPONDING
+SELECT f1 AS five FROM INT4_TBL
+  WHERE f1 BETWEEN 0 AND 1000000;
+
+SELECT * FROM FLOAT8_TBL
+UNION corresponding SELECT * FROM FLOAT8_TBL;
+
 --
 -- INTERSECT and EXCEPT
 --
@@ -112,6 +212,22 @@ SELECT q1 FROM int8_tbl EXCEPT ALL SELECT DISTINCT q2 FROM int8_tbl ORDER BY 1;
 
 SELECT q1 FROM int8_tbl EXCEPT ALL SELECT q1 FROM int8_tbl FOR NO KEY UPDATE;
 
+SELECT q2 FROM int8_tbl INTERSECT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+
+SELECT q2 FROM int8_tbl INTERSECT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+
+SELECT q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q1 AS q2 FROM int8_tbl ORDER BY 1;
+
+SELECT q1 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q2 AS q1 FROM int8_tbl;
+
 --
 -- Mixed types
 --
@@ -120,6 +236,10 @@ SELECT f1 FROM float8_tbl INTERSECT SELECT f1 FROM int4_tbl ORDER BY 1;
 
 SELECT f1 FROM float8_tbl EXCEPT SELECT f1 FROM int4_tbl ORDER BY 1;
 
+SELECT f1 FROM float8_tbl INTERSECT CORRESPONDING SELECT f1 FROM int4_tbl;
+
+SELECT f1 FROM float8_tbl EXCEPT CORRESPONDING SELECT f1 FROM int4_tbl ORDER BY 1;
+
 --
 -- Operator precedence and (((((extra))))) parentheses
 --
@@ -150,6 +270,15 @@ SELECT q1 FROM int8_tbl EXCEPT SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1;
 -- But this should work:
 SELECT q1 FROM int8_tbl EXCEPT (((SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1))) ORDER BY 1;
 
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2,q1 FROM int8_tbl
+ORDER BY q2,q1;
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q1) SELECT q2,q1 FROM int8_tbl
+ORDER BY q1;
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q2) SELECT q2,q1 FROM int8_tbl
+ORDER BY q2;
+
 --
 -- New syntaxes (7.1) permit new tests
 --
@@ -170,6 +299,13 @@ ORDER BY 1;
 -- This should fail, but it should produce an error cursor
 SELECT '3.4'::numeric UNION SELECT 'foo';
 
+SELECT '3.4'::numeric AS a UNION CORRESPONDING SELECT 'foo' AS a;
+
+SELECT a.f1 FROM (SELECT 'test' AS f1 FROM varchar_tbl) a
+UNION CORRESPONDING
+SELECT b.f1 FROM (SELECT f1 FROM varchar_tbl) b
+ORDER BY 1;
+
 --
 -- Test that expression-index constraints can be pushed down through
 -- UNION or UNION ALL
#18Pavel Stehule
pavel.stehule@gmail.com
In reply to: Surafel Temesgen (#17)
1 attachment(s)
Re: New CORRESPONDING clause design

Hi

2017-03-14 16:33 GMT+01:00 Surafel Temesgen <surafel3000@gmail.com>:

hi

Some errors are related to just CORRESPONDING without any columns. So

using expr doesn't help here. So parse node CORRESPONDING can solve both
issues.

In current implementation pointing to a node means pointing to a node’s
first element so I don’t think we can be able to point to CORRESPONDING
without any columns

I find out that there is already a node prepare for the case called
A_Const.
The attached patch use that node

It looks better

I fixed format of comments and some too long lines.

all regress tests passed

I have not any objection - I'll mark this patch as ready for commiter

Regards

Pavel

Show quoted text

Regards
Surafel

Attachments:

corresponding_clause_v6.patchtext/x-patch; charset=US-ASCII; name=corresponding_clause_v6.patchDownload
diff --git a/doc/src/sgml/queries.sgml b/doc/src/sgml/queries.sgml
index 30792f45f1..c3cdee54ad 100644
--- a/doc/src/sgml/queries.sgml
+++ b/doc/src/sgml/queries.sgml
@@ -1601,6 +1601,9 @@ SELECT DISTINCT ON (<replaceable>expression</replaceable> <optional>, <replaceab
    <primary>EXCEPT</primary>
   </indexterm>
   <indexterm zone="queries-union">
+   <primary>CORRESPONDING</primary>
+  </indexterm>
+  <indexterm zone="queries-union">
    <primary>set union</primary>
   </indexterm>
   <indexterm zone="queries-union">
@@ -1617,9 +1620,9 @@ SELECT DISTINCT ON (<replaceable>expression</replaceable> <optional>, <replaceab
    The results of two queries can be combined using the set operations
    union, intersection, and difference.  The syntax is
 <synopsis>
-<replaceable>query1</replaceable> UNION <optional>ALL</optional> <replaceable>query2</replaceable>
-<replaceable>query1</replaceable> INTERSECT <optional>ALL</optional> <replaceable>query2</replaceable>
-<replaceable>query1</replaceable> EXCEPT <optional>ALL</optional> <replaceable>query2</replaceable>
+<replaceable>query1</replaceable> UNION <optional>ALL</optional> <optional>CORRESPONDING</optional> <optional>BY</optional> <replaceable>query2</replaceable>
+<replaceable>query1</replaceable> INTERSECT <optional>ALL</optional> <optional>CORRESPONDING</optional> <optional>BY</optional> <replaceable>query2</replaceable>
+<replaceable>query1</replaceable> EXCEPT <optional>ALL</optional> <optional>CORRESPONDING</optional> <optional>BY</optional> <replaceable>query2</replaceable>
 </synopsis>
    <replaceable>query1</replaceable> and
    <replaceable>query2</replaceable> are queries that can use any of
@@ -1659,11 +1662,22 @@ SELECT DISTINCT ON (<replaceable>expression</replaceable> <optional>, <replaceab
   </para>
 
   <para>
-   In order to calculate the union, intersection, or difference of two
-   queries, the two queries must be <quote>union compatible</quote>,
-   which means that they return the same number of columns and
-   the corresponding columns have compatible data types, as
-   described in <xref linkend="typeconv-union-case">.
+   <literal>EXCEPT</> returns all rows that are in the result of
+   <replaceable>query1</replaceable> but not in the result of
+   <replaceable>query2</replaceable>.  (This is sometimes called the
+   <firstterm>difference</> between two queries.)  Again, duplicates
+   are eliminated unless <literal>EXCEPT ALL</> is used.
+  </para>
+
+  <para>
+   <literal>CORRESPONDING</> returns all columns that are in both 
+   <replaceable>query1</> and <replaceable>query2</> with the same name.
+  </para>
+
+  <para>
+   <literal>CORRESPONDING BY</> returns all columns in the column list 
+   that are also in both <replaceable>query1</> and 
+   <replaceable>query2</> with the same name.
   </para>
  </sect1>
 
diff --git a/doc/src/sgml/sql.sgml b/doc/src/sgml/sql.sgml
index 57396d7c24..f98c22e696 100644
--- a/doc/src/sgml/sql.sgml
+++ b/doc/src/sgml/sql.sgml
@@ -859,7 +859,7 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="PARAMETER">expression</replac
     [ WHERE <replaceable class="PARAMETER">condition</replaceable> ]
     [ GROUP BY <replaceable class="PARAMETER">expression</replaceable> [, ...] ]
     [ HAVING <replaceable class="PARAMETER">condition</replaceable> [, ...] ]
-    [ { UNION | INTERSECT | EXCEPT } [ ALL ] <replaceable class="PARAMETER">select</replaceable> ]
+    [ { UNION | INTERSECT | EXCEPT } [ ALL ] [ CORRESPONDING [ BY ( <replaceable class="PARAMETER">expression</replaceable> ) ] ] <replaceable class="PARAMETER">select</replaceable> ]
     [ ORDER BY <replaceable class="parameter">expression</replaceable> [ ASC | DESC | USING <replaceable class="parameter">operator</replaceable> ] [ NULLS { FIRST | LAST } ] [, ...] ]
     [ LIMIT { <replaceable class="PARAMETER">count</replaceable> | ALL } ]
     [ OFFSET <replaceable class="PARAMETER">start</replaceable> ]
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 25fd051d6e..5dacb33e9e 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2977,6 +2977,7 @@ _copySelectStmt(const SelectStmt *from)
 	COPY_NODE_FIELD(withClause);
 	COPY_SCALAR_FIELD(op);
 	COPY_SCALAR_FIELD(all);
+	COPY_NODE_FIELD(correspondingClause);
 	COPY_NODE_FIELD(larg);
 	COPY_NODE_FIELD(rarg);
 
@@ -2992,6 +2993,7 @@ _copySetOperationStmt(const SetOperationStmt *from)
 	COPY_SCALAR_FIELD(all);
 	COPY_NODE_FIELD(larg);
 	COPY_NODE_FIELD(rarg);
+	COPY_NODE_FIELD(correspondingColumns);
 	COPY_NODE_FIELD(colTypes);
 	COPY_NODE_FIELD(colTypmods);
 	COPY_NODE_FIELD(colCollations);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 67529e3f86..4d66ca309a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1041,6 +1041,7 @@ _equalSelectStmt(const SelectStmt *a, const SelectStmt *b)
 	COMPARE_NODE_FIELD(withClause);
 	COMPARE_SCALAR_FIELD(op);
 	COMPARE_SCALAR_FIELD(all);
+	COMPARE_NODE_FIELD(correspondingClause);
 	COMPARE_NODE_FIELD(larg);
 	COMPARE_NODE_FIELD(rarg);
 
@@ -1054,6 +1055,7 @@ _equalSetOperationStmt(const SetOperationStmt *a, const SetOperationStmt *b)
 	COMPARE_SCALAR_FIELD(all);
 	COMPARE_NODE_FIELD(larg);
 	COMPARE_NODE_FIELD(rarg);
+	COMPARE_NODE_FIELD(correspondingColumns);
 	COMPARE_NODE_FIELD(colTypes);
 	COMPARE_NODE_FIELD(colTypmods);
 	COMPARE_NODE_FIELD(colCollations);
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 6e52eb7231..7102ea96c2 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -3444,6 +3444,8 @@ raw_expression_tree_walker(Node *node,
 					return true;
 				if (walker(stmt->lockingClause, context))
 					return true;
+				if (walker(stmt->correspondingClause, context))
+					return true;
 				if (walker(stmt->withClause, context))
 					return true;
 				if (walker(stmt->larg, context))
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 7418fbeded..74e0a8f257 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2599,6 +2599,7 @@ _outSelectStmt(StringInfo str, const SelectStmt *node)
 	WRITE_NODE_FIELD(withClause);
 	WRITE_ENUM_FIELD(op, SetOperation);
 	WRITE_BOOL_FIELD(all);
+	WRITE_NODE_FIELD(correspondingClause);
 	WRITE_NODE_FIELD(larg);
 	WRITE_NODE_FIELD(rarg);
 }
@@ -2906,6 +2907,7 @@ _outSetOperationStmt(StringInfo str, const SetOperationStmt *node)
 	WRITE_BOOL_FIELD(all);
 	WRITE_NODE_FIELD(larg);
 	WRITE_NODE_FIELD(rarg);
+	WRITE_NODE_FIELD(correspondingColumns);
 	WRITE_NODE_FIELD(colTypes);
 	WRITE_NODE_FIELD(colTypmods);
 	WRITE_NODE_FIELD(colCollations);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index d3bbc02f24..44a8ecb6a5 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -416,6 +416,7 @@ _readSetOperationStmt(void)
 	READ_BOOL_FIELD(all);
 	READ_NODE_FIELD(larg);
 	READ_NODE_FIELD(rarg);
+	READ_NODE_FIELD(correspondingColumns);
 	READ_NODE_FIELD(colTypes);
 	READ_NODE_FIELD(colTypmods);
 	READ_NODE_FIELD(colCollations);
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 1389db18ba..a06451f9fd 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -91,7 +91,8 @@ static List *generate_setop_tlist(List *colTypes, List *colCollations,
 					 Index varno,
 					 bool hack_constants,
 					 List *input_tlist,
-					 List *refnames_tlist);
+					 List *refnames_tlist,
+					 bool no_corresponding);
 static List *generate_append_tlist(List *colTypes, List *colCollations,
 					  bool flag,
 					  List *input_tlists,
@@ -110,6 +111,7 @@ static Node *adjust_appendrel_attrs_mutator(Node *node,
 static Relids adjust_relid_set(Relids relids, Index oldrelid, Index newrelid);
 static List *adjust_inherited_tlist(List *tlist,
 					   AppendRelInfo *context);
+static List *make_corresponding_target(List *corresponding_list, List *subroot_list);
 
 
 /*
@@ -187,6 +189,24 @@ plan_set_operations(PlannerInfo *root)
 									   leftmostQuery->targetList,
 									   &top_tlist);
 	}
+	/*
+	 * If corresponding column specified, we take column names from it.
+	 */
+	else if (topop->correspondingColumns != NIL )
+	{
+		/*
+		 * Recurse on setOperations tree to generate paths for set ops. The
+		 * final output path should have just the column types shown as the
+		 * output from the top-level node, plus possibly resjunk working
+		 * columns (we can rely on upper-level nodes to deal with that).
+		 */
+		path = recurse_set_operations((Node *) topop, root,
+									  topop->colTypes, topop->colCollations,
+									  true, -1,
+									  topop->correspondingColumns,
+									  &top_tlist,
+									  NULL);
+	}
 	else
 	{
 		/*
@@ -252,6 +272,8 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 					   List **pTargetList,
 					   double *pNumGroups)
 {
+	SetOperationStmt *topop = (SetOperationStmt *) root->parse->setOperations;
+
 	if (IsA(setOp, RangeTblRef))
 	{
 		RangeTblRef *rtr = (RangeTblRef *) setOp;
@@ -316,23 +338,53 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 		path = (Path *) create_subqueryscan_path(root, rel, subpath,
 												 NIL, NULL);
 
-		/*
-		 * Figure out the appropriate target list, and update the
-		 * SubqueryScanPath with the PathTarget form of that.
-		 */
-		tlist = generate_setop_tlist(colTypes, colCollations,
+		if (topop->correspondingColumns != NIL )
+		{
+			List	    *correspondingTarget;
+
+			/*
+			 * make target list that only contains corresponding column
+			 * from sub-queries list ito use it for projection
+			 */
+			correspondingTarget = make_corresponding_target(
+											  topop->correspondingColumns,
+											  subroot->processed_tlist);
+
+			/*
+			 * Figure out the appropriate target list, and update the
+			 * SubqueryScanPath with the PathTarget form of that.
+			 */
+			tlist = generate_setop_tlist(colTypes, colCollations, flag,
+											  rtr->rtindex, true,
+											  correspondingTarget,
+											  refnames_tlist, false);
+
+			path = apply_projection_to_path(root, rel, path,
+					create_pathtarget(root, tlist));
+
+			/* Return the fully-fledged tlist to caller, too */
+			*pTargetList = tlist;
+
+		}
+		else
+		{
+			/*
+			 * Figure out the appropriate target list, and update the
+			 * SubqueryScanPath with the PathTarget form of that.
+			 */
+			tlist = generate_setop_tlist(colTypes, colCollations,
 									 flag,
 									 rtr->rtindex,
 									 true,
 									 subroot->processed_tlist,
-									 refnames_tlist);
+									 refnames_tlist, true);
 
-		path = apply_projection_to_path(root, rel, path,
+			path = apply_projection_to_path(root, rel, path,
 										create_pathtarget(root, tlist));
 
-		/* Return the fully-fledged tlist to caller, too */
-		*pTargetList = tlist;
-
+			/* Return the fully-fledged tlist to caller, too */
+			*pTargetList = tlist;
+		}
 		/*
 		 * Estimate number of groups if caller wants it.  If the subquery used
 		 * grouping or aggregation, its output is probably mostly unique
@@ -392,7 +444,7 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 												0,
 												false,
 												*pTargetList,
-												refnames_tlist);
+												refnames_tlist, true);
 			path = apply_projection_to_path(root,
 											path->parent,
 											path,
@@ -1004,7 +1056,8 @@ generate_setop_tlist(List *colTypes, List *colCollations,
 					 Index varno,
 					 bool hack_constants,
 					 List *input_tlist,
-					 List *refnames_tlist)
+					 List *refnames_tlist,
+					 bool no_corresponding)
 {
 	List	   *tlist = NIL;
 	int			resno = 1;
@@ -1025,9 +1078,11 @@ generate_setop_tlist(List *colTypes, List *colCollations,
 		TargetEntry *reftle = (TargetEntry *) lfirst(rtlc);
 
 		rtlc = lnext(rtlc);
-
-		Assert(inputtle->resno == resno);
-		Assert(reftle->resno == resno);
+		if (no_corresponding)
+		{
+			Assert(inputtle->resno == resno);
+			Assert(reftle->resno == resno);
+		}
 		Assert(!inputtle->resjunk);
 		Assert(!reftle->resjunk);
 
@@ -2111,3 +2166,72 @@ adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node,
 	/* Now translate for this child */
 	return adjust_appendrel_attrs(root, node, appinfo);
 }
+
+/*
+ * generate target list from left target list with the order
+ * of right target list
+ */
+static List *
+make_corresponding_target(List *corresponding_list, List *subroot_list)
+{
+	Index internal = 0;
+	ListCell   *ltl;
+	ListCell   *rtl;
+	int			size;
+	int			i;
+	List *matchingColumns = NIL;
+	TargetEntry *simple_te_array;
+
+	size = list_length(corresponding_list) + 1;
+
+	/* Use array to find the order of corresponding columen */
+	simple_te_array = (TargetEntry *) palloc0(size * sizeof(TargetEntry));
+	foreach(ltl, corresponding_list)
+	{
+		foreach(rtl, subroot_list)
+		{
+			TargetEntry *ltle = (TargetEntry *) lfirst(ltl);
+			TargetEntry *rtle = (TargetEntry *) lfirst(rtl);
+
+			elog(DEBUG4, "%s", ltle->resname);
+
+			/* Names of the columns must be resolved before calling this method. */
+			Assert(ltle->resname != NULL);
+			Assert(rtle->resname != NULL);
+
+			/* If column names are the same, add it to array. */
+			if (strcmp(ltle->resname, rtle->resname) == 0)
+			{
+				simple_te_array[internal].xpr = rtle->xpr;
+				simple_te_array[internal].expr = rtle->expr;
+				simple_te_array[internal].resno = rtle->resno;
+				simple_te_array[internal].resname = rtle->resname;
+				simple_te_array[internal].ressortgroupref =
+						rtle->ressortgroupref;
+				simple_te_array[internal].resorigtbl = rtle->resorigtbl;
+				simple_te_array[internal].resorigcol = rtle->resorigcol;
+				simple_te_array[internal].resjunk = rtle->resjunk;
+				internal++;
+				continue;
+			}
+		}
+	}
+	/* traverse the array and make targetlist */
+	for (i = 0; i < internal; i++)
+	{
+		TargetEntry *tle = makeNode(TargetEntry);
+
+		tle->xpr = simple_te_array[i].xpr;
+		tle->expr = simple_te_array[i].expr;
+		tle->resno = simple_te_array[i].resno;
+		tle->resname = simple_te_array[i].resname;
+		tle->ressortgroupref = simple_te_array[i].ressortgroupref;
+		tle->resorigtbl = simple_te_array[i].resorigtbl;
+		tle->resorigcol = simple_te_array[i].resorigcol;
+		tle->resjunk = simple_te_array[i].resjunk;
+
+		matchingColumns = lappend(matchingColumns, tle);
+
+	}
+	return matchingColumns;
+}
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 3571e50aea..e26ce46f3b 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -76,6 +76,10 @@ static Query *transformCreateTableAsStmt(ParseState *pstate,
 						   CreateTableAsStmt *stmt);
 static void transformLockingClause(ParseState *pstate, Query *qry,
 					   LockingClause *lc, bool pushedDown);
+static List *determineMatchingColumns(List *ltargetlist, List *rtargetlist);
+static void *makeUnionDatatype(List *ltargetlist, List *rtargetlist,
+		SetOperationStmt *op, List **targetlist, ParseState *parentParseState,
+		const char *context);
 #ifdef RAW_EXPRESSION_COVERAGE_TEST
 static bool test_raw_expression_coverage(Node *node, void *context);
 #endif
@@ -1661,7 +1665,13 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->targetList = NIL;
 	targetvars = NIL;
 	targetnames = NIL;
-	left_tlist = list_head(leftmostQuery->targetList);
+	/* for corresponding clause limits top-level query targetlist to those
+	 * corresponding column list only
+	 */
+	if (sostmt->correspondingColumns != NIL )
+		left_tlist = list_head(sostmt->correspondingColumns);
+	else
+		left_tlist = list_head(leftmostQuery->targetList);
 
 	forthree(lct, sostmt->colTypes,
 			 lcm, sostmt->colTypmods,
@@ -1921,8 +1931,6 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 		SetOperationStmt *op = makeNode(SetOperationStmt);
 		List	   *ltargetlist;
 		List	   *rtargetlist;
-		ListCell   *ltl;
-		ListCell   *rtl;
 		const char *context;
 
 		context = (stmt->op == SETOP_UNION ? "UNION" :
@@ -1933,6 +1941,215 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 		op->all = stmt->all;
 
 		/*
+		 * If CORRESPONDING is specified, syntax and column name validities checked,
+		 * column filtering is done by a subquery later on.
+		 */
+		if (stmt->correspondingClause == NIL )
+		{
+			/* No CORRESPONDING clause, no operation needed for column filtering */
+			op->correspondingColumns = stmt->correspondingClause;
+		}
+		else if (linitial(stmt->correspondingClause) == NULL )
+		{
+			/*
+			 * CORRESPONDING clause, find matching column names from both tables.
+			 * If there are none then it is a syntax error.
+			 */
+			Query	   *largQuery;
+			Query	   *rargQuery;
+			List	   *matchingColumns;
+			List	   *rightCorrespondingColumns;
+
+			/* Analyze left query to resolve column names. */
+			largQuery = parse_sub_analyze((Node *) stmt->larg,
+										  pstate, NULL, false, false );
+
+			/* Analyze right query to resolve column names. */
+			rargQuery = parse_sub_analyze((Node *) stmt->rarg,
+										  pstate, NULL, false, false );
+
+			/* Find matching columns from both queries. */
+			matchingColumns = determineMatchingColumns(largQuery->targetList,
+													   rargQuery->targetList);
+
+			/* there may be out-of-order resnos in corresponding target list */
+			op->correspondingColumns = orderCorrespondingList(matchingColumns);
+
+			/*
+			 * If matchingColumns is empty, there is an error.
+			 * At least one column in the select lists must have the same name.
+			 */
+			if (list_length(matchingColumns) == 0)
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("there are not any corresponding name"),
+	 errhint("%s queries with a CORRESPONDING clause must have at least one column with the same name",
+								 context),
+						 parser_errposition(pstate,
+									exprLocation((Node *)
+												 linitial(largQuery->targetList)))));
+			}
+
+			/*
+			 * matchingColumns contain target list as it appear in left query
+			 * targetList we need matching column as it appear in right query
+			 * targetlist inorder to make output column type for corresponding
+			 * columns
+			 */
+			rightCorrespondingColumns = determineMatchingColumns(
+														rargQuery->targetList,
+														largQuery->targetList);
+
+			/* make union'd datatype of output column */
+			makeUnionDatatype(matchingColumns, rightCorrespondingColumns,
+								op, targetlist, pstate, context);
+		}
+		else
+		{
+			/*
+			 * CORRESPONDING BY clause, find matching column names from both tables
+			 * and intersect them with BY(...) column list. If there are none
+			 * then it is a syntax error.
+			 */
+			Query	   *largQuery;
+			Query	   *rargQuery;
+			List	   *matchingColumns;
+			List	   *matchingColumnsFiltered;
+			List	   *rightCorrespondingColumns;
+			ListCell   *corrtl;
+			ListCell   *mctl;
+
+			/* Analyze left query to resolve column names. */
+			largQuery = parse_sub_analyze((Node *) stmt->larg,
+										  pstate, NULL,false, false );
+
+			/* Analyze right query to resolve column names. */
+			rargQuery = parse_sub_analyze((Node *) stmt->rarg,
+										  pstate, NULL, false, false );
+
+			/*
+			 * Find matching columns from both queries.
+			 * In CORRESPONDING BY, column names will be removed from
+			 * matchingColumns if they are not in the BY clause.
+			 * All columns in the BY clause must be in matchingColumns,
+			 * otherwise raise syntax error in BY clause.
+			 */
+			matchingColumns = determineMatchingColumns(largQuery->targetList,
+													   rargQuery->targetList);
+
+			/*
+			 * Every column name in correspondingClause must be in matchingColumns,
+			 * otherwise it is a syntax error.
+			 */
+			foreach(corrtl, stmt->correspondingClause)
+			{
+				A_Const	   *corrtle = lfirst(corrtl);
+				Value	   *corrvalue = &corrtle->val;
+				char	   *name;
+				bool		hasMatch = false;
+
+				/* Get column name from correspondingClause. */
+				name = strVal(corrvalue);
+
+				foreach(mctl, matchingColumns)
+				{
+					TargetEntry	   *mctle = (TargetEntry *) lfirst(mctl);
+
+					Assert(mctle->resname != NULL);
+					Assert(name != NULL);
+
+					/*
+					 * Compare correspondingClause column name with
+					 * matchingColumns column names.
+					 */
+					if (strcmp(mctle->resname, name) == 0)
+					{
+						/* we have a match. */
+						hasMatch = true;
+						break;
+					}
+				}
+
+				if (!hasMatch)
+				{
+					/*
+					 * CORRESPONDING BY clause contains a column name that is
+					 * not in both tables.
+					 */
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("column name \"%s\" cannot be used in corresponding by clause", name),
+		 errhint("%s queries with a CORRESPONDING BY clause must only contain column names from both tables.",
+									 context),
+							 parser_errposition(pstate,
+												exprLocation((Node *) stmt->correspondingClause))));
+				}
+			}
+
+			/*
+			 * To preserve column ordering from correspondingClause and to remove
+			 * columns from matchingColumns if they are not in correspondingClause,
+			 * create a new list and finalize our column list for the
+			 * CORRESPONDING BY clause.
+			 */
+			matchingColumnsFiltered = NIL;
+
+			/*
+			 * For each column in CORRESPONDING BY column list, check
+			 * column existence in matchingColumns.
+			 */
+			foreach(corrtl, stmt->correspondingClause)
+			{
+				A_Const	   *corrtle = lfirst(corrtl);
+				Value	   *corrvalue = &corrtle->val;
+
+				/* Get column name from correspondingClause. */
+				char *name = strVal(corrvalue);
+
+				foreach(mctl, matchingColumns)
+				{
+					TargetEntry *mctle = (TargetEntry *) lfirst(mctl);
+
+					Assert(mctle->resname != NULL);
+					Assert(name != NULL);
+
+					if (strcmp(mctle->resname, name) == 0)
+					{
+						/* we have a match.*/
+						matchingColumnsFiltered = lappend(
+								matchingColumnsFiltered, mctle);
+						break;
+					}
+				}
+			}
+
+			/*
+			 * If matchingColumnsFiltered is empty, there is a semantic error.
+			 * At least one column in the select lists must have the same name.
+			 */
+			Assert(list_length(matchingColumnsFiltered) > 0);
+
+			/*
+			 * there may be out-of-order resnos in corresponding target list
+			 */
+			op->correspondingColumns = orderCorrespondingList(matchingColumnsFiltered);
+
+			/*
+			 * matchingColumns contain target list as it appear in left query targetList
+			 * we need matching column as it appear in right query targetlist inorder to
+			 * make output column type for corresponding columns
+			 */
+			rightCorrespondingColumns = determineMatchingColumns(rargQuery->targetList,
+																 matchingColumnsFiltered);
+			/*
+			 * make union'd datatype of output columns
+			 */
+			makeUnionDatatype(matchingColumnsFiltered, rightCorrespondingColumns,
+							  op, targetlist, pstate, context);
+		}
+
+		/*
 		 * Recursively transform the left child node.
 		 */
 		op->larg = transformSetOperationTree(pstate, stmt->larg,
@@ -1957,177 +2174,224 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 											 false,
 											 &rtargetlist);
 
-		/*
-		 * Verify that the two children have the same number of non-junk
-		 * columns, and determine the types of the merged output columns.
-		 */
-		if (list_length(ltargetlist) != list_length(rtargetlist))
-			ereport(ERROR,
-					(errcode(ERRCODE_SYNTAX_ERROR),
-				 errmsg("each %s query must have the same number of columns",
-						context),
-					 parser_errposition(pstate,
-										exprLocation((Node *) rtargetlist))));
-
-		if (targetlist)
-			*targetlist = NIL;
-		op->colTypes = NIL;
-		op->colTypmods = NIL;
-		op->colCollations = NIL;
-		op->groupClauses = NIL;
-		forboth(ltl, ltargetlist, rtl, rtargetlist)
+		if (op->correspondingColumns == NIL )
 		{
-			TargetEntry *ltle = (TargetEntry *) lfirst(ltl);
-			TargetEntry *rtle = (TargetEntry *) lfirst(rtl);
-			Node	   *lcolnode = (Node *) ltle->expr;
-			Node	   *rcolnode = (Node *) rtle->expr;
-			Oid			lcoltype = exprType(lcolnode);
-			Oid			rcoltype = exprType(rcolnode);
-			int32		lcoltypmod = exprTypmod(lcolnode);
-			int32		rcoltypmod = exprTypmod(rcolnode);
-			Node	   *bestexpr;
-			int			bestlocation;
-			Oid			rescoltype;
-			int32		rescoltypmod;
-			Oid			rescolcoll;
-
-			/* select common type, same as CASE et al */
-			rescoltype = select_common_type(pstate,
-											list_make2(lcolnode, rcolnode),
-											context,
-											&bestexpr);
-			bestlocation = exprLocation(bestexpr);
-			/* if same type and same typmod, use typmod; else default */
-			if (lcoltype == rcoltype && lcoltypmod == rcoltypmod)
-				rescoltypmod = lcoltypmod;
-			else
-				rescoltypmod = -1;
-
+			makeUnionDatatype(ltargetlist, rtargetlist, op, targetlist, pstate,
+					context);
 			/*
-			 * Verify the coercions are actually possible.  If not, we'd fail
-			 * later anyway, but we want to fail now while we have sufficient
-			 * context to produce an error cursor position.
-			 *
-			 * For all non-UNKNOWN-type cases, we verify coercibility but we
-			 * don't modify the child's expression, for fear of changing the
-			 * child query's semantics.
-			 *
-			 * If a child expression is an UNKNOWN-type Const or Param, we
-			 * want to replace it with the coerced expression.  This can only
-			 * happen when the child is a leaf set-op node.  It's safe to
-			 * replace the expression because if the child query's semantics
-			 * depended on the type of this output column, it'd have already
-			 * coerced the UNKNOWN to something else.  We want to do this
-			 * because (a) we want to verify that a Const is valid for the
-			 * target type, or resolve the actual type of an UNKNOWN Param,
-			 * and (b) we want to avoid unnecessary discrepancies between the
-			 * output type of the child query and the resolved target type.
-			 * Such a discrepancy would disable optimization in the planner.
-			 *
-			 * If it's some other UNKNOWN-type node, eg a Var, we do nothing
-			 * (knowing that coerce_to_common_type would fail).  The planner
-			 * is sometimes able to fold an UNKNOWN Var to a constant before
-			 * it has to coerce the type, so failing now would just break
-			 * cases that might work.
+			 * Verify that the two children have the same number of non-junk
+			 * columns, and determine the types of the merged output columns.
 			 */
-			if (lcoltype != UNKNOWNOID)
-				lcolnode = coerce_to_common_type(pstate, lcolnode,
-												 rescoltype, context);
-			else if (IsA(lcolnode, Const) ||
-					 IsA(lcolnode, Param))
-			{
-				lcolnode = coerce_to_common_type(pstate, lcolnode,
-												 rescoltype, context);
-				ltle->expr = (Expr *) lcolnode;
-			}
+			if (list_length(ltargetlist) != list_length(rtargetlist))
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("each %s query must have the same number of columns", context),
+						 parser_errposition(pstate,
+											exprLocation((Node *) rtargetlist))));
+		}
 
-			if (rcoltype != UNKNOWNOID)
-				rcolnode = coerce_to_common_type(pstate, rcolnode,
-												 rescoltype, context);
-			else if (IsA(rcolnode, Const) ||
-					 IsA(rcolnode, Param))
-			{
-				rcolnode = coerce_to_common_type(pstate, rcolnode,
-												 rescoltype, context);
-				rtle->expr = (Expr *) rcolnode;
-			}
+		return (Node *) op;
+	}
+}
 
-			/*
-			 * Select common collation.  A common collation is required for
-			 * all set operators except UNION ALL; see SQL:2008 7.13 <query
-			 * expression> Syntax Rule 15c.  (If we fail to identify a common
-			 * collation for a UNION ALL column, the curCollations element
-			 * will be set to InvalidOid, which may result in a runtime error
-			 * if something at a higher query level wants to use the column's
-			 * collation.)
-			 */
-			rescolcoll = select_common_collation(pstate,
-											  list_make2(lcolnode, rcolnode),
-										 (op->op == SETOP_UNION && op->all));
+/*
+ * Processes targetlists of two queries for column equivalence to use
+ * with UNION/INTERSECT/EXCEPT CORRESPONDING.
+ */
+static List *
+determineMatchingColumns(List *ltargetlist, List *rtargetlist)
+{
+	List	   *matchingColumns = NIL;
+	ListCell   *ltl;
+	ListCell   *rtl;
+
+	foreach(ltl, ltargetlist)
+	{
+		foreach(rtl, rtargetlist)
+		{
+			TargetEntry	   *ltle = (TargetEntry *) lfirst(ltl);
+			TargetEntry	   *rtle = (TargetEntry *) lfirst(rtl);
 
-			/* emit results */
-			op->colTypes = lappend_oid(op->colTypes, rescoltype);
-			op->colTypmods = lappend_int(op->colTypmods, rescoltypmod);
-			op->colCollations = lappend_oid(op->colCollations, rescolcoll);
+			elog(DEBUG4, "%s", ltle->resname);
 
-			/*
-			 * For all cases except UNION ALL, identify the grouping operators
-			 * (and, if available, sorting operators) that will be used to
-			 * eliminate duplicates.
-			 */
-			if (op->op != SETOP_UNION || !op->all)
-			{
-				SortGroupClause *grpcl = makeNode(SortGroupClause);
-				Oid			sortop;
-				Oid			eqop;
-				bool		hashable;
-				ParseCallbackState pcbstate;
-
-				setup_parser_errposition_callback(&pcbstate, pstate,
-												  bestlocation);
-
-				/* determine the eqop and optional sortop */
-				get_sort_group_operators(rescoltype,
-										 false, true, false,
-										 &sortop, &eqop, NULL,
-										 &hashable);
-
-				cancel_parser_errposition_callback(&pcbstate);
-
-				/* we don't have a tlist yet, so can't assign sortgrouprefs */
-				grpcl->tleSortGroupRef = 0;
-				grpcl->eqop = eqop;
-				grpcl->sortop = sortop;
-				grpcl->nulls_first = false;		/* OK with or without sortop */
-				grpcl->hashable = hashable;
-
-				op->groupClauses = lappend(op->groupClauses, grpcl);
-			}
+			/* Names of the columns must be resolved before calling this method. */
+			Assert(ltle->resname != NULL);
+			Assert(rtle->resname != NULL);
 
-			/*
-			 * Construct a dummy tlist entry to return.  We use a SetToDefault
-			 * node for the expression, since it carries exactly the fields
-			 * needed, but any other expression node type would do as well.
-			 */
-			if (targetlist)
+			/* If column names are the same, append it to the result. */
+			if (strcmp(ltle->resname, rtle->resname) == 0)
 			{
-				SetToDefault *rescolnode = makeNode(SetToDefault);
-				TargetEntry *restle;
-
-				rescolnode->typeId = rescoltype;
-				rescolnode->typeMod = rescoltypmod;
-				rescolnode->collation = rescolcoll;
-				rescolnode->location = bestlocation;
-				restle = makeTargetEntry((Expr *) rescolnode,
-										 0,		/* no need to set resno */
-										 NULL,
-										 false);
-				*targetlist = lappend(*targetlist, restle);
+				matchingColumns = lappend(matchingColumns, ltle);
+				continue;
 			}
 		}
+	}
 
-		return (Node *) op;
+	return matchingColumns;
+}
+
+/*
+ * process right and left target list to set up union'd datatype
+ */
+static void *
+makeUnionDatatype(List *ltargetlist, List *rtargetlist, SetOperationStmt *op,
+		List **targetlist, ParseState *pstate, const char *context)
+{
+	ListCell   *ltl;
+	ListCell   *rtl;
+
+	if (targetlist)
+		*targetlist = NIL;
+
+	op->colTypes = NIL;
+	op->colTypmods = NIL;
+	op->colCollations = NIL;
+	op->groupClauses = NIL;
+
+	forboth(ltl, ltargetlist, rtl, rtargetlist)
+	{
+		TargetEntry	   *ltle = (TargetEntry *) lfirst(ltl);
+		TargetEntry	   *rtle = (TargetEntry *) lfirst(rtl);
+		Node		   *lcolnode = (Node *) ltle->expr;
+		Node		   *rcolnode = (Node *) rtle->expr;
+		Oid			lcoltype = exprType(lcolnode);
+		Oid			rcoltype = exprType(rcolnode);
+		int32		lcoltypmod = exprTypmod(lcolnode);
+		int32		rcoltypmod = exprTypmod(rcolnode);
+		Node	   *bestexpr;
+		int			bestlocation;
+		Oid			rescoltype;
+		int32		rescoltypmod;
+		Oid			rescolcoll;
+
+		/* select common type, same as CASE et al */
+		rescoltype = select_common_type(pstate, list_make2(lcolnode, rcolnode),
+				context, &bestexpr);
+		bestlocation = exprLocation(bestexpr);
+		/* if same type and same typmod, use typmod; else default */
+		if (lcoltype == rcoltype && lcoltypmod == rcoltypmod)
+			rescoltypmod = lcoltypmod;
+		else
+			rescoltypmod = -1;
+
+		/*
+		 * Verify the coercions are actually possible.  If not, we'd fail
+		 * later anyway, but we want to fail now while we have sufficient
+		 * context to produce an error cursor position.
+		 *
+		 * For all non-UNKNOWN-type cases, we verify coercibility but we
+		 * don't modify the child's expression, for fear of changing the
+		 * child query's semantics.
+		 *
+		 * If a child expression is an UNKNOWN-type Const or Param, we
+		 * want to replace it with the coerced expression.  This can only
+		 * happen when the child is a leaf set-op node.  It's safe to
+		 * replace the expression because if the child query's semantics
+		 * depended on the type of this output column, it'd have already
+		 * coerced the UNKNOWN to something else.  We want to do this
+		 * because (a) we want to verify that a Const is valid for the
+		 * target type, or resolve the actual type of an UNKNOWN Param,
+		 * and (b) we want to avoid unnecessary discrepancies between the
+		 * output type of the child query and the resolved target type.
+		 * Such a discrepancy would disable optimization in the planner.
+		 *
+		 * If it's some other UNKNOWN-type node, eg a Var, we do nothing
+		 * (knowing that coerce_to_common_type would fail).  The planner
+		 * is sometimes able to fold an UNKNOWN Var to a constant before
+		 * it has to coerce the type, so failing now would just break
+		 * cases that might work.
+		 */
+		if (lcoltype != UNKNOWNOID)
+			lcolnode = coerce_to_common_type(pstate, lcolnode, rescoltype,
+					context);
+		else if (IsA(lcolnode, Const) || IsA(lcolnode, Param))
+		{
+			lcolnode = coerce_to_common_type(pstate, lcolnode, rescoltype,
+					context);
+			ltle->expr = (Expr *) lcolnode;
+		}
+
+		if (rcoltype != UNKNOWNOID)
+			rcolnode = coerce_to_common_type(pstate, rcolnode, rescoltype,
+					context);
+		else if (IsA(rcolnode, Const) || IsA(rcolnode, Param))
+		{
+			rcolnode = coerce_to_common_type(pstate, rcolnode, rescoltype,
+					context);
+			rtle->expr = (Expr *) rcolnode;
+		}
+
+		/*
+		 * Select common collation.  A common collation is required for
+		 * all set operators except UNION ALL; see SQL:2008 7.13 <query
+		 * expression> Syntax Rule 15c.  (If we fail to identify a common
+		 * collation for a UNION ALL column, the curCollations element
+		 * will be set to InvalidOid, which may result in a runtime error
+		 * if something at a higher query level wants to use the column's
+		 * collation.)
+		 */
+		rescolcoll = select_common_collation(pstate,
+				list_make2(lcolnode, rcolnode),
+				(op->op == SETOP_UNION && op->all));
+
+		/* emit results */
+		op->colTypes = lappend_oid(op->colTypes, rescoltype);
+		op->colTypmods = lappend_int(op->colTypmods, rescoltypmod);
+		op->colCollations = lappend_oid(op->colCollations, rescolcoll);
+
+		/*
+		 * For all cases except UNION ALL, identify the grouping operators
+		 * (and, if available, sorting operators) that will be used to
+		 * eliminate duplicates.
+		 */
+		if (op->op != SETOP_UNION || !op->all)
+		{
+			SortGroupClause *grpcl = makeNode(SortGroupClause);
+			Oid			sortop;
+			Oid			eqop;
+			bool		hashable;
+			ParseCallbackState pcbstate;
+
+			setup_parser_errposition_callback(&pcbstate, pstate, bestlocation);
+
+			/* determine the eqop and optional sortop */
+			get_sort_group_operators(rescoltype, false, true, false, &sortop,
+					&eqop, NULL, &hashable);
+
+			cancel_parser_errposition_callback(&pcbstate);
+
+			/* we don't have a tlist yet, so can't assign sortgrouprefs */
+			grpcl->tleSortGroupRef = 0;
+			grpcl->eqop = eqop;
+			grpcl->sortop = sortop;
+			grpcl->nulls_first = false; /* OK with or without sortop */
+			grpcl->hashable = hashable;
+
+			op->groupClauses = lappend(op->groupClauses, grpcl);
+		}
+
+		/*
+		 * Construct a dummy tlist entry to return.  We use a SetToDefault
+		 * node for the expression, since it carries exactly the fields
+		 * needed, but any other expression node type would do as well.
+		 */
+		if (targetlist)
+		{
+			SetToDefault   *rescolnode = makeNode(SetToDefault);
+			TargetEntry	   *restle;
+
+			rescolnode->typeId = rescoltype;
+			rescolnode->typeMod = rescoltypmod;
+			rescolnode->collation = rescolcoll;
+			rescolnode->location = bestlocation;
+
+			/* no need to set resno */
+			restle = makeTargetEntry((Expr *) rescolnode, 0,
+			NULL, false );
+			*targetlist = lappend(*targetlist, restle);
+		}
 	}
+	return 0;
 }
 
 /*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 6316688a88..6e10718ccc 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -166,7 +166,7 @@ static void insertSelectOptions(SelectStmt *stmt,
 								Node *limitOffset, Node *limitCount,
 								WithClause *withClause,
 								core_yyscan_t yyscanner);
-static Node *makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg);
+static Node *makeSetOp(SetOperation op, bool all, List *correspondingClause, Node *larg, Node *rarg);
 static Node *doNegate(Node *n, int location);
 static void doNegateFloat(Value *v);
 static Node *makeAndExpr(Node *lexpr, Node *rexpr, int location);
@@ -394,7 +394,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				relation_expr_list dostmt_opt_list
 				transform_element_list transform_type_list
 				TriggerTransitions TriggerReferencing
-				publication_name_list
+				publication_name_list opt_corresponding_clause corresponding_list
 
 %type <list>	group_by_list
 %type <node>	group_by_item empty_grouping_set rollup_clause cube_clause
@@ -614,7 +614,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
 	CLUSTER COALESCE COLLATE COLLATION COLUMN COLUMNS COMMENT COMMENTS COMMIT
 	COMMITTED CONCURRENTLY CONFIGURATION CONFLICT CONNECTION CONSTRAINT
-	CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE
+	CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY CORRESPONDING COST CREATE
 	CROSS CSV CUBE CURRENT_P
 	CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA
 	CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
@@ -10752,20 +10752,26 @@ simple_select:
 					n->fromClause = list_make1($2);
 					$$ = (Node *)n;
 				}
-			| select_clause UNION all_or_distinct select_clause
+			| select_clause UNION all_or_distinct opt_corresponding_clause select_clause
 				{
-					$$ = makeSetOp(SETOP_UNION, $3, $1, $4);
+					$$ = makeSetOp(SETOP_UNION, $3, $4, $1, $5);
 				}
-			| select_clause INTERSECT all_or_distinct select_clause
+			| select_clause INTERSECT all_or_distinct opt_corresponding_clause select_clause
 				{
-					$$ = makeSetOp(SETOP_INTERSECT, $3, $1, $4);
+					$$ = makeSetOp(SETOP_INTERSECT, $3, $4, $1, $5);
 				}
-			| select_clause EXCEPT all_or_distinct select_clause
+			| select_clause EXCEPT all_or_distinct opt_corresponding_clause select_clause
 				{
-					$$ = makeSetOp(SETOP_EXCEPT, $3, $1, $4);
+					$$ = makeSetOp(SETOP_EXCEPT, $3, $4, $1, $5);
 				}
 		;
 
+opt_corresponding_clause:
+			CORRESPONDING BY '(' corresponding_list ')'		{ $$ = $4; }
+			| CORRESPONDING							{ $$ = list_make1(NIL); }
+			| /*EMPTY*/								{ $$ = NIL; }
+			;
+
 /*
  * SQL standard WITH clause looks like:
  *
@@ -14107,6 +14113,13 @@ name_list:	name
 		;
 
 
+corresponding_list:	ColId
+					{ $$ = list_make1(makeStringConst($1, @1)); }
+			| corresponding_list ',' ColId
+					{ $$ = lappend($1, makeStringConst($3, @1)); }
+		;
+
+
 name:		ColId									{ $$ = $1; };
 
 database_name:
@@ -14423,6 +14436,7 @@ unreserved_keyword:
 			| CONTINUE_P
 			| CONVERSION_P
 			| COPY
+			| CORRESPONDING
 			| COST
 			| CSV
 			| CUBE
@@ -15255,7 +15269,7 @@ insertSelectOptions(SelectStmt *stmt,
 }
 
 static Node *
-makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg)
+makeSetOp(SetOperation op, bool all, List *correspondingClause, Node *larg, Node *rarg)
 {
 	SelectStmt *n = makeNode(SelectStmt);
 
@@ -15263,6 +15277,7 @@ makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg)
 	n->all = all;
 	n->larg = (SelectStmt *) larg;
 	n->rarg = (SelectStmt *) rarg;
+	n->correspondingClause = correspondingClause;
 	return (Node *) n;
 }
 
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 3b84140a9b..90eb4e48aa 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1910,3 +1910,25 @@ FigureColnameInternal(Node *node, char **name)
 
 	return strength;
 }
+
+/*
+ * orderCorrespondingList()
+ * order target list resno .
+ */
+List *
+orderCorrespondingList(List *targetlist)
+{
+	List *p_target = NIL;
+	ListCell *o_target;
+	int pos = 1;
+
+	foreach(o_target, targetlist)
+	{
+		TargetEntry *tar = (TargetEntry *) lfirst(o_target);
+
+		p_target = lappend(p_target,
+				makeTargetEntry(tar->expr, (AttrNumber) pos++, tar->resname, false));
+	}
+
+	return p_target;
+}
diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
index 0d7a2b1e1b..b553d847d8 100644
--- a/src/backend/parser/parse_type.c
+++ b/src/backend/parser/parse_type.c
@@ -735,7 +735,8 @@ typeStringToTypeName(const char *str)
 		stmt->limitCount != NULL ||
 		stmt->lockingClause != NIL ||
 		stmt->withClause != NULL ||
-		stmt->op != SETOP_NONE)
+		stmt->op != SETOP_NONE ||
+		stmt->correspondingClause != NULL)
 		goto fail;
 	if (list_length(stmt->targetList) != 1)
 		goto fail;
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d576523f6a..fd1883f903 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1459,7 +1459,9 @@ typedef struct SelectStmt
 	 * These fields are used only in "leaf" SelectStmts.
 	 */
 	List	   *distinctClause; /* NULL, list of DISTINCT ON exprs, or
-								 * lcons(NIL,NIL) for all (SELECT DISTINCT) */
+								* lcons(NIL,NIL) for all (SELECT DISTINCT) */
+	List	   *correspondingClause; /* NULL, list of CORRESPONDING BY exprs, or */
+								 * lcons(NIL, NIL) for CORRESPONDING */
 	IntoClause *intoClause;		/* target for SELECT INTO */
 	List	   *targetList;		/* the target list (of ResTarget) */
 	List	   *fromClause;		/* the FROM clause */
@@ -1525,7 +1527,7 @@ typedef struct SetOperationStmt
 	bool		all;			/* ALL specified? */
 	Node	   *larg;			/* left child */
 	Node	   *rarg;			/* right child */
-	/* Eventually add fields for CORRESPONDING spec here */
+	List	   *correspondingColumns; /* list of column names (A_Const) */
 
 	/* Fields derived during parse analysis: */
 	List	   *colTypes;		/* OID list of output column type OIDs */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 28c4dab258..36ada7928a 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -97,6 +97,7 @@ PG_KEYWORD("content", CONTENT_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("continue", CONTINUE_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("conversion", CONVERSION_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("copy", COPY, UNRESERVED_KEYWORD)
+PG_KEYWORD("corresponding", CORRESPONDING, UNRESERVED_KEYWORD)
 PG_KEYWORD("cost", COST, UNRESERVED_KEYWORD)
 PG_KEYWORD("create", CREATE, RESERVED_KEYWORD)
 PG_KEYWORD("cross", CROSS, TYPE_FUNC_NAME_KEYWORD)
diff --git a/src/include/parser/parse_target.h b/src/include/parser/parse_target.h
index d06a235df0..32b9cfda17 100644
--- a/src/include/parser/parse_target.h
+++ b/src/include/parser/parse_target.h
@@ -42,5 +42,6 @@ extern TupleDesc expandRecordVariable(ParseState *pstate, Var *var,
 					 int levelsup);
 extern char *FigureColname(Node *node);
 extern char *FigureIndexColname(Node *node);
+extern List *orderCorrespondingList(List *targetlist);
 
 #endif   /* PARSE_TARGET_H */
diff --git a/src/test/regress/expected/union.out b/src/test/regress/expected/union.out
index 4d697bada7..e4ce1b9e13 100644
--- a/src/test/regress/expected/union.out
+++ b/src/test/regress/expected/union.out
@@ -59,6 +59,221 @@ SELECT 1.1 AS two UNION SELECT 2.2 ORDER BY 1;
  2.2
 (2 rows)
 
+SELECT 1 AS two UNION CORRESPONDING SELECT 2 two;
+ two 
+-----
+   1
+   2
+(2 rows)
+
+SELECT 1 AS one UNION CORRESPONDING SELECT 1 one;
+ one 
+-----
+   1
+(1 row)
+
+SELECT 1 AS two UNION ALL CORRESPONDING SELECT 2 two;
+ two 
+-----
+   1
+   2
+(2 rows)
+
+SELECT 1 AS two UNION ALL CORRESPONDING SELECT 1 two;
+ two 
+-----
+   1
+   1
+(2 rows)
+
+SELECT 1 AS two UNION CORRESPONDING SELECT 2 two UNION CORRESPONDING SELECT 2 two;
+ two 
+-----
+   2
+   1
+(2 rows)
+
+SELECT 1 AS three UNION CORRESPONDING SELECT 2 three UNION ALL CORRESPONDING SELECT 2 three;
+ three 
+-------
+     1
+     2
+     2
+(3 rows)
+
+SELECT 1 AS three UNION CORRESPONDING SELECT 2 three UNION CORRESPONDING SELECT 3 three;
+ three 
+-------
+     3
+     2
+     1
+(3 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+SELECT 3 c, 2 b, 1 a UNION CORRESPONDING SELECT 6 c, 4 a, 5 b;
+ c | b | a 
+---+---+---
+ 3 | 2 | 1
+ 6 | 5 | 4
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a) SELECT 4 a, 5 b, 6 c;
+ a 
+---
+ 1
+ 4
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b) SELECT 4 a, 5 b, 6 c;
+ b 
+---
+ 2
+ 5
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(c) SELECT 4 a, 5 b, 6 c;
+ c 
+---
+ 3
+ 6
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b) SELECT 4 a, 5 b, 6 c;
+ a | b 
+---+---
+ 1 | 2
+ 4 | 5
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c) SELECT 4 a, 5 b, 6 c;
+ b | c 
+---+---
+ 2 | 3
+ 5 | 6
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, c) SELECT 4 a, 5 b, 6 c;
+ a | c 
+---+---
+ 1 | 3
+ 4 | 6
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 4 a, 5 b, 6 c;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY (c,b) SELECT 4 a, 5 b, 6 c, 8 d;
+ c | b 
+---+---
+ 3 | 2
+ 6 | 5
+(2 rows)
+
+-- CORRESPONDING column ordering, left clause's column ordering must be preserved.
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+SELECT 2 b, 1 a, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+ b | a | c 
+---+---+---
+ 2 | 1 | 3
+ 5 | 4 | 6
+(2 rows)
+
+SELECT 1 a, 3 c, 2 b UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+ a | c | b 
+---+---+---
+ 1 | 3 | 2
+ 4 | 6 | 5
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 5 b, 6 c, 4 a;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+SELECT 2 b, 1 a, 3 c UNION CORRESPONDING SELECT 5 b, 6 c, 4 a;
+ b | a | c 
+---+---+---
+ 2 | 1 | 3
+ 5 | 4 | 6
+(2 rows)
+
+-- CORRESPONDING BY column ordering, BY clause column ordering must be preserved.
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 4 a, 5 b, 6 c;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c, a) SELECT 4 a, 5 b, 6 c;
+ b | c | a 
+---+---+---
+ 2 | 3 | 1
+ 5 | 6 | 4
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(c, a, b) SELECT 4 a, 5 b, 6 c;
+ c | a | b 
+---+---+---
+ 3 | 1 | 2
+ 6 | 4 | 5
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 5 b, 6 c, 4 a;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+SELECT 2 b, 3 c, 1 a UNION CORRESPONDING BY(b, c, a) SELECT 4 a, 5 b, 6 c;
+ b | c | a 
+---+---+---
+ 2 | 3 | 1
+ 5 | 6 | 4
+(2 rows)
+
+SELECT 3 c, 2 b, 1 a UNION CORRESPONDING BY(c, a, b) SELECT 4 a, 5 b, 6 c;
+ c | a | b 
+---+---+---
+ 3 | 1 | 2
+ 6 | 4 | 5
+(2 rows)
+
+-- should to fail
+SELECT 10 a, 20 b UNION ALL CORRESPONDING SELECT 10 c, 20 d;
+ERROR:  there are not any corresponding name
+LINE 1: SELECT 10 a, 20 b UNION ALL CORRESPONDING SELECT 10 c, 20 d;
+               ^
+HINT:  UNION queries with a CORRESPONDING clause must have at least one column with the same name
+SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (a,b) SELECT 10 c, 20 d;
+ERROR:  column name "a" cannot be used in corresponding by clause
+LINE 1: SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (a,b) SELECT 10...
+                                                      ^
+HINT:  UNION queries with a CORRESPONDING BY clause must only contain column names from both tables.
+SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (x) SELECT 10 c, 20 d;
+ERROR:  column name "x" cannot be used in corresponding by clause
+LINE 1: SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (x) SELECT 10 c...
+                                                      ^
+HINT:  UNION queries with a CORRESPONDING BY clause must only contain column names from both tables.
 -- Mixed types
 SELECT 1.1 AS two UNION SELECT 2 ORDER BY 1;
  two 
@@ -258,6 +473,108 @@ ORDER BY 1;
  hi de ho neighbor
 (5 rows)
 
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+         five          
+-----------------------
+ -1.2345678901234e+200
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+                     0
+(5 rows)
+
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING BY(five)
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+         five          
+-----------------------
+ -1.2345678901234e+200
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+                     0
+(5 rows)
+
+SELECT f1 AS ten FROM FLOAT8_TBL
+UNION ALL CORRESPONDING
+SELECT f1 AS ten FROM FLOAT8_TBL;
+          ten          
+-----------------------
+                     0
+                -34.84
+               -1004.3
+ -1.2345678901234e+200
+ -1.2345678901234e-200
+                     0
+                -34.84
+               -1004.3
+ -1.2345678901234e+200
+ -1.2345678901234e-200
+(10 rows)
+
+SELECT f1 AS nine FROM FLOAT8_TBL
+UNION CORRESPONDING
+SELECT f1 AS nine FROM INT4_TBL
+ORDER BY 1;
+         nine          
+-----------------------
+ -1.2345678901234e+200
+           -2147483647
+               -123456
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+                     0
+                123456
+            2147483647
+(9 rows)
+
+SELECT f1 AS ten FROM FLOAT8_TBL
+UNION ALL CORRESPONDING
+SELECT f1 AS ten FROM INT4_TBL;
+          ten          
+-----------------------
+                     0
+                -34.84
+               -1004.3
+ -1.2345678901234e+200
+ -1.2345678901234e-200
+                     0
+                123456
+               -123456
+            2147483647
+           -2147483647
+(10 rows)
+
+SELECT f1 AS five FROM FLOAT8_TBL
+  WHERE f1 BETWEEN -1e6 AND 1e6
+UNION CORRESPONDING
+SELECT f1 AS five FROM INT4_TBL
+  WHERE f1 BETWEEN 0 AND 1000000;
+         five          
+-----------------------
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+                     0
+                123456
+(5 rows)
+
+SELECT * FROM FLOAT8_TBL
+UNION corresponding SELECT * FROM FLOAT8_TBL;
+          f1           
+-----------------------
+                     0
+ -1.2345678901234e-200
+                -34.84
+ -1.2345678901234e+200
+               -1004.3
+(5 rows)
+
 --
 -- INTERSECT and EXCEPT
 --
@@ -320,6 +637,63 @@ SELECT q1 FROM int8_tbl EXCEPT ALL SELECT DISTINCT q2 FROM int8_tbl ORDER BY 1;
 
 SELECT q1 FROM int8_tbl EXCEPT ALL SELECT q1 FROM int8_tbl FOR NO KEY UPDATE;
 ERROR:  FOR NO KEY UPDATE is not allowed with UNION/INTERSECT/EXCEPT
+SELECT q2 FROM int8_tbl INTERSECT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+        q2        
+------------------
+              123
+ 4567890123456789
+(2 rows)
+
+SELECT q2 FROM int8_tbl INTERSECT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+        q2        
+------------------
+              123
+ 4567890123456789
+ 4567890123456789
+(3 rows)
+
+SELECT q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+        q2         
+-------------------
+ -4567890123456789
+               456
+(2 rows)
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+        q2         
+-------------------
+ -4567890123456789
+               456
+(2 rows)
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q1 AS q2 FROM int8_tbl ORDER BY 1;
+        q2         
+-------------------
+ -4567890123456789
+               456
+  4567890123456789
+(3 rows)
+
+SELECT q1 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+ q1 
+----
+(0 rows)
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+        q1        
+------------------
+              123
+ 4567890123456789
+(2 rows)
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q2 AS q1 FROM int8_tbl;
+        q1        
+------------------
+              123
+ 4567890123456789
+ 4567890123456789
+(3 rows)
+
 --
 -- Mixed types
 --
@@ -338,6 +712,21 @@ SELECT f1 FROM float8_tbl EXCEPT SELECT f1 FROM int4_tbl ORDER BY 1;
  -1.2345678901234e-200
 (4 rows)
 
+SELECT f1 FROM float8_tbl INTERSECT CORRESPONDING SELECT f1 FROM int4_tbl;
+ f1 
+----
+  0
+(1 row)
+
+SELECT f1 FROM float8_tbl EXCEPT CORRESPONDING SELECT f1 FROM int4_tbl ORDER BY 1;
+          f1           
+-----------------------
+ -1.2345678901234e+200
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+(4 rows)
+
 --
 -- Operator precedence and (((((extra))))) parentheses
 --
@@ -424,6 +813,24 @@ SELECT q1 FROM int8_tbl EXCEPT (((SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1)))
  4567890123456789
 (2 rows)
 
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2,q1 FROM int8_tbl
+ORDER BY q2,q1;
+ q1 | q2 
+----+----
+(0 rows)
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q1) SELECT q2,q1 FROM int8_tbl
+ORDER BY q1;
+ q1 
+----
+(0 rows)
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q2) SELECT q2,q1 FROM int8_tbl
+ORDER BY q2;
+ q2 
+----
+(0 rows)
+
 --
 -- New syntaxes (7.1) permit new tests
 --
@@ -459,6 +866,22 @@ SELECT '3.4'::numeric UNION SELECT 'foo';
 ERROR:  invalid input syntax for type numeric: "foo"
 LINE 1: SELECT '3.4'::numeric UNION SELECT 'foo';
                                            ^
+SELECT '3.4'::numeric AS a UNION CORRESPONDING SELECT 'foo' AS a;
+ERROR:  invalid input syntax for type numeric: "foo"
+LINE 1: ...CT '3.4'::numeric AS a UNION CORRESPONDING SELECT 'foo' AS a...
+                                                             ^
+SELECT a.f1 FROM (SELECT 'test' AS f1 FROM varchar_tbl) a
+UNION CORRESPONDING
+SELECT b.f1 FROM (SELECT f1 FROM varchar_tbl) b
+ORDER BY 1;
+  f1  
+------
+ a
+ ab
+ abcd
+ test
+(4 rows)
+
 --
 -- Test that expression-index constraints can be pushed down through
 -- UNION or UNION ALL
diff --git a/src/test/regress/sql/union.sql b/src/test/regress/sql/union.sql
index 48e6850798..b0f3152ef1 100644
--- a/src/test/regress/sql/union.sql
+++ b/src/test/regress/sql/union.sql
@@ -20,6 +20,74 @@ SELECT 1 AS three UNION SELECT 2 UNION ALL SELECT 2 ORDER BY 1;
 
 SELECT 1.1 AS two UNION SELECT 2.2 ORDER BY 1;
 
+SELECT 1 AS two UNION CORRESPONDING SELECT 2 two;
+
+SELECT 1 AS one UNION CORRESPONDING SELECT 1 one;
+
+SELECT 1 AS two UNION ALL CORRESPONDING SELECT 2 two;
+
+SELECT 1 AS two UNION ALL CORRESPONDING SELECT 1 two;
+
+SELECT 1 AS two UNION CORRESPONDING SELECT 2 two UNION CORRESPONDING SELECT 2 two;
+
+SELECT 1 AS three UNION CORRESPONDING SELECT 2 three UNION ALL CORRESPONDING SELECT 2 three;
+
+SELECT 1 AS three UNION CORRESPONDING SELECT 2 three UNION CORRESPONDING SELECT 3 three;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+
+SELECT 3 c, 2 b, 1 a UNION CORRESPONDING SELECT 6 c, 4 a, 5 b;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(c) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, c) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY (c,b) SELECT 4 a, 5 b, 6 c, 8 d;
+
+-- CORRESPONDING column ordering, left clause's column ordering must be preserved.
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+
+SELECT 2 b, 1 a, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 3 c, 2 b UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 5 b, 6 c, 4 a;
+
+SELECT 2 b, 1 a, 3 c UNION CORRESPONDING SELECT 5 b, 6 c, 4 a;
+
+-- CORRESPONDING BY column ordering, BY clause column ordering must be preserved.
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c, a) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(c, a, b) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 5 b, 6 c, 4 a;
+
+SELECT 2 b, 3 c, 1 a UNION CORRESPONDING BY(b, c, a) SELECT 4 a, 5 b, 6 c;
+
+SELECT 3 c, 2 b, 1 a UNION CORRESPONDING BY(c, a, b) SELECT 4 a, 5 b, 6 c;
+
+
+-- should to fail
+SELECT 10 a, 20 b UNION ALL CORRESPONDING SELECT 10 c, 20 d;
+
+SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (a,b) SELECT 10 c, 20 d;
+
+SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (x) SELECT 10 c, 20 d;
+
 -- Mixed types
 
 SELECT 1.1 AS two UNION SELECT 2 ORDER BY 1;
@@ -90,6 +158,38 @@ UNION
 SELECT TRIM(TRAILING FROM f1) FROM CHAR_TBL
 ORDER BY 1;
 
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING BY(five)
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+
+SELECT f1 AS ten FROM FLOAT8_TBL
+UNION ALL CORRESPONDING
+SELECT f1 AS ten FROM FLOAT8_TBL;
+
+SELECT f1 AS nine FROM FLOAT8_TBL
+UNION CORRESPONDING
+SELECT f1 AS nine FROM INT4_TBL
+ORDER BY 1;
+
+SELECT f1 AS ten FROM FLOAT8_TBL
+UNION ALL CORRESPONDING
+SELECT f1 AS ten FROM INT4_TBL;
+
+SELECT f1 AS five FROM FLOAT8_TBL
+  WHERE f1 BETWEEN -1e6 AND 1e6
+UNION CORRESPONDING
+SELECT f1 AS five FROM INT4_TBL
+  WHERE f1 BETWEEN 0 AND 1000000;
+
+SELECT * FROM FLOAT8_TBL
+UNION corresponding SELECT * FROM FLOAT8_TBL;
+
 --
 -- INTERSECT and EXCEPT
 --
@@ -112,6 +212,22 @@ SELECT q1 FROM int8_tbl EXCEPT ALL SELECT DISTINCT q2 FROM int8_tbl ORDER BY 1;
 
 SELECT q1 FROM int8_tbl EXCEPT ALL SELECT q1 FROM int8_tbl FOR NO KEY UPDATE;
 
+SELECT q2 FROM int8_tbl INTERSECT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+
+SELECT q2 FROM int8_tbl INTERSECT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+
+SELECT q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q1 AS q2 FROM int8_tbl ORDER BY 1;
+
+SELECT q1 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q2 AS q1 FROM int8_tbl;
+
 --
 -- Mixed types
 --
@@ -120,6 +236,10 @@ SELECT f1 FROM float8_tbl INTERSECT SELECT f1 FROM int4_tbl ORDER BY 1;
 
 SELECT f1 FROM float8_tbl EXCEPT SELECT f1 FROM int4_tbl ORDER BY 1;
 
+SELECT f1 FROM float8_tbl INTERSECT CORRESPONDING SELECT f1 FROM int4_tbl;
+
+SELECT f1 FROM float8_tbl EXCEPT CORRESPONDING SELECT f1 FROM int4_tbl ORDER BY 1;
+
 --
 -- Operator precedence and (((((extra))))) parentheses
 --
@@ -150,6 +270,15 @@ SELECT q1 FROM int8_tbl EXCEPT SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1;
 -- But this should work:
 SELECT q1 FROM int8_tbl EXCEPT (((SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1))) ORDER BY 1;
 
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2,q1 FROM int8_tbl
+ORDER BY q2,q1;
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q1) SELECT q2,q1 FROM int8_tbl
+ORDER BY q1;
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q2) SELECT q2,q1 FROM int8_tbl
+ORDER BY q2;
+
 --
 -- New syntaxes (7.1) permit new tests
 --
@@ -170,6 +299,13 @@ ORDER BY 1;
 -- This should fail, but it should produce an error cursor
 SELECT '3.4'::numeric UNION SELECT 'foo';
 
+SELECT '3.4'::numeric AS a UNION CORRESPONDING SELECT 'foo' AS a;
+
+SELECT a.f1 FROM (SELECT 'test' AS f1 FROM varchar_tbl) a
+UNION CORRESPONDING
+SELECT b.f1 FROM (SELECT f1 FROM varchar_tbl) b
+ORDER BY 1;
+
 --
 -- Test that expression-index constraints can be pushed down through
 -- UNION or UNION ALL
#19Tom Lane
tgl@sss.pgh.pa.us
In reply to: Pavel Stehule (#18)
Re: New CORRESPONDING clause design

Pavel Stehule <pavel.stehule@gmail.com> writes:

I have not any objection - I'll mark this patch as ready for commiter

I took a quick look through this and noted that it fails to touch
ruleutils.c, which means that dumping of views containing CORRESPONDING
certainly doesn't work.

Also, the changes in parser/analyze.c seem rather massive and
correspondingly hard to review. Is it possible to rearrange the
patch to reduce the size of that diff? If you can avoid moving
or reindenting existing code, that'd help.

The code in that area seems rather confused, too. For instance,
I'm not sure exactly what orderCorrespondingList() is good for,
but it certainly doesn't look to be doing anything that either its
name or its header comment (or the comments at the call sites) would
suggest. Its input and output tlists are always in the same order.

I'm a little disturbed by the fact that determineMatchingColumns()
is called twice, and more disturbed by the fact that it looks to be
O(N^2). This could be really slow with a lot of columns, couldn't it?

I also think there should be some comments about exactly what matching
semantics we're enforcing. The SQL standard says

a) If CORRESPONDING is specified, then:
i) Within the columns of T1, equivalent <column name>s shall
not be specified more than once and within the columns of
T2, equivalent <column name>s shall not be specified more
than once.

That seems unreasonably stringent to me; it ought to be sufficient to
forbid duplicates of the names listed in CORRESPONDING, or the common
column names if there's no BY list. But whichever restriction you prefer,
this code seems to be failing to check it --- I certainly don't see any
new error message about "column name "foo" appears more than once".

I don't think you want to be leaving behind debugging cruft like this:
+ elog(DEBUG4, "%s", ltle->resname);

I'm not impressed by using A_Const for the members of the CORRESPONDING
name list. That's not a clever solution, that's a confusing kluge,
because it's a complete violation of the meaning of A_Const. Elsewhere
we just use lists of String for name lists, and that seems sufficient
here. Personally I'd just use the existing columnList production rather
than rolling your own.

The comments in parsenodes.h about the new fields seem rather confused,
in fact I think they're probably backwards.

Also, I thought the test cases were excessively uninspired and repetitive.
This, for example, seems like about two tests too many:

+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a) SELECT 4 a, 5 b, 6 c;
+ a 
+---
+ 1
+ 4
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b) SELECT 4 a, 5 b, 6 c;
+ b 
+---
+ 2
+ 5
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(c) SELECT 4 a, 5 b, 6 c;
+ c 
+---
+ 3
+ 6
+(2 rows)

without even considering the fact that these are pretty duplicative of
tests around them. And some of the added tests seem to be just testing
basic UNION functionality, which we already have tests for. I fail to
see the point of adding any of these:

+SELECT 1 AS two UNION CORRESPONDING SELECT 2 two;
+ two 
+-----
+   1
+   2
+(2 rows)
+
+SELECT 1 AS one UNION CORRESPONDING SELECT 1 one;
+ one 
+-----
+   1
+(1 row)
+
+SELECT 1 AS two UNION ALL CORRESPONDING SELECT 2 two;
+ two 
+-----
+   1
+   2
+(2 rows)
+
+SELECT 1 AS two UNION ALL CORRESPONDING SELECT 1 two;
+ two 
+-----
+   1
+   1
+(2 rows)

What I think actually is needed is some targeted testing showing
non-interference with nearby features. As an example, CORRESPONDING
can't safely be implemented by just replacing the tlists of the
input sub-selects with shortened versions, because that would break
situations such as DISTINCT in the sub-selects. I think it'd be a
good idea to have a test along the lines of

SELECT DISTINCT x, y FROM ...
UNION ALL CORRESPONDING
SELECT DISTINCT x, z FROM ...

with values chosen to show that the DISTINCT operators did operate
on x/y and x/z, not just x alone.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#20Pavel Stehule
pavel.stehule@gmail.com
In reply to: Tom Lane (#19)
Re: New CORRESPONDING clause design

2017-03-18 17:50 GMT+01:00 Tom Lane <tgl@sss.pgh.pa.us>:

Pavel Stehule <pavel.stehule@gmail.com> writes:

I have not any objection - I'll mark this patch as ready for commiter

I took a quick look through this and noted that it fails to touch
ruleutils.c, which means that dumping of views containing CORRESPONDING
certainly doesn't work.

it my mistake - this bug I should to see :(

Also, the changes in parser/analyze.c seem rather massive and
correspondingly hard to review. Is it possible to rearrange the
patch to reduce the size of that diff? If you can avoid moving
or reindenting existing code, that'd help.

The code in that area seems rather confused, too. For instance,
I'm not sure exactly what orderCorrespondingList() is good for,
but it certainly doesn't look to be doing anything that either its
name or its header comment (or the comments at the call sites) would
suggest. Its input and output tlists are always in the same order.

I'm a little disturbed by the fact that determineMatchingColumns()
is called twice, and more disturbed by the fact that it looks to be
O(N^2). This could be really slow with a lot of columns, couldn't it?

I also think there should be some comments about exactly what matching
semantics we're enforcing. The SQL standard says

a) If CORRESPONDING is specified, then:
i) Within the columns of T1, equivalent <column name>s shall
not be specified more than once and within the columns of
T2, equivalent <column name>s shall not be specified more
than once.

That seems unreasonably stringent to me; it ought to be sufficient to
forbid duplicates of the names listed in CORRESPONDING, or the common
column names if there's no BY list. But whichever restriction you prefer,
this code seems to be failing to check it --- I certainly don't see any
new error message about "column name "foo" appears more than once".

I don't think you want to be leaving behind debugging cruft like this:
+ elog(DEBUG4, "%s", ltle->resname);

I'm not impressed by using A_Const for the members of the CORRESPONDING
name list. That's not a clever solution, that's a confusing kluge,
because it's a complete violation of the meaning of A_Const. Elsewhere
we just use lists of String for name lists, and that seems sufficient
here. Personally I'd just use the existing columnList production rather
than rolling your own.

The reason was attach a location to name for more descriptive error
message.

Show quoted text

The comments in parsenodes.h about the new fields seem rather confused,
in fact I think they're probably backwards.

Also, I thought the test cases were excessively uninspired and repetitive.
This, for example, seems like about two tests too many:

+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a) SELECT 4 a, 5 b, 6 c;
+ a
+---
+ 1
+ 4
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b) SELECT 4 a, 5 b, 6 c;
+ b
+---
+ 2
+ 5
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(c) SELECT 4 a, 5 b, 6 c;
+ c
+---
+ 3
+ 6
+(2 rows)

without even considering the fact that these are pretty duplicative of
tests around them. And some of the added tests seem to be just testing
basic UNION functionality, which we already have tests for. I fail to
see the point of adding any of these:

+SELECT 1 AS two UNION CORRESPONDING SELECT 2 two;
+ two
+-----
+   1
+   2
+(2 rows)
+
+SELECT 1 AS one UNION CORRESPONDING SELECT 1 one;
+ one
+-----
+   1
+(1 row)
+
+SELECT 1 AS two UNION ALL CORRESPONDING SELECT 2 two;
+ two
+-----
+   1
+   2
+(2 rows)
+
+SELECT 1 AS two UNION ALL CORRESPONDING SELECT 1 two;
+ two
+-----
+   1
+   1
+(2 rows)

What I think actually is needed is some targeted testing showing
non-interference with nearby features. As an example, CORRESPONDING
can't safely be implemented by just replacing the tlists of the
input sub-selects with shortened versions, because that would break
situations such as DISTINCT in the sub-selects. I think it'd be a
good idea to have a test along the lines of

SELECT DISTINCT x, y FROM ...
UNION ALL CORRESPONDING
SELECT DISTINCT x, z FROM ...

with values chosen to show that the DISTINCT operators did operate
on x/y and x/z, not just x alone.

regards, tom lane

#21Tom Lane
tgl@sss.pgh.pa.us
In reply to: Pavel Stehule (#20)
Re: New CORRESPONDING clause design

Pavel Stehule <pavel.stehule@gmail.com> writes:

2017-03-18 17:50 GMT+01:00 Tom Lane <tgl@sss.pgh.pa.us>:

I'm not impressed by using A_Const for the members of the CORRESPONDING
name list. That's not a clever solution, that's a confusing kluge,
because it's a complete violation of the meaning of A_Const. Elsewhere
we just use lists of String for name lists, and that seems sufficient
here. Personally I'd just use the existing columnList production rather
than rolling your own.

The reason was attach a location to name for more descriptive error
message.

[ shrug... ] The patch fails to actually use the location anywhere.
If it had, you might have noticed that it's attaching the wrong location
to all elements except the first :-(. So I'm not very excited about that.
I definitely don't see a reason for CORRESPONDING to track locations of
name list elements when no other name list productions do. It might be
worth proposing a followon patch to change all of them (perhaps by adding
a location field to struct "Value") and then make use of the locations in
error messages more widely.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#22Pavel Stehule
pavel.stehule@gmail.com
In reply to: Tom Lane (#21)
Re: New CORRESPONDING clause design

2017-03-18 18:32 GMT+01:00 Tom Lane <tgl@sss.pgh.pa.us>:

Pavel Stehule <pavel.stehule@gmail.com> writes:

2017-03-18 17:50 GMT+01:00 Tom Lane <tgl@sss.pgh.pa.us>:

I'm not impressed by using A_Const for the members of the CORRESPONDING
name list. That's not a clever solution, that's a confusing kluge,
because it's a complete violation of the meaning of A_Const. Elsewhere
we just use lists of String for name lists, and that seems sufficient
here. Personally I'd just use the existing columnList production rather
than rolling your own.

The reason was attach a location to name for more descriptive error
message.

[ shrug... ] The patch fails to actually use the location anywhere.
If it had, you might have noticed that it's attaching the wrong location
to all elements except the first :-(. So I'm not very excited about that.
I definitely don't see a reason for CORRESPONDING to track locations of
name list elements when no other name list productions do. It might be
worth proposing a followon patch to change all of them (perhaps by adding
a location field to struct "Value") and then make use of the locations in
error messages more widely.

I had a idea use own node for CORRESPONDING with location - and using this
location in related error messages.

What do you think about it?

Regards

Pavel

Show quoted text

regards, tom lane

#23Tom Lane
tgl@sss.pgh.pa.us
In reply to: Pavel Stehule (#22)
Re: New CORRESPONDING clause design

Pavel Stehule <pavel.stehule@gmail.com> writes:

2017-03-18 18:32 GMT+01:00 Tom Lane <tgl@sss.pgh.pa.us>:

I definitely don't see a reason for CORRESPONDING to track locations of
name list elements when no other name list productions do. It might be
worth proposing a followon patch to change all of them (perhaps by adding
a location field to struct "Value") and then make use of the locations in
error messages more widely.

I had a idea use own node for CORRESPONDING with location - and using this
location in related error messages.

I think using a private node type for CORRESPONDING is exactly the wrong
thing. It's a columnList and it should be like other columnLists. If
there's an argument for providing a location for "no such column" errors
for CORRESPONDING, then surely there's also an argument for providing
a location for "no such column" errors for FOREIGN KEY and the other
places where we have lists of column names.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#24Pavel Stehule
pavel.stehule@gmail.com
In reply to: Tom Lane (#23)
Re: New CORRESPONDING clause design

2017-03-18 19:12 GMT+01:00 Tom Lane <tgl@sss.pgh.pa.us>:

Pavel Stehule <pavel.stehule@gmail.com> writes:

2017-03-18 18:32 GMT+01:00 Tom Lane <tgl@sss.pgh.pa.us>:

I definitely don't see a reason for CORRESPONDING to track locations of
name list elements when no other name list productions do. It might be
worth proposing a followon patch to change all of them (perhaps by

adding

a location field to struct "Value") and then make use of the locations

in

error messages more widely.

I had a idea use own node for CORRESPONDING with location - and using

this

location in related error messages.

I think using a private node type for CORRESPONDING is exactly the wrong
thing. It's a columnList and it should be like other columnLists. If
there's an argument for providing a location for "no such column" errors
for CORRESPONDING, then surely there's also an argument for providing
a location for "no such column" errors for FOREIGN KEY and the other
places where we have lists of column names.

The corresponding clause is used in UNION queries - these queries can be
pretty long, so marking wrong corresponding clause can be helpful.

Probably there are not any other argument for special node,

Regards

Pavel

Show quoted text

regards, tom lane

#25Surafel Temesgen
surafel3000@gmail.com
In reply to: Tom Lane (#19)
Re: New CORRESPONDING clause design

On Sat, Mar 18, 2017 at 7:50 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Pavel Stehule <pavel.stehule@gmail.com> writes:

I have not any objection - I'll mark this patch as ready for commiter

I'm a little disturbed by the fact that determineMatchingColumns()
is called twice, and more disturbed by the fact that it looks to be
O(N^2). This could be really slow with a lot of columns, couldn't it?

I also think there should be some comments about exactly what matching
semantics we're enforcing. The SQL standard says

a) If CORRESPONDING is specified, then:
i) Within the columns of T1, equivalent <column name>s shall
not be specified more than once and within the columns of
T2, equivalent <column name>s shall not be specified more
than once.

That seems unreasonably stringent to me; it ought to be sufficient to
forbid duplicates of the names listed in CORRESPONDING, or the common
column names if there's no BY list. But whichever restriction you prefer,
this code seems to be failing to check it --- I certainly don't see any
new error message about "column name "foo" appears more than once".

determineMatchingColumns can be reduce to one call but in order

to enforce the above semantics I leave it as it is by thinking
the performance penalty can be compromise with sql standard
conformance.

What I think actually is needed is some targeted testing showing
non-interference with nearby features. As an example, CORRESPONDING
can't safely be implemented by just replacing the tlists of the
input sub-selects with shortened versions, because that would break
situations such as DISTINCT in the sub-selects. I think it'd be a
good idea to have a test along the lines of

SELECT DISTINCT x, y FROM ...
UNION ALL CORRESPONDING
SELECT DISTINCT x, z FROM ...

with values chosen to show that the DISTINCT operators did operate
on x/y and x/z, not just x alone.

Even if without replacing the tlists of the top-level query
which is leftmostQuery tagretlist with shortened versioned
corresponding target list DISTINCT operators did’t operate
on x/z on left query because top-level Query has a dummy
targetlist that exists mainly to show the union'd datatype
of each output column, and it carries any sortClause, limitClause,
etc needed for the output of the entire operation

reference /messages/by-id/4939.970506269@sss.pgh.pa.us

if I don’t miss something that means in current implementation we cannot
use sortClause limitClause with target list outside final out put or on
individual query and I think planning doing as a whole will be good

Regrades

Surafel

#26Surafel Temesgen
surafel3000@gmail.com
In reply to: Tom Lane (#19)
1 attachment(s)
Re: New CORRESPONDING clause design

I took a quick look through this and noted that it fails to touch
ruleutils.c, which means that dumping of views containing CORRESPONDING
certainly doesn't work.

fixed

Also, the changes in parser/analyze.c seem rather massive and
correspondingly hard to review. Is it possible to rearrange the
patch to reduce the size of that diff? If you can avoid moving
or reindenting existing code, that'd help.

Part of transformSetOperationTree that make union data type of
set operation target list became makeUnionDatatype inorder to
easy using it multiple time and avoid very long transformSetOperationTree
function

The code in that area seems rather confused, too. For instance,
I'm not sure exactly what orderCorrespondingList() is good for,
but it certainly doesn't look to be doing anything that either its
name or its header comment (or the comments at the call sites) would
suggest. Its input and output tlists are always in the same order.

It give corresponding target list a sequential resnos

Inorder to avoid touching generate_append_tlist I change
the comment and function name as such

I also think there should be some comments about exactly what matching

semantics we're enforcing. The SQL standard says

a) If CORRESPONDING is specified, then:
i) Within the columns of T1, equivalent <column name>s shall
not be specified more than once and within the columns of
T2, equivalent <column name>s shall not be specified more
than once.

That seems unreasonably stringent to me; it ought to be sufficient to
forbid duplicates of the names listed in CORRESPONDING, or the common
column names if there's no BY list. But whichever restriction you prefer,
this code seems to be failing to check it --- I certainly don't see any
new error message about "column name "foo" appears more than once".

fixed

I'm not impressed by using A_Const for the members of the CORRESPONDING

name list. That's not a clever solution, that's a confusing kluge,
because it's a complete violation of the meaning of A_Const. Elsewhere
we just use lists of String for name lists, and that seems sufficient
here. Personally I'd just use the existing columnList production rather
than rolling your own.

fixed

Show quoted text

Attachments:

corresponding_clause_v7.patchapplication/octet-stream; name=corresponding_clause_v7.patchDownload
diff --git a/doc/src/sgml/queries.sgml b/doc/src/sgml/queries.sgml
index 30792f4..c3cdee5 100644
--- a/doc/src/sgml/queries.sgml
+++ b/doc/src/sgml/queries.sgml
@@ -1601,6 +1601,9 @@ SELECT DISTINCT ON (<replaceable>expression</replaceable> <optional>, <replaceab
    <primary>EXCEPT</primary>
   </indexterm>
   <indexterm zone="queries-union">
+   <primary>CORRESPONDING</primary>
+  </indexterm>
+  <indexterm zone="queries-union">
    <primary>set union</primary>
   </indexterm>
   <indexterm zone="queries-union">
@@ -1617,9 +1620,9 @@ SELECT DISTINCT ON (<replaceable>expression</replaceable> <optional>, <replaceab
    The results of two queries can be combined using the set operations
    union, intersection, and difference.  The syntax is
 <synopsis>
-<replaceable>query1</replaceable> UNION <optional>ALL</optional> <replaceable>query2</replaceable>
-<replaceable>query1</replaceable> INTERSECT <optional>ALL</optional> <replaceable>query2</replaceable>
-<replaceable>query1</replaceable> EXCEPT <optional>ALL</optional> <replaceable>query2</replaceable>
+<replaceable>query1</replaceable> UNION <optional>ALL</optional> <optional>CORRESPONDING</optional> <optional>BY</optional> <replaceable>query2</replaceable>
+<replaceable>query1</replaceable> INTERSECT <optional>ALL</optional> <optional>CORRESPONDING</optional> <optional>BY</optional> <replaceable>query2</replaceable>
+<replaceable>query1</replaceable> EXCEPT <optional>ALL</optional> <optional>CORRESPONDING</optional> <optional>BY</optional> <replaceable>query2</replaceable>
 </synopsis>
    <replaceable>query1</replaceable> and
    <replaceable>query2</replaceable> are queries that can use any of
@@ -1659,11 +1662,22 @@ SELECT DISTINCT ON (<replaceable>expression</replaceable> <optional>, <replaceab
   </para>
 
   <para>
-   In order to calculate the union, intersection, or difference of two
-   queries, the two queries must be <quote>union compatible</quote>,
-   which means that they return the same number of columns and
-   the corresponding columns have compatible data types, as
-   described in <xref linkend="typeconv-union-case">.
+   <literal>EXCEPT</> returns all rows that are in the result of
+   <replaceable>query1</replaceable> but not in the result of
+   <replaceable>query2</replaceable>.  (This is sometimes called the
+   <firstterm>difference</> between two queries.)  Again, duplicates
+   are eliminated unless <literal>EXCEPT ALL</> is used.
+  </para>
+
+  <para>
+   <literal>CORRESPONDING</> returns all columns that are in both 
+   <replaceable>query1</> and <replaceable>query2</> with the same name.
+  </para>
+
+  <para>
+   <literal>CORRESPONDING BY</> returns all columns in the column list 
+   that are also in both <replaceable>query1</> and 
+   <replaceable>query2</> with the same name.
   </para>
  </sect1>
 
diff --git a/doc/src/sgml/sql.sgml b/doc/src/sgml/sql.sgml
index 57396d7..f98c22e 100644
--- a/doc/src/sgml/sql.sgml
+++ b/doc/src/sgml/sql.sgml
@@ -859,7 +859,7 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="PARAMETER">expression</replac
     [ WHERE <replaceable class="PARAMETER">condition</replaceable> ]
     [ GROUP BY <replaceable class="PARAMETER">expression</replaceable> [, ...] ]
     [ HAVING <replaceable class="PARAMETER">condition</replaceable> [, ...] ]
-    [ { UNION | INTERSECT | EXCEPT } [ ALL ] <replaceable class="PARAMETER">select</replaceable> ]
+    [ { UNION | INTERSECT | EXCEPT } [ ALL ] [ CORRESPONDING [ BY ( <replaceable class="PARAMETER">expression</replaceable> ) ] ] <replaceable class="PARAMETER">select</replaceable> ]
     [ ORDER BY <replaceable class="parameter">expression</replaceable> [ ASC | DESC | USING <replaceable class="parameter">operator</replaceable> ] [ NULLS { FIRST | LAST } ] [, ...] ]
     [ LIMIT { <replaceable class="PARAMETER">count</replaceable> | ALL } ]
     [ OFFSET <replaceable class="PARAMETER">start</replaceable> ]
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 30d733e..18854bc 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2870,6 +2870,7 @@ _copySelectStmt(const SelectStmt *from)
 	COPY_NODE_FIELD(withClause);
 	COPY_SCALAR_FIELD(op);
 	COPY_SCALAR_FIELD(all);
+	COPY_NODE_FIELD(correspondingClause);
 	COPY_NODE_FIELD(larg);
 	COPY_NODE_FIELD(rarg);
 
@@ -2885,6 +2886,8 @@ _copySetOperationStmt(const SetOperationStmt *from)
 	COPY_SCALAR_FIELD(all);
 	COPY_NODE_FIELD(larg);
 	COPY_NODE_FIELD(rarg);
+	COPY_NODE_FIELD(correspondingColumns);
+ 	COPY_SCALAR_FIELD(hasCorrespondingBy);
 	COPY_NODE_FIELD(colTypes);
 	COPY_NODE_FIELD(colTypmods);
 	COPY_NODE_FIELD(colCollations);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 55c73b7..218e599 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1020,6 +1020,7 @@ _equalSelectStmt(const SelectStmt *a, const SelectStmt *b)
 	COMPARE_NODE_FIELD(withClause);
 	COMPARE_SCALAR_FIELD(op);
 	COMPARE_SCALAR_FIELD(all);
+	COMPARE_NODE_FIELD(correspondingClause);
 	COMPARE_NODE_FIELD(larg);
 	COMPARE_NODE_FIELD(rarg);
 
@@ -1033,6 +1034,8 @@ _equalSetOperationStmt(const SetOperationStmt *a, const SetOperationStmt *b)
 	COMPARE_SCALAR_FIELD(all);
 	COMPARE_NODE_FIELD(larg);
 	COMPARE_NODE_FIELD(rarg);
+	COMPARE_NODE_FIELD(correspondingColumns);
+ 	COMPARE_SCALAR_FIELD(hasCorrespondingBy);
 	COMPARE_NODE_FIELD(colTypes);
 	COMPARE_NODE_FIELD(colTypmods);
 	COMPARE_NODE_FIELD(colCollations);
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index c2f03b7..ca39c1c 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -3411,6 +3411,8 @@ raw_expression_tree_walker(Node *node,
 					return true;
 				if (walker(stmt->lockingClause, context))
 					return true;
+				if (walker(stmt->correspondingClause, context))
+					return true;
 				if (walker(stmt->withClause, context))
 					return true;
 				if (walker(stmt->larg, context))
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 1560ac3..05602e9 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2526,6 +2526,7 @@ _outSelectStmt(StringInfo str, const SelectStmt *node)
 	WRITE_NODE_FIELD(withClause);
 	WRITE_ENUM_FIELD(op, SetOperation);
 	WRITE_BOOL_FIELD(all);
+	WRITE_NODE_FIELD(correspondingClause);
 	WRITE_NODE_FIELD(larg);
 	WRITE_NODE_FIELD(rarg);
 }
@@ -2833,6 +2834,8 @@ _outSetOperationStmt(StringInfo str, const SetOperationStmt *node)
 	WRITE_BOOL_FIELD(all);
 	WRITE_NODE_FIELD(larg);
 	WRITE_NODE_FIELD(rarg);
+	WRITE_NODE_FIELD(correspondingColumns);
+ 	WRITE_BOOL_FIELD(hasCorrespondingBy);
 	WRITE_NODE_FIELD(colTypes);
 	WRITE_NODE_FIELD(colTypmods);
 	WRITE_NODE_FIELD(colCollations);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index dcfa6ee..9a3d8b7 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -418,6 +418,8 @@ _readSetOperationStmt(void)
 	READ_BOOL_FIELD(all);
 	READ_NODE_FIELD(larg);
 	READ_NODE_FIELD(rarg);
+	READ_NODE_FIELD(correspondingColumns);
+ 	READ_BOOL_FIELD(hasCorrespondingBy);
 	READ_NODE_FIELD(colTypes);
 	READ_NODE_FIELD(colTypmods);
 	READ_NODE_FIELD(colCollations);
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 06e843d..3b92c93 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -91,7 +91,8 @@ static List *generate_setop_tlist(List *colTypes, List *colCollations,
 					 Index varno,
 					 bool hack_constants,
 					 List *input_tlist,
-					 List *refnames_tlist);
+					 List *refnames_tlist,
+					 bool no_corresponding);
 static List *generate_append_tlist(List *colTypes, List *colCollations,
 					  bool flag,
 					  List *input_tlists,
@@ -110,6 +111,7 @@ static Node *adjust_appendrel_attrs_mutator(Node *node,
 static Relids adjust_relid_set(Relids relids, Index oldrelid, Index newrelid);
 static List *adjust_inherited_tlist(List *tlist,
 					   AppendRelInfo *context);
+static List *make_corresponding_target(List *corresponding_list, List *subroot_list);
 
 
 /*
@@ -187,6 +189,24 @@ plan_set_operations(PlannerInfo *root)
 									   leftmostQuery->targetList,
 									   &top_tlist);
 	}
+	/*
+	 * If corresponding column specified, we take column names from it.
+	 */
+	else if (topop->correspondingColumns != NIL )
+	{
+		/*
+		 * Recurse on setOperations tree to generate paths for set ops. The
+		 * final output path should have just the column types shown as the
+		 * output from the top-level node, plus possibly resjunk working
+		 * columns (we can rely on upper-level nodes to deal with that).
+		 */
+		path = recurse_set_operations((Node *) topop, root,
+									  topop->colTypes, topop->colCollations,
+									  true, -1,
+									  topop->correspondingColumns,
+									  &top_tlist,
+									  NULL);
+	}
 	else
 	{
 		/*
@@ -252,6 +272,8 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 					   List **pTargetList,
 					   double *pNumGroups)
 {
+	SetOperationStmt *topop = (SetOperationStmt *) root->parse->setOperations;
+
 	if (IsA(setOp, RangeTblRef))
 	{
 		RangeTblRef *rtr = (RangeTblRef *) setOp;
@@ -316,23 +338,53 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 		path = (Path *) create_subqueryscan_path(root, rel, subpath,
 												 NIL, NULL);
 
-		/*
-		 * Figure out the appropriate target list, and update the
-		 * SubqueryScanPath with the PathTarget form of that.
-		 */
-		tlist = generate_setop_tlist(colTypes, colCollations,
+		if (topop->correspondingColumns != NIL )
+		{
+			List	    *correspondingTarget;
+
+			/*
+			 * make target list that only contains corresponding column
+			 * from sub-queries list ito use it for projection
+			 */
+			correspondingTarget = make_corresponding_target(
+											  topop->correspondingColumns,
+											  subroot->processed_tlist);
+
+			/*
+			 * Figure out the appropriate target list, and update the
+			 * SubqueryScanPath with the PathTarget form of that.
+			 */
+			tlist = generate_setop_tlist(colTypes, colCollations, flag,
+											  rtr->rtindex, true,
+											  correspondingTarget,
+											  refnames_tlist, false);
+
+			path = apply_projection_to_path(root, rel, path,
+					create_pathtarget(root, tlist));
+
+			/* Return the fully-fledged tlist to caller, too */
+			*pTargetList = tlist;
+
+		}
+		else
+		{
+			/*
+			 * Figure out the appropriate target list, and update the
+			 * SubqueryScanPath with the PathTarget form of that.
+			 */
+			tlist = generate_setop_tlist(colTypes, colCollations,
 									 flag,
 									 rtr->rtindex,
 									 true,
 									 subroot->processed_tlist,
-									 refnames_tlist);
+									 refnames_tlist, true);
 
-		path = apply_projection_to_path(root, rel, path,
+			path = apply_projection_to_path(root, rel, path,
 										create_pathtarget(root, tlist));
 
-		/* Return the fully-fledged tlist to caller, too */
-		*pTargetList = tlist;
-
+			/* Return the fully-fledged tlist to caller, too */
+			*pTargetList = tlist;
+		}
 		/*
 		 * Estimate number of groups if caller wants it.  If the subquery used
 		 * grouping or aggregation, its output is probably mostly unique
@@ -392,7 +444,7 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 												0,
 												false,
 												*pTargetList,
-												refnames_tlist);
+												refnames_tlist, true);
 			path = apply_projection_to_path(root,
 											path->parent,
 											path,
@@ -1004,7 +1056,8 @@ generate_setop_tlist(List *colTypes, List *colCollations,
 					 Index varno,
 					 bool hack_constants,
 					 List *input_tlist,
-					 List *refnames_tlist)
+					 List *refnames_tlist,
+					 bool no_corresponding)
 {
 	List	   *tlist = NIL;
 	int			resno = 1;
@@ -1025,9 +1078,11 @@ generate_setop_tlist(List *colTypes, List *colCollations,
 		TargetEntry *reftle = (TargetEntry *) lfirst(rtlc);
 
 		rtlc = lnext(rtlc);
-
-		Assert(inputtle->resno == resno);
-		Assert(reftle->resno == resno);
+		if (no_corresponding)
+		{
+			Assert(inputtle->resno == resno);
+			Assert(reftle->resno == resno);
+		}
 		Assert(!inputtle->resjunk);
 		Assert(!reftle->resjunk);
 
@@ -2112,3 +2167,70 @@ adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node,
 	/* Now translate for this child */
 	return adjust_appendrel_attrs(root, node, appinfo);
 }
+
+/*
+ * generate target list from left target list with the order
+ * of right target list
+ */
+static List *
+make_corresponding_target(List *corresponding_list, List *subroot_list)
+{
+	Index internal = 0;
+	ListCell   *ltl;
+	ListCell   *rtl;
+	int			size;
+	int			i;
+	List *matchingColumns = NIL;
+	TargetEntry *simple_te_array;
+
+	size = list_length(corresponding_list) + 1;
+
+	/* Use array to find the order of corresponding columen */
+	simple_te_array = (TargetEntry *) palloc0(size * sizeof(TargetEntry));
+	foreach(ltl, corresponding_list)
+	{
+		foreach(rtl, subroot_list)
+		{
+			TargetEntry *ltle = (TargetEntry *) lfirst(ltl);
+			TargetEntry *rtle = (TargetEntry *) lfirst(rtl);
+
+			/* Names of the columns must be resolved before calling this method. */
+			Assert(ltle->resname != NULL);
+			Assert(rtle->resname != NULL);
+
+			/* If column names are the same, add it to array. */
+			if (strcmp(ltle->resname, rtle->resname) == 0)
+			{
+				simple_te_array[internal].xpr = rtle->xpr;
+				simple_te_array[internal].expr = rtle->expr;
+				simple_te_array[internal].resno = rtle->resno;
+				simple_te_array[internal].resname = rtle->resname;
+				simple_te_array[internal].ressortgroupref =
+						rtle->ressortgroupref;
+				simple_te_array[internal].resorigtbl = rtle->resorigtbl;
+				simple_te_array[internal].resorigcol = rtle->resorigcol;
+				simple_te_array[internal].resjunk = rtle->resjunk;
+				internal++;
+				continue;
+			}
+		}
+	}
+	/* traverse the array and make targetlist */
+	for (i = 0; i < internal; i++)
+	{
+		TargetEntry *tle = makeNode(TargetEntry);
+
+		tle->xpr = simple_te_array[i].xpr;
+		tle->expr = simple_te_array[i].expr;
+		tle->resno = simple_te_array[i].resno;
+		tle->resname = simple_te_array[i].resname;
+		tle->ressortgroupref = simple_te_array[i].ressortgroupref;
+		tle->resorigtbl = simple_te_array[i].resorigtbl;
+		tle->resorigcol = simple_te_array[i].resorigcol;
+		tle->resjunk = simple_te_array[i].resjunk;
+
+		matchingColumns = lappend(matchingColumns, tle);
+
+	}
+	return matchingColumns;
+}
diff --git a/src/backend/optimizer/util/tlist.c b/src/backend/optimizer/util/tlist.c
index 5728f70..02c86d2 100644
--- a/src/backend/optimizer/util/tlist.c
+++ b/src/backend/optimizer/util/tlist.c
@@ -1122,3 +1122,25 @@ split_pathtarget_walker(Node *node, split_pathtarget_context *context)
 	return expression_tree_walker(node, split_pathtarget_walker,
 								  (void *) context);
 }
+
+/*
+ * get_corresponding_tle
+ *Find the targetlist entry matching the given column name,
+ *and return it.
+ */
+TargetEntry *
+get_corresponding_tle(char *name, List *targetList)
+{
+	ListCell *l;
+
+	foreach(l, targetList)
+	{
+		TargetEntry *tle = (TargetEntry *) lfirst(l);
+
+		if (strcmp(tle->resname, name) == 0)
+			return tle;
+	}
+
+	elog(ERROR, "CORRESPONDING column name not found in targetlist");
+	return NULL ; /* keep compiler quiet */
+}
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 0f7659b..bcf699c 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -76,6 +76,10 @@ static Query *transformCreateTableAsStmt(ParseState *pstate,
 						   CreateTableAsStmt *stmt);
 static void transformLockingClause(ParseState *pstate, Query *qry,
 					   LockingClause *lc, bool pushedDown);
+static List *determineMatchingColumns(List *ltargetlist, List *rtargetlist, ParseState *pstate, const char *context);
+static void makeUnionDatatype(List *ltargetlist, List *rtargetlist,
+		SetOperationStmt *op, List **targetlist, ParseState *parentParseState,
+		const char *context);
 #ifdef RAW_EXPRESSION_COVERAGE_TEST
 static bool test_raw_expression_coverage(Node *node, void *context);
 #endif
@@ -1664,7 +1668,13 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->targetList = NIL;
 	targetvars = NIL;
 	targetnames = NIL;
-	left_tlist = list_head(leftmostQuery->targetList);
+	/* for corresponding clause limits top-level query targetlist to those
+	 * corresponding column list only
+	 */
+	if (sostmt->correspondingColumns != NIL )
+		left_tlist = list_head(sostmt->correspondingColumns);
+	else
+		left_tlist = list_head(leftmostQuery->targetList);
 
 	forthree(lct, sostmt->colTypes,
 			 lcm, sostmt->colTypmods,
@@ -1924,8 +1934,6 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 		SetOperationStmt *op = makeNode(SetOperationStmt);
 		List	   *ltargetlist;
 		List	   *rtargetlist;
-		ListCell   *ltl;
-		ListCell   *rtl;
 		const char *context;
 
 		context = (stmt->op == SETOP_UNION ? "UNION" :
@@ -1936,6 +1944,218 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 		op->all = stmt->all;
 
 		/*
+		 * If CORRESPONDING is specified, syntax and column name validities checked,
+		 * column filtering is done by a subquery later on.
+		 */
+		if (stmt->correspondingClause == NIL )
+		{
+			/* No CORRESPONDING clause, no operation needed for column filtering */
+			op->correspondingColumns = stmt->correspondingClause;
+			op->hasCorrespondingBy = false;
+		}
+		else if (linitial(stmt->correspondingClause) == NULL )
+		{
+			/*
+			 * CORRESPONDING clause, find matching column names from both tables.
+			 * If there are none then it is a syntax error.
+			 */
+			Query	   *largQuery;
+			Query	   *rargQuery;
+			List	   *matchingColumns;
+			List	   *rightCorrespondingColumns;
+
+			/* Analyze left query to resolve column names. */
+			largQuery = parse_sub_analyze((Node *) stmt->larg,
+										  pstate, NULL, false, false );
+
+			/* Analyze right query to resolve column names. */
+			rargQuery = parse_sub_analyze((Node *) stmt->rarg,
+										  pstate, NULL, false, false );
+
+			/* Find matching columns from both queries. */
+			matchingColumns = determineMatchingColumns(largQuery->targetList,
+													   rargQuery->targetList, pstate, context);
+
+			/*
+			 * If matchingColumns is empty, there is an error.
+			 * At least one column in the select lists must have the same name.
+			 */
+			if (list_length(matchingColumns) == 0)
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("there are not any corresponding name"),
+	 errhint("%s queries with a CORRESPONDING clause must have at least one column with the same name",
+								 context),
+						 parser_errposition(pstate,
+									exprLocation((Node *)
+												 linitial(largQuery->targetList)))));
+			}
+
+			/*
+			 * make corresponding targetlist resnos sequential 
+			 */
+			op->correspondingColumns = makeSequence(matchingColumns);
+			op->hasCorrespondingBy = false;
+
+			/*
+			 * matchingColumns contain target list as it appear in left query
+			 * targetList we need matching column as it appear in right query
+			 * targetlist inorder to make output column type for corresponding
+			 * columns
+			 */
+			rightCorrespondingColumns = determineMatchingColumns(
+														rargQuery->targetList,
+														largQuery->targetList, pstate, context);
+
+			/* make union'd datatype of output column */
+			makeUnionDatatype(matchingColumns, rightCorrespondingColumns,
+								op, targetlist, pstate, context);
+		}
+		else
+		{
+			/*
+			 * CORRESPONDING BY clause, find matching column names from both tables
+			 * and intersect them with BY(...) column list. If there are none
+			 * then it is a syntax error.
+			 */
+			Query	   *largQuery;
+			Query	   *rargQuery;
+			List	   *matchingColumns;
+			List	   *matchingColumnsFiltered;
+			List	   *rightCorrespondingColumns;
+			ListCell   *corrtl;
+			ListCell   *mctl;
+
+			/* Analyze left query to resolve column names. */
+			largQuery = parse_sub_analyze((Node *) stmt->larg,
+										  pstate, NULL,false, false );
+
+			/* Analyze right query to resolve column names. */
+			rargQuery = parse_sub_analyze((Node *) stmt->rarg,
+										  pstate, NULL, false, false );
+
+			/*
+			 * Find matching columns from both queries.
+			 * In CORRESPONDING BY, column names will be removed from
+			 * matchingColumns if they are not in the BY clause.
+			 * All columns in the BY clause must be in matchingColumns,
+			 * otherwise raise syntax error in BY clause.
+			 */
+			matchingColumns = determineMatchingColumns(largQuery->targetList,rargQuery->targetList, 
+							  	  pstate, context);
+
+			/*
+			 * Every column name in correspondingClause must be in matchingColumns,
+			 * otherwise it is a syntax error.
+			 */
+			foreach(corrtl, stmt->correspondingClause)
+			{
+				Value	   *corrtle = lfirst(corrtl);
+				char	   *name;
+				bool		hasMatch = false;
+
+				/* Get column name from correspondingClause. */
+				name = strVal(corrtle);
+
+				foreach(mctl, matchingColumns)
+				{
+					TargetEntry	   *mctle = (TargetEntry *) lfirst(mctl);
+
+					Assert(mctle->resname != NULL);
+					Assert(name != NULL);
+
+					/*
+					 * Compare correspondingClause column name with
+					 * matchingColumns column names.
+					 */
+					if (strcmp(mctle->resname, name) == 0)
+					{
+						/* we have a match. */
+						hasMatch = true;
+						break;
+					}
+				}
+
+				if (!hasMatch)
+				{
+					/*
+					 * CORRESPONDING BY clause contains a column name that is
+					 * not in both tables.
+					 */
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("column name \"%s\" cannot be used in corresponding by clause", name),
+		 errhint("%s queries with a CORRESPONDING BY clause must only contain column names from both tables.",
+									 context),
+							 parser_errposition(pstate,
+												exprLocation((Node *) linitial(largQuery->targetList)))));
+				}
+			}
+
+			/*
+			 * To preserve column ordering from correspondingClause and to remove
+			 * columns from matchingColumns if they are not in correspondingClause,
+			 * create a new list and finalize our column list for the
+			 * CORRESPONDING BY clause.
+			 */
+			matchingColumnsFiltered = NIL;
+
+			/*
+			 * For each column in CORRESPONDING BY column list, check
+			 * column existence in matchingColumns.
+			 */
+			foreach(corrtl, stmt->correspondingClause)
+			{
+				Value	   *corrtle = lfirst(corrtl);
+
+				/* Get column name from correspondingClause. */
+				char *name = strVal(corrtle);
+
+				foreach(mctl, matchingColumns)
+				{
+					TargetEntry *mctle = (TargetEntry *) lfirst(mctl);
+
+					Assert(mctle->resname != NULL);
+					Assert(name != NULL);
+
+					if (strcmp(mctle->resname, name) == 0)
+					{
+						/* we have a match.*/
+						matchingColumnsFiltered = lappend(
+								matchingColumnsFiltered, mctle);
+						break;
+					}
+				}
+			}
+
+			/*
+			 * If matchingColumnsFiltered is empty, there is a semantic error.
+			 * At least one column in the select lists must have the same name.
+			 */
+			Assert(list_length(matchingColumnsFiltered) > 0);
+
+			/*
+			 * make corresponding targetlist resnos sequential 
+			 */
+			op->correspondingColumns = makeSequence(matchingColumnsFiltered);
+			op->hasCorrespondingBy = true;
+
+			/*
+			 * matchingColumns contain target list as it appear in left query targetList
+			 * we need matching column as it appear in right query targetlist inorder to
+			 * make output column type for corresponding columns
+			 */
+			rightCorrespondingColumns = determineMatchingColumns(rargQuery->targetList,
+							  		largQuery->targetList, pstate, context);
+			/*
+			 * make union'd datatype of output columns
+			 */
+			makeUnionDatatype(matchingColumnsFiltered, rightCorrespondingColumns,
+							  op, targetlist, pstate, context);
+		}
+
+		/*
 		 * Recursively transform the left child node.
 		 */
 		op->larg = transformSetOperationTree(pstate, stmt->larg,
@@ -1960,173 +2180,20 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 											 false,
 											 &rtargetlist);
 
-		/*
-		 * Verify that the two children have the same number of non-junk
-		 * columns, and determine the types of the merged output columns.
-		 */
-		if (list_length(ltargetlist) != list_length(rtargetlist))
-			ereport(ERROR,
-					(errcode(ERRCODE_SYNTAX_ERROR),
-				 errmsg("each %s query must have the same number of columns",
-						context),
-					 parser_errposition(pstate,
-										exprLocation((Node *) rtargetlist))));
-
-		if (targetlist)
-			*targetlist = NIL;
-		op->colTypes = NIL;
-		op->colTypmods = NIL;
-		op->colCollations = NIL;
-		op->groupClauses = NIL;
-		forboth(ltl, ltargetlist, rtl, rtargetlist)
+		if (op->correspondingColumns == NIL )
 		{
-			TargetEntry *ltle = (TargetEntry *) lfirst(ltl);
-			TargetEntry *rtle = (TargetEntry *) lfirst(rtl);
-			Node	   *lcolnode = (Node *) ltle->expr;
-			Node	   *rcolnode = (Node *) rtle->expr;
-			Oid			lcoltype = exprType(lcolnode);
-			Oid			rcoltype = exprType(rcolnode);
-			int32		lcoltypmod = exprTypmod(lcolnode);
-			int32		rcoltypmod = exprTypmod(rcolnode);
-			Node	   *bestexpr;
-			int			bestlocation;
-			Oid			rescoltype;
-			int32		rescoltypmod;
-			Oid			rescolcoll;
-
-			/* select common type, same as CASE et al */
-			rescoltype = select_common_type(pstate,
-											list_make2(lcolnode, rcolnode),
-											context,
-											&bestexpr);
-			bestlocation = exprLocation(bestexpr);
-			/* if same type and same typmod, use typmod; else default */
-			if (lcoltype == rcoltype && lcoltypmod == rcoltypmod)
-				rescoltypmod = lcoltypmod;
-			else
-				rescoltypmod = -1;
-
-			/*
-			 * Verify the coercions are actually possible.  If not, we'd fail
-			 * later anyway, but we want to fail now while we have sufficient
-			 * context to produce an error cursor position.
-			 *
-			 * For all non-UNKNOWN-type cases, we verify coercibility but we
-			 * don't modify the child's expression, for fear of changing the
-			 * child query's semantics.
-			 *
-			 * If a child expression is an UNKNOWN-type Const or Param, we
-			 * want to replace it with the coerced expression.  This can only
-			 * happen when the child is a leaf set-op node.  It's safe to
-			 * replace the expression because if the child query's semantics
-			 * depended on the type of this output column, it'd have already
-			 * coerced the UNKNOWN to something else.  We want to do this
-			 * because (a) we want to verify that a Const is valid for the
-			 * target type, or resolve the actual type of an UNKNOWN Param,
-			 * and (b) we want to avoid unnecessary discrepancies between the
-			 * output type of the child query and the resolved target type.
-			 * Such a discrepancy would disable optimization in the planner.
-			 *
-			 * If it's some other UNKNOWN-type node, eg a Var, we do nothing
-			 * (knowing that coerce_to_common_type would fail).  The planner
-			 * is sometimes able to fold an UNKNOWN Var to a constant before
-			 * it has to coerce the type, so failing now would just break
-			 * cases that might work.
-			 */
-			if (lcoltype != UNKNOWNOID)
-				lcolnode = coerce_to_common_type(pstate, lcolnode,
-												 rescoltype, context);
-			else if (IsA(lcolnode, Const) ||
-					 IsA(lcolnode, Param))
-			{
-				lcolnode = coerce_to_common_type(pstate, lcolnode,
-												 rescoltype, context);
-				ltle->expr = (Expr *) lcolnode;
-			}
-
-			if (rcoltype != UNKNOWNOID)
-				rcolnode = coerce_to_common_type(pstate, rcolnode,
-												 rescoltype, context);
-			else if (IsA(rcolnode, Const) ||
-					 IsA(rcolnode, Param))
-			{
-				rcolnode = coerce_to_common_type(pstate, rcolnode,
-												 rescoltype, context);
-				rtle->expr = (Expr *) rcolnode;
-			}
-
-			/*
-			 * Select common collation.  A common collation is required for
-			 * all set operators except UNION ALL; see SQL:2008 7.13 <query
-			 * expression> Syntax Rule 15c.  (If we fail to identify a common
-			 * collation for a UNION ALL column, the curCollations element
-			 * will be set to InvalidOid, which may result in a runtime error
-			 * if something at a higher query level wants to use the column's
-			 * collation.)
-			 */
-			rescolcoll = select_common_collation(pstate,
-											  list_make2(lcolnode, rcolnode),
-										 (op->op == SETOP_UNION && op->all));
-
-			/* emit results */
-			op->colTypes = lappend_oid(op->colTypes, rescoltype);
-			op->colTypmods = lappend_int(op->colTypmods, rescoltypmod);
-			op->colCollations = lappend_oid(op->colCollations, rescolcoll);
-
-			/*
-			 * For all cases except UNION ALL, identify the grouping operators
-			 * (and, if available, sorting operators) that will be used to
-			 * eliminate duplicates.
-			 */
-			if (op->op != SETOP_UNION || !op->all)
-			{
-				SortGroupClause *grpcl = makeNode(SortGroupClause);
-				Oid			sortop;
-				Oid			eqop;
-				bool		hashable;
-				ParseCallbackState pcbstate;
-
-				setup_parser_errposition_callback(&pcbstate, pstate,
-												  bestlocation);
-
-				/* determine the eqop and optional sortop */
-				get_sort_group_operators(rescoltype,
-										 false, true, false,
-										 &sortop, &eqop, NULL,
-										 &hashable);
-
-				cancel_parser_errposition_callback(&pcbstate);
-
-				/* we don't have a tlist yet, so can't assign sortgrouprefs */
-				grpcl->tleSortGroupRef = 0;
-				grpcl->eqop = eqop;
-				grpcl->sortop = sortop;
-				grpcl->nulls_first = false;		/* OK with or without sortop */
-				grpcl->hashable = hashable;
-
-				op->groupClauses = lappend(op->groupClauses, grpcl);
-			}
-
-			/*
-			 * Construct a dummy tlist entry to return.  We use a SetToDefault
-			 * node for the expression, since it carries exactly the fields
-			 * needed, but any other expression node type would do as well.
-			 */
-			if (targetlist)
-			{
-				SetToDefault *rescolnode = makeNode(SetToDefault);
-				TargetEntry *restle;
-
-				rescolnode->typeId = rescoltype;
-				rescolnode->typeMod = rescoltypmod;
-				rescolnode->collation = rescolcoll;
-				rescolnode->location = bestlocation;
-				restle = makeTargetEntry((Expr *) rescolnode,
-										 0,		/* no need to set resno */
-										 NULL,
-										 false);
-				*targetlist = lappend(*targetlist, restle);
-			}
+			makeUnionDatatype(ltargetlist, rtargetlist, op, targetlist, pstate,
+					context);
+			/*
+			 * Verify that the two children have the same number of non-junk
+			 * columns, and determine the types of the merged output columns.
+			 */
+			if (list_length(ltargetlist) != list_length(rtargetlist))
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("each %s query must have the same number of columns", context),
+						 parser_errposition(pstate,
+											exprLocation((Node *) rtargetlist))));
 		}
 
 		return (Node *) op;
@@ -2134,6 +2201,212 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 }
 
 /*
+ * Processes targetlists of two queries for column equivalence to use
+ * with UNION/INTERSECT/EXCEPT CORRESPONDING.
+ */
+static List *
+determineMatchingColumns(List *ltargetlist, List *rtargetlist, ParseState *pstate, const char *context)
+{
+	List	   *matchingColumns = NIL;
+	ListCell   *ltl;
+	ListCell   *rtl;
+	int   numOfMatchingColumen;
+
+	foreach(ltl, ltargetlist)
+	{
+		numOfMatchingColumen = 0;
+
+		foreach(rtl, rtargetlist)
+		{
+			TargetEntry	   *ltle = (TargetEntry *) lfirst(ltl);
+			TargetEntry	   *rtle = (TargetEntry *) lfirst(rtl);
+
+			/* Names of the columns must be resolved before calling this method. */
+			Assert(ltle->resname != NULL);
+			Assert(rtle->resname != NULL);
+
+			/* If column names are the same, append it to the result. */
+			if (strcmp(ltle->resname, rtle->resname) == 0)
+			{
+				numOfMatchingColumen++;
+				/* If same column name mentioned more than once it is syntax error . */
+				if (numOfMatchingColumen >= 2)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR), errmsg("column can not be specified more than once "), parser_errposition(pstate, exprLocation((Node *) rtle))));
+				matchingColumns = lappend(matchingColumns, ltle);
+				continue;
+			}
+		}
+	}
+
+	return matchingColumns;
+}
+
+/*
+ * process right and left target list to set up union'd datatype
+ */
+static void 
+makeUnionDatatype(List *ltargetlist, List *rtargetlist, SetOperationStmt *op,
+		List **targetlist, ParseState *pstate, const char *context)
+{
+	ListCell   *ltl;
+	ListCell   *rtl;
+
+	if (targetlist)
+		*targetlist = NIL;
+
+	op->colTypes = NIL;
+	op->colTypmods = NIL;
+	op->colCollations = NIL;
+	op->groupClauses = NIL;
+
+	forboth(ltl, ltargetlist, rtl, rtargetlist)
+	{
+		TargetEntry	   *ltle = (TargetEntry *) lfirst(ltl);
+		TargetEntry	   *rtle = (TargetEntry *) lfirst(rtl);
+		Node		   *lcolnode = (Node *) ltle->expr;
+		Node		   *rcolnode = (Node *) rtle->expr;
+		Oid			lcoltype = exprType(lcolnode);
+		Oid			rcoltype = exprType(rcolnode);
+		int32		lcoltypmod = exprTypmod(lcolnode);
+		int32		rcoltypmod = exprTypmod(rcolnode);
+		Node	   *bestexpr;
+		int			bestlocation;
+		Oid			rescoltype;
+		int32		rescoltypmod;
+		Oid			rescolcoll;
+
+		/* select common type, same as CASE et al */
+		rescoltype = select_common_type(pstate, list_make2(lcolnode, rcolnode),
+				context, &bestexpr);
+		bestlocation = exprLocation(bestexpr);
+		/* if same type and same typmod, use typmod; else default */
+		if (lcoltype == rcoltype && lcoltypmod == rcoltypmod)
+			rescoltypmod = lcoltypmod;
+		else
+			rescoltypmod = -1;
+
+		/*
+		 * Verify the coercions are actually possible.  If not, we'd fail
+		 * later anyway, but we want to fail now while we have sufficient
+		 * context to produce an error cursor position.
+		 *
+		 * For all non-UNKNOWN-type cases, we verify coercibility but we
+		 * don't modify the child's expression, for fear of changing the
+		 * child query's semantics.
+		 *
+		 * If a child expression is an UNKNOWN-type Const or Param, we
+		 * want to replace it with the coerced expression.  This can only
+		 * happen when the child is a leaf set-op node.  It's safe to
+		 * replace the expression because if the child query's semantics
+		 * depended on the type of this output column, it'd have already
+		 * coerced the UNKNOWN to something else.  We want to do this
+		 * because (a) we want to verify that a Const is valid for the
+		 * target type, or resolve the actual type of an UNKNOWN Param,
+		 * and (b) we want to avoid unnecessary discrepancies between the
+		 * output type of the child query and the resolved target type.
+		 * Such a discrepancy would disable optimization in the planner.
+		 *
+		 * If it's some other UNKNOWN-type node, eg a Var, we do nothing
+		 * (knowing that coerce_to_common_type would fail).  The planner
+		 * is sometimes able to fold an UNKNOWN Var to a constant before
+		 * it has to coerce the type, so failing now would just break
+		 * cases that might work.
+		 */
+		if (lcoltype != UNKNOWNOID)
+			lcolnode = coerce_to_common_type(pstate, lcolnode, rescoltype,
+					context);
+		else if (IsA(lcolnode, Const) || IsA(lcolnode, Param))
+		{
+			lcolnode = coerce_to_common_type(pstate, lcolnode, rescoltype,
+					context);
+			ltle->expr = (Expr *) lcolnode;
+		}
+
+		if (rcoltype != UNKNOWNOID)
+			rcolnode = coerce_to_common_type(pstate, rcolnode, rescoltype,
+					context);
+		else if (IsA(rcolnode, Const) || IsA(rcolnode, Param))
+		{
+			rcolnode = coerce_to_common_type(pstate, rcolnode, rescoltype,
+					context);
+			rtle->expr = (Expr *) rcolnode;
+		}
+
+		/*
+		 * Select common collation.  A common collation is required for
+		 * all set operators except UNION ALL; see SQL:2008 7.13 <query
+		 * expression> Syntax Rule 15c.  (If we fail to identify a common
+		 * collation for a UNION ALL column, the curCollations element
+		 * will be set to InvalidOid, which may result in a runtime error
+		 * if something at a higher query level wants to use the column's
+		 * collation.)
+		 */
+		rescolcoll = select_common_collation(pstate,
+				list_make2(lcolnode, rcolnode),
+				(op->op == SETOP_UNION && op->all));
+
+		/* emit results */
+		op->colTypes = lappend_oid(op->colTypes, rescoltype);
+		op->colTypmods = lappend_int(op->colTypmods, rescoltypmod);
+		op->colCollations = lappend_oid(op->colCollations, rescolcoll);
+
+		/*
+		 * For all cases except UNION ALL, identify the grouping operators
+		 * (and, if available, sorting operators) that will be used to
+		 * eliminate duplicates.
+		 */
+		if (op->op != SETOP_UNION || !op->all)
+		{
+			SortGroupClause *grpcl = makeNode(SortGroupClause);
+			Oid			sortop;
+			Oid			eqop;
+			bool		hashable;
+			ParseCallbackState pcbstate;
+
+			setup_parser_errposition_callback(&pcbstate, pstate, bestlocation);
+
+			/* determine the eqop and optional sortop */
+			get_sort_group_operators(rescoltype, false, true, false, &sortop,
+					&eqop, NULL, &hashable);
+
+			cancel_parser_errposition_callback(&pcbstate);
+
+			/* we don't have a tlist yet, so can't assign sortgrouprefs */
+			grpcl->tleSortGroupRef = 0;
+			grpcl->eqop = eqop;
+			grpcl->sortop = sortop;
+			grpcl->nulls_first = false; /* OK with or without sortop */
+			grpcl->hashable = hashable;
+
+			op->groupClauses = lappend(op->groupClauses, grpcl);
+		}
+
+		/*
+		 * Construct a dummy tlist entry to return.  We use a SetToDefault
+		 * node for the expression, since it carries exactly the fields
+		 * needed, but any other expression node type would do as well.
+		 */
+		if (targetlist)
+		{
+			SetToDefault   *rescolnode = makeNode(SetToDefault);
+			TargetEntry	   *restle;
+
+			rescolnode->typeId = rescoltype;
+			rescolnode->typeMod = rescoltypmod;
+			rescolnode->collation = rescolcoll;
+			rescolnode->location = bestlocation;
+
+			/* no need to set resno */
+			restle = makeTargetEntry((Expr *) rescolnode, 0,
+			NULL, false );
+			*targetlist = lappend(*targetlist, restle);
+		}
+	}
+
+}
+
+/*
  * Process the outputs of the non-recursive term of a recursive union
  * to set up the parent CTE's columns
  */
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 174773b..d73a7f1 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -166,7 +166,7 @@ static void insertSelectOptions(SelectStmt *stmt,
 								Node *limitOffset, Node *limitCount,
 								WithClause *withClause,
 								core_yyscan_t yyscanner);
-static Node *makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg);
+static Node *makeSetOp(SetOperation op, bool all, List *correspondingClause, Node *larg, Node *rarg);
 static Node *doNegate(Node *n, int location);
 static void doNegateFloat(Value *v);
 static Node *makeAndExpr(Node *lexpr, Node *rexpr, int location);
@@ -394,7 +394,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				relation_expr_list dostmt_opt_list
 				transform_element_list transform_type_list
 				TriggerTransitions TriggerReferencing
-				publication_name_list
+				publication_name_list opt_corresponding_clause
 
 %type <list>	group_by_list
 %type <node>	group_by_item empty_grouping_set rollup_clause cube_clause
@@ -608,7 +608,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
 	CLUSTER COALESCE COLLATE COLLATION COLUMN COMMENT COMMENTS COMMIT
 	COMMITTED CONCURRENTLY CONFIGURATION CONFLICT CONNECTION CONSTRAINT
-	CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE
+	CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY CORRESPONDING COST CREATE
 	CROSS CSV CUBE CURRENT_P
 	CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA
 	CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
@@ -10808,20 +10808,26 @@ simple_select:
 					n->fromClause = list_make1($2);
 					$$ = (Node *)n;
 				}
-			| select_clause UNION all_or_distinct select_clause
+			| select_clause UNION all_or_distinct opt_corresponding_clause select_clause
 				{
-					$$ = makeSetOp(SETOP_UNION, $3, $1, $4);
+					$$ = makeSetOp(SETOP_UNION, $3, $4, $1, $5);
 				}
-			| select_clause INTERSECT all_or_distinct select_clause
+			| select_clause INTERSECT all_or_distinct opt_corresponding_clause select_clause
 				{
-					$$ = makeSetOp(SETOP_INTERSECT, $3, $1, $4);
+					$$ = makeSetOp(SETOP_INTERSECT, $3, $4, $1, $5);
 				}
-			| select_clause EXCEPT all_or_distinct select_clause
+			| select_clause EXCEPT all_or_distinct opt_corresponding_clause select_clause
 				{
-					$$ = makeSetOp(SETOP_EXCEPT, $3, $1, $4);
+					$$ = makeSetOp(SETOP_EXCEPT, $3, $4, $1, $5);
 				}
 		;
 
+opt_corresponding_clause:
+			CORRESPONDING BY '(' columnList ')'		{ $$ = $4; }
+			| CORRESPONDING							{ $$ = list_make1(NIL); }
+			| /*EMPTY*/								{ $$ = NIL; }
+			;
+
 /*
  * SQL standard WITH clause looks like:
  *
@@ -13999,7 +14005,6 @@ name_list:	name
 					{ $$ = lappend($1, makeString($3)); }
 		;
 
-
 name:		ColId									{ $$ = $1; };
 
 database_name:
@@ -14315,6 +14320,7 @@ unreserved_keyword:
 			| CONTINUE_P
 			| CONVERSION_P
 			| COPY
+			| CORRESPONDING
 			| COST
 			| CSV
 			| CUBE
@@ -15145,7 +15151,7 @@ insertSelectOptions(SelectStmt *stmt,
 }
 
 static Node *
-makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg)
+makeSetOp(SetOperation op, bool all, List *correspondingClause, Node *larg, Node *rarg)
 {
 	SelectStmt *n = makeNode(SelectStmt);
 
@@ -15153,6 +15159,7 @@ makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg)
 	n->all = all;
 	n->larg = (SelectStmt *) larg;
 	n->rarg = (SelectStmt *) rarg;
+	n->correspondingClause = correspondingClause;
 	return (Node *) n;
 }
 
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 2576e31..ad2f83d 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1903,3 +1903,25 @@ FigureColnameInternal(Node *node, char **name)
 
 	return strength;
 }
+
+/*
+ * makeSequence()
+ * make corresponding target list resno sequential .
+ */
+List *
+makeSequence(List *targetlist)
+{
+	List *p_target = NIL;
+	ListCell *o_target;
+	int pos = 1;
+
+	foreach(o_target, targetlist)
+	{
+		TargetEntry *tar = (TargetEntry *) lfirst(o_target);
+
+		p_target = lappend(p_target,
+				makeTargetEntry(tar->expr, (AttrNumber) pos++, tar->resname, false));
+	}
+
+	return p_target;
+}
diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
index 8feec0b..ac23b1b 100644
--- a/src/backend/parser/parse_type.c
+++ b/src/backend/parser/parse_type.c
@@ -736,7 +736,8 @@ typeStringToTypeName(const char *str)
 		stmt->limitCount != NULL ||
 		stmt->lockingClause != NIL ||
 		stmt->withClause != NULL ||
-		stmt->op != SETOP_NONE)
+		stmt->op != SETOP_NONE ||
+		stmt->correspondingClause != NULL)
 		goto fail;
 	if (list_length(stmt->targetList) != 1)
 		goto fail;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index f355954..4643f52 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -386,6 +386,8 @@ static void get_setop_query(Node *setOp, Query *query,
 static Node *get_rule_sortgroupclause(Index ref, List *tlist,
 						 bool force_colno,
 						 deparse_context *context);
+static void get_rule_correspondingclause(char *name, List *tlist,
+						 deparse_context *context);
 static void get_rule_groupingset(GroupingSet *gset, List *targetlist,
 					 bool omit_parens, deparse_context *context);
 static void get_rule_orderby(List *orderList, List *targetList,
@@ -5349,6 +5351,30 @@ get_setop_query(Node *setOp, Query *query, deparse_context *context,
 		}
 		if (op->all)
 			appendStringInfoString(buf, "ALL ");
+		if (op->correspondingColumns != NIL )
+		{
+			if (op->hasCorrespondingBy)
+			{
+				const char *sep;
+				ListCell *l;
+				appendStringInfoString(buf, "CORRESPONDING BY(");
+				sep = "";
+				foreach(l, op->correspondingColumns)
+				{
+					TargetEntry *tle = (TargetEntry *) lfirst(l);
+
+					appendStringInfoString(buf, sep);
+					get_rule_correspondingclause(tle->resname,
+							query->targetList, context);
+					sep = ", ";
+				}
+				appendStringInfoChar(buf, ')');
+
+			}
+			else
+
+				appendStringInfoString(buf, "CORRESPONDING ");
+		}
 
 		/* Always parenthesize if RHS is another setop */
 		need_paren = IsA(op->rarg, SetOperationStmt);
@@ -10499,3 +10525,15 @@ flatten_reloptions(Oid relid)
 
 	return result;
 }
+
+/*
+ * Display corresponding clause.
+ */
+static void get_rule_correspondingclause(char *name, List *tlist,
+		 deparse_context *context)
+{
+	TargetEntry *tle;
+	StringInfo buf = context->buf;
+	tle = get_corresponding_tle(name, tlist);
+	appendStringInfo(buf, "%s", tle->resname);
+}
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 07a8436..8d95aaa 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1420,7 +1420,8 @@ typedef struct SelectStmt
 	 * These fields are used only in "leaf" SelectStmts.
 	 */
 	List	   *distinctClause; /* NULL, list of DISTINCT ON exprs, or
-								 * lcons(NIL,NIL) for all (SELECT DISTINCT) */
+								* lcons(NIL,NIL) for all (SELECT DISTINCT) */
+	List	   *correspondingClause; /* CORRESPONDING BY  clauses*/
 	IntoClause *intoClause;		/* target for SELECT INTO */
 	List	   *targetList;		/* the target list (of ResTarget) */
 	List	   *fromClause;		/* the FROM clause */
@@ -1456,7 +1457,6 @@ typedef struct SelectStmt
 	bool		all;			/* ALL specified? */
 	struct SelectStmt *larg;	/* left child */
 	struct SelectStmt *rarg;	/* right child */
-	/* Eventually add fields for CORRESPONDING spec here */
 } SelectStmt;
 
 
@@ -1486,8 +1486,8 @@ typedef struct SetOperationStmt
 	bool		all;			/* ALL specified? */
 	Node	   *larg;			/* left child */
 	Node	   *rarg;			/* right child */
-	/* Eventually add fields for CORRESPONDING spec here */
-
+	List	   *correspondingColumns;		/* list of corresponding column names */
+ 	bool	   hasCorrespondingBy;		/* has corresponding by cluase? */
 	/* Fields derived during parse analysis: */
 	List	   *colTypes;		/* OID list of output column type OIDs */
 	List	   *colTypmods;		/* integer list of output column typmods */
diff --git a/src/include/optimizer/tlist.h b/src/include/optimizer/tlist.h
index 976024a..f773386 100644
--- a/src/include/optimizer/tlist.h
+++ b/src/include/optimizer/tlist.h
@@ -35,6 +35,8 @@ extern void apply_tlist_labeling(List *dest_tlist, List *src_tlist);
 
 extern TargetEntry *get_sortgroupref_tle(Index sortref,
 					 List *targetList);
+extern TargetEntry *get_corresponding_tle(char *name,
+					 List *targetList);
 extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
 						List *targetList);
 extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 985d650..77e0396 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -96,6 +96,7 @@ PG_KEYWORD("content", CONTENT_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("continue", CONTINUE_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("conversion", CONVERSION_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("copy", COPY, UNRESERVED_KEYWORD)
+PG_KEYWORD("corresponding", CORRESPONDING, UNRESERVED_KEYWORD)
 PG_KEYWORD("cost", COST, UNRESERVED_KEYWORD)
 PG_KEYWORD("create", CREATE, RESERVED_KEYWORD)
 PG_KEYWORD("cross", CROSS, TYPE_FUNC_NAME_KEYWORD)
diff --git a/src/include/parser/parse_target.h b/src/include/parser/parse_target.h
index d06a235..77157b5 100644
--- a/src/include/parser/parse_target.h
+++ b/src/include/parser/parse_target.h
@@ -42,5 +42,6 @@ extern TupleDesc expandRecordVariable(ParseState *pstate, Var *var,
 					 int levelsup);
 extern char *FigureColname(Node *node);
 extern char *FigureIndexColname(Node *node);
+extern List *makeSequence(List *targetlist);
 
 #endif   /* PARSE_TARGET_H */
diff --git a/src/test/regress/expected/union.out b/src/test/regress/expected/union.out
index 4d697ba..224892d 100644
--- a/src/test/regress/expected/union.out
+++ b/src/test/regress/expected/union.out
@@ -59,6 +59,87 @@ SELECT 1.1 AS two UNION SELECT 2.2 ORDER BY 1;
  2.2
 (2 rows)
 
+SELECT 1 AS two UNION CORRESPONDING SELECT 2 two UNION CORRESPONDING SELECT 2 two;
+ two 
+-----
+   2
+   1
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a) SELECT 4 a, 5 b, 6 c;
+ a 
+---
+ 1
+ 4
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b) SELECT 4 a, 5 b, 6 c;
+ a | b 
+---+---
+ 1 | 2
+ 4 | 5
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c) SELECT 4 a, 5 b, 6 c;
+ b | c 
+---+---
+ 2 | 3
+ 5 | 6
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY (c,b) SELECT 4 a, 5 b, 6 c, 8 d;
+ c | b 
+---+---
+ 3 | 2
+ 6 | 5
+(2 rows)
+
+-- CORRESPONDING column ordering, left clause's column ordering must be preserved.
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+SELECT 2 b, 1 a, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+ b | a | c 
+---+---+---
+ 2 | 1 | 3
+ 5 | 4 | 6
+(2 rows)
+
+-- CORRESPONDING BY column ordering, BY clause column ordering must be preserved.
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 4 a, 5 b, 6 c;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c, a) SELECT 4 a, 5 b, 6 c;
+ b | c | a 
+---+---+---
+ 2 | 3 | 1
+ 5 | 6 | 4
+(2 rows)
+
+-- should to fail
+SELECT 10 a, 20 b UNION ALL CORRESPONDING SELECT 10 c, 20 d;
+ERROR:  there are not any corresponding name
+LINE 1: SELECT 10 a, 20 b UNION ALL CORRESPONDING SELECT 10 c, 20 d;
+               ^
+HINT:  UNION queries with a CORRESPONDING clause must have at least one column with the same name
+SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (a,b) SELECT 10 c, 20 d;
+ERROR:  column name "a" cannot be used in corresponding by clause
+LINE 1: SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (a,b) SELECT 10...
+               ^
+HINT:  UNION queries with a CORRESPONDING BY clause must only contain column names from both tables.
+SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (x) SELECT 10 c, 20 d;
+ERROR:  column name "x" cannot be used in corresponding by clause
+LINE 1: SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (x) SELECT 10 c...
+               ^
+HINT:  UNION queries with a CORRESPONDING BY clause must only contain column names from both tables.
 -- Mixed types
 SELECT 1.1 AS two UNION SELECT 2 ORDER BY 1;
  two 
@@ -258,6 +339,74 @@ ORDER BY 1;
  hi de ho neighbor
 (5 rows)
 
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+         five          
+-----------------------
+ -1.2345678901234e+200
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+                     0
+(5 rows)
+
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING BY(five)
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+         five          
+-----------------------
+ -1.2345678901234e+200
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+                     0
+(5 rows)
+
+SELECT f1 AS ten FROM FLOAT8_TBL
+UNION ALL CORRESPONDING
+SELECT f1 AS ten FROM FLOAT8_TBL;
+          ten          
+-----------------------
+                     0
+                -34.84
+               -1004.3
+ -1.2345678901234e+200
+ -1.2345678901234e-200
+                     0
+                -34.84
+               -1004.3
+ -1.2345678901234e+200
+ -1.2345678901234e-200
+(10 rows)
+
+SELECT f1 AS five FROM FLOAT8_TBL
+  WHERE f1 BETWEEN -1e6 AND 1e6
+UNION CORRESPONDING
+SELECT f1 AS five FROM INT4_TBL
+  WHERE f1 BETWEEN 0 AND 1000000;
+         five          
+-----------------------
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+                     0
+                123456
+(5 rows)
+
+SELECT * FROM FLOAT8_TBL
+UNION corresponding SELECT * FROM FLOAT8_TBL;
+          f1           
+-----------------------
+                     0
+ -1.2345678901234e-200
+                -34.84
+ -1.2345678901234e+200
+               -1004.3
+(5 rows)
+
 --
 -- INTERSECT and EXCEPT
 --
@@ -320,6 +469,63 @@ SELECT q1 FROM int8_tbl EXCEPT ALL SELECT DISTINCT q2 FROM int8_tbl ORDER BY 1;
 
 SELECT q1 FROM int8_tbl EXCEPT ALL SELECT q1 FROM int8_tbl FOR NO KEY UPDATE;
 ERROR:  FOR NO KEY UPDATE is not allowed with UNION/INTERSECT/EXCEPT
+SELECT q2 FROM int8_tbl INTERSECT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+        q2        
+------------------
+              123
+ 4567890123456789
+(2 rows)
+
+SELECT q2 FROM int8_tbl INTERSECT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+        q2        
+------------------
+              123
+ 4567890123456789
+ 4567890123456789
+(3 rows)
+
+SELECT q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+        q2         
+-------------------
+ -4567890123456789
+               456
+(2 rows)
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+        q2         
+-------------------
+ -4567890123456789
+               456
+(2 rows)
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q1 AS q2 FROM int8_tbl ORDER BY 1;
+        q2         
+-------------------
+ -4567890123456789
+               456
+  4567890123456789
+(3 rows)
+
+SELECT q1 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+ q1 
+----
+(0 rows)
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+        q1        
+------------------
+              123
+ 4567890123456789
+(2 rows)
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q2 AS q1 FROM int8_tbl;
+        q1        
+------------------
+              123
+ 4567890123456789
+ 4567890123456789
+(3 rows)
+
 --
 -- Mixed types
 --
@@ -338,6 +544,21 @@ SELECT f1 FROM float8_tbl EXCEPT SELECT f1 FROM int4_tbl ORDER BY 1;
  -1.2345678901234e-200
 (4 rows)
 
+SELECT f1 FROM float8_tbl INTERSECT CORRESPONDING SELECT f1 FROM int4_tbl;
+ f1 
+----
+  0
+(1 row)
+
+SELECT f1 FROM float8_tbl EXCEPT CORRESPONDING SELECT f1 FROM int4_tbl ORDER BY 1;
+          f1           
+-----------------------
+ -1.2345678901234e+200
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+(4 rows)
+
 --
 -- Operator precedence and (((((extra))))) parentheses
 --
@@ -424,6 +645,24 @@ SELECT q1 FROM int8_tbl EXCEPT (((SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1)))
  4567890123456789
 (2 rows)
 
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2,q1 FROM int8_tbl
+ORDER BY q2,q1;
+ q1 | q2 
+----+----
+(0 rows)
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q1) SELECT q2,q1 FROM int8_tbl
+ORDER BY q1;
+ q1 
+----
+(0 rows)
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q2) SELECT q2,q1 FROM int8_tbl
+ORDER BY q2;
+ q2 
+----
+(0 rows)
+
 --
 -- New syntaxes (7.1) permit new tests
 --
@@ -459,6 +698,22 @@ SELECT '3.4'::numeric UNION SELECT 'foo';
 ERROR:  invalid input syntax for type numeric: "foo"
 LINE 1: SELECT '3.4'::numeric UNION SELECT 'foo';
                                            ^
+SELECT '3.4'::numeric AS a UNION CORRESPONDING SELECT 'foo' AS a;
+ERROR:  invalid input syntax for type numeric: "foo"
+LINE 1: ...CT '3.4'::numeric AS a UNION CORRESPONDING SELECT 'foo' AS a...
+                                                             ^
+SELECT a.f1 FROM (SELECT 'test' AS f1 FROM varchar_tbl) a
+UNION CORRESPONDING
+SELECT b.f1 FROM (SELECT f1 FROM varchar_tbl) b
+ORDER BY 1;
+  f1  
+------
+ a
+ ab
+ abcd
+ test
+(4 rows)
+
 --
 -- Test that expression-index constraints can be pushed down through
 -- UNION or UNION ALL
diff --git a/src/test/regress/sql/union.sql b/src/test/regress/sql/union.sql
index 48e6850..d6094b6 100644
--- a/src/test/regress/sql/union.sql
+++ b/src/test/regress/sql/union.sql
@@ -20,6 +20,35 @@ SELECT 1 AS three UNION SELECT 2 UNION ALL SELECT 2 ORDER BY 1;
 
 SELECT 1.1 AS two UNION SELECT 2.2 ORDER BY 1;
 
+SELECT 1 AS two UNION CORRESPONDING SELECT 2 two UNION CORRESPONDING SELECT 2 two;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY (c,b) SELECT 4 a, 5 b, 6 c, 8 d;
+
+-- CORRESPONDING column ordering, left clause's column ordering must be preserved.
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+
+SELECT 2 b, 1 a, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+
+-- CORRESPONDING BY column ordering, BY clause column ordering must be preserved.
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c, a) SELECT 4 a, 5 b, 6 c;
+
+-- should to fail
+SELECT 10 a, 20 b UNION ALL CORRESPONDING SELECT 10 c, 20 d;
+
+SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (a,b) SELECT 10 c, 20 d;
+
+SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (x) SELECT 10 c, 20 d;
+
 -- Mixed types
 
 SELECT 1.1 AS two UNION SELECT 2 ORDER BY 1;
@@ -90,6 +119,29 @@ UNION
 SELECT TRIM(TRAILING FROM f1) FROM CHAR_TBL
 ORDER BY 1;
 
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING BY(five)
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+
+SELECT f1 AS ten FROM FLOAT8_TBL
+UNION ALL CORRESPONDING
+SELECT f1 AS ten FROM FLOAT8_TBL;
+
+SELECT f1 AS five FROM FLOAT8_TBL
+  WHERE f1 BETWEEN -1e6 AND 1e6
+UNION CORRESPONDING
+SELECT f1 AS five FROM INT4_TBL
+  WHERE f1 BETWEEN 0 AND 1000000;
+
+SELECT * FROM FLOAT8_TBL
+UNION corresponding SELECT * FROM FLOAT8_TBL;
+
 --
 -- INTERSECT and EXCEPT
 --
@@ -112,6 +164,22 @@ SELECT q1 FROM int8_tbl EXCEPT ALL SELECT DISTINCT q2 FROM int8_tbl ORDER BY 1;
 
 SELECT q1 FROM int8_tbl EXCEPT ALL SELECT q1 FROM int8_tbl FOR NO KEY UPDATE;
 
+SELECT q2 FROM int8_tbl INTERSECT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+
+SELECT q2 FROM int8_tbl INTERSECT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+
+SELECT q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q1 AS q2 FROM int8_tbl ORDER BY 1;
+
+SELECT q1 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q2 AS q1 FROM int8_tbl;
+
 --
 -- Mixed types
 --
@@ -120,6 +188,10 @@ SELECT f1 FROM float8_tbl INTERSECT SELECT f1 FROM int4_tbl ORDER BY 1;
 
 SELECT f1 FROM float8_tbl EXCEPT SELECT f1 FROM int4_tbl ORDER BY 1;
 
+SELECT f1 FROM float8_tbl INTERSECT CORRESPONDING SELECT f1 FROM int4_tbl;
+
+SELECT f1 FROM float8_tbl EXCEPT CORRESPONDING SELECT f1 FROM int4_tbl ORDER BY 1;
+
 --
 -- Operator precedence and (((((extra))))) parentheses
 --
@@ -150,6 +222,15 @@ SELECT q1 FROM int8_tbl EXCEPT SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1;
 -- But this should work:
 SELECT q1 FROM int8_tbl EXCEPT (((SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1))) ORDER BY 1;
 
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2,q1 FROM int8_tbl
+ORDER BY q2,q1;
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q1) SELECT q2,q1 FROM int8_tbl
+ORDER BY q1;
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q2) SELECT q2,q1 FROM int8_tbl
+ORDER BY q2;
+
 --
 -- New syntaxes (7.1) permit new tests
 --
@@ -170,6 +251,13 @@ ORDER BY 1;
 -- This should fail, but it should produce an error cursor
 SELECT '3.4'::numeric UNION SELECT 'foo';
 
+SELECT '3.4'::numeric AS a UNION CORRESPONDING SELECT 'foo' AS a;
+
+SELECT a.f1 FROM (SELECT 'test' AS f1 FROM varchar_tbl) a
+UNION CORRESPONDING
+SELECT b.f1 FROM (SELECT f1 FROM varchar_tbl) b
+ORDER BY 1;
+
 --
 -- Test that expression-index constraints can be pushed down through
 -- UNION or UNION ALL
#27Pavel Stehule
pavel.stehule@gmail.com
In reply to: Surafel Temesgen (#26)
1 attachment(s)
Re: New CORRESPONDING clause design

Hi

2017-03-25 13:41 GMT+01:00 Surafel Temesgen <surafel3000@gmail.com>:

I took a quick look through this and noted that it fails to touch
ruleutils.c, which means that dumping of views containing CORRESPONDING
certainly doesn't work.

fixed

Also, the changes in parser/analyze.c seem rather massive and
correspondingly hard to review. Is it possible to rearrange the
patch to reduce the size of that diff? If you can avoid moving
or reindenting existing code, that'd help.

Part of transformSetOperationTree that make union data type of
set operation target list became makeUnionDatatype inorder to
easy using it multiple time and avoid very long transformSetOperationTree
function

The code in that area seems rather confused, too. For instance,
I'm not sure exactly what orderCorrespondingList() is good for,
but it certainly doesn't look to be doing anything that either its
name or its header comment (or the comments at the call sites) would
suggest. Its input and output tlists are always in the same order.

It give corresponding target list a sequential resnos

Inorder to avoid touching generate_append_tlist I change
the comment and function name as such

I also think there should be some comments about exactly what matching

semantics we're enforcing. The SQL standard says

a) If CORRESPONDING is specified, then:
i) Within the columns of T1, equivalent <column name>s shall
not be specified more than once and within the columns of
T2, equivalent <column name>s shall not be specified more
than once.

That seems unreasonably stringent to me; it ought to be sufficient to
forbid duplicates of the names listed in CORRESPONDING, or the common
column names if there's no BY list. But whichever restriction you prefer,
this code seems to be failing to check it --- I certainly don't see any
new error message about "column name "foo" appears more than once".

fixed

I'm not impressed by using A_Const for the members of the CORRESPONDING

name list. That's not a clever solution, that's a confusing kluge,
because it's a complete violation of the meaning of A_Const. Elsewhere
we just use lists of String for name lists, and that seems sufficient
here. Personally I'd just use the existing columnList production rather
than rolling your own.

fixed

I am sending updated version:

1. the corresponding columns must be unique, other not - code refactoring
(the code is still O(N*M*Z) - but some slow operations (string cmp)
reduced) + regress tests
2. regress tests for views
3. some cleaning (white chars)

Regards

Pavel

Attachments:

corresponding_clause_v8.patchtext/x-patch; charset=US-ASCII; name=corresponding_clause_v8.patchDownload
diff --git a/doc/src/sgml/queries.sgml b/doc/src/sgml/queries.sgml
index 30792f45f1..c3cdee54ad 100644
--- a/doc/src/sgml/queries.sgml
+++ b/doc/src/sgml/queries.sgml
@@ -1601,6 +1601,9 @@ SELECT DISTINCT ON (<replaceable>expression</replaceable> <optional>, <replaceab
    <primary>EXCEPT</primary>
   </indexterm>
   <indexterm zone="queries-union">
+   <primary>CORRESPONDING</primary>
+  </indexterm>
+  <indexterm zone="queries-union">
    <primary>set union</primary>
   </indexterm>
   <indexterm zone="queries-union">
@@ -1617,9 +1620,9 @@ SELECT DISTINCT ON (<replaceable>expression</replaceable> <optional>, <replaceab
    The results of two queries can be combined using the set operations
    union, intersection, and difference.  The syntax is
 <synopsis>
-<replaceable>query1</replaceable> UNION <optional>ALL</optional> <replaceable>query2</replaceable>
-<replaceable>query1</replaceable> INTERSECT <optional>ALL</optional> <replaceable>query2</replaceable>
-<replaceable>query1</replaceable> EXCEPT <optional>ALL</optional> <replaceable>query2</replaceable>
+<replaceable>query1</replaceable> UNION <optional>ALL</optional> <optional>CORRESPONDING</optional> <optional>BY</optional> <replaceable>query2</replaceable>
+<replaceable>query1</replaceable> INTERSECT <optional>ALL</optional> <optional>CORRESPONDING</optional> <optional>BY</optional> <replaceable>query2</replaceable>
+<replaceable>query1</replaceable> EXCEPT <optional>ALL</optional> <optional>CORRESPONDING</optional> <optional>BY</optional> <replaceable>query2</replaceable>
 </synopsis>
    <replaceable>query1</replaceable> and
    <replaceable>query2</replaceable> are queries that can use any of
@@ -1659,11 +1662,22 @@ SELECT DISTINCT ON (<replaceable>expression</replaceable> <optional>, <replaceab
   </para>
 
   <para>
-   In order to calculate the union, intersection, or difference of two
-   queries, the two queries must be <quote>union compatible</quote>,
-   which means that they return the same number of columns and
-   the corresponding columns have compatible data types, as
-   described in <xref linkend="typeconv-union-case">.
+   <literal>EXCEPT</> returns all rows that are in the result of
+   <replaceable>query1</replaceable> but not in the result of
+   <replaceable>query2</replaceable>.  (This is sometimes called the
+   <firstterm>difference</> between two queries.)  Again, duplicates
+   are eliminated unless <literal>EXCEPT ALL</> is used.
+  </para>
+
+  <para>
+   <literal>CORRESPONDING</> returns all columns that are in both 
+   <replaceable>query1</> and <replaceable>query2</> with the same name.
+  </para>
+
+  <para>
+   <literal>CORRESPONDING BY</> returns all columns in the column list 
+   that are also in both <replaceable>query1</> and 
+   <replaceable>query2</> with the same name.
   </para>
  </sect1>
 
diff --git a/doc/src/sgml/sql.sgml b/doc/src/sgml/sql.sgml
index 57396d7c24..f98c22e696 100644
--- a/doc/src/sgml/sql.sgml
+++ b/doc/src/sgml/sql.sgml
@@ -859,7 +859,7 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="PARAMETER">expression</replac
     [ WHERE <replaceable class="PARAMETER">condition</replaceable> ]
     [ GROUP BY <replaceable class="PARAMETER">expression</replaceable> [, ...] ]
     [ HAVING <replaceable class="PARAMETER">condition</replaceable> [, ...] ]
-    [ { UNION | INTERSECT | EXCEPT } [ ALL ] <replaceable class="PARAMETER">select</replaceable> ]
+    [ { UNION | INTERSECT | EXCEPT } [ ALL ] [ CORRESPONDING [ BY ( <replaceable class="PARAMETER">expression</replaceable> ) ] ] <replaceable class="PARAMETER">select</replaceable> ]
     [ ORDER BY <replaceable class="parameter">expression</replaceable> [ ASC | DESC | USING <replaceable class="parameter">operator</replaceable> ] [ NULLS { FIRST | LAST } ] [, ...] ]
     [ LIMIT { <replaceable class="PARAMETER">count</replaceable> | ALL } ]
     [ OFFSET <replaceable class="PARAMETER">start</replaceable> ]
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index c23d5c5285..74db9529a1 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2995,6 +2995,7 @@ _copySelectStmt(const SelectStmt *from)
 	COPY_NODE_FIELD(withClause);
 	COPY_SCALAR_FIELD(op);
 	COPY_SCALAR_FIELD(all);
+	COPY_NODE_FIELD(correspondingClause);
 	COPY_NODE_FIELD(larg);
 	COPY_NODE_FIELD(rarg);
 
@@ -3010,6 +3011,8 @@ _copySetOperationStmt(const SetOperationStmt *from)
 	COPY_SCALAR_FIELD(all);
 	COPY_NODE_FIELD(larg);
 	COPY_NODE_FIELD(rarg);
+	COPY_NODE_FIELD(correspondingColumns);
+	COPY_SCALAR_FIELD(hasCorrespondingBy);
 	COPY_NODE_FIELD(colTypes);
 	COPY_NODE_FIELD(colTypmods);
 	COPY_NODE_FIELD(colCollations);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 5941b7a2bf..26ded380a9 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1050,6 +1050,7 @@ _equalSelectStmt(const SelectStmt *a, const SelectStmt *b)
 	COMPARE_NODE_FIELD(withClause);
 	COMPARE_SCALAR_FIELD(op);
 	COMPARE_SCALAR_FIELD(all);
+	COMPARE_NODE_FIELD(correspondingClause);
 	COMPARE_NODE_FIELD(larg);
 	COMPARE_NODE_FIELD(rarg);
 
@@ -1063,6 +1064,8 @@ _equalSetOperationStmt(const SetOperationStmt *a, const SetOperationStmt *b)
 	COMPARE_SCALAR_FIELD(all);
 	COMPARE_NODE_FIELD(larg);
 	COMPARE_NODE_FIELD(rarg);
+	COMPARE_NODE_FIELD(correspondingColumns);
+	COMPARE_SCALAR_FIELD(hasCorrespondingBy);
 	COMPARE_NODE_FIELD(colTypes);
 	COMPARE_NODE_FIELD(colTypmods);
 	COMPARE_NODE_FIELD(colCollations);
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 6e52eb7231..7102ea96c2 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -3444,6 +3444,8 @@ raw_expression_tree_walker(Node *node,
 					return true;
 				if (walker(stmt->lockingClause, context))
 					return true;
+				if (walker(stmt->correspondingClause, context))
+					return true;
 				if (walker(stmt->withClause, context))
 					return true;
 				if (walker(stmt->larg, context))
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 541af02935..5334c01698 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2642,6 +2642,7 @@ _outSelectStmt(StringInfo str, const SelectStmt *node)
 	WRITE_NODE_FIELD(withClause);
 	WRITE_ENUM_FIELD(op, SetOperation);
 	WRITE_BOOL_FIELD(all);
+	WRITE_NODE_FIELD(correspondingClause);
 	WRITE_NODE_FIELD(larg);
 	WRITE_NODE_FIELD(rarg);
 }
@@ -2949,6 +2950,8 @@ _outSetOperationStmt(StringInfo str, const SetOperationStmt *node)
 	WRITE_BOOL_FIELD(all);
 	WRITE_NODE_FIELD(larg);
 	WRITE_NODE_FIELD(rarg);
+	WRITE_NODE_FIELD(correspondingColumns);
+	WRITE_BOOL_FIELD(hasCorrespondingBy);
 	WRITE_NODE_FIELD(colTypes);
 	WRITE_NODE_FIELD(colTypmods);
 	WRITE_NODE_FIELD(colCollations);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 474f221a75..6e284f9ef8 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -416,6 +416,8 @@ _readSetOperationStmt(void)
 	READ_BOOL_FIELD(all);
 	READ_NODE_FIELD(larg);
 	READ_NODE_FIELD(rarg);
+	READ_NODE_FIELD(correspondingColumns);
+	READ_BOOL_FIELD(hasCorrespondingBy);
 	READ_NODE_FIELD(colTypes);
 	READ_NODE_FIELD(colTypmods);
 	READ_NODE_FIELD(colCollations);
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index d88738ec7c..29f86ebbad 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -91,7 +91,8 @@ static List *generate_setop_tlist(List *colTypes, List *colCollations,
 					 Index varno,
 					 bool hack_constants,
 					 List *input_tlist,
-					 List *refnames_tlist);
+					 List *refnames_tlist,
+					 bool no_corresponding);
 static List *generate_append_tlist(List *colTypes, List *colCollations,
 					  bool flag,
 					  List *input_tlists,
@@ -110,6 +111,7 @@ static Node *adjust_appendrel_attrs_mutator(Node *node,
 static Relids adjust_relid_set(Relids relids, Index oldrelid, Index newrelid);
 static List *adjust_inherited_tlist(List *tlist,
 					   AppendRelInfo *context);
+static List *make_corresponding_target(List *corresponding_list, List *subroot_list);
 
 
 /*
@@ -187,6 +189,24 @@ plan_set_operations(PlannerInfo *root)
 									   leftmostQuery->targetList,
 									   &top_tlist);
 	}
+	/*
+	 * If corresponding column specified, we take column names from it.
+	 */
+	else if (topop->correspondingColumns != NIL )
+	{
+		/*
+		 * Recurse on setOperations tree to generate paths for set ops. The
+		 * final output path should have just the column types shown as the
+		 * output from the top-level node, plus possibly resjunk working
+		 * columns (we can rely on upper-level nodes to deal with that).
+		 */
+		path = recurse_set_operations((Node *) topop, root,
+									  topop->colTypes, topop->colCollations,
+									  true, -1,
+									  topop->correspondingColumns,
+									  &top_tlist,
+									  NULL);
+	}
 	else
 	{
 		/*
@@ -252,6 +272,8 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 					   List **pTargetList,
 					   double *pNumGroups)
 {
+	SetOperationStmt *topop = (SetOperationStmt *) root->parse->setOperations;
+
 	if (IsA(setOp, RangeTblRef))
 	{
 		RangeTblRef *rtr = (RangeTblRef *) setOp;
@@ -316,23 +338,53 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 		path = (Path *) create_subqueryscan_path(root, rel, subpath,
 												 NIL, NULL);
 
-		/*
-		 * Figure out the appropriate target list, and update the
-		 * SubqueryScanPath with the PathTarget form of that.
-		 */
-		tlist = generate_setop_tlist(colTypes, colCollations,
+		if (topop->correspondingColumns != NIL )
+		{
+			List	    *correspondingTarget;
+
+			/*
+			 * make target list that only contains corresponding column
+			 * from sub-queries list ito use it for projection
+			 */
+			correspondingTarget = make_corresponding_target(
+											  topop->correspondingColumns,
+											  subroot->processed_tlist);
+
+			/*
+			 * Figure out the appropriate target list, and update the
+			 * SubqueryScanPath with the PathTarget form of that.
+			 */
+			tlist = generate_setop_tlist(colTypes, colCollations, flag,
+											  rtr->rtindex, true,
+											  correspondingTarget,
+											  refnames_tlist, false);
+
+			path = apply_projection_to_path(root, rel, path,
+					create_pathtarget(root, tlist));
+
+			/* Return the fully-fledged tlist to caller, too */
+			*pTargetList = tlist;
+
+		}
+		else
+		{
+			/*
+			 * Figure out the appropriate target list, and update the
+			 * SubqueryScanPath with the PathTarget form of that.
+			 */
+			tlist = generate_setop_tlist(colTypes, colCollations,
 									 flag,
 									 rtr->rtindex,
 									 true,
 									 subroot->processed_tlist,
-									 refnames_tlist);
+									 refnames_tlist, true);
 
-		path = apply_projection_to_path(root, rel, path,
+			path = apply_projection_to_path(root, rel, path,
 										create_pathtarget(root, tlist));
 
-		/* Return the fully-fledged tlist to caller, too */
-		*pTargetList = tlist;
-
+			/* Return the fully-fledged tlist to caller, too */
+			*pTargetList = tlist;
+		}
 		/*
 		 * Estimate number of groups if caller wants it.  If the subquery used
 		 * grouping or aggregation, its output is probably mostly unique
@@ -392,7 +444,7 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 												0,
 												false,
 												*pTargetList,
-												refnames_tlist);
+												refnames_tlist, true);
 			path = apply_projection_to_path(root,
 											path->parent,
 											path,
@@ -1004,7 +1056,8 @@ generate_setop_tlist(List *colTypes, List *colCollations,
 					 Index varno,
 					 bool hack_constants,
 					 List *input_tlist,
-					 List *refnames_tlist)
+					 List *refnames_tlist,
+					 bool no_corresponding)
 {
 	List	   *tlist = NIL;
 	int			resno = 1;
@@ -1026,8 +1079,8 @@ generate_setop_tlist(List *colTypes, List *colCollations,
 
 		rtlc = lnext(rtlc);
 
-		Assert(inputtle->resno == resno);
-		Assert(reftle->resno == resno);
+		Assert(!no_corresponding || inputtle->resno == resno);
+		Assert(!no_corresponding || reftle->resno == resno);
 		Assert(!inputtle->resjunk);
 		Assert(!reftle->resjunk);
 
@@ -2150,3 +2203,70 @@ adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node,
 	/* Now translate for this child */
 	return adjust_appendrel_attrs(root, node, appinfo);
 }
+
+/*
+ * generate target list from left target list with the order
+ * of right target list
+ */
+static List *
+make_corresponding_target(List *corresponding_list, List *subroot_list)
+{
+	Index internal = 0;
+	ListCell   *ltl;
+	ListCell   *rtl;
+	int			size;
+	int			i;
+	List *matchingColumns = NIL;
+	TargetEntry *simple_te_array;
+
+	size = list_length(corresponding_list) + 1;
+
+	/* Use array to find the order of corresponding columen */
+	simple_te_array = (TargetEntry *) palloc0(size * sizeof(TargetEntry));
+	foreach(ltl, corresponding_list)
+	{
+		foreach(rtl, subroot_list)
+		{
+			TargetEntry *ltle = (TargetEntry *) lfirst(ltl);
+			TargetEntry *rtle = (TargetEntry *) lfirst(rtl);
+
+			/* Names of the columns must be resolved before calling this method. */
+			Assert(ltle->resname != NULL);
+			Assert(rtle->resname != NULL);
+
+			/* If column names are the same, add it to array. */
+			if (strcmp(ltle->resname, rtle->resname) == 0)
+			{
+				simple_te_array[internal].xpr = rtle->xpr;
+				simple_te_array[internal].expr = rtle->expr;
+				simple_te_array[internal].resno = rtle->resno;
+				simple_te_array[internal].resname = rtle->resname;
+				simple_te_array[internal].ressortgroupref =
+						rtle->ressortgroupref;
+				simple_te_array[internal].resorigtbl = rtle->resorigtbl;
+				simple_te_array[internal].resorigcol = rtle->resorigcol;
+				simple_te_array[internal].resjunk = rtle->resjunk;
+				internal++;
+				continue;
+			}
+		}
+	}
+	/* traverse the array and make targetlist */
+	for (i = 0; i < internal; i++)
+	{
+		TargetEntry *tle = makeNode(TargetEntry);
+
+		tle->xpr = simple_te_array[i].xpr;
+		tle->expr = simple_te_array[i].expr;
+		tle->resno = simple_te_array[i].resno;
+		tle->resname = simple_te_array[i].resname;
+		tle->ressortgroupref = simple_te_array[i].ressortgroupref;
+		tle->resorigtbl = simple_te_array[i].resorigtbl;
+		tle->resorigcol = simple_te_array[i].resorigcol;
+		tle->resjunk = simple_te_array[i].resjunk;
+
+		matchingColumns = lappend(matchingColumns, tle);
+
+	}
+	return matchingColumns;
+}
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 3571e50aea..cc57d69ad9 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -76,10 +76,18 @@ static Query *transformCreateTableAsStmt(ParseState *pstate,
 						   CreateTableAsStmt *stmt);
 static void transformLockingClause(ParseState *pstate, Query *qry,
 					   LockingClause *lc, bool pushedDown);
+static void makeUnionDatatype(List *ltargetlist, List *rtargetlist,
+		SetOperationStmt *op, List **targetlist, ParseState *parentParseState,
+		const char *context);
 #ifdef RAW_EXPRESSION_COVERAGE_TEST
 static bool test_raw_expression_coverage(Node *node, void *context);
 #endif
-
+static List *CommonColumns(List *ltargetlist, List *rtargetlist, bool filtered,
+								 ParseState *pstate, const char *context);
+static List *FilterColumnsByNames(List *common_columns, List *filter,
+								 ParseState *pstate, const char *context);
+static List *FilterColumnsByTL(List *targetlist, List *filter, bool check_uniq,
+								 ParseState *pstate, const char *context);
 
 /*
  * parse_analyze
@@ -1661,7 +1669,15 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->targetList = NIL;
 	targetvars = NIL;
 	targetnames = NIL;
-	left_tlist = list_head(leftmostQuery->targetList);
+
+	/*
+	 * for corresponding clause limits top-level query targetlist to those
+	 * corresponding column list only
+	 */
+	if (sostmt->correspondingColumns != NIL )
+		left_tlist = list_head(sostmt->correspondingColumns);
+	else
+		left_tlist = list_head(leftmostQuery->targetList);
 
 	forthree(lct, sostmt->colTypes,
 			 lcm, sostmt->colTypmods,
@@ -1921,8 +1937,6 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 		SetOperationStmt *op = makeNode(SetOperationStmt);
 		List	   *ltargetlist;
 		List	   *rtargetlist;
-		ListCell   *ltl;
-		ListCell   *rtl;
 		const char *context;
 
 		context = (stmt->op == SETOP_UNION ? "UNION" :
@@ -1933,6 +1947,84 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 		op->all = stmt->all;
 
 		/*
+		 * If CORRESPONDING is specified, syntax and column name validities checked,
+		 * column filtering is done by a subquery later on.
+		 */
+		if (stmt->correspondingClause == NIL )
+		{
+			/* No CORRESPONDING clause, no operation needed for column filtering */
+			op->correspondingColumns = stmt->correspondingClause;
+			op->hasCorrespondingBy = false;
+		}
+		else
+		{
+			/*
+			 * CORRESPONDING clause, find matching column names from both tables.
+			 * If there are none then it is a syntax error.
+			 */
+			Query	   *largQuery;
+			Query	   *rargQuery;
+			List	   *matchingColumns;
+			List	   *rightCorrespondingColumns;
+
+			op->hasCorrespondingBy = linitial(stmt->correspondingClause) != NULL;
+
+			/* Analyze left query to resolve column names. */
+			largQuery = parse_sub_analyze((Node *) stmt->larg,
+										  pstate, NULL, false, false );
+
+			/* Analyze right query to resolve column names. */
+			rargQuery = parse_sub_analyze((Node *) stmt->rarg,
+										  pstate, NULL, false, false );
+
+			/* Find matching columns from both queries. */
+			matchingColumns = CommonColumns(largQuery->targetList,
+											rargQuery->targetList,
+											op->hasCorrespondingBy,
+											pstate,
+											context);
+
+			/*
+			 * If matchingColumns is empty, there is an error.
+			 * At least one column in the select lists must have the same name.
+			 */
+			if (matchingColumns == NIL)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("there is not any corresponding name"),
+	 errhint("%s queries with a CORRESPONDING clause must have at least one column with the same name",
+								 context),
+						 parser_errposition(pstate,
+									exprLocation((Node *)
+									linitial(largQuery->targetList)))));
+
+			/* Use column filter when it is known */
+			if (op->hasCorrespondingBy)
+				matchingColumns = FilterColumnsByNames(matchingColumns,
+													   stmt->correspondingClause,
+													   pstate,
+													   context);
+
+			op->correspondingColumns = matchingColumns;
+
+			/*
+			 * When we know matching columns, we can quickly create
+			 * corresponding target list for right target list. It is faster,
+			 * than using symmetry. Ensure unique columns when hasCorrespondingBy
+			 * is true - in this case, the uniq is not checked already.
+			 */
+			rightCorrespondingColumns = FilterColumnsByTL(rargQuery->targetList,
+														  matchingColumns,
+														  op->hasCorrespondingBy,
+														  pstate,
+														  context);
+
+			/* make union'd datatype of output column */
+			makeUnionDatatype(matchingColumns, rightCorrespondingColumns,
+								op, targetlist, pstate, context);
+		}
+
+		/*
 		 * Recursively transform the left child node.
 		 */
 		op->larg = transformSetOperationTree(pstate, stmt->larg,
@@ -1957,177 +2049,416 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 											 false,
 											 &rtargetlist);
 
+		if (op->correspondingColumns == NIL )
+		{
+			makeUnionDatatype(ltargetlist, rtargetlist, op, targetlist, pstate,
+					context);
+			/*
+			 * Verify that the two children have the same number of non-junk
+			 * columns, and determine the types of the merged output columns.
+			 */
+			if (list_length(ltargetlist) != list_length(rtargetlist))
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("each %s query must have the same number of columns", context),
+						 parser_errposition(pstate,
+											exprLocation((Node *) rtargetlist))));
+		}
+
+		return (Node *) op;
+	}
+}
+
+/*
+ * Processes targetlists of two queries for columns with same names to use
+ * with UNION/INTERSECT/EXCEPT CORRESPONDING. filtered is true, when
+ * CORRESPONDING BY is used. When it is false, we can check uniq names
+ * in rtargetlist here.
+ */
+static List *
+CommonColumns(List *ltargetlist, List *rtargetlist, bool filtered,
+			  ParseState *pstate, const char *context)
+{
+	List	   *common_columns = NIL;
+	ListCell   *ltlc;
+	ListCell   *rtlc;
+	int			resno = 1;
+
+	foreach(ltlc, ltargetlist)
+	{
+		TargetEntry *lte = (TargetEntry *) lfirst(ltlc);
+		bool		found = false;
+
+		Assert(lte->resname != NULL);
+
+		foreach(rtlc, rtargetlist)
+		{
+			ListCell   *lc;
+			TargetEntry *rte = (TargetEntry *) lfirst(rtlc);
+
+			Assert(rte->resname != NULL);
+
+			if (strcmp(lte->resname, rte->resname) == 0)
+			{
+				if (filtered)
+				{
+					/*
+					 * We found common column, but we don't know if it
+					 * is in CORRESPONDING BY list - so don't try do more
+					 * work here. The column list will be modified later,
+					 * so use shall copy here.
+					 */
+					common_columns = lappend(common_columns, lte);
+					break;
+				}
+
+				/* If same column name mentioned more than once it is syntax error . */
+				if (found)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("corresponding column \"%s\" is used more times", rte->resname),
+	 errhint("In %s queries with CORRESPONDING clause the corresponding column names must be unique.",
+									context),
+							 parser_errposition(pstate,
+												exprLocation((Node *) rte))));
+
+				found = true;
+
+				/* In this case, common_columns must be unique */
+				foreach(lc, common_columns)
+				{
+					TargetEntry *te = (TargetEntry *) lfirst(lc);
+
+					if (strcmp(te->resname, lte->resname) == 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("corresponding column \"%s\" is used more times", lte->resname),
+	 errhint("In %s queries with CORRESPONDING clause the corresponding column names must be unique.",
+										context),
+								 parser_errposition(pstate,
+													exprLocation((Node *) lte))));
+				}
+
+				/* When is not any other filter create final te */
+				common_columns = lappend(common_columns,
+										 makeTargetEntry(lte->expr,
+														 (AttrNumber) resno++,
+														 lte->resname,
+														 false));
+			}
+		}
+	}
+
+	return common_columns;
+}
+
+/*
+ * Returns filtered common columns list - filter is based on CORRESPONDING BY
+ * list Ensure CORRESPONDING BY list is unique. Result is in CORRESPONDING BY
+ * list order. Common columns list can hold duplicate columns.
+ */
+static List *
+FilterColumnsByNames(List *common_columns, List *filter,
+					 ParseState *pstate, const char *context)
+{
+	List	   *filtered_columns = NIL;
+	ListCell   *flc;
+	int			resno = 1;
+
+	Assert(common_columns != NIL);
+	Assert(filter != NIL);
+
+	foreach(flc, filter)
+	{
+		char	   *name = strVal((Value *) lfirst(flc));
+		ListCell   *tlc;
+		bool		found = false;
+
+		foreach(tlc, common_columns)
+		{
+			TargetEntry   *tec = (TargetEntry *) lfirst(tlc);
+
+			if (strcmp(tec->resname, name) == 0)
+			{
+				ListCell   *lc;
+
+				/*
+				 * When "found" is true, then common_columns contains
+				 * duplicate columns. Raise exception then.
+				 */
+				if (found)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("corresponding column \"%s\" is used more times", name),
+	 errhint("In %s queries with CORRESPONDING BY clause the corresponding column names must be unique.",
+									context),
+							 parser_errposition(pstate,
+												exprLocation((Node *) tec))));
+
+				found = true;
+
+				/* result list should not to contains this name */
+				foreach(lc, filtered_columns)
+				{
+					TargetEntry   *te = (TargetEntry *) lfirst(lc);
+
+					/*
+					 * CORRESPONDING BY clause contains a column name that is
+					 * not in unique in this clause
+					 */
+					if (strcmp(te->resname, name) == 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("column name \"%s\" is not unique in CORRESPONDING BY clause", name),
+								 errhint("CORRESPONDING BY clause must contain unique column names only."),
+								 parser_errposition(pstate,
+												exprLocation((Node *) tec))));
+				}
+
+				/* create te with correct resno */
+				filtered_columns = lappend(filtered_columns,
+										 makeTargetEntry(tec->expr,
+														 (AttrNumber) resno++,
+														 tec->resname,
+														 false));
+			}
+		}
+
 		/*
-		 * Verify that the two children have the same number of non-junk
-		 * columns, and determine the types of the merged output columns.
+		 * CORRESPONDING BY clause contains a column name that is not
+		 * in common columns.
 		 */
-		if (list_length(ltargetlist) != list_length(rtargetlist))
+		if (!found)
 			ereport(ERROR,
 					(errcode(ERRCODE_SYNTAX_ERROR),
-				 errmsg("each %s query must have the same number of columns",
-						context),
+					 errmsg("column name \"%s\" can not be used in CORRESPONDING BY list", name),
+		 errhint("%s queries with a CORRESPONDING BY clause must contain column names from both tables.",
+									 context),
 					 parser_errposition(pstate,
-										exprLocation((Node *) rtargetlist))));
+										exprLocation((Node *) linitial(common_columns)))));
+	}
 
-		if (targetlist)
-			*targetlist = NIL;
-		op->colTypes = NIL;
-		op->colTypmods = NIL;
-		op->colCollations = NIL;
-		op->groupClauses = NIL;
-		forboth(ltl, ltargetlist, rtl, rtargetlist)
+	return filtered_columns;
+}
+
+/*
+ * Prepare target list for right query of CORRESPONDING clause.
+ * When filtered is true, filtered columns in target list are
+ * unique.
+ */
+static List *
+FilterColumnsByTL(List *targetlist, List *filter, bool check_uniq,
+				  ParseState *pstate, const char *context)
+{
+	List	   *result = NIL;
+	ListCell   *lc;
+	int			resno = 1;
+
+	foreach(lc, filter)
+	{
+		TargetEntry *fte = (TargetEntry *) lfirst(lc);
+		ListCell	*tle;
+		bool		found = false;
+
+		foreach(tle, targetlist)
 		{
-			TargetEntry *ltle = (TargetEntry *) lfirst(ltl);
-			TargetEntry *rtle = (TargetEntry *) lfirst(rtl);
-			Node	   *lcolnode = (Node *) ltle->expr;
-			Node	   *rcolnode = (Node *) rtle->expr;
-			Oid			lcoltype = exprType(lcolnode);
-			Oid			rcoltype = exprType(rcolnode);
-			int32		lcoltypmod = exprTypmod(lcolnode);
-			int32		rcoltypmod = exprTypmod(rcolnode);
-			Node	   *bestexpr;
-			int			bestlocation;
-			Oid			rescoltype;
-			int32		rescoltypmod;
-			Oid			rescolcoll;
-
-			/* select common type, same as CASE et al */
-			rescoltype = select_common_type(pstate,
-											list_make2(lcolnode, rcolnode),
-											context,
-											&bestexpr);
-			bestlocation = exprLocation(bestexpr);
-			/* if same type and same typmod, use typmod; else default */
-			if (lcoltype == rcoltype && lcoltypmod == rcoltypmod)
-				rescoltypmod = lcoltypmod;
-			else
-				rescoltypmod = -1;
+			TargetEntry *te = (TargetEntry *) lfirst(tle);
 
-			/*
-			 * Verify the coercions are actually possible.  If not, we'd fail
-			 * later anyway, but we want to fail now while we have sufficient
-			 * context to produce an error cursor position.
-			 *
-			 * For all non-UNKNOWN-type cases, we verify coercibility but we
-			 * don't modify the child's expression, for fear of changing the
-			 * child query's semantics.
-			 *
-			 * If a child expression is an UNKNOWN-type Const or Param, we
-			 * want to replace it with the coerced expression.  This can only
-			 * happen when the child is a leaf set-op node.  It's safe to
-			 * replace the expression because if the child query's semantics
-			 * depended on the type of this output column, it'd have already
-			 * coerced the UNKNOWN to something else.  We want to do this
-			 * because (a) we want to verify that a Const is valid for the
-			 * target type, or resolve the actual type of an UNKNOWN Param,
-			 * and (b) we want to avoid unnecessary discrepancies between the
-			 * output type of the child query and the resolved target type.
-			 * Such a discrepancy would disable optimization in the planner.
-			 *
-			 * If it's some other UNKNOWN-type node, eg a Var, we do nothing
-			 * (knowing that coerce_to_common_type would fail).  The planner
-			 * is sometimes able to fold an UNKNOWN Var to a constant before
-			 * it has to coerce the type, so failing now would just break
-			 * cases that might work.
-			 */
-			if (lcoltype != UNKNOWNOID)
-				lcolnode = coerce_to_common_type(pstate, lcolnode,
-												 rescoltype, context);
-			else if (IsA(lcolnode, Const) ||
-					 IsA(lcolnode, Param))
+			if (strcmp(fte->resname, te->resname) == 0)
 			{
-				lcolnode = coerce_to_common_type(pstate, lcolnode,
-												 rescoltype, context);
-				ltle->expr = (Expr *) lcolnode;
-			}
+				/* create te with correct resno */
+				result = lappend(result,
+								 makeTargetEntry(te->expr,
+												 (AttrNumber) resno++,
+												 te->resname,
+												 false));
+
+				if (!check_uniq)
+					break;
 
-			if (rcoltype != UNKNOWNOID)
-				rcolnode = coerce_to_common_type(pstate, rcolnode,
-												 rescoltype, context);
-			else if (IsA(rcolnode, Const) ||
-					 IsA(rcolnode, Param))
-			{
-				rcolnode = coerce_to_common_type(pstate, rcolnode,
-												 rescoltype, context);
-				rtle->expr = (Expr *) rcolnode;
+				/*
+				 * When "found" is true, then targetlist contains
+				 * duplicate filtered columns. Raise exception then.
+				 */
+				if (found)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("corresponding column \"%s\" is used more times", te->resname),
+							 errhint("In %s queries with CORRESPONDING BY clause the corresponding column names must be unique.",
+									context),
+							 parser_errposition(pstate,
+												exprLocation((Node *) te))));
+
+				found = true;
 			}
+		}
+	}
 
-			/*
-			 * Select common collation.  A common collation is required for
-			 * all set operators except UNION ALL; see SQL:2008 7.13 <query
-			 * expression> Syntax Rule 15c.  (If we fail to identify a common
-			 * collation for a UNION ALL column, the curCollations element
-			 * will be set to InvalidOid, which may result in a runtime error
-			 * if something at a higher query level wants to use the column's
-			 * collation.)
-			 */
-			rescolcoll = select_common_collation(pstate,
-											  list_make2(lcolnode, rcolnode),
-										 (op->op == SETOP_UNION && op->all));
+	return result;
+}
+
+/*
+ * process right and left target list to set up union'd datatype
+ */
+static void
+makeUnionDatatype(List *ltargetlist, List *rtargetlist, SetOperationStmt *op,
+		List **targetlist, ParseState *pstate, const char *context)
+{
+	ListCell   *ltl;
+	ListCell   *rtl;
 
-			/* emit results */
-			op->colTypes = lappend_oid(op->colTypes, rescoltype);
-			op->colTypmods = lappend_int(op->colTypmods, rescoltypmod);
-			op->colCollations = lappend_oid(op->colCollations, rescolcoll);
+	if (targetlist)
+		*targetlist = NIL;
 
-			/*
-			 * For all cases except UNION ALL, identify the grouping operators
-			 * (and, if available, sorting operators) that will be used to
-			 * eliminate duplicates.
-			 */
-			if (op->op != SETOP_UNION || !op->all)
-			{
-				SortGroupClause *grpcl = makeNode(SortGroupClause);
-				Oid			sortop;
-				Oid			eqop;
-				bool		hashable;
-				ParseCallbackState pcbstate;
-
-				setup_parser_errposition_callback(&pcbstate, pstate,
-												  bestlocation);
-
-				/* determine the eqop and optional sortop */
-				get_sort_group_operators(rescoltype,
-										 false, true, false,
-										 &sortop, &eqop, NULL,
-										 &hashable);
-
-				cancel_parser_errposition_callback(&pcbstate);
-
-				/* we don't have a tlist yet, so can't assign sortgrouprefs */
-				grpcl->tleSortGroupRef = 0;
-				grpcl->eqop = eqop;
-				grpcl->sortop = sortop;
-				grpcl->nulls_first = false;		/* OK with or without sortop */
-				grpcl->hashable = hashable;
-
-				op->groupClauses = lappend(op->groupClauses, grpcl);
-			}
+	op->colTypes = NIL;
+	op->colTypmods = NIL;
+	op->colCollations = NIL;
+	op->groupClauses = NIL;
 
-			/*
-			 * Construct a dummy tlist entry to return.  We use a SetToDefault
-			 * node for the expression, since it carries exactly the fields
-			 * needed, but any other expression node type would do as well.
-			 */
-			if (targetlist)
-			{
-				SetToDefault *rescolnode = makeNode(SetToDefault);
-				TargetEntry *restle;
-
-				rescolnode->typeId = rescoltype;
-				rescolnode->typeMod = rescoltypmod;
-				rescolnode->collation = rescolcoll;
-				rescolnode->location = bestlocation;
-				restle = makeTargetEntry((Expr *) rescolnode,
-										 0,		/* no need to set resno */
-										 NULL,
-										 false);
-				*targetlist = lappend(*targetlist, restle);
-			}
+	forboth(ltl, ltargetlist, rtl, rtargetlist)
+	{
+		TargetEntry	   *ltle = (TargetEntry *) lfirst(ltl);
+		TargetEntry	   *rtle = (TargetEntry *) lfirst(rtl);
+		Node		   *lcolnode = (Node *) ltle->expr;
+		Node		   *rcolnode = (Node *) rtle->expr;
+		Oid			lcoltype = exprType(lcolnode);
+		Oid			rcoltype = exprType(rcolnode);
+		int32		lcoltypmod = exprTypmod(lcolnode);
+		int32		rcoltypmod = exprTypmod(rcolnode);
+		Node	   *bestexpr;
+		int			bestlocation;
+		Oid			rescoltype;
+		int32		rescoltypmod;
+		Oid			rescolcoll;
+
+		/* select common type, same as CASE et al */
+		rescoltype = select_common_type(pstate, list_make2(lcolnode, rcolnode),
+				context, &bestexpr);
+		bestlocation = exprLocation(bestexpr);
+		/* if same type and same typmod, use typmod; else default */
+		if (lcoltype == rcoltype && lcoltypmod == rcoltypmod)
+			rescoltypmod = lcoltypmod;
+		else
+			rescoltypmod = -1;
+
+		/*
+		 * Verify the coercions are actually possible.  If not, we'd fail
+		 * later anyway, but we want to fail now while we have sufficient
+		 * context to produce an error cursor position.
+		 *
+		 * For all non-UNKNOWN-type cases, we verify coercibility but we
+		 * don't modify the child's expression, for fear of changing the
+		 * child query's semantics.
+		 *
+		 * If a child expression is an UNKNOWN-type Const or Param, we
+		 * want to replace it with the coerced expression.  This can only
+		 * happen when the child is a leaf set-op node.  It's safe to
+		 * replace the expression because if the child query's semantics
+		 * depended on the type of this output column, it'd have already
+		 * coerced the UNKNOWN to something else.  We want to do this
+		 * because (a) we want to verify that a Const is valid for the
+		 * target type, or resolve the actual type of an UNKNOWN Param,
+		 * and (b) we want to avoid unnecessary discrepancies between the
+		 * output type of the child query and the resolved target type.
+		 * Such a discrepancy would disable optimization in the planner.
+		 *
+		 * If it's some other UNKNOWN-type node, eg a Var, we do nothing
+		 * (knowing that coerce_to_common_type would fail).  The planner
+		 * is sometimes able to fold an UNKNOWN Var to a constant before
+		 * it has to coerce the type, so failing now would just break
+		 * cases that might work.
+		 */
+		if (lcoltype != UNKNOWNOID)
+			lcolnode = coerce_to_common_type(pstate, lcolnode, rescoltype,
+					context);
+		else if (IsA(lcolnode, Const) || IsA(lcolnode, Param))
+		{
+			lcolnode = coerce_to_common_type(pstate, lcolnode, rescoltype,
+					context);
+			ltle->expr = (Expr *) lcolnode;
 		}
 
-		return (Node *) op;
+		if (rcoltype != UNKNOWNOID)
+			rcolnode = coerce_to_common_type(pstate, rcolnode, rescoltype,
+					context);
+		else if (IsA(rcolnode, Const) || IsA(rcolnode, Param))
+		{
+			rcolnode = coerce_to_common_type(pstate, rcolnode, rescoltype,
+					context);
+			rtle->expr = (Expr *) rcolnode;
+		}
+
+		/*
+		 * Select common collation.  A common collation is required for
+		 * all set operators except UNION ALL; see SQL:2008 7.13 <query
+		 * expression> Syntax Rule 15c.  (If we fail to identify a common
+		 * collation for a UNION ALL column, the curCollations element
+		 * will be set to InvalidOid, which may result in a runtime error
+		 * if something at a higher query level wants to use the column's
+		 * collation.)
+		 */
+		rescolcoll = select_common_collation(pstate,
+				list_make2(lcolnode, rcolnode),
+				(op->op == SETOP_UNION && op->all));
+
+		/* emit results */
+		op->colTypes = lappend_oid(op->colTypes, rescoltype);
+		op->colTypmods = lappend_int(op->colTypmods, rescoltypmod);
+		op->colCollations = lappend_oid(op->colCollations, rescolcoll);
+
+		/*
+		 * For all cases except UNION ALL, identify the grouping operators
+		 * (and, if available, sorting operators) that will be used to
+		 * eliminate duplicates.
+		 */
+		if (op->op != SETOP_UNION || !op->all)
+		{
+			SortGroupClause *grpcl = makeNode(SortGroupClause);
+			Oid			sortop;
+			Oid			eqop;
+			bool		hashable;
+			ParseCallbackState pcbstate;
+
+			setup_parser_errposition_callback(&pcbstate, pstate, bestlocation);
+
+			/* determine the eqop and optional sortop */
+			get_sort_group_operators(rescoltype, false, true, false, &sortop,
+					&eqop, NULL, &hashable);
+
+			cancel_parser_errposition_callback(&pcbstate);
+
+			/* we don't have a tlist yet, so can't assign sortgrouprefs */
+			grpcl->tleSortGroupRef = 0;
+			grpcl->eqop = eqop;
+			grpcl->sortop = sortop;
+			grpcl->nulls_first = false; /* OK with or without sortop */
+			grpcl->hashable = hashable;
+
+			op->groupClauses = lappend(op->groupClauses, grpcl);
+		}
+
+		/*
+		 * Construct a dummy tlist entry to return.  We use a SetToDefault
+		 * node for the expression, since it carries exactly the fields
+		 * needed, but any other expression node type would do as well.
+		 */
+		if (targetlist)
+		{
+			SetToDefault   *rescolnode = makeNode(SetToDefault);
+			TargetEntry	   *restle;
+
+			rescolnode->typeId = rescoltype;
+			rescolnode->typeMod = rescoltypmod;
+			rescolnode->collation = rescolcoll;
+			rescolnode->location = bestlocation;
+
+			/* no need to set resno */
+			restle = makeTargetEntry((Expr *) rescolnode, 0,
+			NULL, false );
+			*targetlist = lappend(*targetlist, restle);
+		}
 	}
+
 }
 
 /*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 19dd77d787..a2bef6083a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -166,7 +166,7 @@ static void insertSelectOptions(SelectStmt *stmt,
 								Node *limitOffset, Node *limitCount,
 								WithClause *withClause,
 								core_yyscan_t yyscanner);
-static Node *makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg);
+static Node *makeSetOp(SetOperation op, bool all, List *correspondingClause, Node *larg, Node *rarg);
 static Node *doNegate(Node *n, int location);
 static void doNegateFloat(Value *v);
 static Node *makeAndExpr(Node *lexpr, Node *rexpr, int location);
@@ -375,7 +375,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				func_as createfunc_opt_list alterfunc_opt_list
 				old_aggr_definition old_aggr_list
 				oper_argtypes RuleActionList RuleActionMulti
-				opt_column_list columnList opt_name_list
+				opt_column_list columnList uniqColumnList opt_name_list
 				sort_clause opt_sort_clause sortby_list index_params
 				name_list role_list from_clause from_list opt_array_bounds
 				qualified_name_list any_name any_name_list type_name_list
@@ -394,7 +394,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				relation_expr_list dostmt_opt_list
 				transform_element_list transform_type_list
 				TriggerTransitions TriggerReferencing
-				publication_name_list
+				publication_name_list opt_corresponding_clause
 
 %type <list>	group_by_list
 %type <node>	group_by_item empty_grouping_set rollup_clause cube_clause
@@ -614,7 +614,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
 	CLUSTER COALESCE COLLATE COLLATION COLUMN COLUMNS COMMENT COMMENTS COMMIT
 	COMMITTED CONCURRENTLY CONFIGURATION CONFLICT CONNECTION CONSTRAINT
-	CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE
+	CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY CORRESPONDING COST CREATE
 	CROSS CSV CUBE CURRENT_P
 	CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA
 	CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
@@ -3577,6 +3577,31 @@ columnList:
 			| columnList ',' columnElem				{ $$ = lappend($1, $3); }
 		;
 
+uniqColumnList:
+			columnElem
+				{
+					$$ = list_make1($1);
+				}
+			| uniqColumnList ',' columnElem
+				{
+					ListCell   *lc;
+					char	   *name = strVal($3);
+
+					foreach(lc, $1)
+					{
+						char *prevname = strVal((Value *) lfirst(lc));
+
+						if (strcmp(prevname, name) == 0)
+							ereport(ERROR,
+									(errcode(ERRCODE_SYNTAX_ERROR),
+									 errmsg("name is not unique"),
+									 parser_errposition(@3)));
+					}
+
+					$$ = lappend($1, $3);
+				}
+		;
+
 columnElem: ColId
 				{
 					$$ = (Node *) makeString($1);
@@ -10877,20 +10902,26 @@ simple_select:
 					n->fromClause = list_make1($2);
 					$$ = (Node *)n;
 				}
-			| select_clause UNION all_or_distinct select_clause
+			| select_clause UNION all_or_distinct opt_corresponding_clause select_clause
 				{
-					$$ = makeSetOp(SETOP_UNION, $3, $1, $4);
+					$$ = makeSetOp(SETOP_UNION, $3, $4, $1, $5);
 				}
-			| select_clause INTERSECT all_or_distinct select_clause
+			| select_clause INTERSECT all_or_distinct opt_corresponding_clause select_clause
 				{
-					$$ = makeSetOp(SETOP_INTERSECT, $3, $1, $4);
+					$$ = makeSetOp(SETOP_INTERSECT, $3, $4, $1, $5);
 				}
-			| select_clause EXCEPT all_or_distinct select_clause
+			| select_clause EXCEPT all_or_distinct opt_corresponding_clause select_clause
 				{
-					$$ = makeSetOp(SETOP_EXCEPT, $3, $1, $4);
+					$$ = makeSetOp(SETOP_EXCEPT, $3, $4, $1, $5);
 				}
 		;
 
+opt_corresponding_clause:
+			CORRESPONDING BY '(' uniqColumnList ')'	{ $$ = $4; }
+			| CORRESPONDING							{ $$ = list_make1(NIL); }
+			| /*EMPTY*/								{ $$ = NIL; }
+			;
+
 /*
  * SQL standard WITH clause looks like:
  *
@@ -14231,7 +14262,6 @@ name_list:	name
 					{ $$ = lappend($1, makeString($3)); }
 		;
 
-
 name:		ColId									{ $$ = $1; };
 
 database_name:
@@ -14548,6 +14578,7 @@ unreserved_keyword:
 			| CONTINUE_P
 			| CONVERSION_P
 			| COPY
+			| CORRESPONDING
 			| COST
 			| CSV
 			| CUBE
@@ -15381,7 +15412,7 @@ insertSelectOptions(SelectStmt *stmt,
 }
 
 static Node *
-makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg)
+makeSetOp(SetOperation op, bool all, List *correspondingClause, Node *larg, Node *rarg)
 {
 	SelectStmt *n = makeNode(SelectStmt);
 
@@ -15389,6 +15420,7 @@ makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg)
 	n->all = all;
 	n->larg = (SelectStmt *) larg;
 	n->rarg = (SelectStmt *) rarg;
+	n->correspondingClause = correspondingClause;
 	return (Node *) n;
 }
 
diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
index 0d7a2b1e1b..b553d847d8 100644
--- a/src/backend/parser/parse_type.c
+++ b/src/backend/parser/parse_type.c
@@ -735,7 +735,8 @@ typeStringToTypeName(const char *str)
 		stmt->limitCount != NULL ||
 		stmt->lockingClause != NIL ||
 		stmt->withClause != NULL ||
-		stmt->op != SETOP_NONE)
+		stmt->op != SETOP_NONE ||
+		stmt->correspondingClause != NULL)
 		goto fail;
 	if (list_length(stmt->targetList) != 1)
 		goto fail;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index d57d5568b2..60715398a2 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -5452,6 +5452,30 @@ get_setop_query(Node *setOp, Query *query, deparse_context *context,
 		}
 		if (op->all)
 			appendStringInfoString(buf, "ALL ");
+		if (op->correspondingColumns != NIL )
+		{
+			if (op->hasCorrespondingBy)
+			{
+				const char *sep;
+				ListCell *l;
+				appendStringInfoString(buf, "CORRESPONDING BY(");
+				sep = "";
+
+				foreach(l, op->correspondingColumns)
+				{
+					TargetEntry *tle = (TargetEntry *) lfirst(l);
+
+					appendStringInfoString(buf, sep);
+					appendStringInfo(buf, "%s", tle->resname);
+					sep = ", ";
+				}
+				appendStringInfoChar(buf, ')');
+
+			}
+			else
+
+				appendStringInfoString(buf, "CORRESPONDING ");
+		}
 
 		/* Always parenthesize if RHS is another setop */
 		need_paren = IsA(op->rarg, SetOperationStmt);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3a71dd5b37..ed8ce2f2d0 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1460,6 +1460,7 @@ typedef struct SelectStmt
 	 */
 	List	   *distinctClause; /* NULL, list of DISTINCT ON exprs, or
 								 * lcons(NIL,NIL) for all (SELECT DISTINCT) */
+	List	   *correspondingClause;	/* CORRESPONDING BY  clauses*/
 	IntoClause *intoClause;		/* target for SELECT INTO */
 	List	   *targetList;		/* the target list (of ResTarget) */
 	List	   *fromClause;		/* the FROM clause */
@@ -1495,7 +1496,6 @@ typedef struct SelectStmt
 	bool		all;			/* ALL specified? */
 	struct SelectStmt *larg;	/* left child */
 	struct SelectStmt *rarg;	/* right child */
-	/* Eventually add fields for CORRESPONDING spec here */
 } SelectStmt;
 
 
@@ -1525,8 +1525,8 @@ typedef struct SetOperationStmt
 	bool		all;			/* ALL specified? */
 	Node	   *larg;			/* left child */
 	Node	   *rarg;			/* right child */
-	/* Eventually add fields for CORRESPONDING spec here */
-
+	List	   *correspondingColumns;	/* list of corresponding column names */
+	bool		hasCorrespondingBy;		/* has corresponding by cluase? */
 	/* Fields derived during parse analysis: */
 	List	   *colTypes;		/* OID list of output column type OIDs */
 	List	   *colTypmods;		/* integer list of output column typmods */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 6cd36c7fe3..fad33d3950 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -97,6 +97,7 @@ PG_KEYWORD("content", CONTENT_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("continue", CONTINUE_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("conversion", CONVERSION_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("copy", COPY, UNRESERVED_KEYWORD)
+PG_KEYWORD("corresponding", CORRESPONDING, UNRESERVED_KEYWORD)
 PG_KEYWORD("cost", COST, UNRESERVED_KEYWORD)
 PG_KEYWORD("create", CREATE, RESERVED_KEYWORD)
 PG_KEYWORD("cross", CROSS, TYPE_FUNC_NAME_KEYWORD)
diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out
index ce0c8cedf8..6b44903d7b 100644
--- a/src/test/regress/expected/create_view.out
+++ b/src/test/regress/expected/create_view.out
@@ -1597,3 +1597,28 @@ select pg_get_viewdef('tt19v', true);
 set client_min_messages = warning;
 DROP SCHEMA temp_view_test CASCADE;
 DROP SCHEMA testviewschm2 CASCADE;
+-- views with corresponding clause
+create view view_corresponding_01 as select 1 as a, 2 as b union all corresponding select 3 as a, 4 as b;
+select * from view_corresponding_01;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+create view view_corresponding_02 as select 1 as a, 2 as b union all corresponding by (a,b) select 3 as a, 4 as b, 5 as c;
+select * from view_corresponding_02;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+create view view_corresponding_03 as select 1 as a, 2 as b union all corresponding by (b,a) select 3 as a, 4 as b, 5 as c;
+select * from view_corresponding_03;
+ b | a 
+---+---
+ 2 | 1
+ 4 | 3
+(2 rows)
+
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index e8f8726c53..8c1ff70519 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -2344,6 +2344,23 @@ toyemp| SELECT emp.name,
     emp.location,
     (12 * emp.salary) AS annualsal
    FROM emp;
+view_corresponding_01| SELECT 1 AS a,
+    2 AS b
+UNION ALL CORRESPONDING
+ SELECT 3 AS a,
+    4 AS b;
+view_corresponding_02| SELECT 1 AS a,
+    2 AS b
+UNION ALL CORRESPONDING BY(a, b)
+ SELECT 3 AS a,
+    4 AS b,
+    5 AS c;
+view_corresponding_03| SELECT 1 AS b,
+    2 AS a
+UNION ALL CORRESPONDING BY(b, a)
+ SELECT 3 AS b,
+    4 AS a,
+    5 AS c;
 SELECT tablename, rulename, definition FROM pg_rules
 	ORDER BY tablename, rulename;
 pg_settings|pg_settings_n|CREATE RULE pg_settings_n AS
diff --git a/src/test/regress/expected/union.out b/src/test/regress/expected/union.out
index 4d697bada7..dd64f37efd 100644
--- a/src/test/regress/expected/union.out
+++ b/src/test/regress/expected/union.out
@@ -59,6 +59,87 @@ SELECT 1.1 AS two UNION SELECT 2.2 ORDER BY 1;
  2.2
 (2 rows)
 
+SELECT 1 AS two UNION CORRESPONDING SELECT 2 two UNION CORRESPONDING SELECT 2 two;
+ two 
+-----
+   2
+   1
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a) SELECT 4 a, 5 b, 6 c;
+ a 
+---
+ 1
+ 4
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b) SELECT 4 a, 5 b, 6 c;
+ a | b 
+---+---
+ 1 | 2
+ 4 | 5
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c) SELECT 4 a, 5 b, 6 c;
+ b | c 
+---+---
+ 2 | 3
+ 5 | 6
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY (c,b) SELECT 4 a, 5 b, 6 c, 8 d;
+ c | b 
+---+---
+ 3 | 2
+ 6 | 5
+(2 rows)
+
+-- CORRESPONDING column ordering, left clause's column ordering must be preserved.
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+SELECT 2 b, 1 a, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+ b | a | c 
+---+---+---
+ 2 | 1 | 3
+ 5 | 4 | 6
+(2 rows)
+
+-- CORRESPONDING BY column ordering, BY clause column ordering must be preserved.
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 4 a, 5 b, 6 c;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c, a) SELECT 4 a, 5 b, 6 c;
+ b | c | a 
+---+---+---
+ 2 | 3 | 1
+ 5 | 6 | 4
+(2 rows)
+
+-- should to fail
+SELECT 10 a, 20 b UNION ALL CORRESPONDING SELECT 10 c, 20 d;
+ERROR:  there is not any corresponding name
+LINE 1: SELECT 10 a, 20 b UNION ALL CORRESPONDING SELECT 10 c, 20 d;
+               ^
+HINT:  UNION queries with a CORRESPONDING clause must have at least one column with the same name
+SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (a,b) SELECT 10 c, 20 d;
+ERROR:  there is not any corresponding name
+LINE 1: SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (a,b) SELECT 10...
+               ^
+HINT:  UNION queries with a CORRESPONDING clause must have at least one column with the same name
+SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (x) SELECT 10 c, 20 d;
+ERROR:  there is not any corresponding name
+LINE 1: SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (x) SELECT 10 c...
+               ^
+HINT:  UNION queries with a CORRESPONDING clause must have at least one column with the same name
 -- Mixed types
 SELECT 1.1 AS two UNION SELECT 2 ORDER BY 1;
  two 
@@ -124,6 +205,75 @@ SELECT 1.1 AS two UNION (SELECT 2 UNION ALL SELECT 2) ORDER BY 1;
    2
 (2 rows)
 
+-- other corresponding clause tests,
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+-- when column is not in result, then the name should not be unique
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x3;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x6 UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+-- should fail
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3;
+ERROR:  corresponding column "x3" is used more times
+LINE 1: SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 U...
+                                                          ^
+HINT:  In UNION queries with CORRESPONDING clause the corresponding column names must be unique.
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3, -10 AS x3;
+ERROR:  corresponding column "x3" is used more times
+LINE 1: ...ELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3, -10 AS x3;
+                                                             ^
+HINT:  In UNION queries with CORRESPONDING clause the corresponding column names must be unique.
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(a,b,x3) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3;
+ERROR:  corresponding column "x3" is used more times
+LINE 1: SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 U...
+                                                          ^
+HINT:  In UNION queries with CORRESPONDING BY clause the corresponding column names must be unique.
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING BY(a,b,x3) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3, -10 AS x3;
+ERROR:  corresponding column "x3" is used more times
+LINE 1: ...ELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3, -10 AS x3;
+                                                             ^
+HINT:  In UNION queries with CORRESPONDING BY clause the corresponding column names must be unique.
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x6 UNION ALL CORRESPONDING BY(a,b,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+ERROR:  name is not unique
+LINE 1: ... 0 AS x3, -1 AS x6 UNION ALL CORRESPONDING BY(a,b,b) SELECT ...
+                                                             ^
 --
 -- Try testing from tables...
 --
@@ -258,6 +408,74 @@ ORDER BY 1;
  hi de ho neighbor
 (5 rows)
 
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+         five          
+-----------------------
+ -1.2345678901234e+200
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+                     0
+(5 rows)
+
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING BY(five)
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+         five          
+-----------------------
+ -1.2345678901234e+200
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+                     0
+(5 rows)
+
+SELECT f1 AS ten FROM FLOAT8_TBL
+UNION ALL CORRESPONDING
+SELECT f1 AS ten FROM FLOAT8_TBL;
+          ten          
+-----------------------
+                     0
+                -34.84
+               -1004.3
+ -1.2345678901234e+200
+ -1.2345678901234e-200
+                     0
+                -34.84
+               -1004.3
+ -1.2345678901234e+200
+ -1.2345678901234e-200
+(10 rows)
+
+SELECT f1 AS five FROM FLOAT8_TBL
+  WHERE f1 BETWEEN -1e6 AND 1e6
+UNION CORRESPONDING
+SELECT f1 AS five FROM INT4_TBL
+  WHERE f1 BETWEEN 0 AND 1000000;
+         five          
+-----------------------
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+                     0
+                123456
+(5 rows)
+
+SELECT * FROM FLOAT8_TBL
+UNION corresponding SELECT * FROM FLOAT8_TBL;
+          f1           
+-----------------------
+                     0
+ -1.2345678901234e-200
+                -34.84
+ -1.2345678901234e+200
+               -1004.3
+(5 rows)
+
 --
 -- INTERSECT and EXCEPT
 --
@@ -320,6 +538,63 @@ SELECT q1 FROM int8_tbl EXCEPT ALL SELECT DISTINCT q2 FROM int8_tbl ORDER BY 1;
 
 SELECT q1 FROM int8_tbl EXCEPT ALL SELECT q1 FROM int8_tbl FOR NO KEY UPDATE;
 ERROR:  FOR NO KEY UPDATE is not allowed with UNION/INTERSECT/EXCEPT
+SELECT q2 FROM int8_tbl INTERSECT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+        q2        
+------------------
+              123
+ 4567890123456789
+(2 rows)
+
+SELECT q2 FROM int8_tbl INTERSECT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+        q2        
+------------------
+              123
+ 4567890123456789
+ 4567890123456789
+(3 rows)
+
+SELECT q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+        q2         
+-------------------
+ -4567890123456789
+               456
+(2 rows)
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+        q2         
+-------------------
+ -4567890123456789
+               456
+(2 rows)
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q1 AS q2 FROM int8_tbl ORDER BY 1;
+        q2         
+-------------------
+ -4567890123456789
+               456
+  4567890123456789
+(3 rows)
+
+SELECT q1 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+ q1 
+----
+(0 rows)
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+        q1        
+------------------
+              123
+ 4567890123456789
+(2 rows)
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q2 AS q1 FROM int8_tbl;
+        q1        
+------------------
+              123
+ 4567890123456789
+ 4567890123456789
+(3 rows)
+
 --
 -- Mixed types
 --
@@ -338,6 +613,21 @@ SELECT f1 FROM float8_tbl EXCEPT SELECT f1 FROM int4_tbl ORDER BY 1;
  -1.2345678901234e-200
 (4 rows)
 
+SELECT f1 FROM float8_tbl INTERSECT CORRESPONDING SELECT f1 FROM int4_tbl;
+ f1 
+----
+  0
+(1 row)
+
+SELECT f1 FROM float8_tbl EXCEPT CORRESPONDING SELECT f1 FROM int4_tbl ORDER BY 1;
+          f1           
+-----------------------
+ -1.2345678901234e+200
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+(4 rows)
+
 --
 -- Operator precedence and (((((extra))))) parentheses
 --
@@ -424,6 +714,24 @@ SELECT q1 FROM int8_tbl EXCEPT (((SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1)))
  4567890123456789
 (2 rows)
 
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2,q1 FROM int8_tbl
+ORDER BY q2,q1;
+ q1 | q2 
+----+----
+(0 rows)
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q1) SELECT q2,q1 FROM int8_tbl
+ORDER BY q1;
+ q1 
+----
+(0 rows)
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q2) SELECT q2,q1 FROM int8_tbl
+ORDER BY q2;
+ q2 
+----
+(0 rows)
+
 --
 -- New syntaxes (7.1) permit new tests
 --
@@ -459,6 +767,22 @@ SELECT '3.4'::numeric UNION SELECT 'foo';
 ERROR:  invalid input syntax for type numeric: "foo"
 LINE 1: SELECT '3.4'::numeric UNION SELECT 'foo';
                                            ^
+SELECT '3.4'::numeric AS a UNION CORRESPONDING SELECT 'foo' AS a;
+ERROR:  invalid input syntax for type numeric: "foo"
+LINE 1: ...CT '3.4'::numeric AS a UNION CORRESPONDING SELECT 'foo' AS a...
+                                                             ^
+SELECT a.f1 FROM (SELECT 'test' AS f1 FROM varchar_tbl) a
+UNION CORRESPONDING
+SELECT b.f1 FROM (SELECT f1 FROM varchar_tbl) b
+ORDER BY 1;
+  f1  
+------
+ a
+ ab
+ abcd
+ test
+(4 rows)
+
 --
 -- Test that expression-index constraints can be pushed down through
 -- UNION or UNION ALL
diff --git a/src/test/regress/sql/create_view.sql b/src/test/regress/sql/create_view.sql
index c27f1034e1..3ea31ba621 100644
--- a/src/test/regress/sql/create_view.sql
+++ b/src/test/regress/sql/create_view.sql
@@ -532,3 +532,13 @@ select pg_get_viewdef('tt19v', true);
 set client_min_messages = warning;
 DROP SCHEMA temp_view_test CASCADE;
 DROP SCHEMA testviewschm2 CASCADE;
+
+-- views with corresponding clause
+create view view_corresponding_01 as select 1 as a, 2 as b union all corresponding select 3 as a, 4 as b;
+select * from view_corresponding_01;
+
+create view view_corresponding_02 as select 1 as a, 2 as b union all corresponding by (a,b) select 3 as a, 4 as b, 5 as c;
+select * from view_corresponding_02;
+
+create view view_corresponding_03 as select 1 as a, 2 as b union all corresponding by (b,a) select 3 as a, 4 as b, 5 as c;
+select * from view_corresponding_03;
diff --git a/src/test/regress/sql/union.sql b/src/test/regress/sql/union.sql
index 48e6850798..1f0967e4dd 100644
--- a/src/test/regress/sql/union.sql
+++ b/src/test/regress/sql/union.sql
@@ -20,6 +20,35 @@ SELECT 1 AS three UNION SELECT 2 UNION ALL SELECT 2 ORDER BY 1;
 
 SELECT 1.1 AS two UNION SELECT 2.2 ORDER BY 1;
 
+SELECT 1 AS two UNION CORRESPONDING SELECT 2 two UNION CORRESPONDING SELECT 2 two;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY (c,b) SELECT 4 a, 5 b, 6 c, 8 d;
+
+-- CORRESPONDING column ordering, left clause's column ordering must be preserved.
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+
+SELECT 2 b, 1 a, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+
+-- CORRESPONDING BY column ordering, BY clause column ordering must be preserved.
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c, a) SELECT 4 a, 5 b, 6 c;
+
+-- should to fail
+SELECT 10 a, 20 b UNION ALL CORRESPONDING SELECT 10 c, 20 d;
+
+SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (a,b) SELECT 10 c, 20 d;
+
+SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (x) SELECT 10 c, 20 d;
+
 -- Mixed types
 
 SELECT 1.1 AS two UNION SELECT 2 ORDER BY 1;
@@ -40,6 +69,24 @@ SELECT 1.1 AS three UNION SELECT 2 UNION ALL SELECT 2 ORDER BY 1;
 
 SELECT 1.1 AS two UNION (SELECT 2 UNION ALL SELECT 2) ORDER BY 1;
 
+-- other corresponding clause tests,
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6;
+
+-- when column is not in result, then the name should not be unique
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x3;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x6 UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+
+-- should fail
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3, -10 AS x3;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(a,b,x3) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING BY(a,b,x3) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3, -10 AS x3;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x6 UNION ALL CORRESPONDING BY(a,b,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+
 --
 -- Try testing from tables...
 --
@@ -90,6 +137,29 @@ UNION
 SELECT TRIM(TRAILING FROM f1) FROM CHAR_TBL
 ORDER BY 1;
 
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING BY(five)
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+
+SELECT f1 AS ten FROM FLOAT8_TBL
+UNION ALL CORRESPONDING
+SELECT f1 AS ten FROM FLOAT8_TBL;
+
+SELECT f1 AS five FROM FLOAT8_TBL
+  WHERE f1 BETWEEN -1e6 AND 1e6
+UNION CORRESPONDING
+SELECT f1 AS five FROM INT4_TBL
+  WHERE f1 BETWEEN 0 AND 1000000;
+
+SELECT * FROM FLOAT8_TBL
+UNION corresponding SELECT * FROM FLOAT8_TBL;
+
 --
 -- INTERSECT and EXCEPT
 --
@@ -112,6 +182,22 @@ SELECT q1 FROM int8_tbl EXCEPT ALL SELECT DISTINCT q2 FROM int8_tbl ORDER BY 1;
 
 SELECT q1 FROM int8_tbl EXCEPT ALL SELECT q1 FROM int8_tbl FOR NO KEY UPDATE;
 
+SELECT q2 FROM int8_tbl INTERSECT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+
+SELECT q2 FROM int8_tbl INTERSECT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+
+SELECT q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q1 AS q2 FROM int8_tbl ORDER BY 1;
+
+SELECT q1 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q2 AS q1 FROM int8_tbl;
+
 --
 -- Mixed types
 --
@@ -120,6 +206,10 @@ SELECT f1 FROM float8_tbl INTERSECT SELECT f1 FROM int4_tbl ORDER BY 1;
 
 SELECT f1 FROM float8_tbl EXCEPT SELECT f1 FROM int4_tbl ORDER BY 1;
 
+SELECT f1 FROM float8_tbl INTERSECT CORRESPONDING SELECT f1 FROM int4_tbl;
+
+SELECT f1 FROM float8_tbl EXCEPT CORRESPONDING SELECT f1 FROM int4_tbl ORDER BY 1;
+
 --
 -- Operator precedence and (((((extra))))) parentheses
 --
@@ -150,6 +240,15 @@ SELECT q1 FROM int8_tbl EXCEPT SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1;
 -- But this should work:
 SELECT q1 FROM int8_tbl EXCEPT (((SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1))) ORDER BY 1;
 
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2,q1 FROM int8_tbl
+ORDER BY q2,q1;
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q1) SELECT q2,q1 FROM int8_tbl
+ORDER BY q1;
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q2) SELECT q2,q1 FROM int8_tbl
+ORDER BY q2;
+
 --
 -- New syntaxes (7.1) permit new tests
 --
@@ -170,6 +269,13 @@ ORDER BY 1;
 -- This should fail, but it should produce an error cursor
 SELECT '3.4'::numeric UNION SELECT 'foo';
 
+SELECT '3.4'::numeric AS a UNION CORRESPONDING SELECT 'foo' AS a;
+
+SELECT a.f1 FROM (SELECT 'test' AS f1 FROM varchar_tbl) a
+UNION CORRESPONDING
+SELECT b.f1 FROM (SELECT f1 FROM varchar_tbl) b
+ORDER BY 1;
+
 --
 -- Test that expression-index constraints can be pushed down through
 -- UNION or UNION ALL
#28Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#27)
1 attachment(s)
Re: New CORRESPONDING clause design

Hi

fresh update - I enhanced Value node by location field as Tom proposal.

Few more regress tests.

But I found significant issue, that needs bigger fix - Surafel, please, can
you fix it.

It crash on

SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3
UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6
UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS b, -100 AS x9;

I'll mark this patch as waiting on author

Regards

Pavel

Attachments:

corresponding_clause_v9.patchtext/x-patch; charset=US-ASCII; name=corresponding_clause_v9.patchDownload
diff --git a/doc/src/sgml/queries.sgml b/doc/src/sgml/queries.sgml
index 30792f45f1..2d60718ff1 100644
--- a/doc/src/sgml/queries.sgml
+++ b/doc/src/sgml/queries.sgml
@@ -1601,6 +1601,9 @@ SELECT DISTINCT ON (<replaceable>expression</replaceable> <optional>, <replaceab
    <primary>EXCEPT</primary>
   </indexterm>
   <indexterm zone="queries-union">
+   <primary>CORRESPONDING</primary>
+  </indexterm>
+  <indexterm zone="queries-union">
    <primary>set union</primary>
   </indexterm>
   <indexterm zone="queries-union">
@@ -1617,9 +1620,9 @@ SELECT DISTINCT ON (<replaceable>expression</replaceable> <optional>, <replaceab
    The results of two queries can be combined using the set operations
    union, intersection, and difference.  The syntax is
 <synopsis>
-<replaceable>query1</replaceable> UNION <optional>ALL</optional> <replaceable>query2</replaceable>
-<replaceable>query1</replaceable> INTERSECT <optional>ALL</optional> <replaceable>query2</replaceable>
-<replaceable>query1</replaceable> EXCEPT <optional>ALL</optional> <replaceable>query2</replaceable>
+<replaceable>query1</replaceable> UNION <optional>ALL</optional> <optional>CORRESPONDING</optional> <optional>BY</optional> <replaceable>query2</replaceable>
+<replaceable>query1</replaceable> INTERSECT <optional>ALL</optional> <optional>CORRESPONDING</optional> <optional>BY</optional> <replaceable>query2</replaceable>
+<replaceable>query1</replaceable> EXCEPT <optional>ALL</optional> <optional>CORRESPONDING</optional> <optional>BY</optional> <replaceable>query2</replaceable>
 </synopsis>
    <replaceable>query1</replaceable> and
    <replaceable>query2</replaceable> are queries that can use any of
@@ -1659,14 +1662,31 @@ SELECT DISTINCT ON (<replaceable>expression</replaceable> <optional>, <replaceab
   </para>
 
   <para>
-   In order to calculate the union, intersection, or difference of two
-   queries, the two queries must be <quote>union compatible</quote>,
-   which means that they return the same number of columns and
-   the corresponding columns have compatible data types, as
-   described in <xref linkend="typeconv-union-case">.
+   <literal>EXCEPT</> returns all rows that are in the result of
+   <replaceable>query1</replaceable> but not in the result of
+   <replaceable>query2</replaceable>.  (This is sometimes called the
+   <firstterm>difference</> between two queries.)  Again, duplicates
+   are eliminated unless <literal>EXCEPT ALL</> is used.
   </para>
- </sect1>
 
+  <para>
+   <literal>CORRESPONDING</> returns all columns that are in both 
+   <replaceable>query1</> and <replaceable>query2</> with the same name.
+  </para>
+
+  <para>
+   <literal>CORRESPONDING BY</> returns all columns in the column list 
+   that are also in both <replaceable>query1</> and 
+   <replaceable>query2</> with the same name. The names in column list
+   must be unique.
+  </para>
+
+  <para>
+   The names of columns in result when <literal>CORRESPONDING</> or
+   <literal>CORRESPONDING BY</> clause is used must be unique in
+   <replaceable>query1</replaceable> and <replaceable>query2</replaceable>.
+  </para>
+ </sect1>
 
  <sect1 id="queries-order">
   <title>Sorting Rows</title>
diff --git a/doc/src/sgml/sql.sgml b/doc/src/sgml/sql.sgml
index 57396d7c24..f98c22e696 100644
--- a/doc/src/sgml/sql.sgml
+++ b/doc/src/sgml/sql.sgml
@@ -859,7 +859,7 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="PARAMETER">expression</replac
     [ WHERE <replaceable class="PARAMETER">condition</replaceable> ]
     [ GROUP BY <replaceable class="PARAMETER">expression</replaceable> [, ...] ]
     [ HAVING <replaceable class="PARAMETER">condition</replaceable> [, ...] ]
-    [ { UNION | INTERSECT | EXCEPT } [ ALL ] <replaceable class="PARAMETER">select</replaceable> ]
+    [ { UNION | INTERSECT | EXCEPT } [ ALL ] [ CORRESPONDING [ BY ( <replaceable class="PARAMETER">expression</replaceable> ) ] ] <replaceable class="PARAMETER">select</replaceable> ]
     [ ORDER BY <replaceable class="parameter">expression</replaceable> [ ASC | DESC | USING <replaceable class="parameter">operator</replaceable> ] [ NULLS { FIRST | LAST } ] [, ...] ]
     [ LIMIT { <replaceable class="PARAMETER">count</replaceable> | ALL } ]
     [ OFFSET <replaceable class="PARAMETER">start</replaceable> ]
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index c23d5c5285..93cd9f0ed5 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2995,6 +2995,7 @@ _copySelectStmt(const SelectStmt *from)
 	COPY_NODE_FIELD(withClause);
 	COPY_SCALAR_FIELD(op);
 	COPY_SCALAR_FIELD(all);
+	COPY_NODE_FIELD(correspondingClause);
 	COPY_NODE_FIELD(larg);
 	COPY_NODE_FIELD(rarg);
 
@@ -3010,6 +3011,8 @@ _copySetOperationStmt(const SetOperationStmt *from)
 	COPY_SCALAR_FIELD(all);
 	COPY_NODE_FIELD(larg);
 	COPY_NODE_FIELD(rarg);
+	COPY_NODE_FIELD(correspondingColumns);
+	COPY_SCALAR_FIELD(hasCorrespondingBy);
 	COPY_NODE_FIELD(colTypes);
 	COPY_NODE_FIELD(colTypmods);
 	COPY_NODE_FIELD(colCollations);
@@ -4588,6 +4591,8 @@ _copyValue(const Value *from)
 				 (int) from->type);
 			break;
 	}
+	COPY_LOCATION_FIELD(location);
+
 	return newnode;
 }
 
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 5941b7a2bf..dd6598d85b 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1050,6 +1050,7 @@ _equalSelectStmt(const SelectStmt *a, const SelectStmt *b)
 	COMPARE_NODE_FIELD(withClause);
 	COMPARE_SCALAR_FIELD(op);
 	COMPARE_SCALAR_FIELD(all);
+	COMPARE_NODE_FIELD(correspondingClause);
 	COMPARE_NODE_FIELD(larg);
 	COMPARE_NODE_FIELD(rarg);
 
@@ -1063,6 +1064,8 @@ _equalSetOperationStmt(const SetOperationStmt *a, const SetOperationStmt *b)
 	COMPARE_SCALAR_FIELD(all);
 	COMPARE_NODE_FIELD(larg);
 	COMPARE_NODE_FIELD(rarg);
+	COMPARE_NODE_FIELD(correspondingColumns);
+	COMPARE_SCALAR_FIELD(hasCorrespondingBy);
 	COMPARE_NODE_FIELD(colTypes);
 	COMPARE_NODE_FIELD(colTypmods);
 	COMPARE_NODE_FIELD(colCollations);
@@ -2935,6 +2938,8 @@ _equalValue(const Value *a, const Value *b)
 			break;
 	}
 
+	COMPARE_LOCATION_FIELD(location);
+
 	return true;
 }
 
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 6e52eb7231..7102ea96c2 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -3444,6 +3444,8 @@ raw_expression_tree_walker(Node *node,
 					return true;
 				if (walker(stmt->lockingClause, context))
 					return true;
+				if (walker(stmt->correspondingClause, context))
+					return true;
 				if (walker(stmt->withClause, context))
 					return true;
 				if (walker(stmt->larg, context))
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 541af02935..da85e9d377 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2642,6 +2642,7 @@ _outSelectStmt(StringInfo str, const SelectStmt *node)
 	WRITE_NODE_FIELD(withClause);
 	WRITE_ENUM_FIELD(op, SetOperation);
 	WRITE_BOOL_FIELD(all);
+	WRITE_NODE_FIELD(correspondingClause);
 	WRITE_NODE_FIELD(larg);
 	WRITE_NODE_FIELD(rarg);
 }
@@ -2949,6 +2950,8 @@ _outSetOperationStmt(StringInfo str, const SetOperationStmt *node)
 	WRITE_BOOL_FIELD(all);
 	WRITE_NODE_FIELD(larg);
 	WRITE_NODE_FIELD(rarg);
+	WRITE_NODE_FIELD(correspondingColumns);
+	WRITE_BOOL_FIELD(hasCorrespondingBy);
 	WRITE_NODE_FIELD(colTypes);
 	WRITE_NODE_FIELD(colTypmods);
 	WRITE_NODE_FIELD(colCollations);
@@ -3126,6 +3129,7 @@ _outAExpr(StringInfo str, const A_Expr *node)
 static void
 _outValue(StringInfo str, const Value *value)
 {
+	/* NB: this isn't a complete set of fields */
 	switch (value->type)
 	{
 		case T_Integer:
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 474f221a75..6e284f9ef8 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -416,6 +416,8 @@ _readSetOperationStmt(void)
 	READ_BOOL_FIELD(all);
 	READ_NODE_FIELD(larg);
 	READ_NODE_FIELD(rarg);
+	READ_NODE_FIELD(correspondingColumns);
+	READ_BOOL_FIELD(hasCorrespondingBy);
 	READ_NODE_FIELD(colTypes);
 	READ_NODE_FIELD(colTypmods);
 	READ_NODE_FIELD(colCollations);
diff --git a/src/backend/nodes/value.c b/src/backend/nodes/value.c
index 5d2f96c103..72afc172f9 100644
--- a/src/backend/nodes/value.c
+++ b/src/backend/nodes/value.c
@@ -26,6 +26,7 @@ makeInteger(long i)
 
 	v->type = T_Integer;
 	v->val.ival = i;
+	v->location = -1;
 	return v;
 }
 
@@ -41,6 +42,7 @@ makeFloat(char *numericStr)
 
 	v->type = T_Float;
 	v->val.str = numericStr;
+	v->location = -1;
 	return v;
 }
 
@@ -56,6 +58,7 @@ makeString(char *str)
 
 	v->type = T_String;
 	v->val.str = str;
+	v->location = -1;
 	return v;
 }
 
@@ -71,5 +74,6 @@ makeBitString(char *str)
 
 	v->type = T_BitString;
 	v->val.str = str;
+	v->location = -1;
 	return v;
 }
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index d88738ec7c..29f86ebbad 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -91,7 +91,8 @@ static List *generate_setop_tlist(List *colTypes, List *colCollations,
 					 Index varno,
 					 bool hack_constants,
 					 List *input_tlist,
-					 List *refnames_tlist);
+					 List *refnames_tlist,
+					 bool no_corresponding);
 static List *generate_append_tlist(List *colTypes, List *colCollations,
 					  bool flag,
 					  List *input_tlists,
@@ -110,6 +111,7 @@ static Node *adjust_appendrel_attrs_mutator(Node *node,
 static Relids adjust_relid_set(Relids relids, Index oldrelid, Index newrelid);
 static List *adjust_inherited_tlist(List *tlist,
 					   AppendRelInfo *context);
+static List *make_corresponding_target(List *corresponding_list, List *subroot_list);
 
 
 /*
@@ -187,6 +189,24 @@ plan_set_operations(PlannerInfo *root)
 									   leftmostQuery->targetList,
 									   &top_tlist);
 	}
+	/*
+	 * If corresponding column specified, we take column names from it.
+	 */
+	else if (topop->correspondingColumns != NIL )
+	{
+		/*
+		 * Recurse on setOperations tree to generate paths for set ops. The
+		 * final output path should have just the column types shown as the
+		 * output from the top-level node, plus possibly resjunk working
+		 * columns (we can rely on upper-level nodes to deal with that).
+		 */
+		path = recurse_set_operations((Node *) topop, root,
+									  topop->colTypes, topop->colCollations,
+									  true, -1,
+									  topop->correspondingColumns,
+									  &top_tlist,
+									  NULL);
+	}
 	else
 	{
 		/*
@@ -252,6 +272,8 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 					   List **pTargetList,
 					   double *pNumGroups)
 {
+	SetOperationStmt *topop = (SetOperationStmt *) root->parse->setOperations;
+
 	if (IsA(setOp, RangeTblRef))
 	{
 		RangeTblRef *rtr = (RangeTblRef *) setOp;
@@ -316,23 +338,53 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 		path = (Path *) create_subqueryscan_path(root, rel, subpath,
 												 NIL, NULL);
 
-		/*
-		 * Figure out the appropriate target list, and update the
-		 * SubqueryScanPath with the PathTarget form of that.
-		 */
-		tlist = generate_setop_tlist(colTypes, colCollations,
+		if (topop->correspondingColumns != NIL )
+		{
+			List	    *correspondingTarget;
+
+			/*
+			 * make target list that only contains corresponding column
+			 * from sub-queries list ito use it for projection
+			 */
+			correspondingTarget = make_corresponding_target(
+											  topop->correspondingColumns,
+											  subroot->processed_tlist);
+
+			/*
+			 * Figure out the appropriate target list, and update the
+			 * SubqueryScanPath with the PathTarget form of that.
+			 */
+			tlist = generate_setop_tlist(colTypes, colCollations, flag,
+											  rtr->rtindex, true,
+											  correspondingTarget,
+											  refnames_tlist, false);
+
+			path = apply_projection_to_path(root, rel, path,
+					create_pathtarget(root, tlist));
+
+			/* Return the fully-fledged tlist to caller, too */
+			*pTargetList = tlist;
+
+		}
+		else
+		{
+			/*
+			 * Figure out the appropriate target list, and update the
+			 * SubqueryScanPath with the PathTarget form of that.
+			 */
+			tlist = generate_setop_tlist(colTypes, colCollations,
 									 flag,
 									 rtr->rtindex,
 									 true,
 									 subroot->processed_tlist,
-									 refnames_tlist);
+									 refnames_tlist, true);
 
-		path = apply_projection_to_path(root, rel, path,
+			path = apply_projection_to_path(root, rel, path,
 										create_pathtarget(root, tlist));
 
-		/* Return the fully-fledged tlist to caller, too */
-		*pTargetList = tlist;
-
+			/* Return the fully-fledged tlist to caller, too */
+			*pTargetList = tlist;
+		}
 		/*
 		 * Estimate number of groups if caller wants it.  If the subquery used
 		 * grouping or aggregation, its output is probably mostly unique
@@ -392,7 +444,7 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 												0,
 												false,
 												*pTargetList,
-												refnames_tlist);
+												refnames_tlist, true);
 			path = apply_projection_to_path(root,
 											path->parent,
 											path,
@@ -1004,7 +1056,8 @@ generate_setop_tlist(List *colTypes, List *colCollations,
 					 Index varno,
 					 bool hack_constants,
 					 List *input_tlist,
-					 List *refnames_tlist)
+					 List *refnames_tlist,
+					 bool no_corresponding)
 {
 	List	   *tlist = NIL;
 	int			resno = 1;
@@ -1026,8 +1079,8 @@ generate_setop_tlist(List *colTypes, List *colCollations,
 
 		rtlc = lnext(rtlc);
 
-		Assert(inputtle->resno == resno);
-		Assert(reftle->resno == resno);
+		Assert(!no_corresponding || inputtle->resno == resno);
+		Assert(!no_corresponding || reftle->resno == resno);
 		Assert(!inputtle->resjunk);
 		Assert(!reftle->resjunk);
 
@@ -2150,3 +2203,70 @@ adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node,
 	/* Now translate for this child */
 	return adjust_appendrel_attrs(root, node, appinfo);
 }
+
+/*
+ * generate target list from left target list with the order
+ * of right target list
+ */
+static List *
+make_corresponding_target(List *corresponding_list, List *subroot_list)
+{
+	Index internal = 0;
+	ListCell   *ltl;
+	ListCell   *rtl;
+	int			size;
+	int			i;
+	List *matchingColumns = NIL;
+	TargetEntry *simple_te_array;
+
+	size = list_length(corresponding_list) + 1;
+
+	/* Use array to find the order of corresponding columen */
+	simple_te_array = (TargetEntry *) palloc0(size * sizeof(TargetEntry));
+	foreach(ltl, corresponding_list)
+	{
+		foreach(rtl, subroot_list)
+		{
+			TargetEntry *ltle = (TargetEntry *) lfirst(ltl);
+			TargetEntry *rtle = (TargetEntry *) lfirst(rtl);
+
+			/* Names of the columns must be resolved before calling this method. */
+			Assert(ltle->resname != NULL);
+			Assert(rtle->resname != NULL);
+
+			/* If column names are the same, add it to array. */
+			if (strcmp(ltle->resname, rtle->resname) == 0)
+			{
+				simple_te_array[internal].xpr = rtle->xpr;
+				simple_te_array[internal].expr = rtle->expr;
+				simple_te_array[internal].resno = rtle->resno;
+				simple_te_array[internal].resname = rtle->resname;
+				simple_te_array[internal].ressortgroupref =
+						rtle->ressortgroupref;
+				simple_te_array[internal].resorigtbl = rtle->resorigtbl;
+				simple_te_array[internal].resorigcol = rtle->resorigcol;
+				simple_te_array[internal].resjunk = rtle->resjunk;
+				internal++;
+				continue;
+			}
+		}
+	}
+	/* traverse the array and make targetlist */
+	for (i = 0; i < internal; i++)
+	{
+		TargetEntry *tle = makeNode(TargetEntry);
+
+		tle->xpr = simple_te_array[i].xpr;
+		tle->expr = simple_te_array[i].expr;
+		tle->resno = simple_te_array[i].resno;
+		tle->resname = simple_te_array[i].resname;
+		tle->ressortgroupref = simple_te_array[i].ressortgroupref;
+		tle->resorigtbl = simple_te_array[i].resorigtbl;
+		tle->resorigcol = simple_te_array[i].resorigcol;
+		tle->resjunk = simple_te_array[i].resjunk;
+
+		matchingColumns = lappend(matchingColumns, tle);
+
+	}
+	return matchingColumns;
+}
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 3571e50aea..36f827102d 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -76,10 +76,18 @@ static Query *transformCreateTableAsStmt(ParseState *pstate,
 						   CreateTableAsStmt *stmt);
 static void transformLockingClause(ParseState *pstate, Query *qry,
 					   LockingClause *lc, bool pushedDown);
+static void makeUnionDatatype(List *ltargetlist, List *rtargetlist,
+		SetOperationStmt *op, List **targetlist, ParseState *parentParseState,
+		const char *context);
 #ifdef RAW_EXPRESSION_COVERAGE_TEST
 static bool test_raw_expression_coverage(Node *node, void *context);
 #endif
-
+static List *CommonColumns(List *ltargetlist, List *rtargetlist, bool filtered,
+								 ParseState *pstate, const char *context);
+static List *FilterColumnsByNames(List *common_columns, List *filter,
+								 ParseState *pstate, const char *context);
+static List *FilterColumnsByTL(List *targetlist, List *filter, bool check_uniq,
+								 ParseState *pstate, const char *context);
 
 /*
  * parse_analyze
@@ -1661,7 +1669,15 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->targetList = NIL;
 	targetvars = NIL;
 	targetnames = NIL;
-	left_tlist = list_head(leftmostQuery->targetList);
+
+	/*
+	 * for corresponding clause limits top-level query targetlist to those
+	 * corresponding column list only
+	 */
+	if (sostmt->correspondingColumns != NIL )
+		left_tlist = list_head(sostmt->correspondingColumns);
+	else
+		left_tlist = list_head(leftmostQuery->targetList);
 
 	forthree(lct, sostmt->colTypes,
 			 lcm, sostmt->colTypmods,
@@ -1921,8 +1937,6 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 		SetOperationStmt *op = makeNode(SetOperationStmt);
 		List	   *ltargetlist;
 		List	   *rtargetlist;
-		ListCell   *ltl;
-		ListCell   *rtl;
 		const char *context;
 
 		context = (stmt->op == SETOP_UNION ? "UNION" :
@@ -1933,6 +1947,84 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 		op->all = stmt->all;
 
 		/*
+		 * If CORRESPONDING is specified, syntax and column name validities checked,
+		 * column filtering is done by a subquery later on.
+		 */
+		if (stmt->correspondingClause == NIL )
+		{
+			/* No CORRESPONDING clause, no operation needed for column filtering */
+			op->correspondingColumns = stmt->correspondingClause;
+			op->hasCorrespondingBy = false;
+		}
+		else
+		{
+			/*
+			 * CORRESPONDING clause, find matching column names from both tables.
+			 * If there are none then it is a syntax error.
+			 */
+			Query	   *largQuery;
+			Query	   *rargQuery;
+			List	   *matchingColumns;
+			List	   *rightCorrespondingColumns;
+
+			op->hasCorrespondingBy = linitial(stmt->correspondingClause) != NULL;
+
+			/* Analyze left query to resolve column names. */
+			largQuery = parse_sub_analyze((Node *) stmt->larg,
+										  pstate, NULL, false, false );
+
+			/* Analyze right query to resolve column names. */
+			rargQuery = parse_sub_analyze((Node *) stmt->rarg,
+										  pstate, NULL, false, false );
+
+			/* Find matching columns from both queries. */
+			matchingColumns = CommonColumns(largQuery->targetList,
+											rargQuery->targetList,
+											op->hasCorrespondingBy,
+											pstate,
+											context);
+
+			/*
+			 * If matchingColumns is empty, there is an error.
+			 * At least one column in the select lists must have the same name.
+			 */
+			if (matchingColumns == NIL)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("there is not any corresponding name"),
+	 errhint("%s queries with a CORRESPONDING clause must have at least one column with the same name",
+								 context),
+						 parser_errposition(pstate,
+									exprLocation((Node *)
+									linitial(largQuery->targetList)))));
+
+			/* Use column filter when it is known */
+			if (op->hasCorrespondingBy)
+				matchingColumns = FilterColumnsByNames(matchingColumns,
+													   stmt->correspondingClause,
+													   pstate,
+													   context);
+
+			op->correspondingColumns = matchingColumns;
+
+			/*
+			 * When we know matching columns, we can quickly create
+			 * corresponding target list for right target list. It is faster,
+			 * than using symmetry. Ensure unique columns when hasCorrespondingBy
+			 * is true - in this case, the uniq is not checked already.
+			 */
+			rightCorrespondingColumns = FilterColumnsByTL(rargQuery->targetList,
+														  matchingColumns,
+														  op->hasCorrespondingBy,
+														  pstate,
+														  context);
+
+			/* make union'd datatype of output column */
+			makeUnionDatatype(matchingColumns, rightCorrespondingColumns,
+								op, targetlist, pstate, context);
+		}
+
+		/*
 		 * Recursively transform the left child node.
 		 */
 		op->larg = transformSetOperationTree(pstate, stmt->larg,
@@ -1957,177 +2049,415 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 											 false,
 											 &rtargetlist);
 
-		/*
-		 * Verify that the two children have the same number of non-junk
-		 * columns, and determine the types of the merged output columns.
-		 */
-		if (list_length(ltargetlist) != list_length(rtargetlist))
-			ereport(ERROR,
-					(errcode(ERRCODE_SYNTAX_ERROR),
-				 errmsg("each %s query must have the same number of columns",
-						context),
-					 parser_errposition(pstate,
-										exprLocation((Node *) rtargetlist))));
-
-		if (targetlist)
-			*targetlist = NIL;
-		op->colTypes = NIL;
-		op->colTypmods = NIL;
-		op->colCollations = NIL;
-		op->groupClauses = NIL;
-		forboth(ltl, ltargetlist, rtl, rtargetlist)
+		if (op->correspondingColumns == NIL )
 		{
-			TargetEntry *ltle = (TargetEntry *) lfirst(ltl);
-			TargetEntry *rtle = (TargetEntry *) lfirst(rtl);
-			Node	   *lcolnode = (Node *) ltle->expr;
-			Node	   *rcolnode = (Node *) rtle->expr;
-			Oid			lcoltype = exprType(lcolnode);
-			Oid			rcoltype = exprType(rcolnode);
-			int32		lcoltypmod = exprTypmod(lcolnode);
-			int32		rcoltypmod = exprTypmod(rcolnode);
-			Node	   *bestexpr;
-			int			bestlocation;
-			Oid			rescoltype;
-			int32		rescoltypmod;
-			Oid			rescolcoll;
-
-			/* select common type, same as CASE et al */
-			rescoltype = select_common_type(pstate,
-											list_make2(lcolnode, rcolnode),
-											context,
-											&bestexpr);
-			bestlocation = exprLocation(bestexpr);
-			/* if same type and same typmod, use typmod; else default */
-			if (lcoltype == rcoltype && lcoltypmod == rcoltypmod)
-				rescoltypmod = lcoltypmod;
-			else
-				rescoltypmod = -1;
-
+			makeUnionDatatype(ltargetlist, rtargetlist, op, targetlist, pstate,
+					context);
 			/*
-			 * Verify the coercions are actually possible.  If not, we'd fail
-			 * later anyway, but we want to fail now while we have sufficient
-			 * context to produce an error cursor position.
-			 *
-			 * For all non-UNKNOWN-type cases, we verify coercibility but we
-			 * don't modify the child's expression, for fear of changing the
-			 * child query's semantics.
-			 *
-			 * If a child expression is an UNKNOWN-type Const or Param, we
-			 * want to replace it with the coerced expression.  This can only
-			 * happen when the child is a leaf set-op node.  It's safe to
-			 * replace the expression because if the child query's semantics
-			 * depended on the type of this output column, it'd have already
-			 * coerced the UNKNOWN to something else.  We want to do this
-			 * because (a) we want to verify that a Const is valid for the
-			 * target type, or resolve the actual type of an UNKNOWN Param,
-			 * and (b) we want to avoid unnecessary discrepancies between the
-			 * output type of the child query and the resolved target type.
-			 * Such a discrepancy would disable optimization in the planner.
-			 *
-			 * If it's some other UNKNOWN-type node, eg a Var, we do nothing
-			 * (knowing that coerce_to_common_type would fail).  The planner
-			 * is sometimes able to fold an UNKNOWN Var to a constant before
-			 * it has to coerce the type, so failing now would just break
-			 * cases that might work.
+			 * Verify that the two children have the same number of non-junk
+			 * columns, and determine the types of the merged output columns.
 			 */
-			if (lcoltype != UNKNOWNOID)
-				lcolnode = coerce_to_common_type(pstate, lcolnode,
-												 rescoltype, context);
-			else if (IsA(lcolnode, Const) ||
-					 IsA(lcolnode, Param))
-			{
-				lcolnode = coerce_to_common_type(pstate, lcolnode,
-												 rescoltype, context);
-				ltle->expr = (Expr *) lcolnode;
-			}
+			if (list_length(ltargetlist) != list_length(rtargetlist))
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("each %s query must have the same number of columns", context),
+						 parser_errposition(pstate,
+											exprLocation((Node *) rtargetlist))));
+		}
 
-			if (rcoltype != UNKNOWNOID)
-				rcolnode = coerce_to_common_type(pstate, rcolnode,
-												 rescoltype, context);
-			else if (IsA(rcolnode, Const) ||
-					 IsA(rcolnode, Param))
+		return (Node *) op;
+	}
+}
+
+/*
+ * Processes targetlists of two queries for columns with same names to use
+ * with UNION/INTERSECT/EXCEPT CORRESPONDING. filtered is true, when
+ * CORRESPONDING BY is used. When it is false, we can check uniq names
+ * in rtargetlist here.
+ */
+static List *
+CommonColumns(List *ltargetlist, List *rtargetlist, bool filtered,
+			  ParseState *pstate, const char *context)
+{
+	List	   *common_columns = NIL;
+	ListCell   *ltlc;
+	ListCell   *rtlc;
+	int			resno = 1;
+
+	foreach(ltlc, ltargetlist)
+	{
+		TargetEntry *lte = (TargetEntry *) lfirst(ltlc);
+		bool		found = false;
+
+		Assert(lte->resname != NULL);
+
+		foreach(rtlc, rtargetlist)
+		{
+			ListCell   *lc;
+			TargetEntry *rte = (TargetEntry *) lfirst(rtlc);
+
+			Assert(rte->resname != NULL);
+
+			if (strcmp(lte->resname, rte->resname) == 0)
 			{
-				rcolnode = coerce_to_common_type(pstate, rcolnode,
-												 rescoltype, context);
-				rtle->expr = (Expr *) rcolnode;
+				if (filtered)
+				{
+					/*
+					 * We found common column, but we don't know if it
+					 * is in CORRESPONDING BY list - so don't try do more
+					 * work here. The column list will be modified later,
+					 * so use shall copy here.
+					 */
+					common_columns = lappend(common_columns, lte);
+					break;
+				}
+
+				/* If same column name mentioned more than once it is syntax error . */
+				if (found)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("corresponding column \"%s\" is used more times", rte->resname),
+	 errhint("In %s queries with CORRESPONDING clause the corresponding column names must be unique.",
+									context),
+							 parser_errposition(pstate,
+												exprLocation((Node *) rte))));
+
+				found = true;
+
+				/* In this case, common_columns must be unique */
+				foreach(lc, common_columns)
+				{
+					TargetEntry *te = (TargetEntry *) lfirst(lc);
+
+					if (strcmp(te->resname, lte->resname) == 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("corresponding column \"%s\" is used more times", lte->resname),
+	 errhint("In %s queries with CORRESPONDING clause the corresponding column names must be unique.",
+										context),
+								 parser_errposition(pstate,
+													exprLocation((Node *) lte))));
+				}
+
+				/* When is not any other filter create final te */
+				common_columns = lappend(common_columns,
+										 makeTargetEntry(lte->expr,
+														 (AttrNumber) resno++,
+														 lte->resname,
+														 false));
 			}
+		}
+	}
 
-			/*
-			 * Select common collation.  A common collation is required for
-			 * all set operators except UNION ALL; see SQL:2008 7.13 <query
-			 * expression> Syntax Rule 15c.  (If we fail to identify a common
-			 * collation for a UNION ALL column, the curCollations element
-			 * will be set to InvalidOid, which may result in a runtime error
-			 * if something at a higher query level wants to use the column's
-			 * collation.)
-			 */
-			rescolcoll = select_common_collation(pstate,
-											  list_make2(lcolnode, rcolnode),
-										 (op->op == SETOP_UNION && op->all));
+	return common_columns;
+}
+
+/*
+ * Returns filtered common columns list - filter is based on CORRESPONDING BY
+ * list Ensure CORRESPONDING BY list is unique. Result is in CORRESPONDING BY
+ * list order. Common columns list can hold duplicate columns.
+ */
+static List *
+FilterColumnsByNames(List *common_columns, List *filter,
+					 ParseState *pstate, const char *context)
+{
+	List	   *filtered_columns = NIL;
+	ListCell   *flc;
+	int			resno = 1;
 
-			/* emit results */
-			op->colTypes = lappend_oid(op->colTypes, rescoltype);
-			op->colTypmods = lappend_int(op->colTypmods, rescoltypmod);
-			op->colCollations = lappend_oid(op->colCollations, rescolcoll);
+	Assert(common_columns != NIL);
+	Assert(filter != NIL);
 
-			/*
-			 * For all cases except UNION ALL, identify the grouping operators
-			 * (and, if available, sorting operators) that will be used to
-			 * eliminate duplicates.
-			 */
-			if (op->op != SETOP_UNION || !op->all)
+	foreach(flc, filter)
+	{
+		Value	   *strval = (Value *) lfirst(flc);
+		char	   *name = strVal(strval);
+		ListCell   *tlc;
+		bool		found = false;
+
+		foreach(tlc, common_columns)
+		{
+			TargetEntry   *tec = (TargetEntry *) lfirst(tlc);
+
+			if (strcmp(tec->resname, name) == 0)
 			{
-				SortGroupClause *grpcl = makeNode(SortGroupClause);
-				Oid			sortop;
-				Oid			eqop;
-				bool		hashable;
-				ParseCallbackState pcbstate;
-
-				setup_parser_errposition_callback(&pcbstate, pstate,
-												  bestlocation);
-
-				/* determine the eqop and optional sortop */
-				get_sort_group_operators(rescoltype,
-										 false, true, false,
-										 &sortop, &eqop, NULL,
-										 &hashable);
-
-				cancel_parser_errposition_callback(&pcbstate);
-
-				/* we don't have a tlist yet, so can't assign sortgrouprefs */
-				grpcl->tleSortGroupRef = 0;
-				grpcl->eqop = eqop;
-				grpcl->sortop = sortop;
-				grpcl->nulls_first = false;		/* OK with or without sortop */
-				grpcl->hashable = hashable;
-
-				op->groupClauses = lappend(op->groupClauses, grpcl);
+				ListCell   *lc;
+
+				/*
+				 * When "found" is true, then common_columns contains
+				 * duplicate columns. Raise exception then.
+				 */
+				if (found)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("corresponding column \"%s\" is used more times", name),
+	 errhint("In %s queries with CORRESPONDING BY clause the corresponding column names must be unique.",
+									context),
+							 parser_errposition(pstate,
+												exprLocation((Node *) tec))));
+
+				found = true;
+
+				/* result list should not to contains this name */
+				foreach(lc, filtered_columns)
+				{
+					TargetEntry   *te = (TargetEntry *) lfirst(lc);
+
+					/*
+					 * CORRESPONDING BY clause contains a column name that is
+					 * not in unique in this clause
+					 */
+					if (strcmp(te->resname, name) == 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("column name \"%s\" is not unique in CORRESPONDING BY clause", name),
+								 errhint("CORRESPONDING BY clause must contain unique column names only."),
+								 parser_errposition(pstate, strval->location)));
+				}
+
+				/* create te with correct resno */
+				filtered_columns = lappend(filtered_columns,
+										 makeTargetEntry(tec->expr,
+														 (AttrNumber) resno++,
+														 tec->resname,
+														 false));
 			}
+		}
 
-			/*
-			 * Construct a dummy tlist entry to return.  We use a SetToDefault
-			 * node for the expression, since it carries exactly the fields
-			 * needed, but any other expression node type would do as well.
-			 */
-			if (targetlist)
+		/*
+		 * CORRESPONDING BY clause contains a column name that is not
+		 * in common columns.
+		 */
+		if (!found)
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("column name \"%s\" can not be used in CORRESPONDING BY list", name),
+		 errhint("%s queries with a CORRESPONDING BY clause must contain column names from both tables.",
+									 context),
+					 parser_errposition(pstate, strval->location)));
+	}
+
+	return filtered_columns;
+}
+
+/*
+ * Prepare target list for right query of CORRESPONDING clause.
+ * When filtered is true, filtered columns in target list are
+ * unique.
+ */
+static List *
+FilterColumnsByTL(List *targetlist, List *filter, bool check_uniq,
+				  ParseState *pstate, const char *context)
+{
+	List	   *result = NIL;
+	ListCell   *lc;
+	int			resno = 1;
+
+	foreach(lc, filter)
+	{
+		TargetEntry *fte = (TargetEntry *) lfirst(lc);
+		ListCell	*tle;
+		bool		found = false;
+
+		foreach(tle, targetlist)
+		{
+			TargetEntry *te = (TargetEntry *) lfirst(tle);
+
+			if (strcmp(fte->resname, te->resname) == 0)
 			{
-				SetToDefault *rescolnode = makeNode(SetToDefault);
-				TargetEntry *restle;
-
-				rescolnode->typeId = rescoltype;
-				rescolnode->typeMod = rescoltypmod;
-				rescolnode->collation = rescolcoll;
-				rescolnode->location = bestlocation;
-				restle = makeTargetEntry((Expr *) rescolnode,
-										 0,		/* no need to set resno */
-										 NULL,
-										 false);
-				*targetlist = lappend(*targetlist, restle);
+				/* create te with correct resno */
+				result = lappend(result,
+								 makeTargetEntry(te->expr,
+												 (AttrNumber) resno++,
+												 te->resname,
+												 false));
+
+				if (!check_uniq)
+					break;
+
+				/*
+				 * When "found" is true, then targetlist contains
+				 * duplicate filtered columns. Raise exception then.
+				 */
+				if (found)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("corresponding column \"%s\" is used more times", te->resname),
+							 errhint("In %s queries with CORRESPONDING BY clause the corresponding column names must be unique.",
+									context),
+							 parser_errposition(pstate,
+												exprLocation((Node *) te))));
+
+				found = true;
 			}
 		}
+	}
 
-		return (Node *) op;
+	return result;
+}
+
+/*
+ * process right and left target list to set up union'd datatype
+ */
+static void
+makeUnionDatatype(List *ltargetlist, List *rtargetlist, SetOperationStmt *op,
+		List **targetlist, ParseState *pstate, const char *context)
+{
+	ListCell   *ltl;
+	ListCell   *rtl;
+
+	if (targetlist)
+		*targetlist = NIL;
+
+	op->colTypes = NIL;
+	op->colTypmods = NIL;
+	op->colCollations = NIL;
+	op->groupClauses = NIL;
+
+	forboth(ltl, ltargetlist, rtl, rtargetlist)
+	{
+		TargetEntry	   *ltle = (TargetEntry *) lfirst(ltl);
+		TargetEntry	   *rtle = (TargetEntry *) lfirst(rtl);
+		Node		   *lcolnode = (Node *) ltle->expr;
+		Node		   *rcolnode = (Node *) rtle->expr;
+		Oid			lcoltype = exprType(lcolnode);
+		Oid			rcoltype = exprType(rcolnode);
+		int32		lcoltypmod = exprTypmod(lcolnode);
+		int32		rcoltypmod = exprTypmod(rcolnode);
+		Node	   *bestexpr;
+		int			bestlocation;
+		Oid			rescoltype;
+		int32		rescoltypmod;
+		Oid			rescolcoll;
+
+		/* select common type, same as CASE et al */
+		rescoltype = select_common_type(pstate, list_make2(lcolnode, rcolnode),
+				context, &bestexpr);
+		bestlocation = exprLocation(bestexpr);
+		/* if same type and same typmod, use typmod; else default */
+		if (lcoltype == rcoltype && lcoltypmod == rcoltypmod)
+			rescoltypmod = lcoltypmod;
+		else
+			rescoltypmod = -1;
+
+		/*
+		 * Verify the coercions are actually possible.  If not, we'd fail
+		 * later anyway, but we want to fail now while we have sufficient
+		 * context to produce an error cursor position.
+		 *
+		 * For all non-UNKNOWN-type cases, we verify coercibility but we
+		 * don't modify the child's expression, for fear of changing the
+		 * child query's semantics.
+		 *
+		 * If a child expression is an UNKNOWN-type Const or Param, we
+		 * want to replace it with the coerced expression.  This can only
+		 * happen when the child is a leaf set-op node.  It's safe to
+		 * replace the expression because if the child query's semantics
+		 * depended on the type of this output column, it'd have already
+		 * coerced the UNKNOWN to something else.  We want to do this
+		 * because (a) we want to verify that a Const is valid for the
+		 * target type, or resolve the actual type of an UNKNOWN Param,
+		 * and (b) we want to avoid unnecessary discrepancies between the
+		 * output type of the child query and the resolved target type.
+		 * Such a discrepancy would disable optimization in the planner.
+		 *
+		 * If it's some other UNKNOWN-type node, eg a Var, we do nothing
+		 * (knowing that coerce_to_common_type would fail).  The planner
+		 * is sometimes able to fold an UNKNOWN Var to a constant before
+		 * it has to coerce the type, so failing now would just break
+		 * cases that might work.
+		 */
+		if (lcoltype != UNKNOWNOID)
+			lcolnode = coerce_to_common_type(pstate, lcolnode, rescoltype,
+					context);
+		else if (IsA(lcolnode, Const) || IsA(lcolnode, Param))
+		{
+			lcolnode = coerce_to_common_type(pstate, lcolnode, rescoltype,
+					context);
+			ltle->expr = (Expr *) lcolnode;
+		}
+
+		if (rcoltype != UNKNOWNOID)
+			rcolnode = coerce_to_common_type(pstate, rcolnode, rescoltype,
+					context);
+		else if (IsA(rcolnode, Const) || IsA(rcolnode, Param))
+		{
+			rcolnode = coerce_to_common_type(pstate, rcolnode, rescoltype,
+					context);
+			rtle->expr = (Expr *) rcolnode;
+		}
+
+		/*
+		 * Select common collation.  A common collation is required for
+		 * all set operators except UNION ALL; see SQL:2008 7.13 <query
+		 * expression> Syntax Rule 15c.  (If we fail to identify a common
+		 * collation for a UNION ALL column, the curCollations element
+		 * will be set to InvalidOid, which may result in a runtime error
+		 * if something at a higher query level wants to use the column's
+		 * collation.)
+		 */
+		rescolcoll = select_common_collation(pstate,
+				list_make2(lcolnode, rcolnode),
+				(op->op == SETOP_UNION && op->all));
+
+		/* emit results */
+		op->colTypes = lappend_oid(op->colTypes, rescoltype);
+		op->colTypmods = lappend_int(op->colTypmods, rescoltypmod);
+		op->colCollations = lappend_oid(op->colCollations, rescolcoll);
+
+		/*
+		 * For all cases except UNION ALL, identify the grouping operators
+		 * (and, if available, sorting operators) that will be used to
+		 * eliminate duplicates.
+		 */
+		if (op->op != SETOP_UNION || !op->all)
+		{
+			SortGroupClause *grpcl = makeNode(SortGroupClause);
+			Oid			sortop;
+			Oid			eqop;
+			bool		hashable;
+			ParseCallbackState pcbstate;
+
+			setup_parser_errposition_callback(&pcbstate, pstate, bestlocation);
+
+			/* determine the eqop and optional sortop */
+			get_sort_group_operators(rescoltype, false, true, false, &sortop,
+					&eqop, NULL, &hashable);
+
+			cancel_parser_errposition_callback(&pcbstate);
+
+			/* we don't have a tlist yet, so can't assign sortgrouprefs */
+			grpcl->tleSortGroupRef = 0;
+			grpcl->eqop = eqop;
+			grpcl->sortop = sortop;
+			grpcl->nulls_first = false; /* OK with or without sortop */
+			grpcl->hashable = hashable;
+
+			op->groupClauses = lappend(op->groupClauses, grpcl);
+		}
+
+		/*
+		 * Construct a dummy tlist entry to return.  We use a SetToDefault
+		 * node for the expression, since it carries exactly the fields
+		 * needed, but any other expression node type would do as well.
+		 */
+		if (targetlist)
+		{
+			SetToDefault   *rescolnode = makeNode(SetToDefault);
+			TargetEntry	   *restle;
+
+			rescolnode->typeId = rescoltype;
+			rescolnode->typeMod = rescoltypmod;
+			rescolnode->collation = rescolcoll;
+			rescolnode->location = bestlocation;
+
+			/* no need to set resno */
+			restle = makeTargetEntry((Expr *) rescolnode, 0,
+			NULL, false );
+			*targetlist = lappend(*targetlist, restle);
+		}
 	}
+
 }
 
 /*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 19dd77d787..3dc582317c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -166,7 +166,7 @@ static void insertSelectOptions(SelectStmt *stmt,
 								Node *limitOffset, Node *limitCount,
 								WithClause *withClause,
 								core_yyscan_t yyscanner);
-static Node *makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg);
+static Node *makeSetOp(SetOperation op, bool all, List *correspondingClause, Node *larg, Node *rarg);
 static Node *doNegate(Node *n, int location);
 static void doNegateFloat(Value *v);
 static Node *makeAndExpr(Node *lexpr, Node *rexpr, int location);
@@ -394,7 +394,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				relation_expr_list dostmt_opt_list
 				transform_element_list transform_type_list
 				TriggerTransitions TriggerReferencing
-				publication_name_list
+				publication_name_list opt_corresponding_clause
 
 %type <list>	group_by_list
 %type <node>	group_by_item empty_grouping_set rollup_clause cube_clause
@@ -614,7 +614,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
 	CLUSTER COALESCE COLLATE COLLATION COLUMN COLUMNS COMMENT COMMENTS COMMIT
 	COMMITTED CONCURRENTLY CONFIGURATION CONFLICT CONNECTION CONSTRAINT
-	CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE
+	CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY CORRESPONDING COST CREATE
 	CROSS CSV CUBE CURRENT_P
 	CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA
 	CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
@@ -3579,7 +3579,10 @@ columnList:
 
 columnElem: ColId
 				{
-					$$ = (Node *) makeString($1);
+					Value *v = makeString($1);
+
+					v->location = @1;
+					$$ = (Node *) v;
 				}
 		;
 
@@ -10877,20 +10880,26 @@ simple_select:
 					n->fromClause = list_make1($2);
 					$$ = (Node *)n;
 				}
-			| select_clause UNION all_or_distinct select_clause
+			| select_clause UNION all_or_distinct opt_corresponding_clause select_clause
 				{
-					$$ = makeSetOp(SETOP_UNION, $3, $1, $4);
+					$$ = makeSetOp(SETOP_UNION, $3, $4, $1, $5);
 				}
-			| select_clause INTERSECT all_or_distinct select_clause
+			| select_clause INTERSECT all_or_distinct opt_corresponding_clause select_clause
 				{
-					$$ = makeSetOp(SETOP_INTERSECT, $3, $1, $4);
+					$$ = makeSetOp(SETOP_INTERSECT, $3, $4, $1, $5);
 				}
-			| select_clause EXCEPT all_or_distinct select_clause
+			| select_clause EXCEPT all_or_distinct opt_corresponding_clause select_clause
 				{
-					$$ = makeSetOp(SETOP_EXCEPT, $3, $1, $4);
+					$$ = makeSetOp(SETOP_EXCEPT, $3, $4, $1, $5);
 				}
 		;
 
+opt_corresponding_clause:
+			CORRESPONDING BY '(' columnList ')'	{ $$ = $4; }
+			| CORRESPONDING							{ $$ = list_make1(NIL); }
+			| /*EMPTY*/								{ $$ = NIL; }
+			;
+
 /*
  * SQL standard WITH clause looks like:
  *
@@ -14231,7 +14240,6 @@ name_list:	name
 					{ $$ = lappend($1, makeString($3)); }
 		;
 
-
 name:		ColId									{ $$ = $1; };
 
 database_name:
@@ -14548,6 +14556,7 @@ unreserved_keyword:
 			| CONTINUE_P
 			| CONVERSION_P
 			| COPY
+			| CORRESPONDING
 			| COST
 			| CSV
 			| CUBE
@@ -15381,7 +15390,7 @@ insertSelectOptions(SelectStmt *stmt,
 }
 
 static Node *
-makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg)
+makeSetOp(SetOperation op, bool all, List *correspondingClause, Node *larg, Node *rarg)
 {
 	SelectStmt *n = makeNode(SelectStmt);
 
@@ -15389,6 +15398,7 @@ makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg)
 	n->all = all;
 	n->larg = (SelectStmt *) larg;
 	n->rarg = (SelectStmt *) rarg;
+	n->correspondingClause = correspondingClause;
 	return (Node *) n;
 }
 
diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
index 0d7a2b1e1b..b553d847d8 100644
--- a/src/backend/parser/parse_type.c
+++ b/src/backend/parser/parse_type.c
@@ -735,7 +735,8 @@ typeStringToTypeName(const char *str)
 		stmt->limitCount != NULL ||
 		stmt->lockingClause != NIL ||
 		stmt->withClause != NULL ||
-		stmt->op != SETOP_NONE)
+		stmt->op != SETOP_NONE ||
+		stmt->correspondingClause != NULL)
 		goto fail;
 	if (list_length(stmt->targetList) != 1)
 		goto fail;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index d57d5568b2..60715398a2 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -5452,6 +5452,30 @@ get_setop_query(Node *setOp, Query *query, deparse_context *context,
 		}
 		if (op->all)
 			appendStringInfoString(buf, "ALL ");
+		if (op->correspondingColumns != NIL )
+		{
+			if (op->hasCorrespondingBy)
+			{
+				const char *sep;
+				ListCell *l;
+				appendStringInfoString(buf, "CORRESPONDING BY(");
+				sep = "";
+
+				foreach(l, op->correspondingColumns)
+				{
+					TargetEntry *tle = (TargetEntry *) lfirst(l);
+
+					appendStringInfoString(buf, sep);
+					appendStringInfo(buf, "%s", tle->resname);
+					sep = ", ";
+				}
+				appendStringInfoChar(buf, ')');
+
+			}
+			else
+
+				appendStringInfoString(buf, "CORRESPONDING ");
+		}
 
 		/* Always parenthesize if RHS is another setop */
 		need_paren = IsA(op->rarg, SetOperationStmt);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3a71dd5b37..ed8ce2f2d0 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1460,6 +1460,7 @@ typedef struct SelectStmt
 	 */
 	List	   *distinctClause; /* NULL, list of DISTINCT ON exprs, or
 								 * lcons(NIL,NIL) for all (SELECT DISTINCT) */
+	List	   *correspondingClause;	/* CORRESPONDING BY  clauses*/
 	IntoClause *intoClause;		/* target for SELECT INTO */
 	List	   *targetList;		/* the target list (of ResTarget) */
 	List	   *fromClause;		/* the FROM clause */
@@ -1495,7 +1496,6 @@ typedef struct SelectStmt
 	bool		all;			/* ALL specified? */
 	struct SelectStmt *larg;	/* left child */
 	struct SelectStmt *rarg;	/* right child */
-	/* Eventually add fields for CORRESPONDING spec here */
 } SelectStmt;
 
 
@@ -1525,8 +1525,8 @@ typedef struct SetOperationStmt
 	bool		all;			/* ALL specified? */
 	Node	   *larg;			/* left child */
 	Node	   *rarg;			/* right child */
-	/* Eventually add fields for CORRESPONDING spec here */
-
+	List	   *correspondingColumns;	/* list of corresponding column names */
+	bool		hasCorrespondingBy;		/* has corresponding by cluase? */
 	/* Fields derived during parse analysis: */
 	List	   *colTypes;		/* OID list of output column type OIDs */
 	List	   *colTypmods;		/* integer list of output column typmods */
diff --git a/src/include/nodes/value.h b/src/include/nodes/value.h
index ede97b7bcd..bf3b6e9b68 100644
--- a/src/include/nodes/value.h
+++ b/src/include/nodes/value.h
@@ -47,6 +47,7 @@ typedef struct Value
 		long		ival;		/* machine integer */
 		char	   *str;		/* string */
 	}			val;
+	int			location;		/* token location, or -1 if unknown */
 } Value;
 
 #define intVal(v)		(((Value *)(v))->val.ival)
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 6cd36c7fe3..fad33d3950 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -97,6 +97,7 @@ PG_KEYWORD("content", CONTENT_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("continue", CONTINUE_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("conversion", CONVERSION_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("copy", COPY, UNRESERVED_KEYWORD)
+PG_KEYWORD("corresponding", CORRESPONDING, UNRESERVED_KEYWORD)
 PG_KEYWORD("cost", COST, UNRESERVED_KEYWORD)
 PG_KEYWORD("create", CREATE, RESERVED_KEYWORD)
 PG_KEYWORD("cross", CROSS, TYPE_FUNC_NAME_KEYWORD)
diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out
index ce0c8cedf8..6b44903d7b 100644
--- a/src/test/regress/expected/create_view.out
+++ b/src/test/regress/expected/create_view.out
@@ -1597,3 +1597,28 @@ select pg_get_viewdef('tt19v', true);
 set client_min_messages = warning;
 DROP SCHEMA temp_view_test CASCADE;
 DROP SCHEMA testviewschm2 CASCADE;
+-- views with corresponding clause
+create view view_corresponding_01 as select 1 as a, 2 as b union all corresponding select 3 as a, 4 as b;
+select * from view_corresponding_01;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+create view view_corresponding_02 as select 1 as a, 2 as b union all corresponding by (a,b) select 3 as a, 4 as b, 5 as c;
+select * from view_corresponding_02;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+create view view_corresponding_03 as select 1 as a, 2 as b union all corresponding by (b,a) select 3 as a, 4 as b, 5 as c;
+select * from view_corresponding_03;
+ b | a 
+---+---
+ 2 | 1
+ 4 | 3
+(2 rows)
+
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index e8f8726c53..8c1ff70519 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -2344,6 +2344,23 @@ toyemp| SELECT emp.name,
     emp.location,
     (12 * emp.salary) AS annualsal
    FROM emp;
+view_corresponding_01| SELECT 1 AS a,
+    2 AS b
+UNION ALL CORRESPONDING
+ SELECT 3 AS a,
+    4 AS b;
+view_corresponding_02| SELECT 1 AS a,
+    2 AS b
+UNION ALL CORRESPONDING BY(a, b)
+ SELECT 3 AS a,
+    4 AS b,
+    5 AS c;
+view_corresponding_03| SELECT 1 AS b,
+    2 AS a
+UNION ALL CORRESPONDING BY(b, a)
+ SELECT 3 AS b,
+    4 AS a,
+    5 AS c;
 SELECT tablename, rulename, definition FROM pg_rules
 	ORDER BY tablename, rulename;
 pg_settings|pg_settings_n|CREATE RULE pg_settings_n AS
diff --git a/src/test/regress/expected/union.out b/src/test/regress/expected/union.out
index 4d697bada7..6451576d83 100644
--- a/src/test/regress/expected/union.out
+++ b/src/test/regress/expected/union.out
@@ -59,6 +59,87 @@ SELECT 1.1 AS two UNION SELECT 2.2 ORDER BY 1;
  2.2
 (2 rows)
 
+SELECT 1 AS two UNION CORRESPONDING SELECT 2 two UNION CORRESPONDING SELECT 2 two;
+ two 
+-----
+   2
+   1
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a) SELECT 4 a, 5 b, 6 c;
+ a 
+---
+ 1
+ 4
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b) SELECT 4 a, 5 b, 6 c;
+ a | b 
+---+---
+ 1 | 2
+ 4 | 5
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c) SELECT 4 a, 5 b, 6 c;
+ b | c 
+---+---
+ 2 | 3
+ 5 | 6
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY (c,b) SELECT 4 a, 5 b, 6 c, 8 d;
+ c | b 
+---+---
+ 3 | 2
+ 6 | 5
+(2 rows)
+
+-- CORRESPONDING column ordering, left clause's column ordering must be preserved.
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+SELECT 2 b, 1 a, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+ b | a | c 
+---+---+---
+ 2 | 1 | 3
+ 5 | 4 | 6
+(2 rows)
+
+-- CORRESPONDING BY column ordering, BY clause column ordering must be preserved.
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 4 a, 5 b, 6 c;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c, a) SELECT 4 a, 5 b, 6 c;
+ b | c | a 
+---+---+---
+ 2 | 3 | 1
+ 5 | 6 | 4
+(2 rows)
+
+-- should to fail
+SELECT 10 a, 20 b UNION ALL CORRESPONDING SELECT 10 c, 20 d;
+ERROR:  there is not any corresponding name
+LINE 1: SELECT 10 a, 20 b UNION ALL CORRESPONDING SELECT 10 c, 20 d;
+               ^
+HINT:  UNION queries with a CORRESPONDING clause must have at least one column with the same name
+SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (a,b) SELECT 10 c, 20 d;
+ERROR:  there is not any corresponding name
+LINE 1: SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (a,b) SELECT 10...
+               ^
+HINT:  UNION queries with a CORRESPONDING clause must have at least one column with the same name
+SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (x) SELECT 10 c, 20 d;
+ERROR:  there is not any corresponding name
+LINE 1: SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (x) SELECT 10 c...
+               ^
+HINT:  UNION queries with a CORRESPONDING clause must have at least one column with the same name
 -- Mixed types
 SELECT 1.1 AS two UNION SELECT 2 ORDER BY 1;
  two 
@@ -124,6 +205,105 @@ SELECT 1.1 AS two UNION (SELECT 2 UNION ALL SELECT 2) ORDER BY 1;
    2
 (2 rows)
 
+-- other corresponding clause tests,
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+-- when column is not in result, then the name should not be unique
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS b, 5 AS a;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+ 5 | 6
+(3 rows)
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x3;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x6 UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS b, 5 AS a;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+ 5 | 6
+(3 rows)
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS b, -100 AS a;
+ b 
+---
+ 2
+ 4
+ 6
+(3 rows)
+
+-- should fail
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3;
+ERROR:  corresponding column "x3" is used more times
+LINE 1: SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 U...
+                                                          ^
+HINT:  In UNION queries with CORRESPONDING clause the corresponding column names must be unique.
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3, -10 AS x3;
+ERROR:  corresponding column "x3" is used more times
+LINE 1: ...ELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3, -10 AS x3;
+                                                             ^
+HINT:  In UNION queries with CORRESPONDING clause the corresponding column names must be unique.
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(a,b,x3) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3;
+ERROR:  corresponding column "x3" is used more times
+LINE 1: SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 U...
+                                                          ^
+HINT:  In UNION queries with CORRESPONDING BY clause the corresponding column names must be unique.
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING BY(a,b,x3) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3, -10 AS x3;
+ERROR:  corresponding column "x3" is used more times
+LINE 1: ...ELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3, -10 AS x3;
+                                                             ^
+HINT:  In UNION queries with CORRESPONDING BY clause the corresponding column names must be unique.
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x6 UNION ALL CORRESPONDING BY(a,b,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+ERROR:  column name "b" is not unique in CORRESPONDING BY clause
+LINE 1: ... 0 AS x3, -1 AS x6 UNION ALL CORRESPONDING BY(a,b,b) SELECT ...
+                                                             ^
+HINT:  CORRESPONDING BY clause must contain unique column names only.
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS bb, -100 AS aa;
+ERROR:  there is not any corresponding name
+LINE 1: SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 U...
+                        ^
+HINT:  UNION queries with a CORRESPONDING clause must have at least one column with the same name
 --
 -- Try testing from tables...
 --
@@ -258,6 +438,74 @@ ORDER BY 1;
  hi de ho neighbor
 (5 rows)
 
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+         five          
+-----------------------
+ -1.2345678901234e+200
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+                     0
+(5 rows)
+
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING BY(five)
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+         five          
+-----------------------
+ -1.2345678901234e+200
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+                     0
+(5 rows)
+
+SELECT f1 AS ten FROM FLOAT8_TBL
+UNION ALL CORRESPONDING
+SELECT f1 AS ten FROM FLOAT8_TBL;
+          ten          
+-----------------------
+                     0
+                -34.84
+               -1004.3
+ -1.2345678901234e+200
+ -1.2345678901234e-200
+                     0
+                -34.84
+               -1004.3
+ -1.2345678901234e+200
+ -1.2345678901234e-200
+(10 rows)
+
+SELECT f1 AS five FROM FLOAT8_TBL
+  WHERE f1 BETWEEN -1e6 AND 1e6
+UNION CORRESPONDING
+SELECT f1 AS five FROM INT4_TBL
+  WHERE f1 BETWEEN 0 AND 1000000;
+         five          
+-----------------------
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+                     0
+                123456
+(5 rows)
+
+SELECT * FROM FLOAT8_TBL
+UNION corresponding SELECT * FROM FLOAT8_TBL;
+          f1           
+-----------------------
+                     0
+ -1.2345678901234e-200
+                -34.84
+ -1.2345678901234e+200
+               -1004.3
+(5 rows)
+
 --
 -- INTERSECT and EXCEPT
 --
@@ -320,6 +568,63 @@ SELECT q1 FROM int8_tbl EXCEPT ALL SELECT DISTINCT q2 FROM int8_tbl ORDER BY 1;
 
 SELECT q1 FROM int8_tbl EXCEPT ALL SELECT q1 FROM int8_tbl FOR NO KEY UPDATE;
 ERROR:  FOR NO KEY UPDATE is not allowed with UNION/INTERSECT/EXCEPT
+SELECT q2 FROM int8_tbl INTERSECT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+        q2        
+------------------
+              123
+ 4567890123456789
+(2 rows)
+
+SELECT q2 FROM int8_tbl INTERSECT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+        q2        
+------------------
+              123
+ 4567890123456789
+ 4567890123456789
+(3 rows)
+
+SELECT q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+        q2         
+-------------------
+ -4567890123456789
+               456
+(2 rows)
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+        q2         
+-------------------
+ -4567890123456789
+               456
+(2 rows)
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q1 AS q2 FROM int8_tbl ORDER BY 1;
+        q2         
+-------------------
+ -4567890123456789
+               456
+  4567890123456789
+(3 rows)
+
+SELECT q1 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+ q1 
+----
+(0 rows)
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+        q1        
+------------------
+              123
+ 4567890123456789
+(2 rows)
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q2 AS q1 FROM int8_tbl;
+        q1        
+------------------
+              123
+ 4567890123456789
+ 4567890123456789
+(3 rows)
+
 --
 -- Mixed types
 --
@@ -338,6 +643,21 @@ SELECT f1 FROM float8_tbl EXCEPT SELECT f1 FROM int4_tbl ORDER BY 1;
  -1.2345678901234e-200
 (4 rows)
 
+SELECT f1 FROM float8_tbl INTERSECT CORRESPONDING SELECT f1 FROM int4_tbl;
+ f1 
+----
+  0
+(1 row)
+
+SELECT f1 FROM float8_tbl EXCEPT CORRESPONDING SELECT f1 FROM int4_tbl ORDER BY 1;
+          f1           
+-----------------------
+ -1.2345678901234e+200
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+(4 rows)
+
 --
 -- Operator precedence and (((((extra))))) parentheses
 --
@@ -424,6 +744,24 @@ SELECT q1 FROM int8_tbl EXCEPT (((SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1)))
  4567890123456789
 (2 rows)
 
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2,q1 FROM int8_tbl
+ORDER BY q2,q1;
+ q1 | q2 
+----+----
+(0 rows)
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q1) SELECT q2,q1 FROM int8_tbl
+ORDER BY q1;
+ q1 
+----
+(0 rows)
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q2) SELECT q2,q1 FROM int8_tbl
+ORDER BY q2;
+ q2 
+----
+(0 rows)
+
 --
 -- New syntaxes (7.1) permit new tests
 --
@@ -459,6 +797,22 @@ SELECT '3.4'::numeric UNION SELECT 'foo';
 ERROR:  invalid input syntax for type numeric: "foo"
 LINE 1: SELECT '3.4'::numeric UNION SELECT 'foo';
                                            ^
+SELECT '3.4'::numeric AS a UNION CORRESPONDING SELECT 'foo' AS a;
+ERROR:  invalid input syntax for type numeric: "foo"
+LINE 1: ...CT '3.4'::numeric AS a UNION CORRESPONDING SELECT 'foo' AS a...
+                                                             ^
+SELECT a.f1 FROM (SELECT 'test' AS f1 FROM varchar_tbl) a
+UNION CORRESPONDING
+SELECT b.f1 FROM (SELECT f1 FROM varchar_tbl) b
+ORDER BY 1;
+  f1  
+------
+ a
+ ab
+ abcd
+ test
+(4 rows)
+
 --
 -- Test that expression-index constraints can be pushed down through
 -- UNION or UNION ALL
diff --git a/src/test/regress/sql/create_view.sql b/src/test/regress/sql/create_view.sql
index c27f1034e1..3ea31ba621 100644
--- a/src/test/regress/sql/create_view.sql
+++ b/src/test/regress/sql/create_view.sql
@@ -532,3 +532,13 @@ select pg_get_viewdef('tt19v', true);
 set client_min_messages = warning;
 DROP SCHEMA temp_view_test CASCADE;
 DROP SCHEMA testviewschm2 CASCADE;
+
+-- views with corresponding clause
+create view view_corresponding_01 as select 1 as a, 2 as b union all corresponding select 3 as a, 4 as b;
+select * from view_corresponding_01;
+
+create view view_corresponding_02 as select 1 as a, 2 as b union all corresponding by (a,b) select 3 as a, 4 as b, 5 as c;
+select * from view_corresponding_02;
+
+create view view_corresponding_03 as select 1 as a, 2 as b union all corresponding by (b,a) select 3 as a, 4 as b, 5 as c;
+select * from view_corresponding_03;
diff --git a/src/test/regress/sql/union.sql b/src/test/regress/sql/union.sql
index 48e6850798..9470e1a817 100644
--- a/src/test/regress/sql/union.sql
+++ b/src/test/regress/sql/union.sql
@@ -20,6 +20,35 @@ SELECT 1 AS three UNION SELECT 2 UNION ALL SELECT 2 ORDER BY 1;
 
 SELECT 1.1 AS two UNION SELECT 2.2 ORDER BY 1;
 
+SELECT 1 AS two UNION CORRESPONDING SELECT 2 two UNION CORRESPONDING SELECT 2 two;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY (c,b) SELECT 4 a, 5 b, 6 c, 8 d;
+
+-- CORRESPONDING column ordering, left clause's column ordering must be preserved.
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+
+SELECT 2 b, 1 a, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+
+-- CORRESPONDING BY column ordering, BY clause column ordering must be preserved.
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c, a) SELECT 4 a, 5 b, 6 c;
+
+-- should to fail
+SELECT 10 a, 20 b UNION ALL CORRESPONDING SELECT 10 c, 20 d;
+
+SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (a,b) SELECT 10 c, 20 d;
+
+SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (x) SELECT 10 c, 20 d;
+
 -- Mixed types
 
 SELECT 1.1 AS two UNION SELECT 2 ORDER BY 1;
@@ -40,6 +69,30 @@ SELECT 1.1 AS three UNION SELECT 2 UNION ALL SELECT 2 ORDER BY 1;
 
 SELECT 1.1 AS two UNION (SELECT 2 UNION ALL SELECT 2) ORDER BY 1;
 
+-- other corresponding clause tests,
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6;
+
+-- when column is not in result, then the name should not be unique
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS b, 5 AS a;
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x3;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x6 UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS b, 5 AS a;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS b, -100 AS a;
+
+-- should fail
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3, -10 AS x3;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(a,b,x3) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING BY(a,b,x3) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3, -10 AS x3;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x6 UNION ALL CORRESPONDING BY(a,b,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS bb, -100 AS aa;
+
 --
 -- Try testing from tables...
 --
@@ -90,6 +143,29 @@ UNION
 SELECT TRIM(TRAILING FROM f1) FROM CHAR_TBL
 ORDER BY 1;
 
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING BY(five)
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+
+SELECT f1 AS ten FROM FLOAT8_TBL
+UNION ALL CORRESPONDING
+SELECT f1 AS ten FROM FLOAT8_TBL;
+
+SELECT f1 AS five FROM FLOAT8_TBL
+  WHERE f1 BETWEEN -1e6 AND 1e6
+UNION CORRESPONDING
+SELECT f1 AS five FROM INT4_TBL
+  WHERE f1 BETWEEN 0 AND 1000000;
+
+SELECT * FROM FLOAT8_TBL
+UNION corresponding SELECT * FROM FLOAT8_TBL;
+
 --
 -- INTERSECT and EXCEPT
 --
@@ -112,6 +188,22 @@ SELECT q1 FROM int8_tbl EXCEPT ALL SELECT DISTINCT q2 FROM int8_tbl ORDER BY 1;
 
 SELECT q1 FROM int8_tbl EXCEPT ALL SELECT q1 FROM int8_tbl FOR NO KEY UPDATE;
 
+SELECT q2 FROM int8_tbl INTERSECT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+
+SELECT q2 FROM int8_tbl INTERSECT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+
+SELECT q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q1 AS q2 FROM int8_tbl ORDER BY 1;
+
+SELECT q1 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q2 AS q1 FROM int8_tbl;
+
 --
 -- Mixed types
 --
@@ -120,6 +212,10 @@ SELECT f1 FROM float8_tbl INTERSECT SELECT f1 FROM int4_tbl ORDER BY 1;
 
 SELECT f1 FROM float8_tbl EXCEPT SELECT f1 FROM int4_tbl ORDER BY 1;
 
+SELECT f1 FROM float8_tbl INTERSECT CORRESPONDING SELECT f1 FROM int4_tbl;
+
+SELECT f1 FROM float8_tbl EXCEPT CORRESPONDING SELECT f1 FROM int4_tbl ORDER BY 1;
+
 --
 -- Operator precedence and (((((extra))))) parentheses
 --
@@ -150,6 +246,15 @@ SELECT q1 FROM int8_tbl EXCEPT SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1;
 -- But this should work:
 SELECT q1 FROM int8_tbl EXCEPT (((SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1))) ORDER BY 1;
 
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2,q1 FROM int8_tbl
+ORDER BY q2,q1;
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q1) SELECT q2,q1 FROM int8_tbl
+ORDER BY q1;
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q2) SELECT q2,q1 FROM int8_tbl
+ORDER BY q2;
+
 --
 -- New syntaxes (7.1) permit new tests
 --
@@ -170,6 +275,13 @@ ORDER BY 1;
 -- This should fail, but it should produce an error cursor
 SELECT '3.4'::numeric UNION SELECT 'foo';
 
+SELECT '3.4'::numeric AS a UNION CORRESPONDING SELECT 'foo' AS a;
+
+SELECT a.f1 FROM (SELECT 'test' AS f1 FROM varchar_tbl) a
+UNION CORRESPONDING
+SELECT b.f1 FROM (SELECT f1 FROM varchar_tbl) b
+ORDER BY 1;
+
 --
 -- Test that expression-index constraints can be pushed down through
 -- UNION or UNION ALL
#29Surafel Temesgen
surafel3000@gmail.com
In reply to: Pavel Stehule (#28)
Re: New CORRESPONDING clause design

can you help with fixing it Pavel?

On Mon, Mar 27, 2017 at 11:48 AM, Pavel Stehule <pavel.stehule@gmail.com>
wrote:

Show quoted text

Hi

fresh update - I enhanced Value node by location field as Tom proposal.

Few more regress tests.

But I found significant issue, that needs bigger fix - Surafel, please,
can you fix it.

It crash on

SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3
UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6
UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS b, -100 AS x9;

I'll mark this patch as waiting on author

Regards

Pavel

#30Pavel Stehule
pavel.stehule@gmail.com
In reply to: Surafel Temesgen (#29)
Re: New CORRESPONDING clause design

2017-03-28 13:58 GMT+02:00 Surafel Temesgen <surafel3000@gmail.com>:

can you help with fixing it Pavel?

There must be some new preanalyze stage - you have to know result columns
before you are starting a analyze

Regards

Pavel

Show quoted text

On Mon, Mar 27, 2017 at 11:48 AM, Pavel Stehule <pavel.stehule@gmail.com>
wrote:

Hi

fresh update - I enhanced Value node by location field as Tom proposal.

Few more regress tests.

But I found significant issue, that needs bigger fix - Surafel, please,
can you fix it.

It crash on

SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3
UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6
UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS b, -100 AS x9;

I'll mark this patch as waiting on author

Regards

Pavel

#31Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#30)
Re: New CORRESPONDING clause design

2017-03-28 14:18 GMT+02:00 Pavel Stehule <pavel.stehule@gmail.com>:

2017-03-28 13:58 GMT+02:00 Surafel Temesgen <surafel3000@gmail.com>:

can you help with fixing it Pavel?

There must be some new preanalyze stage - you have to know result columns
before you are starting a analyze

maybe some recheck after analyze stage to remove invalid columns can be
good enough.

Regards

Pavel

Show quoted text

Regards

Pavel

On Mon, Mar 27, 2017 at 11:48 AM, Pavel Stehule <pavel.stehule@gmail.com>
wrote:

Hi

fresh update - I enhanced Value node by location field as Tom proposal.

Few more regress tests.

But I found significant issue, that needs bigger fix - Surafel, please,
can you fix it.

It crash on

SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3
UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS
x6
UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS b, -100 AS x9;

I'll mark this patch as waiting on author

Regards

Pavel

#32Surafel Temesgen
surafel3000@gmail.com
In reply to: Pavel Stehule (#31)
1 attachment(s)
Re: New CORRESPONDING clause design

hi

Thank you very much for your help .
here is the patch fix that issue as you suggest

Regards

Surafel

On Tue, Mar 28, 2017 at 5:44 PM, Pavel Stehule <pavel.stehule@gmail.com>
wrote:

Show quoted text

2017-03-28 14:18 GMT+02:00 Pavel Stehule <pavel.stehule@gmail.com>:

2017-03-28 13:58 GMT+02:00 Surafel Temesgen <surafel3000@gmail.com>:

can you help with fixing it Pavel?

There must be some new preanalyze stage - you have to know result columns
before you are starting a analyze

maybe some recheck after analyze stage to remove invalid columns can be
good enough.

Regards

Pavel

Regards

Pavel

On Mon, Mar 27, 2017 at 11:48 AM, Pavel Stehule <pavel.stehule@gmail.com

wrote:

Hi

fresh update - I enhanced Value node by location field as Tom proposal.

Few more regress tests.

But I found significant issue, that needs bigger fix - Surafel, please,
can you fix it.

It crash on

SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3
UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS
x6
UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS b, -100 AS x9;

I'll mark this patch as waiting on author

Regards

Pavel

Attachments:

corresponding_clause_v10.patchapplication/octet-stream; name=corresponding_clause_v10.patchDownload
diff --git a/doc/src/sgml/queries.sgml b/doc/src/sgml/queries.sgml
index 30792f4..2d60718 100644
--- a/doc/src/sgml/queries.sgml
+++ b/doc/src/sgml/queries.sgml
@@ -1601,6 +1601,9 @@ SELECT DISTINCT ON (<replaceable>expression</replaceable> <optional>, <replaceab
    <primary>EXCEPT</primary>
   </indexterm>
   <indexterm zone="queries-union">
+   <primary>CORRESPONDING</primary>
+  </indexterm>
+  <indexterm zone="queries-union">
    <primary>set union</primary>
   </indexterm>
   <indexterm zone="queries-union">
@@ -1617,9 +1620,9 @@ SELECT DISTINCT ON (<replaceable>expression</replaceable> <optional>, <replaceab
    The results of two queries can be combined using the set operations
    union, intersection, and difference.  The syntax is
 <synopsis>
-<replaceable>query1</replaceable> UNION <optional>ALL</optional> <replaceable>query2</replaceable>
-<replaceable>query1</replaceable> INTERSECT <optional>ALL</optional> <replaceable>query2</replaceable>
-<replaceable>query1</replaceable> EXCEPT <optional>ALL</optional> <replaceable>query2</replaceable>
+<replaceable>query1</replaceable> UNION <optional>ALL</optional> <optional>CORRESPONDING</optional> <optional>BY</optional> <replaceable>query2</replaceable>
+<replaceable>query1</replaceable> INTERSECT <optional>ALL</optional> <optional>CORRESPONDING</optional> <optional>BY</optional> <replaceable>query2</replaceable>
+<replaceable>query1</replaceable> EXCEPT <optional>ALL</optional> <optional>CORRESPONDING</optional> <optional>BY</optional> <replaceable>query2</replaceable>
 </synopsis>
    <replaceable>query1</replaceable> and
    <replaceable>query2</replaceable> are queries that can use any of
@@ -1659,15 +1662,32 @@ SELECT DISTINCT ON (<replaceable>expression</replaceable> <optional>, <replaceab
   </para>
 
   <para>
-   In order to calculate the union, intersection, or difference of two
-   queries, the two queries must be <quote>union compatible</quote>,
-   which means that they return the same number of columns and
-   the corresponding columns have compatible data types, as
-   described in <xref linkend="typeconv-union-case">.
+   <literal>EXCEPT</> returns all rows that are in the result of
+   <replaceable>query1</replaceable> but not in the result of
+   <replaceable>query2</replaceable>.  (This is sometimes called the
+   <firstterm>difference</> between two queries.)  Again, duplicates
+   are eliminated unless <literal>EXCEPT ALL</> is used.
+  </para>
+
+  <para>
+   <literal>CORRESPONDING</> returns all columns that are in both 
+   <replaceable>query1</> and <replaceable>query2</> with the same name.
+  </para>
+
+  <para>
+   <literal>CORRESPONDING BY</> returns all columns in the column list 
+   that are also in both <replaceable>query1</> and 
+   <replaceable>query2</> with the same name. The names in column list
+   must be unique.
+  </para>
+
+  <para>
+   The names of columns in result when <literal>CORRESPONDING</> or
+   <literal>CORRESPONDING BY</> clause is used must be unique in
+   <replaceable>query1</replaceable> and <replaceable>query2</replaceable>.
   </para>
  </sect1>
 
-
  <sect1 id="queries-order">
   <title>Sorting Rows</title>
 
diff --git a/doc/src/sgml/sql.sgml b/doc/src/sgml/sql.sgml
index 57396d7..f98c22e 100644
--- a/doc/src/sgml/sql.sgml
+++ b/doc/src/sgml/sql.sgml
@@ -859,7 +859,7 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="PARAMETER">expression</replac
     [ WHERE <replaceable class="PARAMETER">condition</replaceable> ]
     [ GROUP BY <replaceable class="PARAMETER">expression</replaceable> [, ...] ]
     [ HAVING <replaceable class="PARAMETER">condition</replaceable> [, ...] ]
-    [ { UNION | INTERSECT | EXCEPT } [ ALL ] <replaceable class="PARAMETER">select</replaceable> ]
+    [ { UNION | INTERSECT | EXCEPT } [ ALL ] [ CORRESPONDING [ BY ( <replaceable class="PARAMETER">expression</replaceable> ) ] ] <replaceable class="PARAMETER">select</replaceable> ]
     [ ORDER BY <replaceable class="parameter">expression</replaceable> [ ASC | DESC | USING <replaceable class="parameter">operator</replaceable> ] [ NULLS { FIRST | LAST } ] [, ...] ]
     [ LIMIT { <replaceable class="PARAMETER">count</replaceable> | ALL } ]
     [ OFFSET <replaceable class="PARAMETER">start</replaceable> ]
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 30d733e..41f5155 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2870,6 +2870,7 @@ _copySelectStmt(const SelectStmt *from)
 	COPY_NODE_FIELD(withClause);
 	COPY_SCALAR_FIELD(op);
 	COPY_SCALAR_FIELD(all);
+	COPY_NODE_FIELD(correspondingClause);
 	COPY_NODE_FIELD(larg);
 	COPY_NODE_FIELD(rarg);
 
@@ -2885,6 +2886,8 @@ _copySetOperationStmt(const SetOperationStmt *from)
 	COPY_SCALAR_FIELD(all);
 	COPY_NODE_FIELD(larg);
 	COPY_NODE_FIELD(rarg);
+	COPY_NODE_FIELD(correspondingColumns);
+	COPY_SCALAR_FIELD(hasCorrespondingBy);
 	COPY_NODE_FIELD(colTypes);
 	COPY_NODE_FIELD(colTypmods);
 	COPY_NODE_FIELD(colCollations);
@@ -4442,6 +4445,8 @@ _copyValue(const Value *from)
 				 (int) from->type);
 			break;
 	}
+	COPY_LOCATION_FIELD(location);
+
 	return newnode;
 }
 
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 55c73b7..8483939 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1020,6 +1020,7 @@ _equalSelectStmt(const SelectStmt *a, const SelectStmt *b)
 	COMPARE_NODE_FIELD(withClause);
 	COMPARE_SCALAR_FIELD(op);
 	COMPARE_SCALAR_FIELD(all);
+	COMPARE_NODE_FIELD(correspondingClause);
 	COMPARE_NODE_FIELD(larg);
 	COMPARE_NODE_FIELD(rarg);
 
@@ -1033,6 +1034,8 @@ _equalSetOperationStmt(const SetOperationStmt *a, const SetOperationStmt *b)
 	COMPARE_SCALAR_FIELD(all);
 	COMPARE_NODE_FIELD(larg);
 	COMPARE_NODE_FIELD(rarg);
+	COMPARE_NODE_FIELD(correspondingColumns);
+	COMPARE_SCALAR_FIELD(hasCorrespondingBy);
 	COMPARE_NODE_FIELD(colTypes);
 	COMPARE_NODE_FIELD(colTypmods);
 	COMPARE_NODE_FIELD(colCollations);
@@ -2857,6 +2860,8 @@ _equalValue(const Value *a, const Value *b)
 			break;
 	}
 
+	COMPARE_LOCATION_FIELD(location);
+
 	return true;
 }
 
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index c2f03b7..ca39c1c 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -3411,6 +3411,8 @@ raw_expression_tree_walker(Node *node,
 					return true;
 				if (walker(stmt->lockingClause, context))
 					return true;
+				if (walker(stmt->correspondingClause, context))
+					return true;
 				if (walker(stmt->withClause, context))
 					return true;
 				if (walker(stmt->larg, context))
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 1560ac3..46e60e8 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2526,6 +2526,7 @@ _outSelectStmt(StringInfo str, const SelectStmt *node)
 	WRITE_NODE_FIELD(withClause);
 	WRITE_ENUM_FIELD(op, SetOperation);
 	WRITE_BOOL_FIELD(all);
+	WRITE_NODE_FIELD(correspondingClause);
 	WRITE_NODE_FIELD(larg);
 	WRITE_NODE_FIELD(rarg);
 }
@@ -2833,6 +2834,8 @@ _outSetOperationStmt(StringInfo str, const SetOperationStmt *node)
 	WRITE_BOOL_FIELD(all);
 	WRITE_NODE_FIELD(larg);
 	WRITE_NODE_FIELD(rarg);
+	WRITE_NODE_FIELD(correspondingColumns);
+	WRITE_BOOL_FIELD(hasCorrespondingBy);
 	WRITE_NODE_FIELD(colTypes);
 	WRITE_NODE_FIELD(colTypmods);
 	WRITE_NODE_FIELD(colCollations);
@@ -3007,6 +3010,7 @@ _outAExpr(StringInfo str, const A_Expr *node)
 static void
 _outValue(StringInfo str, const Value *value)
 {
+	/* NB: this isn't a complete set of fields */
 	switch (value->type)
 	{
 		case T_Integer:
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index dcfa6ee..bf72f52 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -418,6 +418,8 @@ _readSetOperationStmt(void)
 	READ_BOOL_FIELD(all);
 	READ_NODE_FIELD(larg);
 	READ_NODE_FIELD(rarg);
+	READ_NODE_FIELD(correspondingColumns);
+	READ_BOOL_FIELD(hasCorrespondingBy);
 	READ_NODE_FIELD(colTypes);
 	READ_NODE_FIELD(colTypmods);
 	READ_NODE_FIELD(colCollations);
diff --git a/src/backend/nodes/value.c b/src/backend/nodes/value.c
index 5d2f96c..72afc17 100644
--- a/src/backend/nodes/value.c
+++ b/src/backend/nodes/value.c
@@ -26,6 +26,7 @@ makeInteger(long i)
 
 	v->type = T_Integer;
 	v->val.ival = i;
+	v->location = -1;
 	return v;
 }
 
@@ -41,6 +42,7 @@ makeFloat(char *numericStr)
 
 	v->type = T_Float;
 	v->val.str = numericStr;
+	v->location = -1;
 	return v;
 }
 
@@ -56,6 +58,7 @@ makeString(char *str)
 
 	v->type = T_String;
 	v->val.str = str;
+	v->location = -1;
 	return v;
 }
 
@@ -71,5 +74,6 @@ makeBitString(char *str)
 
 	v->type = T_BitString;
 	v->val.str = str;
+	v->location = -1;
 	return v;
 }
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 06e843d..519c921 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -91,7 +91,8 @@ static List *generate_setop_tlist(List *colTypes, List *colCollations,
 					 Index varno,
 					 bool hack_constants,
 					 List *input_tlist,
-					 List *refnames_tlist);
+					 List *refnames_tlist,
+					 bool no_corresponding);
 static List *generate_append_tlist(List *colTypes, List *colCollations,
 					  bool flag,
 					  List *input_tlists,
@@ -110,6 +111,7 @@ static Node *adjust_appendrel_attrs_mutator(Node *node,
 static Relids adjust_relid_set(Relids relids, Index oldrelid, Index newrelid);
 static List *adjust_inherited_tlist(List *tlist,
 					   AppendRelInfo *context);
+static List *make_corresponding_target(List *corresponding_list, List *subroot_list);
 
 
 /*
@@ -187,6 +189,24 @@ plan_set_operations(PlannerInfo *root)
 									   leftmostQuery->targetList,
 									   &top_tlist);
 	}
+	/*
+	 * If corresponding column specified, we take column names from it.
+	 */
+	else if (topop->correspondingColumns != NIL )
+	{
+		/*
+		 * Recurse on setOperations tree to generate paths for set ops. The
+		 * final output path should have just the column types shown as the
+		 * output from the top-level node, plus possibly resjunk working
+		 * columns (we can rely on upper-level nodes to deal with that).
+		 */
+		path = recurse_set_operations((Node *) topop, root,
+									  topop->colTypes, topop->colCollations,
+									  true, -1,
+									  topop->correspondingColumns,
+									  &top_tlist,
+									  NULL);
+	}
 	else
 	{
 		/*
@@ -252,6 +272,8 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 					   List **pTargetList,
 					   double *pNumGroups)
 {
+	SetOperationStmt *topop = (SetOperationStmt *) root->parse->setOperations;
+
 	if (IsA(setOp, RangeTblRef))
 	{
 		RangeTblRef *rtr = (RangeTblRef *) setOp;
@@ -316,23 +338,53 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 		path = (Path *) create_subqueryscan_path(root, rel, subpath,
 												 NIL, NULL);
 
-		/*
-		 * Figure out the appropriate target list, and update the
-		 * SubqueryScanPath with the PathTarget form of that.
-		 */
-		tlist = generate_setop_tlist(colTypes, colCollations,
+		if (topop->correspondingColumns != NIL )
+		{
+			List	    *correspondingTarget;
+
+			/*
+			 * make target list that only contains corresponding column
+			 * from sub-queries list ito use it for projection
+			 */
+			correspondingTarget = make_corresponding_target(
+											  topop->correspondingColumns,
+											  subroot->processed_tlist);
+
+			/*
+			 * Figure out the appropriate target list, and update the
+			 * SubqueryScanPath with the PathTarget form of that.
+			 */
+			tlist = generate_setop_tlist(colTypes, colCollations, flag,
+											  rtr->rtindex, true,
+											  correspondingTarget,
+											  refnames_tlist, false);
+
+			path = apply_projection_to_path(root, rel, path,
+					create_pathtarget(root, tlist));
+
+			/* Return the fully-fledged tlist to caller, too */
+			*pTargetList = tlist;
+
+		}
+		else
+		{
+			/*
+			 * Figure out the appropriate target list, and update the
+			 * SubqueryScanPath with the PathTarget form of that.
+			 */
+			tlist = generate_setop_tlist(colTypes, colCollations,
 									 flag,
 									 rtr->rtindex,
 									 true,
 									 subroot->processed_tlist,
-									 refnames_tlist);
+									 refnames_tlist, true);
 
-		path = apply_projection_to_path(root, rel, path,
+			path = apply_projection_to_path(root, rel, path,
 										create_pathtarget(root, tlist));
 
-		/* Return the fully-fledged tlist to caller, too */
-		*pTargetList = tlist;
-
+			/* Return the fully-fledged tlist to caller, too */
+			*pTargetList = tlist;
+		}
 		/*
 		 * Estimate number of groups if caller wants it.  If the subquery used
 		 * grouping or aggregation, its output is probably mostly unique
@@ -392,7 +444,7 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 												0,
 												false,
 												*pTargetList,
-												refnames_tlist);
+												refnames_tlist, true);
 			path = apply_projection_to_path(root,
 											path->parent,
 											path,
@@ -1004,7 +1056,8 @@ generate_setop_tlist(List *colTypes, List *colCollations,
 					 Index varno,
 					 bool hack_constants,
 					 List *input_tlist,
-					 List *refnames_tlist)
+					 List *refnames_tlist,
+					 bool no_corresponding)
 {
 	List	   *tlist = NIL;
 	int			resno = 1;
@@ -1026,8 +1079,8 @@ generate_setop_tlist(List *colTypes, List *colCollations,
 
 		rtlc = lnext(rtlc);
 
-		Assert(inputtle->resno == resno);
-		Assert(reftle->resno == resno);
+		Assert(!no_corresponding || inputtle->resno == resno);
+		Assert(!no_corresponding || reftle->resno == resno);
 		Assert(!inputtle->resjunk);
 		Assert(!reftle->resjunk);
 
@@ -2112,3 +2165,70 @@ adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node,
 	/* Now translate for this child */
 	return adjust_appendrel_attrs(root, node, appinfo);
 }
+
+/*
+ * generate target list from left target list with the order
+ * of right target list
+ */
+static List *
+make_corresponding_target(List *corresponding_list, List *subroot_list)
+{
+	Index internal = 0;
+	ListCell   *ltl;
+	ListCell   *rtl;
+	int			size;
+	int			i;
+	List *matchingColumns = NIL;
+	TargetEntry *simple_te_array;
+
+	size = list_length(corresponding_list) + 1;
+
+	/* Use array to find the order of corresponding columen */
+	simple_te_array = (TargetEntry *) palloc0(size * sizeof(TargetEntry));
+	foreach(ltl, corresponding_list)
+	{
+		foreach(rtl, subroot_list)
+		{
+			TargetEntry *ltle = (TargetEntry *) lfirst(ltl);
+			TargetEntry *rtle = (TargetEntry *) lfirst(rtl);
+
+			/* Names of the columns must be resolved before calling this method. */
+			Assert(ltle->resname != NULL);
+			Assert(rtle->resname != NULL);
+
+			/* If column names are the same, add it to array. */
+			if (strcmp(ltle->resname, rtle->resname) == 0)
+			{
+				simple_te_array[internal].xpr = rtle->xpr;
+				simple_te_array[internal].expr = rtle->expr;
+				simple_te_array[internal].resno = rtle->resno;
+				simple_te_array[internal].resname = rtle->resname;
+				simple_te_array[internal].ressortgroupref =
+						rtle->ressortgroupref;
+				simple_te_array[internal].resorigtbl = rtle->resorigtbl;
+				simple_te_array[internal].resorigcol = rtle->resorigcol;
+				simple_te_array[internal].resjunk = rtle->resjunk;
+				internal++;
+				continue;
+			}
+		}
+	}
+	/* traverse the array and make targetlist */
+	for (i = 0; i < internal; i++)
+	{
+		TargetEntry *tle = makeNode(TargetEntry);
+
+		tle->xpr = simple_te_array[i].xpr;
+		tle->expr = simple_te_array[i].expr;
+		tle->resno = simple_te_array[i].resno;
+		tle->resname = simple_te_array[i].resname;
+		tle->ressortgroupref = simple_te_array[i].ressortgroupref;
+		tle->resorigtbl = simple_te_array[i].resorigtbl;
+		tle->resorigcol = simple_te_array[i].resorigcol;
+		tle->resjunk = simple_te_array[i].resjunk;
+
+		matchingColumns = lappend(matchingColumns, tle);
+
+	}
+	return matchingColumns;
+}
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 0f7659b..20b780e 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -76,10 +76,18 @@ static Query *transformCreateTableAsStmt(ParseState *pstate,
 						   CreateTableAsStmt *stmt);
 static void transformLockingClause(ParseState *pstate, Query *qry,
 					   LockingClause *lc, bool pushedDown);
+static void makeUnionDatatype(List *ltargetlist, List *rtargetlist,
+		SetOperationStmt *op, List **targetlist, ParseState *parentParseState,
+		const char *context);
 #ifdef RAW_EXPRESSION_COVERAGE_TEST
 static bool test_raw_expression_coverage(Node *node, void *context);
 #endif
-
+static List *CommonColumns(List *ltargetlist, List *rtargetlist, bool filtered,
+								 ParseState *pstate, const char *context);
+static List *FilterColumnsByNames(List *common_columns, List *filter,
+								 ParseState *pstate, const char *context);
+static List *FilterColumnsByTL(List *targetlist, List *filter, bool check_uniq,
+								 ParseState *pstate, const char *context);
 
 /*
  * parse_analyze
@@ -1664,7 +1672,37 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->targetList = NIL;
 	targetvars = NIL;
 	targetnames = NIL;
-	left_tlist = list_head(leftmostQuery->targetList);
+
+	/*
+	 * for corresponding clause limits top-level query targetlist to those
+	 * corresponding column list only
+	 */
+	if (sostmt->correspondingColumns != NIL )
+	{
+		left_tlist = list_head(sostmt->correspondingColumns);
+		/*
+		 * In the case of corresponding without by clause property across
+		 * the statement may differ
+		 */
+		if (!sostmt->hasCorrespondingBy)
+		{
+			Node *correspodning_node;
+			correspodning_node = sostmt->larg;
+			while (correspodning_node && IsA(correspodning_node, SetOperationStmt))
+			{
+				SetOperationStmt *op = (SetOperationStmt *) correspodning_node;
+				op->correspondingColumns = sostmt->correspondingColumns;
+				op->colTypes = sostmt->colTypes;
+				op->colTypmods = sostmt->colTypmods;
+				op->colCollations = sostmt->colCollations;
+				op->groupClauses = sostmt->groupClauses;
+
+				correspodning_node = op->larg;
+			}
+		}
+	}
+	else
+		left_tlist = list_head(leftmostQuery->targetList);
 
 	forthree(lct, sostmt->colTypes,
 			 lcm, sostmt->colTypmods,
@@ -1924,8 +1962,6 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 		SetOperationStmt *op = makeNode(SetOperationStmt);
 		List	   *ltargetlist;
 		List	   *rtargetlist;
-		ListCell   *ltl;
-		ListCell   *rtl;
 		const char *context;
 
 		context = (stmt->op == SETOP_UNION ? "UNION" :
@@ -1936,6 +1972,84 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 		op->all = stmt->all;
 
 		/*
+		 * If CORRESPONDING is specified, syntax and column name validities checked,
+		 * column filtering is done by a subquery later on.
+		 */
+		if (stmt->correspondingClause == NIL )
+		{
+			/* No CORRESPONDING clause, no operation needed for column filtering */
+			op->correspondingColumns = stmt->correspondingClause;
+			op->hasCorrespondingBy = false;
+		}
+		else
+		{
+			/*
+			 * CORRESPONDING clause, find matching column names from both tables.
+			 * If there are none then it is a syntax error.
+			 */
+			Query	   *largQuery;
+			Query	   *rargQuery;
+			List	   *matchingColumns;
+			List	   *rightCorrespondingColumns;
+
+			op->hasCorrespondingBy = linitial(stmt->correspondingClause) != NULL;
+
+			/* Analyze left query to resolve column names. */
+			largQuery = parse_sub_analyze((Node *) stmt->larg,
+										  pstate, NULL, false, false );
+
+			/* Analyze right query to resolve column names. */
+			rargQuery = parse_sub_analyze((Node *) stmt->rarg,
+										  pstate, NULL, false, false );
+
+			/* Find matching columns from both queries. */
+			matchingColumns = CommonColumns(largQuery->targetList,
+											rargQuery->targetList,
+											op->hasCorrespondingBy,
+											pstate,
+											context);
+
+			/*
+			 * If matchingColumns is empty, there is an error.
+			 * At least one column in the select lists must have the same name.
+			 */
+			if (matchingColumns == NIL)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("there is not any corresponding name"),
+	 errhint("%s queries with a CORRESPONDING clause must have at least one column with the same name",
+								 context),
+						 parser_errposition(pstate,
+									exprLocation((Node *)
+									linitial(largQuery->targetList)))));
+
+			/* Use column filter when it is known */
+			if (op->hasCorrespondingBy)
+				matchingColumns = FilterColumnsByNames(matchingColumns,
+													   stmt->correspondingClause,
+													   pstate,
+													   context);
+
+			op->correspondingColumns = matchingColumns;
+
+			/*
+			 * When we know matching columns, we can quickly create
+			 * corresponding target list for right target list. It is faster,
+			 * than using symmetry. Ensure unique columns when hasCorrespondingBy
+			 * is true - in this case, the uniq is not checked already.
+			 */
+			rightCorrespondingColumns = FilterColumnsByTL(rargQuery->targetList,
+														  matchingColumns,
+														  op->hasCorrespondingBy,
+														  pstate,
+														  context);
+
+			/* make union'd datatype of output column */
+			makeUnionDatatype(matchingColumns, rightCorrespondingColumns,
+								op, targetlist, pstate, context);
+		}
+
+		/*
 		 * Recursively transform the left child node.
 		 */
 		op->larg = transformSetOperationTree(pstate, stmt->larg,
@@ -1960,173 +2074,20 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 											 false,
 											 &rtargetlist);
 
-		/*
-		 * Verify that the two children have the same number of non-junk
-		 * columns, and determine the types of the merged output columns.
-		 */
-		if (list_length(ltargetlist) != list_length(rtargetlist))
-			ereport(ERROR,
-					(errcode(ERRCODE_SYNTAX_ERROR),
-				 errmsg("each %s query must have the same number of columns",
-						context),
-					 parser_errposition(pstate,
-										exprLocation((Node *) rtargetlist))));
-
-		if (targetlist)
-			*targetlist = NIL;
-		op->colTypes = NIL;
-		op->colTypmods = NIL;
-		op->colCollations = NIL;
-		op->groupClauses = NIL;
-		forboth(ltl, ltargetlist, rtl, rtargetlist)
+		if (op->correspondingColumns == NIL )
 		{
-			TargetEntry *ltle = (TargetEntry *) lfirst(ltl);
-			TargetEntry *rtle = (TargetEntry *) lfirst(rtl);
-			Node	   *lcolnode = (Node *) ltle->expr;
-			Node	   *rcolnode = (Node *) rtle->expr;
-			Oid			lcoltype = exprType(lcolnode);
-			Oid			rcoltype = exprType(rcolnode);
-			int32		lcoltypmod = exprTypmod(lcolnode);
-			int32		rcoltypmod = exprTypmod(rcolnode);
-			Node	   *bestexpr;
-			int			bestlocation;
-			Oid			rescoltype;
-			int32		rescoltypmod;
-			Oid			rescolcoll;
-
-			/* select common type, same as CASE et al */
-			rescoltype = select_common_type(pstate,
-											list_make2(lcolnode, rcolnode),
-											context,
-											&bestexpr);
-			bestlocation = exprLocation(bestexpr);
-			/* if same type and same typmod, use typmod; else default */
-			if (lcoltype == rcoltype && lcoltypmod == rcoltypmod)
-				rescoltypmod = lcoltypmod;
-			else
-				rescoltypmod = -1;
-
-			/*
-			 * Verify the coercions are actually possible.  If not, we'd fail
-			 * later anyway, but we want to fail now while we have sufficient
-			 * context to produce an error cursor position.
-			 *
-			 * For all non-UNKNOWN-type cases, we verify coercibility but we
-			 * don't modify the child's expression, for fear of changing the
-			 * child query's semantics.
-			 *
-			 * If a child expression is an UNKNOWN-type Const or Param, we
-			 * want to replace it with the coerced expression.  This can only
-			 * happen when the child is a leaf set-op node.  It's safe to
-			 * replace the expression because if the child query's semantics
-			 * depended on the type of this output column, it'd have already
-			 * coerced the UNKNOWN to something else.  We want to do this
-			 * because (a) we want to verify that a Const is valid for the
-			 * target type, or resolve the actual type of an UNKNOWN Param,
-			 * and (b) we want to avoid unnecessary discrepancies between the
-			 * output type of the child query and the resolved target type.
-			 * Such a discrepancy would disable optimization in the planner.
-			 *
-			 * If it's some other UNKNOWN-type node, eg a Var, we do nothing
-			 * (knowing that coerce_to_common_type would fail).  The planner
-			 * is sometimes able to fold an UNKNOWN Var to a constant before
-			 * it has to coerce the type, so failing now would just break
-			 * cases that might work.
-			 */
-			if (lcoltype != UNKNOWNOID)
-				lcolnode = coerce_to_common_type(pstate, lcolnode,
-												 rescoltype, context);
-			else if (IsA(lcolnode, Const) ||
-					 IsA(lcolnode, Param))
-			{
-				lcolnode = coerce_to_common_type(pstate, lcolnode,
-												 rescoltype, context);
-				ltle->expr = (Expr *) lcolnode;
-			}
-
-			if (rcoltype != UNKNOWNOID)
-				rcolnode = coerce_to_common_type(pstate, rcolnode,
-												 rescoltype, context);
-			else if (IsA(rcolnode, Const) ||
-					 IsA(rcolnode, Param))
-			{
-				rcolnode = coerce_to_common_type(pstate, rcolnode,
-												 rescoltype, context);
-				rtle->expr = (Expr *) rcolnode;
-			}
-
-			/*
-			 * Select common collation.  A common collation is required for
-			 * all set operators except UNION ALL; see SQL:2008 7.13 <query
-			 * expression> Syntax Rule 15c.  (If we fail to identify a common
-			 * collation for a UNION ALL column, the curCollations element
-			 * will be set to InvalidOid, which may result in a runtime error
-			 * if something at a higher query level wants to use the column's
-			 * collation.)
-			 */
-			rescolcoll = select_common_collation(pstate,
-											  list_make2(lcolnode, rcolnode),
-										 (op->op == SETOP_UNION && op->all));
-
-			/* emit results */
-			op->colTypes = lappend_oid(op->colTypes, rescoltype);
-			op->colTypmods = lappend_int(op->colTypmods, rescoltypmod);
-			op->colCollations = lappend_oid(op->colCollations, rescolcoll);
-
-			/*
-			 * For all cases except UNION ALL, identify the grouping operators
-			 * (and, if available, sorting operators) that will be used to
-			 * eliminate duplicates.
-			 */
-			if (op->op != SETOP_UNION || !op->all)
-			{
-				SortGroupClause *grpcl = makeNode(SortGroupClause);
-				Oid			sortop;
-				Oid			eqop;
-				bool		hashable;
-				ParseCallbackState pcbstate;
-
-				setup_parser_errposition_callback(&pcbstate, pstate,
-												  bestlocation);
-
-				/* determine the eqop and optional sortop */
-				get_sort_group_operators(rescoltype,
-										 false, true, false,
-										 &sortop, &eqop, NULL,
-										 &hashable);
-
-				cancel_parser_errposition_callback(&pcbstate);
-
-				/* we don't have a tlist yet, so can't assign sortgrouprefs */
-				grpcl->tleSortGroupRef = 0;
-				grpcl->eqop = eqop;
-				grpcl->sortop = sortop;
-				grpcl->nulls_first = false;		/* OK with or without sortop */
-				grpcl->hashable = hashable;
-
-				op->groupClauses = lappend(op->groupClauses, grpcl);
-			}
-
-			/*
-			 * Construct a dummy tlist entry to return.  We use a SetToDefault
-			 * node for the expression, since it carries exactly the fields
-			 * needed, but any other expression node type would do as well.
-			 */
-			if (targetlist)
-			{
-				SetToDefault *rescolnode = makeNode(SetToDefault);
-				TargetEntry *restle;
-
-				rescolnode->typeId = rescoltype;
-				rescolnode->typeMod = rescoltypmod;
-				rescolnode->collation = rescolcoll;
-				rescolnode->location = bestlocation;
-				restle = makeTargetEntry((Expr *) rescolnode,
-										 0,		/* no need to set resno */
-										 NULL,
-										 false);
-				*targetlist = lappend(*targetlist, restle);
-			}
+			makeUnionDatatype(ltargetlist, rtargetlist, op, targetlist, pstate,
+					context);
+			/*
+			 * Verify that the two children have the same number of non-junk
+			 * columns, and determine the types of the merged output columns.
+			 */
+			if (list_length(ltargetlist) != list_length(rtargetlist))
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("each %s query must have the same number of columns", context),
+						 parser_errposition(pstate,
+											exprLocation((Node *) rtargetlist))));
 		}
 
 		return (Node *) op;
@@ -2134,6 +2095,397 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 }
 
 /*
+ * Processes targetlists of two queries for columns with same names to use
+ * with UNION/INTERSECT/EXCEPT CORRESPONDING. filtered is true, when
+ * CORRESPONDING BY is used. When it is false, we can check uniq names
+ * in rtargetlist here.
+ */
+static List *
+CommonColumns(List *ltargetlist, List *rtargetlist, bool filtered,
+			  ParseState *pstate, const char *context)
+{
+	List	   *common_columns = NIL;
+	ListCell   *ltlc;
+	ListCell   *rtlc;
+	int			resno = 1;
+
+	foreach(ltlc, ltargetlist)
+	{
+		TargetEntry *lte = (TargetEntry *) lfirst(ltlc);
+		bool		found = false;
+
+		Assert(lte->resname != NULL);
+
+		foreach(rtlc, rtargetlist)
+		{
+			ListCell   *lc;
+			TargetEntry *rte = (TargetEntry *) lfirst(rtlc);
+
+			Assert(rte->resname != NULL);
+
+			if (strcmp(lte->resname, rte->resname) == 0)
+			{
+				if (filtered)
+				{
+					/*
+					 * We found common column, but we don't know if it
+					 * is in CORRESPONDING BY list - so don't try do more
+					 * work here. The column list will be modified later,
+					 * so use shall copy here.
+					 */
+					common_columns = lappend(common_columns, lte);
+					break;
+				}
+
+				/* If same column name mentioned more than once it is syntax error . */
+				if (found)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("corresponding column \"%s\" is used more times", rte->resname),
+	 errhint("In %s queries with CORRESPONDING clause the corresponding column names must be unique.",
+									context),
+							 parser_errposition(pstate,
+												exprLocation((Node *) rte))));
+
+				found = true;
+
+				/* In this case, common_columns must be unique */
+				foreach(lc, common_columns)
+				{
+					TargetEntry *te = (TargetEntry *) lfirst(lc);
+
+					if (strcmp(te->resname, lte->resname) == 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("corresponding column \"%s\" is used more times", lte->resname),
+	 errhint("In %s queries with CORRESPONDING clause the corresponding column names must be unique.",
+										context),
+								 parser_errposition(pstate,
+													exprLocation((Node *) lte))));
+				}
+
+				/* When is not any other filter create final te */
+				common_columns = lappend(common_columns,
+										 makeTargetEntry(lte->expr,
+														 (AttrNumber) resno++,
+														 lte->resname,
+														 false));
+			}
+		}
+	}
+
+	return common_columns;
+}
+
+/*
+ * Returns filtered common columns list - filter is based on CORRESPONDING BY
+ * list Ensure CORRESPONDING BY list is unique. Result is in CORRESPONDING BY
+ * list order. Common columns list can hold duplicate columns.
+ */
+static List *
+FilterColumnsByNames(List *common_columns, List *filter,
+					 ParseState *pstate, const char *context)
+{
+	List	   *filtered_columns = NIL;
+	ListCell   *flc;
+	int			resno = 1;
+
+	Assert(common_columns != NIL);
+	Assert(filter != NIL);
+
+	foreach(flc, filter)
+	{
+		Value	   *strval = (Value *) lfirst(flc);
+		char	   *name = strVal(strval);
+		ListCell   *tlc;
+		bool		found = false;
+
+		foreach(tlc, common_columns)
+		{
+			TargetEntry   *tec = (TargetEntry *) lfirst(tlc);
+
+			if (strcmp(tec->resname, name) == 0)
+			{
+				ListCell   *lc;
+
+				/*
+				 * When "found" is true, then common_columns contains
+				 * duplicate columns. Raise exception then.
+				 */
+				if (found)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("corresponding column \"%s\" is used more times", name),
+	 errhint("In %s queries with CORRESPONDING BY clause the corresponding column names must be unique.",
+									context),
+							 parser_errposition(pstate,
+												exprLocation((Node *) tec))));
+
+				found = true;
+
+				/* result list should not to contains this name */
+				foreach(lc, filtered_columns)
+				{
+					TargetEntry   *te = (TargetEntry *) lfirst(lc);
+
+					/*
+					 * CORRESPONDING BY clause contains a column name that is
+					 * not in unique in this clause
+					 */
+					if (strcmp(te->resname, name) == 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("column name \"%s\" is not unique in CORRESPONDING BY clause", name),
+								 errhint("CORRESPONDING BY clause must contain unique column names only."),
+								 parser_errposition(pstate, strval->location)));
+				}
+
+				/* create te with correct resno */
+				filtered_columns = lappend(filtered_columns,
+										 makeTargetEntry(tec->expr,
+														 (AttrNumber) resno++,
+														 tec->resname,
+														 false));
+			}
+		}
+
+		/*
+		 * CORRESPONDING BY clause contains a column name that is not
+		 * in common columns.
+		 */
+		if (!found)
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("column name \"%s\" can not be used in CORRESPONDING BY list", name),
+		 errhint("%s queries with a CORRESPONDING BY clause must contain column names from both tables.",
+									 context),
+					 parser_errposition(pstate, strval->location)));
+	}
+
+	return filtered_columns;
+}
+
+/*
+ * Prepare target list for right query of CORRESPONDING clause.
+ * When filtered is true, filtered columns in target list are
+ * unique.
+ */
+static List *
+FilterColumnsByTL(List *targetlist, List *filter, bool check_uniq,
+				  ParseState *pstate, const char *context)
+{
+	List	   *result = NIL;
+	ListCell   *lc;
+	int			resno = 1;
+
+	foreach(lc, filter)
+	{
+		TargetEntry *fte = (TargetEntry *) lfirst(lc);
+		ListCell	*tle;
+		bool		found = false;
+
+		foreach(tle, targetlist)
+		{
+			TargetEntry *te = (TargetEntry *) lfirst(tle);
+
+			if (strcmp(fte->resname, te->resname) == 0)
+			{
+				/* create te with correct resno */
+				result = lappend(result,
+								 makeTargetEntry(te->expr,
+												 (AttrNumber) resno++,
+												 te->resname,
+												 false));
+
+				if (!check_uniq)
+					break;
+
+				/*
+				 * When "found" is true, then targetlist contains
+				 * duplicate filtered columns. Raise exception then.
+				 */
+				if (found)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("corresponding column \"%s\" is used more times", te->resname),
+							 errhint("In %s queries with CORRESPONDING BY clause the corresponding column names must be unique.",
+									context),
+							 parser_errposition(pstate,
+												exprLocation((Node *) te))));
+
+				found = true;
+			}
+		}
+	}
+
+	return result;
+}
+
+/*
+ * process right and left target list to set up union'd datatype
+ */
+static void
+makeUnionDatatype(List *ltargetlist, List *rtargetlist, SetOperationStmt *op,
+		List **targetlist, ParseState *pstate, const char *context)
+{
+	ListCell   *ltl;
+	ListCell   *rtl;
+
+	if (targetlist)
+		*targetlist = NIL;
+
+	op->colTypes = NIL;
+	op->colTypmods = NIL;
+	op->colCollations = NIL;
+	op->groupClauses = NIL;
+
+	forboth(ltl, ltargetlist, rtl, rtargetlist)
+	{
+		TargetEntry	   *ltle = (TargetEntry *) lfirst(ltl);
+		TargetEntry	   *rtle = (TargetEntry *) lfirst(rtl);
+		Node		   *lcolnode = (Node *) ltle->expr;
+		Node		   *rcolnode = (Node *) rtle->expr;
+		Oid			lcoltype = exprType(lcolnode);
+		Oid			rcoltype = exprType(rcolnode);
+		int32		lcoltypmod = exprTypmod(lcolnode);
+		int32		rcoltypmod = exprTypmod(rcolnode);
+		Node	   *bestexpr;
+		int			bestlocation;
+		Oid			rescoltype;
+		int32		rescoltypmod;
+		Oid			rescolcoll;
+
+		/* select common type, same as CASE et al */
+		rescoltype = select_common_type(pstate, list_make2(lcolnode, rcolnode),
+				context, &bestexpr);
+		bestlocation = exprLocation(bestexpr);
+		/* if same type and same typmod, use typmod; else default */
+		if (lcoltype == rcoltype && lcoltypmod == rcoltypmod)
+			rescoltypmod = lcoltypmod;
+		else
+			rescoltypmod = -1;
+
+		/*
+		 * Verify the coercions are actually possible.  If not, we'd fail
+		 * later anyway, but we want to fail now while we have sufficient
+		 * context to produce an error cursor position.
+		 *
+		 * For all non-UNKNOWN-type cases, we verify coercibility but we
+		 * don't modify the child's expression, for fear of changing the
+		 * child query's semantics.
+		 *
+		 * If a child expression is an UNKNOWN-type Const or Param, we
+		 * want to replace it with the coerced expression.  This can only
+		 * happen when the child is a leaf set-op node.  It's safe to
+		 * replace the expression because if the child query's semantics
+		 * depended on the type of this output column, it'd have already
+		 * coerced the UNKNOWN to something else.  We want to do this
+		 * because (a) we want to verify that a Const is valid for the
+		 * target type, or resolve the actual type of an UNKNOWN Param,
+		 * and (b) we want to avoid unnecessary discrepancies between the
+		 * output type of the child query and the resolved target type.
+		 * Such a discrepancy would disable optimization in the planner.
+		 *
+		 * If it's some other UNKNOWN-type node, eg a Var, we do nothing
+		 * (knowing that coerce_to_common_type would fail).  The planner
+		 * is sometimes able to fold an UNKNOWN Var to a constant before
+		 * it has to coerce the type, so failing now would just break
+		 * cases that might work.
+		 */
+		if (lcoltype != UNKNOWNOID)
+			lcolnode = coerce_to_common_type(pstate, lcolnode, rescoltype,
+					context);
+		else if (IsA(lcolnode, Const) || IsA(lcolnode, Param))
+		{
+			lcolnode = coerce_to_common_type(pstate, lcolnode, rescoltype,
+					context);
+			ltle->expr = (Expr *) lcolnode;
+		}
+
+		if (rcoltype != UNKNOWNOID)
+			rcolnode = coerce_to_common_type(pstate, rcolnode, rescoltype,
+					context);
+		else if (IsA(rcolnode, Const) || IsA(rcolnode, Param))
+		{
+			rcolnode = coerce_to_common_type(pstate, rcolnode, rescoltype,
+					context);
+			rtle->expr = (Expr *) rcolnode;
+		}
+
+		/*
+		 * Select common collation.  A common collation is required for
+		 * all set operators except UNION ALL; see SQL:2008 7.13 <query
+		 * expression> Syntax Rule 15c.  (If we fail to identify a common
+		 * collation for a UNION ALL column, the curCollations element
+		 * will be set to InvalidOid, which may result in a runtime error
+		 * if something at a higher query level wants to use the column's
+		 * collation.)
+		 */
+		rescolcoll = select_common_collation(pstate,
+				list_make2(lcolnode, rcolnode),
+				(op->op == SETOP_UNION && op->all));
+
+		/* emit results */
+		op->colTypes = lappend_oid(op->colTypes, rescoltype);
+		op->colTypmods = lappend_int(op->colTypmods, rescoltypmod);
+		op->colCollations = lappend_oid(op->colCollations, rescolcoll);
+
+		/*
+		 * For all cases except UNION ALL, identify the grouping operators
+		 * (and, if available, sorting operators) that will be used to
+		 * eliminate duplicates.
+		 */
+		if (op->op != SETOP_UNION || !op->all)
+		{
+			SortGroupClause *grpcl = makeNode(SortGroupClause);
+			Oid			sortop;
+			Oid			eqop;
+			bool		hashable;
+			ParseCallbackState pcbstate;
+
+			setup_parser_errposition_callback(&pcbstate, pstate, bestlocation);
+
+			/* determine the eqop and optional sortop */
+			get_sort_group_operators(rescoltype, false, true, false, &sortop,
+					&eqop, NULL, &hashable);
+
+			cancel_parser_errposition_callback(&pcbstate);
+
+			/* we don't have a tlist yet, so can't assign sortgrouprefs */
+			grpcl->tleSortGroupRef = 0;
+			grpcl->eqop = eqop;
+			grpcl->sortop = sortop;
+			grpcl->nulls_first = false; /* OK with or without sortop */
+			grpcl->hashable = hashable;
+
+			op->groupClauses = lappend(op->groupClauses, grpcl);
+		}
+
+		/*
+		 * Construct a dummy tlist entry to return.  We use a SetToDefault
+		 * node for the expression, since it carries exactly the fields
+		 * needed, but any other expression node type would do as well.
+		 */
+		if (targetlist)
+		{
+			SetToDefault   *rescolnode = makeNode(SetToDefault);
+			TargetEntry	   *restle;
+
+			rescolnode->typeId = rescoltype;
+			rescolnode->typeMod = rescoltypmod;
+			rescolnode->collation = rescolcoll;
+			rescolnode->location = bestlocation;
+
+			/* no need to set resno */
+			restle = makeTargetEntry((Expr *) rescolnode, 0,
+			NULL, false );
+			*targetlist = lappend(*targetlist, restle);
+		}
+	}
+
+}
+
+/*
  * Process the outputs of the non-recursive term of a recursive union
  * to set up the parent CTE's columns
  */
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 174773b..577cc2a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -166,7 +166,7 @@ static void insertSelectOptions(SelectStmt *stmt,
 								Node *limitOffset, Node *limitCount,
 								WithClause *withClause,
 								core_yyscan_t yyscanner);
-static Node *makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg);
+static Node *makeSetOp(SetOperation op, bool all, List *correspondingClause, Node *larg, Node *rarg);
 static Node *doNegate(Node *n, int location);
 static void doNegateFloat(Value *v);
 static Node *makeAndExpr(Node *lexpr, Node *rexpr, int location);
@@ -394,7 +394,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				relation_expr_list dostmt_opt_list
 				transform_element_list transform_type_list
 				TriggerTransitions TriggerReferencing
-				publication_name_list
+				publication_name_list opt_corresponding_clause
 
 %type <list>	group_by_list
 %type <node>	group_by_item empty_grouping_set rollup_clause cube_clause
@@ -608,7 +608,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
 	CLUSTER COALESCE COLLATE COLLATION COLUMN COMMENT COMMENTS COMMIT
 	COMMITTED CONCURRENTLY CONFIGURATION CONFLICT CONNECTION CONSTRAINT
-	CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE
+	CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY CORRESPONDING COST CREATE
 	CROSS CSV CUBE CURRENT_P
 	CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA
 	CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
@@ -3578,7 +3578,10 @@ columnList:
 
 columnElem: ColId
 				{
-					$$ = (Node *) makeString($1);
+					Value *v = makeString($1);
+
+					v->location = @1;
+					$$ = (Node *) v;
 				}
 		;
 
@@ -10808,20 +10811,26 @@ simple_select:
 					n->fromClause = list_make1($2);
 					$$ = (Node *)n;
 				}
-			| select_clause UNION all_or_distinct select_clause
+			| select_clause UNION all_or_distinct opt_corresponding_clause select_clause
 				{
-					$$ = makeSetOp(SETOP_UNION, $3, $1, $4);
+					$$ = makeSetOp(SETOP_UNION, $3, $4, $1, $5);
 				}
-			| select_clause INTERSECT all_or_distinct select_clause
+			| select_clause INTERSECT all_or_distinct opt_corresponding_clause select_clause
 				{
-					$$ = makeSetOp(SETOP_INTERSECT, $3, $1, $4);
+					$$ = makeSetOp(SETOP_INTERSECT, $3, $4, $1, $5);
 				}
-			| select_clause EXCEPT all_or_distinct select_clause
+			| select_clause EXCEPT all_or_distinct opt_corresponding_clause select_clause
 				{
-					$$ = makeSetOp(SETOP_EXCEPT, $3, $1, $4);
+					$$ = makeSetOp(SETOP_EXCEPT, $3, $4, $1, $5);
 				}
 		;
 
+opt_corresponding_clause:
+			CORRESPONDING BY '(' columnList ')'	{ $$ = $4; }
+			| CORRESPONDING							{ $$ = list_make1(NIL); }
+			| /*EMPTY*/								{ $$ = NIL; }
+			;
+
 /*
  * SQL standard WITH clause looks like:
  *
@@ -13999,7 +14008,6 @@ name_list:	name
 					{ $$ = lappend($1, makeString($3)); }
 		;
 
-
 name:		ColId									{ $$ = $1; };
 
 database_name:
@@ -14315,6 +14323,7 @@ unreserved_keyword:
 			| CONTINUE_P
 			| CONVERSION_P
 			| COPY
+			| CORRESPONDING
 			| COST
 			| CSV
 			| CUBE
@@ -15145,7 +15154,7 @@ insertSelectOptions(SelectStmt *stmt,
 }
 
 static Node *
-makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg)
+makeSetOp(SetOperation op, bool all, List *correspondingClause, Node *larg, Node *rarg)
 {
 	SelectStmt *n = makeNode(SelectStmt);
 
@@ -15153,6 +15162,7 @@ makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg)
 	n->all = all;
 	n->larg = (SelectStmt *) larg;
 	n->rarg = (SelectStmt *) rarg;
+	n->correspondingClause = correspondingClause;
 	return (Node *) n;
 }
 
diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
index 8feec0b..ac23b1b 100644
--- a/src/backend/parser/parse_type.c
+++ b/src/backend/parser/parse_type.c
@@ -736,7 +736,8 @@ typeStringToTypeName(const char *str)
 		stmt->limitCount != NULL ||
 		stmt->lockingClause != NIL ||
 		stmt->withClause != NULL ||
-		stmt->op != SETOP_NONE)
+		stmt->op != SETOP_NONE ||
+		stmt->correspondingClause != NULL)
 		goto fail;
 	if (list_length(stmt->targetList) != 1)
 		goto fail;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index f355954..b342961 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -5349,6 +5349,30 @@ get_setop_query(Node *setOp, Query *query, deparse_context *context,
 		}
 		if (op->all)
 			appendStringInfoString(buf, "ALL ");
+		if (op->correspondingColumns != NIL )
+		{
+			if (op->hasCorrespondingBy)
+			{
+				const char *sep;
+				ListCell *l;
+				appendStringInfoString(buf, "CORRESPONDING BY(");
+				sep = "";
+
+				foreach(l, op->correspondingColumns)
+				{
+					TargetEntry *tle = (TargetEntry *) lfirst(l);
+
+					appendStringInfoString(buf, sep);
+					appendStringInfo(buf, "%s", tle->resname);
+					sep = ", ";
+				}
+				appendStringInfoChar(buf, ')');
+
+			}
+			else
+
+				appendStringInfoString(buf, "CORRESPONDING ");
+		}
 
 		/* Always parenthesize if RHS is another setop */
 		need_paren = IsA(op->rarg, SetOperationStmt);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 07a8436..f95accc 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1421,6 +1421,7 @@ typedef struct SelectStmt
 	 */
 	List	   *distinctClause; /* NULL, list of DISTINCT ON exprs, or
 								 * lcons(NIL,NIL) for all (SELECT DISTINCT) */
+	List	   *correspondingClause;	/* CORRESPONDING BY  clauses*/
 	IntoClause *intoClause;		/* target for SELECT INTO */
 	List	   *targetList;		/* the target list (of ResTarget) */
 	List	   *fromClause;		/* the FROM clause */
@@ -1456,7 +1457,6 @@ typedef struct SelectStmt
 	bool		all;			/* ALL specified? */
 	struct SelectStmt *larg;	/* left child */
 	struct SelectStmt *rarg;	/* right child */
-	/* Eventually add fields for CORRESPONDING spec here */
 } SelectStmt;
 
 
@@ -1486,8 +1486,8 @@ typedef struct SetOperationStmt
 	bool		all;			/* ALL specified? */
 	Node	   *larg;			/* left child */
 	Node	   *rarg;			/* right child */
-	/* Eventually add fields for CORRESPONDING spec here */
-
+	List	   *correspondingColumns;	/* list of corresponding column names */
+	bool		hasCorrespondingBy;		/* has corresponding by cluase? */
 	/* Fields derived during parse analysis: */
 	List	   *colTypes;		/* OID list of output column type OIDs */
 	List	   *colTypmods;		/* integer list of output column typmods */
diff --git a/src/include/nodes/value.h b/src/include/nodes/value.h
index ede97b7..bf3b6e9 100644
--- a/src/include/nodes/value.h
+++ b/src/include/nodes/value.h
@@ -47,6 +47,7 @@ typedef struct Value
 		long		ival;		/* machine integer */
 		char	   *str;		/* string */
 	}			val;
+	int			location;		/* token location, or -1 if unknown */
 } Value;
 
 #define intVal(v)		(((Value *)(v))->val.ival)
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 985d650..77e0396 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -96,6 +96,7 @@ PG_KEYWORD("content", CONTENT_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("continue", CONTINUE_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("conversion", CONVERSION_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("copy", COPY, UNRESERVED_KEYWORD)
+PG_KEYWORD("corresponding", CORRESPONDING, UNRESERVED_KEYWORD)
 PG_KEYWORD("cost", COST, UNRESERVED_KEYWORD)
 PG_KEYWORD("create", CREATE, RESERVED_KEYWORD)
 PG_KEYWORD("cross", CROSS, TYPE_FUNC_NAME_KEYWORD)
diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out
index ce0c8ce..6b44903 100644
--- a/src/test/regress/expected/create_view.out
+++ b/src/test/regress/expected/create_view.out
@@ -1597,3 +1597,28 @@ select pg_get_viewdef('tt19v', true);
 set client_min_messages = warning;
 DROP SCHEMA temp_view_test CASCADE;
 DROP SCHEMA testviewschm2 CASCADE;
+-- views with corresponding clause
+create view view_corresponding_01 as select 1 as a, 2 as b union all corresponding select 3 as a, 4 as b;
+select * from view_corresponding_01;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+create view view_corresponding_02 as select 1 as a, 2 as b union all corresponding by (a,b) select 3 as a, 4 as b, 5 as c;
+select * from view_corresponding_02;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+create view view_corresponding_03 as select 1 as a, 2 as b union all corresponding by (b,a) select 3 as a, 4 as b, 5 as c;
+select * from view_corresponding_03;
+ b | a 
+---+---
+ 2 | 1
+ 4 | 3
+(2 rows)
+
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index c661f1d..fdd5a46 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -2309,6 +2309,23 @@ toyemp| SELECT emp.name,
     emp.location,
     (12 * emp.salary) AS annualsal
    FROM emp;
+view_corresponding_01| SELECT 1 AS a,
+    2 AS b
+UNION ALL CORRESPONDING
+ SELECT 3 AS a,
+    4 AS b;
+view_corresponding_02| SELECT 1 AS a,
+    2 AS b
+UNION ALL CORRESPONDING BY(a, b)
+ SELECT 3 AS a,
+    4 AS b,
+    5 AS c;
+view_corresponding_03| SELECT 1 AS b,
+    2 AS a
+UNION ALL CORRESPONDING BY(b, a)
+ SELECT 3 AS b,
+    4 AS a,
+    5 AS c;
 SELECT tablename, rulename, definition FROM pg_rules
 	ORDER BY tablename, rulename;
 pg_settings|pg_settings_n|CREATE RULE pg_settings_n AS
diff --git a/src/test/regress/expected/union.out b/src/test/regress/expected/union.out
index 4d697ba..6451576 100644
--- a/src/test/regress/expected/union.out
+++ b/src/test/regress/expected/union.out
@@ -59,6 +59,87 @@ SELECT 1.1 AS two UNION SELECT 2.2 ORDER BY 1;
  2.2
 (2 rows)
 
+SELECT 1 AS two UNION CORRESPONDING SELECT 2 two UNION CORRESPONDING SELECT 2 two;
+ two 
+-----
+   2
+   1
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a) SELECT 4 a, 5 b, 6 c;
+ a 
+---
+ 1
+ 4
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b) SELECT 4 a, 5 b, 6 c;
+ a | b 
+---+---
+ 1 | 2
+ 4 | 5
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c) SELECT 4 a, 5 b, 6 c;
+ b | c 
+---+---
+ 2 | 3
+ 5 | 6
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY (c,b) SELECT 4 a, 5 b, 6 c, 8 d;
+ c | b 
+---+---
+ 3 | 2
+ 6 | 5
+(2 rows)
+
+-- CORRESPONDING column ordering, left clause's column ordering must be preserved.
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+SELECT 2 b, 1 a, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+ b | a | c 
+---+---+---
+ 2 | 1 | 3
+ 5 | 4 | 6
+(2 rows)
+
+-- CORRESPONDING BY column ordering, BY clause column ordering must be preserved.
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 4 a, 5 b, 6 c;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c, a) SELECT 4 a, 5 b, 6 c;
+ b | c | a 
+---+---+---
+ 2 | 3 | 1
+ 5 | 6 | 4
+(2 rows)
+
+-- should to fail
+SELECT 10 a, 20 b UNION ALL CORRESPONDING SELECT 10 c, 20 d;
+ERROR:  there is not any corresponding name
+LINE 1: SELECT 10 a, 20 b UNION ALL CORRESPONDING SELECT 10 c, 20 d;
+               ^
+HINT:  UNION queries with a CORRESPONDING clause must have at least one column with the same name
+SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (a,b) SELECT 10 c, 20 d;
+ERROR:  there is not any corresponding name
+LINE 1: SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (a,b) SELECT 10...
+               ^
+HINT:  UNION queries with a CORRESPONDING clause must have at least one column with the same name
+SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (x) SELECT 10 c, 20 d;
+ERROR:  there is not any corresponding name
+LINE 1: SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (x) SELECT 10 c...
+               ^
+HINT:  UNION queries with a CORRESPONDING clause must have at least one column with the same name
 -- Mixed types
 SELECT 1.1 AS two UNION SELECT 2 ORDER BY 1;
  two 
@@ -124,6 +205,105 @@ SELECT 1.1 AS two UNION (SELECT 2 UNION ALL SELECT 2) ORDER BY 1;
    2
 (2 rows)
 
+-- other corresponding clause tests,
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+-- when column is not in result, then the name should not be unique
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS b, 5 AS a;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+ 5 | 6
+(3 rows)
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x3;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x6 UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS b, 5 AS a;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+ 5 | 6
+(3 rows)
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS b, -100 AS a;
+ b 
+---
+ 2
+ 4
+ 6
+(3 rows)
+
+-- should fail
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3;
+ERROR:  corresponding column "x3" is used more times
+LINE 1: SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 U...
+                                                          ^
+HINT:  In UNION queries with CORRESPONDING clause the corresponding column names must be unique.
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3, -10 AS x3;
+ERROR:  corresponding column "x3" is used more times
+LINE 1: ...ELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3, -10 AS x3;
+                                                             ^
+HINT:  In UNION queries with CORRESPONDING clause the corresponding column names must be unique.
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(a,b,x3) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3;
+ERROR:  corresponding column "x3" is used more times
+LINE 1: SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 U...
+                                                          ^
+HINT:  In UNION queries with CORRESPONDING BY clause the corresponding column names must be unique.
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING BY(a,b,x3) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3, -10 AS x3;
+ERROR:  corresponding column "x3" is used more times
+LINE 1: ...ELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3, -10 AS x3;
+                                                             ^
+HINT:  In UNION queries with CORRESPONDING BY clause the corresponding column names must be unique.
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x6 UNION ALL CORRESPONDING BY(a,b,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+ERROR:  column name "b" is not unique in CORRESPONDING BY clause
+LINE 1: ... 0 AS x3, -1 AS x6 UNION ALL CORRESPONDING BY(a,b,b) SELECT ...
+                                                             ^
+HINT:  CORRESPONDING BY clause must contain unique column names only.
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS bb, -100 AS aa;
+ERROR:  there is not any corresponding name
+LINE 1: SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 U...
+                        ^
+HINT:  UNION queries with a CORRESPONDING clause must have at least one column with the same name
 --
 -- Try testing from tables...
 --
@@ -258,6 +438,74 @@ ORDER BY 1;
  hi de ho neighbor
 (5 rows)
 
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+         five          
+-----------------------
+ -1.2345678901234e+200
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+                     0
+(5 rows)
+
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING BY(five)
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+         five          
+-----------------------
+ -1.2345678901234e+200
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+                     0
+(5 rows)
+
+SELECT f1 AS ten FROM FLOAT8_TBL
+UNION ALL CORRESPONDING
+SELECT f1 AS ten FROM FLOAT8_TBL;
+          ten          
+-----------------------
+                     0
+                -34.84
+               -1004.3
+ -1.2345678901234e+200
+ -1.2345678901234e-200
+                     0
+                -34.84
+               -1004.3
+ -1.2345678901234e+200
+ -1.2345678901234e-200
+(10 rows)
+
+SELECT f1 AS five FROM FLOAT8_TBL
+  WHERE f1 BETWEEN -1e6 AND 1e6
+UNION CORRESPONDING
+SELECT f1 AS five FROM INT4_TBL
+  WHERE f1 BETWEEN 0 AND 1000000;
+         five          
+-----------------------
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+                     0
+                123456
+(5 rows)
+
+SELECT * FROM FLOAT8_TBL
+UNION corresponding SELECT * FROM FLOAT8_TBL;
+          f1           
+-----------------------
+                     0
+ -1.2345678901234e-200
+                -34.84
+ -1.2345678901234e+200
+               -1004.3
+(5 rows)
+
 --
 -- INTERSECT and EXCEPT
 --
@@ -320,6 +568,63 @@ SELECT q1 FROM int8_tbl EXCEPT ALL SELECT DISTINCT q2 FROM int8_tbl ORDER BY 1;
 
 SELECT q1 FROM int8_tbl EXCEPT ALL SELECT q1 FROM int8_tbl FOR NO KEY UPDATE;
 ERROR:  FOR NO KEY UPDATE is not allowed with UNION/INTERSECT/EXCEPT
+SELECT q2 FROM int8_tbl INTERSECT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+        q2        
+------------------
+              123
+ 4567890123456789
+(2 rows)
+
+SELECT q2 FROM int8_tbl INTERSECT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+        q2        
+------------------
+              123
+ 4567890123456789
+ 4567890123456789
+(3 rows)
+
+SELECT q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+        q2         
+-------------------
+ -4567890123456789
+               456
+(2 rows)
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+        q2         
+-------------------
+ -4567890123456789
+               456
+(2 rows)
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q1 AS q2 FROM int8_tbl ORDER BY 1;
+        q2         
+-------------------
+ -4567890123456789
+               456
+  4567890123456789
+(3 rows)
+
+SELECT q1 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+ q1 
+----
+(0 rows)
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+        q1        
+------------------
+              123
+ 4567890123456789
+(2 rows)
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q2 AS q1 FROM int8_tbl;
+        q1        
+------------------
+              123
+ 4567890123456789
+ 4567890123456789
+(3 rows)
+
 --
 -- Mixed types
 --
@@ -338,6 +643,21 @@ SELECT f1 FROM float8_tbl EXCEPT SELECT f1 FROM int4_tbl ORDER BY 1;
  -1.2345678901234e-200
 (4 rows)
 
+SELECT f1 FROM float8_tbl INTERSECT CORRESPONDING SELECT f1 FROM int4_tbl;
+ f1 
+----
+  0
+(1 row)
+
+SELECT f1 FROM float8_tbl EXCEPT CORRESPONDING SELECT f1 FROM int4_tbl ORDER BY 1;
+          f1           
+-----------------------
+ -1.2345678901234e+200
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+(4 rows)
+
 --
 -- Operator precedence and (((((extra))))) parentheses
 --
@@ -424,6 +744,24 @@ SELECT q1 FROM int8_tbl EXCEPT (((SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1)))
  4567890123456789
 (2 rows)
 
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2,q1 FROM int8_tbl
+ORDER BY q2,q1;
+ q1 | q2 
+----+----
+(0 rows)
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q1) SELECT q2,q1 FROM int8_tbl
+ORDER BY q1;
+ q1 
+----
+(0 rows)
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q2) SELECT q2,q1 FROM int8_tbl
+ORDER BY q2;
+ q2 
+----
+(0 rows)
+
 --
 -- New syntaxes (7.1) permit new tests
 --
@@ -459,6 +797,22 @@ SELECT '3.4'::numeric UNION SELECT 'foo';
 ERROR:  invalid input syntax for type numeric: "foo"
 LINE 1: SELECT '3.4'::numeric UNION SELECT 'foo';
                                            ^
+SELECT '3.4'::numeric AS a UNION CORRESPONDING SELECT 'foo' AS a;
+ERROR:  invalid input syntax for type numeric: "foo"
+LINE 1: ...CT '3.4'::numeric AS a UNION CORRESPONDING SELECT 'foo' AS a...
+                                                             ^
+SELECT a.f1 FROM (SELECT 'test' AS f1 FROM varchar_tbl) a
+UNION CORRESPONDING
+SELECT b.f1 FROM (SELECT f1 FROM varchar_tbl) b
+ORDER BY 1;
+  f1  
+------
+ a
+ ab
+ abcd
+ test
+(4 rows)
+
 --
 -- Test that expression-index constraints can be pushed down through
 -- UNION or UNION ALL
diff --git a/src/test/regress/sql/create_view.sql b/src/test/regress/sql/create_view.sql
index c27f103..3ea31ba 100644
--- a/src/test/regress/sql/create_view.sql
+++ b/src/test/regress/sql/create_view.sql
@@ -532,3 +532,13 @@ select pg_get_viewdef('tt19v', true);
 set client_min_messages = warning;
 DROP SCHEMA temp_view_test CASCADE;
 DROP SCHEMA testviewschm2 CASCADE;
+
+-- views with corresponding clause
+create view view_corresponding_01 as select 1 as a, 2 as b union all corresponding select 3 as a, 4 as b;
+select * from view_corresponding_01;
+
+create view view_corresponding_02 as select 1 as a, 2 as b union all corresponding by (a,b) select 3 as a, 4 as b, 5 as c;
+select * from view_corresponding_02;
+
+create view view_corresponding_03 as select 1 as a, 2 as b union all corresponding by (b,a) select 3 as a, 4 as b, 5 as c;
+select * from view_corresponding_03;
diff --git a/src/test/regress/sql/union.sql b/src/test/regress/sql/union.sql
index 48e6850..9470e1a 100644
--- a/src/test/regress/sql/union.sql
+++ b/src/test/regress/sql/union.sql
@@ -20,6 +20,35 @@ SELECT 1 AS three UNION SELECT 2 UNION ALL SELECT 2 ORDER BY 1;
 
 SELECT 1.1 AS two UNION SELECT 2.2 ORDER BY 1;
 
+SELECT 1 AS two UNION CORRESPONDING SELECT 2 two UNION CORRESPONDING SELECT 2 two;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY (c,b) SELECT 4 a, 5 b, 6 c, 8 d;
+
+-- CORRESPONDING column ordering, left clause's column ordering must be preserved.
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+
+SELECT 2 b, 1 a, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+
+-- CORRESPONDING BY column ordering, BY clause column ordering must be preserved.
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c, a) SELECT 4 a, 5 b, 6 c;
+
+-- should to fail
+SELECT 10 a, 20 b UNION ALL CORRESPONDING SELECT 10 c, 20 d;
+
+SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (a,b) SELECT 10 c, 20 d;
+
+SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (x) SELECT 10 c, 20 d;
+
 -- Mixed types
 
 SELECT 1.1 AS two UNION SELECT 2 ORDER BY 1;
@@ -40,6 +69,30 @@ SELECT 1.1 AS three UNION SELECT 2 UNION ALL SELECT 2 ORDER BY 1;
 
 SELECT 1.1 AS two UNION (SELECT 2 UNION ALL SELECT 2) ORDER BY 1;
 
+-- other corresponding clause tests,
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6;
+
+-- when column is not in result, then the name should not be unique
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS b, 5 AS a;
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x3;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x6 UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS b, 5 AS a;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS b, -100 AS a;
+
+-- should fail
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3, -10 AS x3;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(a,b,x3) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING BY(a,b,x3) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3, -10 AS x3;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x6 UNION ALL CORRESPONDING BY(a,b,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS bb, -100 AS aa;
+
 --
 -- Try testing from tables...
 --
@@ -90,6 +143,29 @@ UNION
 SELECT TRIM(TRAILING FROM f1) FROM CHAR_TBL
 ORDER BY 1;
 
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING BY(five)
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+
+SELECT f1 AS ten FROM FLOAT8_TBL
+UNION ALL CORRESPONDING
+SELECT f1 AS ten FROM FLOAT8_TBL;
+
+SELECT f1 AS five FROM FLOAT8_TBL
+  WHERE f1 BETWEEN -1e6 AND 1e6
+UNION CORRESPONDING
+SELECT f1 AS five FROM INT4_TBL
+  WHERE f1 BETWEEN 0 AND 1000000;
+
+SELECT * FROM FLOAT8_TBL
+UNION corresponding SELECT * FROM FLOAT8_TBL;
+
 --
 -- INTERSECT and EXCEPT
 --
@@ -112,6 +188,22 @@ SELECT q1 FROM int8_tbl EXCEPT ALL SELECT DISTINCT q2 FROM int8_tbl ORDER BY 1;
 
 SELECT q1 FROM int8_tbl EXCEPT ALL SELECT q1 FROM int8_tbl FOR NO KEY UPDATE;
 
+SELECT q2 FROM int8_tbl INTERSECT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+
+SELECT q2 FROM int8_tbl INTERSECT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+
+SELECT q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q1 AS q2 FROM int8_tbl ORDER BY 1;
+
+SELECT q1 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q2 AS q1 FROM int8_tbl;
+
 --
 -- Mixed types
 --
@@ -120,6 +212,10 @@ SELECT f1 FROM float8_tbl INTERSECT SELECT f1 FROM int4_tbl ORDER BY 1;
 
 SELECT f1 FROM float8_tbl EXCEPT SELECT f1 FROM int4_tbl ORDER BY 1;
 
+SELECT f1 FROM float8_tbl INTERSECT CORRESPONDING SELECT f1 FROM int4_tbl;
+
+SELECT f1 FROM float8_tbl EXCEPT CORRESPONDING SELECT f1 FROM int4_tbl ORDER BY 1;
+
 --
 -- Operator precedence and (((((extra))))) parentheses
 --
@@ -150,6 +246,15 @@ SELECT q1 FROM int8_tbl EXCEPT SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1;
 -- But this should work:
 SELECT q1 FROM int8_tbl EXCEPT (((SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1))) ORDER BY 1;
 
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2,q1 FROM int8_tbl
+ORDER BY q2,q1;
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q1) SELECT q2,q1 FROM int8_tbl
+ORDER BY q1;
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q2) SELECT q2,q1 FROM int8_tbl
+ORDER BY q2;
+
 --
 -- New syntaxes (7.1) permit new tests
 --
@@ -170,6 +275,13 @@ ORDER BY 1;
 -- This should fail, but it should produce an error cursor
 SELECT '3.4'::numeric UNION SELECT 'foo';
 
+SELECT '3.4'::numeric AS a UNION CORRESPONDING SELECT 'foo' AS a;
+
+SELECT a.f1 FROM (SELECT 'test' AS f1 FROM varchar_tbl) a
+UNION CORRESPONDING
+SELECT b.f1 FROM (SELECT f1 FROM varchar_tbl) b
+ORDER BY 1;
+
 --
 -- Test that expression-index constraints can be pushed down through
 -- UNION or UNION ALL
#33Pavel Stehule
pavel.stehule@gmail.com
In reply to: Surafel Temesgen (#32)
1 attachment(s)
Re: New CORRESPONDING clause design

Hi

2017-03-30 13:11 GMT+02:00 Surafel Temesgen <surafel3000@gmail.com>:

hi

Thank you very much for your help .
here is the patch fix that issue as you suggest

The crash is fixed

I did a rebase + few more regress tests.

Is following use case defined in standard?

postgres=# SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3
UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS a,
0 AS x6, -1 AS x6
UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS a, -100 AS aa;
┌───┐
│ a │
╞═══╡
│ 1 │
│ 3 │
│ 6 │
└───┘
(3 rows)

It depends on order of implementation

if we do (T1 U T2) U T3 ---> then result is correct,
but if we do T1 U (T2 U T3) ---> than it should to fail

I am not sure, if this result is expected (correct). I expect more syntax
error because corresponding by is not filled.

Show quoted text

Regards

Surafel

On Tue, Mar 28, 2017 at 5:44 PM, Pavel Stehule <pavel.stehule@gmail.com>
wrote:

2017-03-28 14:18 GMT+02:00 Pavel Stehule <pavel.stehule@gmail.com>:

2017-03-28 13:58 GMT+02:00 Surafel Temesgen <surafel3000@gmail.com>:

can you help with fixing it Pavel?

There must be some new preanalyze stage - you have to know result
columns before you are starting a analyze

maybe some recheck after analyze stage to remove invalid columns can be
good enough.

Regards

Pavel

Regards

Pavel

On Mon, Mar 27, 2017 at 11:48 AM, Pavel Stehule <
pavel.stehule@gmail.com> wrote:

Hi

fresh update - I enhanced Value node by location field as Tom proposal.

Few more regress tests.

But I found significant issue, that needs bigger fix - Surafel,
please, can you fix it.

It crash on

SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3
UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS
x6
UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS b, -100 AS x9;

I'll mark this patch as waiting on author

Regards

Pavel

Attachments:

corresponding_clause_v11.patchtext/x-patch; charset=US-ASCII; name=corresponding_clause_v11.patchDownload
diff --git a/doc/src/sgml/queries.sgml b/doc/src/sgml/queries.sgml
index 30792f45f1..2d60718ff1 100644
--- a/doc/src/sgml/queries.sgml
+++ b/doc/src/sgml/queries.sgml
@@ -1601,6 +1601,9 @@ SELECT DISTINCT ON (<replaceable>expression</replaceable> <optional>, <replaceab
    <primary>EXCEPT</primary>
   </indexterm>
   <indexterm zone="queries-union">
+   <primary>CORRESPONDING</primary>
+  </indexterm>
+  <indexterm zone="queries-union">
    <primary>set union</primary>
   </indexterm>
   <indexterm zone="queries-union">
@@ -1617,9 +1620,9 @@ SELECT DISTINCT ON (<replaceable>expression</replaceable> <optional>, <replaceab
    The results of two queries can be combined using the set operations
    union, intersection, and difference.  The syntax is
 <synopsis>
-<replaceable>query1</replaceable> UNION <optional>ALL</optional> <replaceable>query2</replaceable>
-<replaceable>query1</replaceable> INTERSECT <optional>ALL</optional> <replaceable>query2</replaceable>
-<replaceable>query1</replaceable> EXCEPT <optional>ALL</optional> <replaceable>query2</replaceable>
+<replaceable>query1</replaceable> UNION <optional>ALL</optional> <optional>CORRESPONDING</optional> <optional>BY</optional> <replaceable>query2</replaceable>
+<replaceable>query1</replaceable> INTERSECT <optional>ALL</optional> <optional>CORRESPONDING</optional> <optional>BY</optional> <replaceable>query2</replaceable>
+<replaceable>query1</replaceable> EXCEPT <optional>ALL</optional> <optional>CORRESPONDING</optional> <optional>BY</optional> <replaceable>query2</replaceable>
 </synopsis>
    <replaceable>query1</replaceable> and
    <replaceable>query2</replaceable> are queries that can use any of
@@ -1659,14 +1662,31 @@ SELECT DISTINCT ON (<replaceable>expression</replaceable> <optional>, <replaceab
   </para>
 
   <para>
-   In order to calculate the union, intersection, or difference of two
-   queries, the two queries must be <quote>union compatible</quote>,
-   which means that they return the same number of columns and
-   the corresponding columns have compatible data types, as
-   described in <xref linkend="typeconv-union-case">.
+   <literal>EXCEPT</> returns all rows that are in the result of
+   <replaceable>query1</replaceable> but not in the result of
+   <replaceable>query2</replaceable>.  (This is sometimes called the
+   <firstterm>difference</> between two queries.)  Again, duplicates
+   are eliminated unless <literal>EXCEPT ALL</> is used.
   </para>
- </sect1>
 
+  <para>
+   <literal>CORRESPONDING</> returns all columns that are in both 
+   <replaceable>query1</> and <replaceable>query2</> with the same name.
+  </para>
+
+  <para>
+   <literal>CORRESPONDING BY</> returns all columns in the column list 
+   that are also in both <replaceable>query1</> and 
+   <replaceable>query2</> with the same name. The names in column list
+   must be unique.
+  </para>
+
+  <para>
+   The names of columns in result when <literal>CORRESPONDING</> or
+   <literal>CORRESPONDING BY</> clause is used must be unique in
+   <replaceable>query1</replaceable> and <replaceable>query2</replaceable>.
+  </para>
+ </sect1>
 
  <sect1 id="queries-order">
   <title>Sorting Rows</title>
diff --git a/doc/src/sgml/sql.sgml b/doc/src/sgml/sql.sgml
index 57396d7c24..f98c22e696 100644
--- a/doc/src/sgml/sql.sgml
+++ b/doc/src/sgml/sql.sgml
@@ -859,7 +859,7 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="PARAMETER">expression</replac
     [ WHERE <replaceable class="PARAMETER">condition</replaceable> ]
     [ GROUP BY <replaceable class="PARAMETER">expression</replaceable> [, ...] ]
     [ HAVING <replaceable class="PARAMETER">condition</replaceable> [, ...] ]
-    [ { UNION | INTERSECT | EXCEPT } [ ALL ] <replaceable class="PARAMETER">select</replaceable> ]
+    [ { UNION | INTERSECT | EXCEPT } [ ALL ] [ CORRESPONDING [ BY ( <replaceable class="PARAMETER">expression</replaceable> ) ] ] <replaceable class="PARAMETER">select</replaceable> ]
     [ ORDER BY <replaceable class="parameter">expression</replaceable> [ ASC | DESC | USING <replaceable class="parameter">operator</replaceable> ] [ NULLS { FIRST | LAST } ] [, ...] ]
     [ LIMIT { <replaceable class="PARAMETER">count</replaceable> | ALL } ]
     [ OFFSET <replaceable class="PARAMETER">start</replaceable> ]
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 1c88d601bd..11e0590eec 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2995,6 +2995,7 @@ _copySelectStmt(const SelectStmt *from)
 	COPY_NODE_FIELD(withClause);
 	COPY_SCALAR_FIELD(op);
 	COPY_SCALAR_FIELD(all);
+	COPY_NODE_FIELD(correspondingClause);
 	COPY_NODE_FIELD(larg);
 	COPY_NODE_FIELD(rarg);
 
@@ -3010,6 +3011,8 @@ _copySetOperationStmt(const SetOperationStmt *from)
 	COPY_SCALAR_FIELD(all);
 	COPY_NODE_FIELD(larg);
 	COPY_NODE_FIELD(rarg);
+	COPY_NODE_FIELD(correspondingColumns);
+	COPY_SCALAR_FIELD(hasCorrespondingBy);
 	COPY_NODE_FIELD(colTypes);
 	COPY_NODE_FIELD(colTypmods);
 	COPY_NODE_FIELD(colCollations);
@@ -4588,6 +4591,8 @@ _copyValue(const Value *from)
 				 (int) from->type);
 			break;
 	}
+	COPY_LOCATION_FIELD(location);
+
 	return newnode;
 }
 
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 5941b7a2bf..dd6598d85b 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1050,6 +1050,7 @@ _equalSelectStmt(const SelectStmt *a, const SelectStmt *b)
 	COMPARE_NODE_FIELD(withClause);
 	COMPARE_SCALAR_FIELD(op);
 	COMPARE_SCALAR_FIELD(all);
+	COMPARE_NODE_FIELD(correspondingClause);
 	COMPARE_NODE_FIELD(larg);
 	COMPARE_NODE_FIELD(rarg);
 
@@ -1063,6 +1064,8 @@ _equalSetOperationStmt(const SetOperationStmt *a, const SetOperationStmt *b)
 	COMPARE_SCALAR_FIELD(all);
 	COMPARE_NODE_FIELD(larg);
 	COMPARE_NODE_FIELD(rarg);
+	COMPARE_NODE_FIELD(correspondingColumns);
+	COMPARE_SCALAR_FIELD(hasCorrespondingBy);
 	COMPARE_NODE_FIELD(colTypes);
 	COMPARE_NODE_FIELD(colTypmods);
 	COMPARE_NODE_FIELD(colCollations);
@@ -2935,6 +2938,8 @@ _equalValue(const Value *a, const Value *b)
 			break;
 	}
 
+	COMPARE_LOCATION_FIELD(location);
+
 	return true;
 }
 
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 6e52eb7231..7102ea96c2 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -3444,6 +3444,8 @@ raw_expression_tree_walker(Node *node,
 					return true;
 				if (walker(stmt->lockingClause, context))
 					return true;
+				if (walker(stmt->correspondingClause, context))
+					return true;
 				if (walker(stmt->withClause, context))
 					return true;
 				if (walker(stmt->larg, context))
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index bbb63a4bfa..09c097857d 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2664,6 +2664,7 @@ _outSelectStmt(StringInfo str, const SelectStmt *node)
 	WRITE_NODE_FIELD(withClause);
 	WRITE_ENUM_FIELD(op, SetOperation);
 	WRITE_BOOL_FIELD(all);
+	WRITE_NODE_FIELD(correspondingClause);
 	WRITE_NODE_FIELD(larg);
 	WRITE_NODE_FIELD(rarg);
 }
@@ -2971,6 +2972,8 @@ _outSetOperationStmt(StringInfo str, const SetOperationStmt *node)
 	WRITE_BOOL_FIELD(all);
 	WRITE_NODE_FIELD(larg);
 	WRITE_NODE_FIELD(rarg);
+	WRITE_NODE_FIELD(correspondingColumns);
+	WRITE_BOOL_FIELD(hasCorrespondingBy);
 	WRITE_NODE_FIELD(colTypes);
 	WRITE_NODE_FIELD(colTypmods);
 	WRITE_NODE_FIELD(colCollations);
@@ -3148,6 +3151,7 @@ _outAExpr(StringInfo str, const A_Expr *node)
 static void
 _outValue(StringInfo str, const Value *value)
 {
+	/* NB: this isn't a complete set of fields */
 	switch (value->type)
 	{
 		case T_Integer:
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 474f221a75..6e284f9ef8 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -416,6 +416,8 @@ _readSetOperationStmt(void)
 	READ_BOOL_FIELD(all);
 	READ_NODE_FIELD(larg);
 	READ_NODE_FIELD(rarg);
+	READ_NODE_FIELD(correspondingColumns);
+	READ_BOOL_FIELD(hasCorrespondingBy);
 	READ_NODE_FIELD(colTypes);
 	READ_NODE_FIELD(colTypmods);
 	READ_NODE_FIELD(colCollations);
diff --git a/src/backend/nodes/value.c b/src/backend/nodes/value.c
index 5d2f96c103..72afc172f9 100644
--- a/src/backend/nodes/value.c
+++ b/src/backend/nodes/value.c
@@ -26,6 +26,7 @@ makeInteger(long i)
 
 	v->type = T_Integer;
 	v->val.ival = i;
+	v->location = -1;
 	return v;
 }
 
@@ -41,6 +42,7 @@ makeFloat(char *numericStr)
 
 	v->type = T_Float;
 	v->val.str = numericStr;
+	v->location = -1;
 	return v;
 }
 
@@ -56,6 +58,7 @@ makeString(char *str)
 
 	v->type = T_String;
 	v->val.str = str;
+	v->location = -1;
 	return v;
 }
 
@@ -71,5 +74,6 @@ makeBitString(char *str)
 
 	v->type = T_BitString;
 	v->val.str = str;
+	v->location = -1;
 	return v;
 }
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index e327e66f6b..f02066dd5b 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -91,7 +91,8 @@ static List *generate_setop_tlist(List *colTypes, List *colCollations,
 					 Index varno,
 					 bool hack_constants,
 					 List *input_tlist,
-					 List *refnames_tlist);
+					 List *refnames_tlist,
+					 bool no_corresponding);
 static List *generate_append_tlist(List *colTypes, List *colCollations,
 					  bool flag,
 					  List *input_tlists,
@@ -110,6 +111,7 @@ static Node *adjust_appendrel_attrs_mutator(Node *node,
 static Relids adjust_relid_set(Relids relids, Index oldrelid, Index newrelid);
 static List *adjust_inherited_tlist(List *tlist,
 					   AppendRelInfo *context);
+static List *make_corresponding_target(List *corresponding_list, List *subroot_list);
 
 
 /*
@@ -187,6 +189,24 @@ plan_set_operations(PlannerInfo *root)
 									   leftmostQuery->targetList,
 									   &top_tlist);
 	}
+	/*
+	 * If corresponding column specified, we take column names from it.
+	 */
+	else if (topop->correspondingColumns != NIL )
+	{
+		/*
+		 * Recurse on setOperations tree to generate paths for set ops. The
+		 * final output path should have just the column types shown as the
+		 * output from the top-level node, plus possibly resjunk working
+		 * columns (we can rely on upper-level nodes to deal with that).
+		 */
+		path = recurse_set_operations((Node *) topop, root,
+									  topop->colTypes, topop->colCollations,
+									  true, -1,
+									  topop->correspondingColumns,
+									  &top_tlist,
+									  NULL);
+	}
 	else
 	{
 		/*
@@ -252,6 +272,8 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 					   List **pTargetList,
 					   double *pNumGroups)
 {
+	SetOperationStmt *topop = (SetOperationStmt *) root->parse->setOperations;
+
 	if (IsA(setOp, RangeTblRef))
 	{
 		RangeTblRef *rtr = (RangeTblRef *) setOp;
@@ -316,23 +338,53 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 		path = (Path *) create_subqueryscan_path(root, rel, subpath,
 												 NIL, NULL);
 
-		/*
-		 * Figure out the appropriate target list, and update the
-		 * SubqueryScanPath with the PathTarget form of that.
-		 */
-		tlist = generate_setop_tlist(colTypes, colCollations,
+		if (topop->correspondingColumns != NIL )
+		{
+			List	    *correspondingTarget;
+
+			/*
+			 * make target list that only contains corresponding column
+			 * from sub-queries list ito use it for projection
+			 */
+			correspondingTarget = make_corresponding_target(
+											  topop->correspondingColumns,
+											  subroot->processed_tlist);
+
+			/*
+			 * Figure out the appropriate target list, and update the
+			 * SubqueryScanPath with the PathTarget form of that.
+			 */
+			tlist = generate_setop_tlist(colTypes, colCollations, flag,
+											  rtr->rtindex, true,
+											  correspondingTarget,
+											  refnames_tlist, false);
+
+			path = apply_projection_to_path(root, rel, path,
+					create_pathtarget(root, tlist));
+
+			/* Return the fully-fledged tlist to caller, too */
+			*pTargetList = tlist;
+
+		}
+		else
+		{
+			/*
+			 * Figure out the appropriate target list, and update the
+			 * SubqueryScanPath with the PathTarget form of that.
+			 */
+			tlist = generate_setop_tlist(colTypes, colCollations,
 									 flag,
 									 rtr->rtindex,
 									 true,
 									 subroot->processed_tlist,
-									 refnames_tlist);
+									 refnames_tlist, true);
 
-		path = apply_projection_to_path(root, rel, path,
+			path = apply_projection_to_path(root, rel, path,
 										create_pathtarget(root, tlist));
 
-		/* Return the fully-fledged tlist to caller, too */
-		*pTargetList = tlist;
-
+			/* Return the fully-fledged tlist to caller, too */
+			*pTargetList = tlist;
+		}
 		/*
 		 * Estimate number of groups if caller wants it.  If the subquery used
 		 * grouping or aggregation, its output is probably mostly unique
@@ -392,7 +444,7 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 												0,
 												false,
 												*pTargetList,
-												refnames_tlist);
+												refnames_tlist, true);
 			path = apply_projection_to_path(root,
 											path->parent,
 											path,
@@ -1004,7 +1056,8 @@ generate_setop_tlist(List *colTypes, List *colCollations,
 					 Index varno,
 					 bool hack_constants,
 					 List *input_tlist,
-					 List *refnames_tlist)
+					 List *refnames_tlist,
+					 bool no_corresponding)
 {
 	List	   *tlist = NIL;
 	int			resno = 1;
@@ -1026,8 +1079,8 @@ generate_setop_tlist(List *colTypes, List *colCollations,
 
 		rtlc = lnext(rtlc);
 
-		Assert(inputtle->resno == resno);
-		Assert(reftle->resno == resno);
+		Assert(!no_corresponding || inputtle->resno == resno);
+		Assert(!no_corresponding || reftle->resno == resno);
 		Assert(!inputtle->resjunk);
 		Assert(!reftle->resjunk);
 
@@ -2150,3 +2203,70 @@ adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node,
 	/* Now translate for this child */
 	return adjust_appendrel_attrs(root, node, appinfo);
 }
+
+/*
+ * generate target list from left target list with the order
+ * of right target list
+ */
+static List *
+make_corresponding_target(List *corresponding_list, List *subroot_list)
+{
+	Index internal = 0;
+	ListCell   *ltl;
+	ListCell   *rtl;
+	int			size;
+	int			i;
+	List *matchingColumns = NIL;
+	TargetEntry *simple_te_array;
+
+	size = list_length(corresponding_list) + 1;
+
+	/* Use array to find the order of corresponding columen */
+	simple_te_array = (TargetEntry *) palloc0(size * sizeof(TargetEntry));
+	foreach(ltl, corresponding_list)
+	{
+		foreach(rtl, subroot_list)
+		{
+			TargetEntry *ltle = (TargetEntry *) lfirst(ltl);
+			TargetEntry *rtle = (TargetEntry *) lfirst(rtl);
+
+			/* Names of the columns must be resolved before calling this method. */
+			Assert(ltle->resname != NULL);
+			Assert(rtle->resname != NULL);
+
+			/* If column names are the same, add it to array. */
+			if (strcmp(ltle->resname, rtle->resname) == 0)
+			{
+				simple_te_array[internal].xpr = rtle->xpr;
+				simple_te_array[internal].expr = rtle->expr;
+				simple_te_array[internal].resno = rtle->resno;
+				simple_te_array[internal].resname = rtle->resname;
+				simple_te_array[internal].ressortgroupref =
+						rtle->ressortgroupref;
+				simple_te_array[internal].resorigtbl = rtle->resorigtbl;
+				simple_te_array[internal].resorigcol = rtle->resorigcol;
+				simple_te_array[internal].resjunk = rtle->resjunk;
+				internal++;
+				continue;
+			}
+		}
+	}
+	/* traverse the array and make targetlist */
+	for (i = 0; i < internal; i++)
+	{
+		TargetEntry *tle = makeNode(TargetEntry);
+
+		tle->xpr = simple_te_array[i].xpr;
+		tle->expr = simple_te_array[i].expr;
+		tle->resno = simple_te_array[i].resno;
+		tle->resname = simple_te_array[i].resname;
+		tle->ressortgroupref = simple_te_array[i].ressortgroupref;
+		tle->resorigtbl = simple_te_array[i].resorigtbl;
+		tle->resorigcol = simple_te_array[i].resorigcol;
+		tle->resjunk = simple_te_array[i].resjunk;
+
+		matchingColumns = lappend(matchingColumns, tle);
+
+	}
+	return matchingColumns;
+}
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index f6025225be..653f10a09a 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -76,10 +76,18 @@ static Query *transformCreateTableAsStmt(ParseState *pstate,
 						   CreateTableAsStmt *stmt);
 static void transformLockingClause(ParseState *pstate, Query *qry,
 					   LockingClause *lc, bool pushedDown);
+static void makeUnionDatatype(List *ltargetlist, List *rtargetlist,
+		SetOperationStmt *op, List **targetlist, ParseState *parentParseState,
+		const char *context);
 #ifdef RAW_EXPRESSION_COVERAGE_TEST
 static bool test_raw_expression_coverage(Node *node, void *context);
 #endif
-
+static List *CommonColumns(List *ltargetlist, List *rtargetlist, bool filtered,
+								 ParseState *pstate, const char *context);
+static List *FilterColumnsByNames(List *common_columns, List *filter,
+								 ParseState *pstate, const char *context);
+static List *FilterColumnsByTL(List *targetlist, List *filter, bool check_uniq,
+								 ParseState *pstate, const char *context);
 
 /*
  * parse_analyze
@@ -1653,7 +1661,37 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->targetList = NIL;
 	targetvars = NIL;
 	targetnames = NIL;
-	left_tlist = list_head(leftmostQuery->targetList);
+
+	/*
+	 * for corresponding clause limits top-level query targetlist to those
+	 * corresponding column list only
+	 */
+	if (sostmt->correspondingColumns != NIL )
+	{
+		left_tlist = list_head(sostmt->correspondingColumns);
+		/*
+		 * In the case of corresponding without by clause property across
+		 * the statement may differ
+		 */
+		if (!sostmt->hasCorrespondingBy)
+		{
+			Node *correspodning_node;
+			correspodning_node = sostmt->larg;
+			while (correspodning_node && IsA(correspodning_node, SetOperationStmt))
+			{
+				SetOperationStmt *op = (SetOperationStmt *) correspodning_node;
+				op->correspondingColumns = sostmt->correspondingColumns;
+				op->colTypes = sostmt->colTypes;
+				op->colTypmods = sostmt->colTypmods;
+				op->colCollations = sostmt->colCollations;
+				op->groupClauses = sostmt->groupClauses;
+
+				correspodning_node = op->larg;
+			}
+		}
+	}
+	else
+		left_tlist = list_head(leftmostQuery->targetList);
 
 	forthree(lct, sostmt->colTypes,
 			 lcm, sostmt->colTypmods,
@@ -1913,8 +1951,6 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 		SetOperationStmt *op = makeNode(SetOperationStmt);
 		List	   *ltargetlist;
 		List	   *rtargetlist;
-		ListCell   *ltl;
-		ListCell   *rtl;
 		const char *context;
 
 		context = (stmt->op == SETOP_UNION ? "UNION" :
@@ -1925,6 +1961,84 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 		op->all = stmt->all;
 
 		/*
+		 * If CORRESPONDING is specified, syntax and column name validities checked,
+		 * column filtering is done by a subquery later on.
+		 */
+		if (stmt->correspondingClause == NIL )
+		{
+			/* No CORRESPONDING clause, no operation needed for column filtering */
+			op->correspondingColumns = stmt->correspondingClause;
+			op->hasCorrespondingBy = false;
+		}
+		else
+		{
+			/*
+			 * CORRESPONDING clause, find matching column names from both tables.
+			 * If there are none then it is a syntax error.
+			 */
+			Query	   *largQuery;
+			Query	   *rargQuery;
+			List	   *matchingColumns;
+			List	   *rightCorrespondingColumns;
+
+			op->hasCorrespondingBy = linitial(stmt->correspondingClause) != NULL;
+
+			/* Analyze left query to resolve column names. */
+			largQuery = parse_sub_analyze((Node *) stmt->larg,
+										  pstate, NULL, false, false );
+
+			/* Analyze right query to resolve column names. */
+			rargQuery = parse_sub_analyze((Node *) stmt->rarg,
+										  pstate, NULL, false, false );
+
+			/* Find matching columns from both queries. */
+			matchingColumns = CommonColumns(largQuery->targetList,
+											rargQuery->targetList,
+											op->hasCorrespondingBy,
+											pstate,
+											context);
+
+			/*
+			 * If matchingColumns is empty, there is an error.
+			 * At least one column in the select lists must have the same name.
+			 */
+			if (matchingColumns == NIL)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("there is not any corresponding name"),
+	 errhint("%s queries with a CORRESPONDING clause must have at least one column with the same name",
+								 context),
+						 parser_errposition(pstate,
+									exprLocation((Node *)
+									linitial(largQuery->targetList)))));
+
+			/* Use column filter when it is known */
+			if (op->hasCorrespondingBy)
+				matchingColumns = FilterColumnsByNames(matchingColumns,
+													   stmt->correspondingClause,
+													   pstate,
+													   context);
+
+			op->correspondingColumns = matchingColumns;
+
+			/*
+			 * When we know matching columns, we can quickly create
+			 * corresponding target list for right target list. It is faster,
+			 * than using symmetry. Ensure unique columns when hasCorrespondingBy
+			 * is true - in this case, the uniq is not checked already.
+			 */
+			rightCorrespondingColumns = FilterColumnsByTL(rargQuery->targetList,
+														  matchingColumns,
+														  op->hasCorrespondingBy,
+														  pstate,
+														  context);
+
+			/* make union'd datatype of output column */
+			makeUnionDatatype(matchingColumns, rightCorrespondingColumns,
+								op, targetlist, pstate, context);
+		}
+
+		/*
 		 * Recursively transform the left child node.
 		 */
 		op->larg = transformSetOperationTree(pstate, stmt->larg,
@@ -1949,177 +2063,415 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 											 false,
 											 &rtargetlist);
 
-		/*
-		 * Verify that the two children have the same number of non-junk
-		 * columns, and determine the types of the merged output columns.
-		 */
-		if (list_length(ltargetlist) != list_length(rtargetlist))
-			ereport(ERROR,
-					(errcode(ERRCODE_SYNTAX_ERROR),
-				 errmsg("each %s query must have the same number of columns",
-						context),
-					 parser_errposition(pstate,
-										exprLocation((Node *) rtargetlist))));
-
-		if (targetlist)
-			*targetlist = NIL;
-		op->colTypes = NIL;
-		op->colTypmods = NIL;
-		op->colCollations = NIL;
-		op->groupClauses = NIL;
-		forboth(ltl, ltargetlist, rtl, rtargetlist)
+		if (op->correspondingColumns == NIL )
 		{
-			TargetEntry *ltle = (TargetEntry *) lfirst(ltl);
-			TargetEntry *rtle = (TargetEntry *) lfirst(rtl);
-			Node	   *lcolnode = (Node *) ltle->expr;
-			Node	   *rcolnode = (Node *) rtle->expr;
-			Oid			lcoltype = exprType(lcolnode);
-			Oid			rcoltype = exprType(rcolnode);
-			int32		lcoltypmod = exprTypmod(lcolnode);
-			int32		rcoltypmod = exprTypmod(rcolnode);
-			Node	   *bestexpr;
-			int			bestlocation;
-			Oid			rescoltype;
-			int32		rescoltypmod;
-			Oid			rescolcoll;
-
-			/* select common type, same as CASE et al */
-			rescoltype = select_common_type(pstate,
-											list_make2(lcolnode, rcolnode),
-											context,
-											&bestexpr);
-			bestlocation = exprLocation(bestexpr);
-			/* if same type and same typmod, use typmod; else default */
-			if (lcoltype == rcoltype && lcoltypmod == rcoltypmod)
-				rescoltypmod = lcoltypmod;
-			else
-				rescoltypmod = -1;
-
+			makeUnionDatatype(ltargetlist, rtargetlist, op, targetlist, pstate,
+					context);
 			/*
-			 * Verify the coercions are actually possible.  If not, we'd fail
-			 * later anyway, but we want to fail now while we have sufficient
-			 * context to produce an error cursor position.
-			 *
-			 * For all non-UNKNOWN-type cases, we verify coercibility but we
-			 * don't modify the child's expression, for fear of changing the
-			 * child query's semantics.
-			 *
-			 * If a child expression is an UNKNOWN-type Const or Param, we
-			 * want to replace it with the coerced expression.  This can only
-			 * happen when the child is a leaf set-op node.  It's safe to
-			 * replace the expression because if the child query's semantics
-			 * depended on the type of this output column, it'd have already
-			 * coerced the UNKNOWN to something else.  We want to do this
-			 * because (a) we want to verify that a Const is valid for the
-			 * target type, or resolve the actual type of an UNKNOWN Param,
-			 * and (b) we want to avoid unnecessary discrepancies between the
-			 * output type of the child query and the resolved target type.
-			 * Such a discrepancy would disable optimization in the planner.
-			 *
-			 * If it's some other UNKNOWN-type node, eg a Var, we do nothing
-			 * (knowing that coerce_to_common_type would fail).  The planner
-			 * is sometimes able to fold an UNKNOWN Var to a constant before
-			 * it has to coerce the type, so failing now would just break
-			 * cases that might work.
+			 * Verify that the two children have the same number of non-junk
+			 * columns, and determine the types of the merged output columns.
 			 */
-			if (lcoltype != UNKNOWNOID)
-				lcolnode = coerce_to_common_type(pstate, lcolnode,
-												 rescoltype, context);
-			else if (IsA(lcolnode, Const) ||
-					 IsA(lcolnode, Param))
-			{
-				lcolnode = coerce_to_common_type(pstate, lcolnode,
-												 rescoltype, context);
-				ltle->expr = (Expr *) lcolnode;
-			}
+			if (list_length(ltargetlist) != list_length(rtargetlist))
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("each %s query must have the same number of columns", context),
+						 parser_errposition(pstate,
+											exprLocation((Node *) rtargetlist))));
+		}
+
+		return (Node *) op;
+	}
+}
+
+/*
+ * Processes targetlists of two queries for columns with same names to use
+ * with UNION/INTERSECT/EXCEPT CORRESPONDING. filtered is true, when
+ * CORRESPONDING BY is used. When it is false, we can check uniq names
+ * in rtargetlist here.
+ */
+static List *
+CommonColumns(List *ltargetlist, List *rtargetlist, bool filtered,
+			  ParseState *pstate, const char *context)
+{
+	List	   *common_columns = NIL;
+	ListCell   *ltlc;
+	ListCell   *rtlc;
+	int			resno = 1;
+
+	foreach(ltlc, ltargetlist)
+	{
+		TargetEntry *lte = (TargetEntry *) lfirst(ltlc);
+		bool		found = false;
+
+		Assert(lte->resname != NULL);
 
-			if (rcoltype != UNKNOWNOID)
-				rcolnode = coerce_to_common_type(pstate, rcolnode,
-												 rescoltype, context);
-			else if (IsA(rcolnode, Const) ||
-					 IsA(rcolnode, Param))
+		foreach(rtlc, rtargetlist)
+		{
+			ListCell   *lc;
+			TargetEntry *rte = (TargetEntry *) lfirst(rtlc);
+
+			Assert(rte->resname != NULL);
+
+			if (strcmp(lte->resname, rte->resname) == 0)
 			{
-				rcolnode = coerce_to_common_type(pstate, rcolnode,
-												 rescoltype, context);
-				rtle->expr = (Expr *) rcolnode;
+				if (filtered)
+				{
+					/*
+					 * We found common column, but we don't know if it
+					 * is in CORRESPONDING BY list - so don't try do more
+					 * work here. The column list will be modified later,
+					 * so use shall copy here.
+					 */
+					common_columns = lappend(common_columns, lte);
+					break;
+				}
+
+				/* If same column name mentioned more than once it is syntax error . */
+				if (found)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("corresponding column \"%s\" is used more times", rte->resname),
+	 errhint("In %s queries with CORRESPONDING clause the corresponding column names must be unique.",
+									context),
+							 parser_errposition(pstate,
+												exprLocation((Node *) rte))));
+
+				found = true;
+
+				/* In this case, common_columns must be unique */
+				foreach(lc, common_columns)
+				{
+					TargetEntry *te = (TargetEntry *) lfirst(lc);
+
+					if (strcmp(te->resname, lte->resname) == 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("corresponding column \"%s\" is used more times", lte->resname),
+	 errhint("In %s queries with CORRESPONDING clause the corresponding column names must be unique.",
+										context),
+								 parser_errposition(pstate,
+													exprLocation((Node *) lte))));
+				}
+
+				/* When is not any other filter create final te */
+				common_columns = lappend(common_columns,
+										 makeTargetEntry(lte->expr,
+														 (AttrNumber) resno++,
+														 lte->resname,
+														 false));
 			}
+		}
+	}
 
-			/*
-			 * Select common collation.  A common collation is required for
-			 * all set operators except UNION ALL; see SQL:2008 7.13 <query
-			 * expression> Syntax Rule 15c.  (If we fail to identify a common
-			 * collation for a UNION ALL column, the curCollations element
-			 * will be set to InvalidOid, which may result in a runtime error
-			 * if something at a higher query level wants to use the column's
-			 * collation.)
-			 */
-			rescolcoll = select_common_collation(pstate,
-											  list_make2(lcolnode, rcolnode),
-										 (op->op == SETOP_UNION && op->all));
+	return common_columns;
+}
 
-			/* emit results */
-			op->colTypes = lappend_oid(op->colTypes, rescoltype);
-			op->colTypmods = lappend_int(op->colTypmods, rescoltypmod);
-			op->colCollations = lappend_oid(op->colCollations, rescolcoll);
+/*
+ * Returns filtered common columns list - filter is based on CORRESPONDING BY
+ * list Ensure CORRESPONDING BY list is unique. Result is in CORRESPONDING BY
+ * list order. Common columns list can hold duplicate columns.
+ */
+static List *
+FilterColumnsByNames(List *common_columns, List *filter,
+					 ParseState *pstate, const char *context)
+{
+	List	   *filtered_columns = NIL;
+	ListCell   *flc;
+	int			resno = 1;
 
-			/*
-			 * For all cases except UNION ALL, identify the grouping operators
-			 * (and, if available, sorting operators) that will be used to
-			 * eliminate duplicates.
-			 */
-			if (op->op != SETOP_UNION || !op->all)
+	Assert(common_columns != NIL);
+	Assert(filter != NIL);
+
+	foreach(flc, filter)
+	{
+		Value	   *strval = (Value *) lfirst(flc);
+		char	   *name = strVal(strval);
+		ListCell   *tlc;
+		bool		found = false;
+
+		foreach(tlc, common_columns)
+		{
+			TargetEntry   *tec = (TargetEntry *) lfirst(tlc);
+
+			if (strcmp(tec->resname, name) == 0)
 			{
-				SortGroupClause *grpcl = makeNode(SortGroupClause);
-				Oid			sortop;
-				Oid			eqop;
-				bool		hashable;
-				ParseCallbackState pcbstate;
-
-				setup_parser_errposition_callback(&pcbstate, pstate,
-												  bestlocation);
-
-				/* determine the eqop and optional sortop */
-				get_sort_group_operators(rescoltype,
-										 false, true, false,
-										 &sortop, &eqop, NULL,
-										 &hashable);
-
-				cancel_parser_errposition_callback(&pcbstate);
-
-				/* we don't have a tlist yet, so can't assign sortgrouprefs */
-				grpcl->tleSortGroupRef = 0;
-				grpcl->eqop = eqop;
-				grpcl->sortop = sortop;
-				grpcl->nulls_first = false;		/* OK with or without sortop */
-				grpcl->hashable = hashable;
-
-				op->groupClauses = lappend(op->groupClauses, grpcl);
+				ListCell   *lc;
+
+				/*
+				 * When "found" is true, then common_columns contains
+				 * duplicate columns. Raise exception then.
+				 */
+				if (found)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("corresponding column \"%s\" is used more times", name),
+	 errhint("In %s queries with CORRESPONDING BY clause the corresponding column names must be unique.",
+									context),
+							 parser_errposition(pstate,
+												exprLocation((Node *) tec))));
+
+				found = true;
+
+				/* result list should not to contains this name */
+				foreach(lc, filtered_columns)
+				{
+					TargetEntry   *te = (TargetEntry *) lfirst(lc);
+
+					/*
+					 * CORRESPONDING BY clause contains a column name that is
+					 * not in unique in this clause
+					 */
+					if (strcmp(te->resname, name) == 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("column name \"%s\" is not unique in CORRESPONDING BY clause", name),
+								 errhint("CORRESPONDING BY clause must contain unique column names only."),
+								 parser_errposition(pstate, strval->location)));
+				}
+
+				/* create te with correct resno */
+				filtered_columns = lappend(filtered_columns,
+										 makeTargetEntry(tec->expr,
+														 (AttrNumber) resno++,
+														 tec->resname,
+														 false));
 			}
+		}
 
-			/*
-			 * Construct a dummy tlist entry to return.  We use a SetToDefault
-			 * node for the expression, since it carries exactly the fields
-			 * needed, but any other expression node type would do as well.
-			 */
-			if (targetlist)
+		/*
+		 * CORRESPONDING BY clause contains a column name that is not
+		 * in common columns.
+		 */
+		if (!found)
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("column name \"%s\" can not be used in CORRESPONDING BY list", name),
+		 errhint("%s queries with a CORRESPONDING BY clause must contain column names from both tables.",
+									 context),
+					 parser_errposition(pstate, strval->location)));
+	}
+
+	return filtered_columns;
+}
+
+/*
+ * Prepare target list for right query of CORRESPONDING clause.
+ * When filtered is true, filtered columns in target list are
+ * unique.
+ */
+static List *
+FilterColumnsByTL(List *targetlist, List *filter, bool check_uniq,
+				  ParseState *pstate, const char *context)
+{
+	List	   *result = NIL;
+	ListCell   *lc;
+	int			resno = 1;
+
+	foreach(lc, filter)
+	{
+		TargetEntry *fte = (TargetEntry *) lfirst(lc);
+		ListCell	*tle;
+		bool		found = false;
+
+		foreach(tle, targetlist)
+		{
+			TargetEntry *te = (TargetEntry *) lfirst(tle);
+
+			if (strcmp(fte->resname, te->resname) == 0)
 			{
-				SetToDefault *rescolnode = makeNode(SetToDefault);
-				TargetEntry *restle;
-
-				rescolnode->typeId = rescoltype;
-				rescolnode->typeMod = rescoltypmod;
-				rescolnode->collation = rescolcoll;
-				rescolnode->location = bestlocation;
-				restle = makeTargetEntry((Expr *) rescolnode,
-										 0,		/* no need to set resno */
-										 NULL,
-										 false);
-				*targetlist = lappend(*targetlist, restle);
+				/* create te with correct resno */
+				result = lappend(result,
+								 makeTargetEntry(te->expr,
+												 (AttrNumber) resno++,
+												 te->resname,
+												 false));
+
+				if (!check_uniq)
+					break;
+
+				/*
+				 * When "found" is true, then targetlist contains
+				 * duplicate filtered columns. Raise exception then.
+				 */
+				if (found)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("corresponding column \"%s\" is used more times", te->resname),
+							 errhint("In %s queries with CORRESPONDING BY clause the corresponding column names must be unique.",
+									context),
+							 parser_errposition(pstate,
+												exprLocation((Node *) te))));
+
+				found = true;
 			}
 		}
+	}
 
-		return (Node *) op;
+	return result;
+}
+
+/*
+ * process right and left target list to set up union'd datatype
+ */
+static void
+makeUnionDatatype(List *ltargetlist, List *rtargetlist, SetOperationStmt *op,
+		List **targetlist, ParseState *pstate, const char *context)
+{
+	ListCell   *ltl;
+	ListCell   *rtl;
+
+	if (targetlist)
+		*targetlist = NIL;
+
+	op->colTypes = NIL;
+	op->colTypmods = NIL;
+	op->colCollations = NIL;
+	op->groupClauses = NIL;
+
+	forboth(ltl, ltargetlist, rtl, rtargetlist)
+	{
+		TargetEntry	   *ltle = (TargetEntry *) lfirst(ltl);
+		TargetEntry	   *rtle = (TargetEntry *) lfirst(rtl);
+		Node		   *lcolnode = (Node *) ltle->expr;
+		Node		   *rcolnode = (Node *) rtle->expr;
+		Oid			lcoltype = exprType(lcolnode);
+		Oid			rcoltype = exprType(rcolnode);
+		int32		lcoltypmod = exprTypmod(lcolnode);
+		int32		rcoltypmod = exprTypmod(rcolnode);
+		Node	   *bestexpr;
+		int			bestlocation;
+		Oid			rescoltype;
+		int32		rescoltypmod;
+		Oid			rescolcoll;
+
+		/* select common type, same as CASE et al */
+		rescoltype = select_common_type(pstate, list_make2(lcolnode, rcolnode),
+				context, &bestexpr);
+		bestlocation = exprLocation(bestexpr);
+		/* if same type and same typmod, use typmod; else default */
+		if (lcoltype == rcoltype && lcoltypmod == rcoltypmod)
+			rescoltypmod = lcoltypmod;
+		else
+			rescoltypmod = -1;
+
+		/*
+		 * Verify the coercions are actually possible.  If not, we'd fail
+		 * later anyway, but we want to fail now while we have sufficient
+		 * context to produce an error cursor position.
+		 *
+		 * For all non-UNKNOWN-type cases, we verify coercibility but we
+		 * don't modify the child's expression, for fear of changing the
+		 * child query's semantics.
+		 *
+		 * If a child expression is an UNKNOWN-type Const or Param, we
+		 * want to replace it with the coerced expression.  This can only
+		 * happen when the child is a leaf set-op node.  It's safe to
+		 * replace the expression because if the child query's semantics
+		 * depended on the type of this output column, it'd have already
+		 * coerced the UNKNOWN to something else.  We want to do this
+		 * because (a) we want to verify that a Const is valid for the
+		 * target type, or resolve the actual type of an UNKNOWN Param,
+		 * and (b) we want to avoid unnecessary discrepancies between the
+		 * output type of the child query and the resolved target type.
+		 * Such a discrepancy would disable optimization in the planner.
+		 *
+		 * If it's some other UNKNOWN-type node, eg a Var, we do nothing
+		 * (knowing that coerce_to_common_type would fail).  The planner
+		 * is sometimes able to fold an UNKNOWN Var to a constant before
+		 * it has to coerce the type, so failing now would just break
+		 * cases that might work.
+		 */
+		if (lcoltype != UNKNOWNOID)
+			lcolnode = coerce_to_common_type(pstate, lcolnode, rescoltype,
+					context);
+		else if (IsA(lcolnode, Const) || IsA(lcolnode, Param))
+		{
+			lcolnode = coerce_to_common_type(pstate, lcolnode, rescoltype,
+					context);
+			ltle->expr = (Expr *) lcolnode;
+		}
+
+		if (rcoltype != UNKNOWNOID)
+			rcolnode = coerce_to_common_type(pstate, rcolnode, rescoltype,
+					context);
+		else if (IsA(rcolnode, Const) || IsA(rcolnode, Param))
+		{
+			rcolnode = coerce_to_common_type(pstate, rcolnode, rescoltype,
+					context);
+			rtle->expr = (Expr *) rcolnode;
+		}
+
+		/*
+		 * Select common collation.  A common collation is required for
+		 * all set operators except UNION ALL; see SQL:2008 7.13 <query
+		 * expression> Syntax Rule 15c.  (If we fail to identify a common
+		 * collation for a UNION ALL column, the curCollations element
+		 * will be set to InvalidOid, which may result in a runtime error
+		 * if something at a higher query level wants to use the column's
+		 * collation.)
+		 */
+		rescolcoll = select_common_collation(pstate,
+				list_make2(lcolnode, rcolnode),
+				(op->op == SETOP_UNION && op->all));
+
+		/* emit results */
+		op->colTypes = lappend_oid(op->colTypes, rescoltype);
+		op->colTypmods = lappend_int(op->colTypmods, rescoltypmod);
+		op->colCollations = lappend_oid(op->colCollations, rescolcoll);
+
+		/*
+		 * For all cases except UNION ALL, identify the grouping operators
+		 * (and, if available, sorting operators) that will be used to
+		 * eliminate duplicates.
+		 */
+		if (op->op != SETOP_UNION || !op->all)
+		{
+			SortGroupClause *grpcl = makeNode(SortGroupClause);
+			Oid			sortop;
+			Oid			eqop;
+			bool		hashable;
+			ParseCallbackState pcbstate;
+
+			setup_parser_errposition_callback(&pcbstate, pstate, bestlocation);
+
+			/* determine the eqop and optional sortop */
+			get_sort_group_operators(rescoltype, false, true, false, &sortop,
+					&eqop, NULL, &hashable);
+
+			cancel_parser_errposition_callback(&pcbstate);
+
+			/* we don't have a tlist yet, so can't assign sortgrouprefs */
+			grpcl->tleSortGroupRef = 0;
+			grpcl->eqop = eqop;
+			grpcl->sortop = sortop;
+			grpcl->nulls_first = false; /* OK with or without sortop */
+			grpcl->hashable = hashable;
+
+			op->groupClauses = lappend(op->groupClauses, grpcl);
+		}
+
+		/*
+		 * Construct a dummy tlist entry to return.  We use a SetToDefault
+		 * node for the expression, since it carries exactly the fields
+		 * needed, but any other expression node type would do as well.
+		 */
+		if (targetlist)
+		{
+			SetToDefault   *rescolnode = makeNode(SetToDefault);
+			TargetEntry	   *restle;
+
+			rescolnode->typeId = rescoltype;
+			rescolnode->typeMod = rescoltypmod;
+			rescolnode->collation = rescolcoll;
+			rescolnode->location = bestlocation;
+
+			/* no need to set resno */
+			restle = makeTargetEntry((Expr *) rescolnode, 0,
+			NULL, false );
+			*targetlist = lappend(*targetlist, restle);
+		}
 	}
+
 }
 
 /*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9d53a29ad2..6e5257d30d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -166,7 +166,7 @@ static void insertSelectOptions(SelectStmt *stmt,
 								Node *limitOffset, Node *limitCount,
 								WithClause *withClause,
 								core_yyscan_t yyscanner);
-static Node *makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg);
+static Node *makeSetOp(SetOperation op, bool all, List *correspondingClause, Node *larg, Node *rarg);
 static Node *doNegate(Node *n, int location);
 static void doNegateFloat(Value *v);
 static Node *makeAndExpr(Node *lexpr, Node *rexpr, int location);
@@ -394,7 +394,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				relation_expr_list dostmt_opt_list
 				transform_element_list transform_type_list
 				TriggerTransitions TriggerReferencing
-				publication_name_list
+				publication_name_list opt_corresponding_clause
 
 %type <list>	group_by_list
 %type <node>	group_by_item empty_grouping_set rollup_clause cube_clause
@@ -614,7 +614,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
 	CLUSTER COALESCE COLLATE COLLATION COLUMN COLUMNS COMMENT COMMENTS COMMIT
 	COMMITTED CONCURRENTLY CONFIGURATION CONFLICT CONNECTION CONSTRAINT
-	CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE
+	CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY CORRESPONDING COST CREATE
 	CROSS CSV CUBE CURRENT_P
 	CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA
 	CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
@@ -3579,7 +3579,10 @@ columnList:
 
 columnElem: ColId
 				{
-					$$ = (Node *) makeString($1);
+					Value *v = makeString($1);
+
+					v->location = @1;
+					$$ = (Node *) v;
 				}
 		;
 
@@ -10878,20 +10881,26 @@ simple_select:
 					n->fromClause = list_make1($2);
 					$$ = (Node *)n;
 				}
-			| select_clause UNION all_or_distinct select_clause
+			| select_clause UNION all_or_distinct opt_corresponding_clause select_clause
 				{
-					$$ = makeSetOp(SETOP_UNION, $3, $1, $4);
+					$$ = makeSetOp(SETOP_UNION, $3, $4, $1, $5);
 				}
-			| select_clause INTERSECT all_or_distinct select_clause
+			| select_clause INTERSECT all_or_distinct opt_corresponding_clause select_clause
 				{
-					$$ = makeSetOp(SETOP_INTERSECT, $3, $1, $4);
+					$$ = makeSetOp(SETOP_INTERSECT, $3, $4, $1, $5);
 				}
-			| select_clause EXCEPT all_or_distinct select_clause
+			| select_clause EXCEPT all_or_distinct opt_corresponding_clause select_clause
 				{
-					$$ = makeSetOp(SETOP_EXCEPT, $3, $1, $4);
+					$$ = makeSetOp(SETOP_EXCEPT, $3, $4, $1, $5);
 				}
 		;
 
+opt_corresponding_clause:
+			CORRESPONDING BY '(' columnList ')'	{ $$ = $4; }
+			| CORRESPONDING							{ $$ = list_make1(NIL); }
+			| /*EMPTY*/								{ $$ = NIL; }
+			;
+
 /*
  * SQL standard WITH clause looks like:
  *
@@ -14232,7 +14241,6 @@ name_list:	name
 					{ $$ = lappend($1, makeString($3)); }
 		;
 
-
 name:		ColId									{ $$ = $1; };
 
 database_name:
@@ -14549,6 +14557,7 @@ unreserved_keyword:
 			| CONTINUE_P
 			| CONVERSION_P
 			| COPY
+			| CORRESPONDING
 			| COST
 			| CSV
 			| CUBE
@@ -15383,7 +15392,7 @@ insertSelectOptions(SelectStmt *stmt,
 }
 
 static Node *
-makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg)
+makeSetOp(SetOperation op, bool all, List *correspondingClause, Node *larg, Node *rarg)
 {
 	SelectStmt *n = makeNode(SelectStmt);
 
@@ -15391,6 +15400,7 @@ makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg)
 	n->all = all;
 	n->larg = (SelectStmt *) larg;
 	n->rarg = (SelectStmt *) rarg;
+	n->correspondingClause = correspondingClause;
 	return (Node *) n;
 }
 
diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
index 0d7a2b1e1b..b553d847d8 100644
--- a/src/backend/parser/parse_type.c
+++ b/src/backend/parser/parse_type.c
@@ -735,7 +735,8 @@ typeStringToTypeName(const char *str)
 		stmt->limitCount != NULL ||
 		stmt->lockingClause != NIL ||
 		stmt->withClause != NULL ||
-		stmt->op != SETOP_NONE)
+		stmt->op != SETOP_NONE ||
+		stmt->correspondingClause != NULL)
 		goto fail;
 	if (list_length(stmt->targetList) != 1)
 		goto fail;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index c2681ced2a..adf797d600 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -5442,6 +5442,30 @@ get_setop_query(Node *setOp, Query *query, deparse_context *context,
 		}
 		if (op->all)
 			appendStringInfoString(buf, "ALL ");
+		if (op->correspondingColumns != NIL )
+		{
+			if (op->hasCorrespondingBy)
+			{
+				const char *sep;
+				ListCell *l;
+				appendStringInfoString(buf, "CORRESPONDING BY(");
+				sep = "";
+
+				foreach(l, op->correspondingColumns)
+				{
+					TargetEntry *tle = (TargetEntry *) lfirst(l);
+
+					appendStringInfoString(buf, sep);
+					appendStringInfo(buf, "%s", tle->resname);
+					sep = ", ";
+				}
+				appendStringInfoChar(buf, ')');
+
+			}
+			else
+
+				appendStringInfoString(buf, "CORRESPONDING ");
+		}
 
 		/* Always parenthesize if RHS is another setop */
 		need_paren = IsA(op->rarg, SetOperationStmt);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3a71dd5b37..ed8ce2f2d0 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1460,6 +1460,7 @@ typedef struct SelectStmt
 	 */
 	List	   *distinctClause; /* NULL, list of DISTINCT ON exprs, or
 								 * lcons(NIL,NIL) for all (SELECT DISTINCT) */
+	List	   *correspondingClause;	/* CORRESPONDING BY  clauses*/
 	IntoClause *intoClause;		/* target for SELECT INTO */
 	List	   *targetList;		/* the target list (of ResTarget) */
 	List	   *fromClause;		/* the FROM clause */
@@ -1495,7 +1496,6 @@ typedef struct SelectStmt
 	bool		all;			/* ALL specified? */
 	struct SelectStmt *larg;	/* left child */
 	struct SelectStmt *rarg;	/* right child */
-	/* Eventually add fields for CORRESPONDING spec here */
 } SelectStmt;
 
 
@@ -1525,8 +1525,8 @@ typedef struct SetOperationStmt
 	bool		all;			/* ALL specified? */
 	Node	   *larg;			/* left child */
 	Node	   *rarg;			/* right child */
-	/* Eventually add fields for CORRESPONDING spec here */
-
+	List	   *correspondingColumns;	/* list of corresponding column names */
+	bool		hasCorrespondingBy;		/* has corresponding by cluase? */
 	/* Fields derived during parse analysis: */
 	List	   *colTypes;		/* OID list of output column type OIDs */
 	List	   *colTypmods;		/* integer list of output column typmods */
diff --git a/src/include/nodes/value.h b/src/include/nodes/value.h
index ede97b7bcd..bf3b6e9b68 100644
--- a/src/include/nodes/value.h
+++ b/src/include/nodes/value.h
@@ -47,6 +47,7 @@ typedef struct Value
 		long		ival;		/* machine integer */
 		char	   *str;		/* string */
 	}			val;
+	int			location;		/* token location, or -1 if unknown */
 } Value;
 
 #define intVal(v)		(((Value *)(v))->val.ival)
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index cd21a789d5..7f1c2554e3 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -97,6 +97,7 @@ PG_KEYWORD("content", CONTENT_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("continue", CONTINUE_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("conversion", CONVERSION_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("copy", COPY, UNRESERVED_KEYWORD)
+PG_KEYWORD("corresponding", CORRESPONDING, UNRESERVED_KEYWORD)
 PG_KEYWORD("cost", COST, UNRESERVED_KEYWORD)
 PG_KEYWORD("create", CREATE, RESERVED_KEYWORD)
 PG_KEYWORD("cross", CROSS, TYPE_FUNC_NAME_KEYWORD)
diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out
index c719262720..3bcb89ceca 100644
--- a/src/test/regress/expected/create_view.out
+++ b/src/test/regress/expected/create_view.out
@@ -1626,3 +1626,28 @@ select pg_get_viewdef('tt19v', true);
 set client_min_messages = warning;
 DROP SCHEMA temp_view_test CASCADE;
 DROP SCHEMA testviewschm2 CASCADE;
+-- views with corresponding clause
+create view view_corresponding_01 as select 1 as a, 2 as b union all corresponding select 3 as a, 4 as b;
+select * from view_corresponding_01;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+create view view_corresponding_02 as select 1 as a, 2 as b union all corresponding by (a,b) select 3 as a, 4 as b, 5 as c;
+select * from view_corresponding_02;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+create view view_corresponding_03 as select 1 as a, 2 as b union all corresponding by (b,a) select 3 as a, 4 as b, 5 as c;
+select * from view_corresponding_03;
+ b | a 
+---+---
+ 2 | 1
+ 4 | 3
+(2 rows)
+
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index d706f42b2d..3e36dc58f9 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -2345,6 +2345,23 @@ toyemp| SELECT emp.name,
     emp.location,
     (12 * emp.salary) AS annualsal
    FROM emp;
+view_corresponding_01| SELECT 1 AS a,
+    2 AS b
+UNION ALL CORRESPONDING
+ SELECT 3 AS a,
+    4 AS b;
+view_corresponding_02| SELECT 1 AS a,
+    2 AS b
+UNION ALL CORRESPONDING BY(a, b)
+ SELECT 3 AS a,
+    4 AS b,
+    5 AS c;
+view_corresponding_03| SELECT 1 AS b,
+    2 AS a
+UNION ALL CORRESPONDING BY(b, a)
+ SELECT 3 AS b,
+    4 AS a,
+    5 AS c;
 SELECT tablename, rulename, definition FROM pg_rules
 	ORDER BY tablename, rulename;
 pg_settings|pg_settings_n|CREATE RULE pg_settings_n AS
diff --git a/src/test/regress/expected/union.out b/src/test/regress/expected/union.out
index 4d697bada7..59d6c001ef 100644
--- a/src/test/regress/expected/union.out
+++ b/src/test/regress/expected/union.out
@@ -59,6 +59,87 @@ SELECT 1.1 AS two UNION SELECT 2.2 ORDER BY 1;
  2.2
 (2 rows)
 
+SELECT 1 AS two UNION CORRESPONDING SELECT 2 two UNION CORRESPONDING SELECT 2 two;
+ two 
+-----
+   2
+   1
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a) SELECT 4 a, 5 b, 6 c;
+ a 
+---
+ 1
+ 4
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b) SELECT 4 a, 5 b, 6 c;
+ a | b 
+---+---
+ 1 | 2
+ 4 | 5
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c) SELECT 4 a, 5 b, 6 c;
+ b | c 
+---+---
+ 2 | 3
+ 5 | 6
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY (c,b) SELECT 4 a, 5 b, 6 c, 8 d;
+ c | b 
+---+---
+ 3 | 2
+ 6 | 5
+(2 rows)
+
+-- CORRESPONDING column ordering, left clause's column ordering must be preserved.
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+SELECT 2 b, 1 a, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+ b | a | c 
+---+---+---
+ 2 | 1 | 3
+ 5 | 4 | 6
+(2 rows)
+
+-- CORRESPONDING BY column ordering, BY clause column ordering must be preserved.
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 4 a, 5 b, 6 c;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c, a) SELECT 4 a, 5 b, 6 c;
+ b | c | a 
+---+---+---
+ 2 | 3 | 1
+ 5 | 6 | 4
+(2 rows)
+
+-- should to fail
+SELECT 10 a, 20 b UNION ALL CORRESPONDING SELECT 10 c, 20 d;
+ERROR:  there is not any corresponding name
+LINE 1: SELECT 10 a, 20 b UNION ALL CORRESPONDING SELECT 10 c, 20 d;
+               ^
+HINT:  UNION queries with a CORRESPONDING clause must have at least one column with the same name
+SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (a,b) SELECT 10 c, 20 d;
+ERROR:  there is not any corresponding name
+LINE 1: SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (a,b) SELECT 10...
+               ^
+HINT:  UNION queries with a CORRESPONDING clause must have at least one column with the same name
+SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (x) SELECT 10 c, 20 d;
+ERROR:  there is not any corresponding name
+LINE 1: SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (x) SELECT 10 c...
+               ^
+HINT:  UNION queries with a CORRESPONDING clause must have at least one column with the same name
 -- Mixed types
 SELECT 1.1 AS two UNION SELECT 2 ORDER BY 1;
  two 
@@ -124,6 +205,147 @@ SELECT 1.1 AS two UNION (SELECT 2 UNION ALL SELECT 2) ORDER BY 1;
    2
 (2 rows)
 
+-- other corresponding clause tests,
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+-- when column is not in result, then the name should not be unique
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS b, 5 AS a;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+ 5 | 6
+(3 rows)
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x3;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x6 UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS b, 5 AS a;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+ 5 | 6
+(3 rows)
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS b, -100 AS a;
+ b 
+---
+ 2
+ 4
+ 6
+(3 rows)
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS a, -100 AS x9;
+ a 
+---
+ 1
+ 3
+ 6
+(3 rows)
+
+SELECT 0 AS a, 1 AS b, 0 AS c UNION ALL CORRESPONDING SELECT 2 AS a, 3 AS b, 10 AS c, 20 AS d UNION ALL CORRESPONDING SELECT 11 AS c, 21 AS d;
+ c  
+----
+  0
+ 10
+ 11
+(3 rows)
+
+SELECT 0 AS a, 1 AS b  UNION ALL CORRESPONDING SELECT 2 AS a, 3 AS b, 10 AS c, 20 AS d UNION ALL CORRESPONDING SELECT 11 AS c, 21 AS d, 4 AS a;
+ a 
+---
+ 0
+ 2
+ 4
+(3 rows)
+
+SELECT 0 AS a, 1 AS b  UNION ALL CORRESPONDING SELECT 2 AS a, 3 AS b, 10 AS c, 20 AS d UNION ALL CORRESPONDING SELECT 11 AS c, 21 AS d, 5 AS b;
+ b 
+---
+ 1
+ 3
+ 5
+(3 rows)
+
+-- should fail
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3;
+ERROR:  corresponding column "x3" is used more times
+LINE 1: SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 U...
+                                                          ^
+HINT:  In UNION queries with CORRESPONDING clause the corresponding column names must be unique.
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3, -10 AS x3;
+ERROR:  corresponding column "x3" is used more times
+LINE 1: ...ELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3, -10 AS x3;
+                                                             ^
+HINT:  In UNION queries with CORRESPONDING clause the corresponding column names must be unique.
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(a,b,x3) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3;
+ERROR:  corresponding column "x3" is used more times
+LINE 1: SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 U...
+                                                          ^
+HINT:  In UNION queries with CORRESPONDING BY clause the corresponding column names must be unique.
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING BY(a,b,x3) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3, -10 AS x3;
+ERROR:  corresponding column "x3" is used more times
+LINE 1: ...ELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3, -10 AS x3;
+                                                             ^
+HINT:  In UNION queries with CORRESPONDING BY clause the corresponding column names must be unique.
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x6 UNION ALL CORRESPONDING BY(a,b,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+ERROR:  column name "b" is not unique in CORRESPONDING BY clause
+LINE 1: ... 0 AS x3, -1 AS x6 UNION ALL CORRESPONDING BY(a,b,b) SELECT ...
+                                                             ^
+HINT:  CORRESPONDING BY clause must contain unique column names only.
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS bb, -100 AS aa;
+ERROR:  there is not any corresponding name
+LINE 1: SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 U...
+                        ^
+HINT:  UNION queries with a CORRESPONDING clause must have at least one column with the same name
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS xxx, -100 AS x9;
+ERROR:  there is not any corresponding name
+LINE 1: SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 U...
+                        ^
+HINT:  UNION queries with a CORRESPONDING clause must have at least one column with the same name
+SELECT 0 AS a, 1 AS b UNION ALL CORRESPONDING SELECT 2 AS a, 3 AS b, 10 AS c, 20 AS d UNION ALL CORRESPONDING SELECT 11 AS c, 21 AS d;
+ERROR:  there is not any corresponding name
+LINE 1: SELECT 0 AS a, 1 AS b UNION ALL CORRESPONDING SELECT 2 AS a,...
+               ^
+HINT:  UNION queries with a CORRESPONDING clause must have at least one column with the same name
 --
 -- Try testing from tables...
 --
@@ -258,6 +480,74 @@ ORDER BY 1;
  hi de ho neighbor
 (5 rows)
 
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+         five          
+-----------------------
+ -1.2345678901234e+200
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+                     0
+(5 rows)
+
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING BY(five)
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+         five          
+-----------------------
+ -1.2345678901234e+200
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+                     0
+(5 rows)
+
+SELECT f1 AS ten FROM FLOAT8_TBL
+UNION ALL CORRESPONDING
+SELECT f1 AS ten FROM FLOAT8_TBL;
+          ten          
+-----------------------
+                     0
+                -34.84
+               -1004.3
+ -1.2345678901234e+200
+ -1.2345678901234e-200
+                     0
+                -34.84
+               -1004.3
+ -1.2345678901234e+200
+ -1.2345678901234e-200
+(10 rows)
+
+SELECT f1 AS five FROM FLOAT8_TBL
+  WHERE f1 BETWEEN -1e6 AND 1e6
+UNION CORRESPONDING
+SELECT f1 AS five FROM INT4_TBL
+  WHERE f1 BETWEEN 0 AND 1000000;
+         five          
+-----------------------
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+                     0
+                123456
+(5 rows)
+
+SELECT * FROM FLOAT8_TBL
+UNION corresponding SELECT * FROM FLOAT8_TBL;
+          f1           
+-----------------------
+                     0
+ -1.2345678901234e-200
+                -34.84
+ -1.2345678901234e+200
+               -1004.3
+(5 rows)
+
 --
 -- INTERSECT and EXCEPT
 --
@@ -320,6 +610,63 @@ SELECT q1 FROM int8_tbl EXCEPT ALL SELECT DISTINCT q2 FROM int8_tbl ORDER BY 1;
 
 SELECT q1 FROM int8_tbl EXCEPT ALL SELECT q1 FROM int8_tbl FOR NO KEY UPDATE;
 ERROR:  FOR NO KEY UPDATE is not allowed with UNION/INTERSECT/EXCEPT
+SELECT q2 FROM int8_tbl INTERSECT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+        q2        
+------------------
+              123
+ 4567890123456789
+(2 rows)
+
+SELECT q2 FROM int8_tbl INTERSECT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+        q2        
+------------------
+              123
+ 4567890123456789
+ 4567890123456789
+(3 rows)
+
+SELECT q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+        q2         
+-------------------
+ -4567890123456789
+               456
+(2 rows)
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+        q2         
+-------------------
+ -4567890123456789
+               456
+(2 rows)
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q1 AS q2 FROM int8_tbl ORDER BY 1;
+        q2         
+-------------------
+ -4567890123456789
+               456
+  4567890123456789
+(3 rows)
+
+SELECT q1 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+ q1 
+----
+(0 rows)
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+        q1        
+------------------
+              123
+ 4567890123456789
+(2 rows)
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q2 AS q1 FROM int8_tbl;
+        q1        
+------------------
+              123
+ 4567890123456789
+ 4567890123456789
+(3 rows)
+
 --
 -- Mixed types
 --
@@ -338,6 +685,21 @@ SELECT f1 FROM float8_tbl EXCEPT SELECT f1 FROM int4_tbl ORDER BY 1;
  -1.2345678901234e-200
 (4 rows)
 
+SELECT f1 FROM float8_tbl INTERSECT CORRESPONDING SELECT f1 FROM int4_tbl;
+ f1 
+----
+  0
+(1 row)
+
+SELECT f1 FROM float8_tbl EXCEPT CORRESPONDING SELECT f1 FROM int4_tbl ORDER BY 1;
+          f1           
+-----------------------
+ -1.2345678901234e+200
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+(4 rows)
+
 --
 -- Operator precedence and (((((extra))))) parentheses
 --
@@ -424,6 +786,24 @@ SELECT q1 FROM int8_tbl EXCEPT (((SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1)))
  4567890123456789
 (2 rows)
 
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2,q1 FROM int8_tbl
+ORDER BY q2,q1;
+ q1 | q2 
+----+----
+(0 rows)
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q1) SELECT q2,q1 FROM int8_tbl
+ORDER BY q1;
+ q1 
+----
+(0 rows)
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q2) SELECT q2,q1 FROM int8_tbl
+ORDER BY q2;
+ q2 
+----
+(0 rows)
+
 --
 -- New syntaxes (7.1) permit new tests
 --
@@ -459,6 +839,22 @@ SELECT '3.4'::numeric UNION SELECT 'foo';
 ERROR:  invalid input syntax for type numeric: "foo"
 LINE 1: SELECT '3.4'::numeric UNION SELECT 'foo';
                                            ^
+SELECT '3.4'::numeric AS a UNION CORRESPONDING SELECT 'foo' AS a;
+ERROR:  invalid input syntax for type numeric: "foo"
+LINE 1: ...CT '3.4'::numeric AS a UNION CORRESPONDING SELECT 'foo' AS a...
+                                                             ^
+SELECT a.f1 FROM (SELECT 'test' AS f1 FROM varchar_tbl) a
+UNION CORRESPONDING
+SELECT b.f1 FROM (SELECT f1 FROM varchar_tbl) b
+ORDER BY 1;
+  f1  
+------
+ a
+ ab
+ abcd
+ test
+(4 rows)
+
 --
 -- Test that expression-index constraints can be pushed down through
 -- UNION or UNION ALL
diff --git a/src/test/regress/sql/create_view.sql b/src/test/regress/sql/create_view.sql
index d6f50d6105..f61a01323b 100644
--- a/src/test/regress/sql/create_view.sql
+++ b/src/test/regress/sql/create_view.sql
@@ -551,3 +551,13 @@ select pg_get_viewdef('tt19v', true);
 set client_min_messages = warning;
 DROP SCHEMA temp_view_test CASCADE;
 DROP SCHEMA testviewschm2 CASCADE;
+
+-- views with corresponding clause
+create view view_corresponding_01 as select 1 as a, 2 as b union all corresponding select 3 as a, 4 as b;
+select * from view_corresponding_01;
+
+create view view_corresponding_02 as select 1 as a, 2 as b union all corresponding by (a,b) select 3 as a, 4 as b, 5 as c;
+select * from view_corresponding_02;
+
+create view view_corresponding_03 as select 1 as a, 2 as b union all corresponding by (b,a) select 3 as a, 4 as b, 5 as c;
+select * from view_corresponding_03;
diff --git a/src/test/regress/sql/union.sql b/src/test/regress/sql/union.sql
index 48e6850798..731e886b9a 100644
--- a/src/test/regress/sql/union.sql
+++ b/src/test/regress/sql/union.sql
@@ -20,6 +20,35 @@ SELECT 1 AS three UNION SELECT 2 UNION ALL SELECT 2 ORDER BY 1;
 
 SELECT 1.1 AS two UNION SELECT 2.2 ORDER BY 1;
 
+SELECT 1 AS two UNION CORRESPONDING SELECT 2 two UNION CORRESPONDING SELECT 2 two;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY (c,b) SELECT 4 a, 5 b, 6 c, 8 d;
+
+-- CORRESPONDING column ordering, left clause's column ordering must be preserved.
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+
+SELECT 2 b, 1 a, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+
+-- CORRESPONDING BY column ordering, BY clause column ordering must be preserved.
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c, a) SELECT 4 a, 5 b, 6 c;
+
+-- should to fail
+SELECT 10 a, 20 b UNION ALL CORRESPONDING SELECT 10 c, 20 d;
+
+SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (a,b) SELECT 10 c, 20 d;
+
+SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (x) SELECT 10 c, 20 d;
+
 -- Mixed types
 
 SELECT 1.1 AS two UNION SELECT 2 ORDER BY 1;
@@ -40,6 +69,37 @@ SELECT 1.1 AS three UNION SELECT 2 UNION ALL SELECT 2 ORDER BY 1;
 
 SELECT 1.1 AS two UNION (SELECT 2 UNION ALL SELECT 2) ORDER BY 1;
 
+-- other corresponding clause tests,
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6;
+
+-- when column is not in result, then the name should not be unique
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS b, 5 AS a;
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x3;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x6 UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS b, 5 AS a;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS b, -100 AS a;
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS a, -100 AS x9;
+SELECT 0 AS a, 1 AS b, 0 AS c UNION ALL CORRESPONDING SELECT 2 AS a, 3 AS b, 10 AS c, 20 AS d UNION ALL CORRESPONDING SELECT 11 AS c, 21 AS d;
+SELECT 0 AS a, 1 AS b  UNION ALL CORRESPONDING SELECT 2 AS a, 3 AS b, 10 AS c, 20 AS d UNION ALL CORRESPONDING SELECT 11 AS c, 21 AS d, 4 AS a;
+SELECT 0 AS a, 1 AS b  UNION ALL CORRESPONDING SELECT 2 AS a, 3 AS b, 10 AS c, 20 AS d UNION ALL CORRESPONDING SELECT 11 AS c, 21 AS d, 5 AS b;
+
+-- should fail
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3, -10 AS x3;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(a,b,x3) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING BY(a,b,x3) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3, -10 AS x3;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x6 UNION ALL CORRESPONDING BY(a,b,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS bb, -100 AS aa;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS xxx, -100 AS x9;
+SELECT 0 AS a, 1 AS b UNION ALL CORRESPONDING SELECT 2 AS a, 3 AS b, 10 AS c, 20 AS d UNION ALL CORRESPONDING SELECT 11 AS c, 21 AS d;
+
 --
 -- Try testing from tables...
 --
@@ -90,6 +150,29 @@ UNION
 SELECT TRIM(TRAILING FROM f1) FROM CHAR_TBL
 ORDER BY 1;
 
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING BY(five)
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+
+SELECT f1 AS ten FROM FLOAT8_TBL
+UNION ALL CORRESPONDING
+SELECT f1 AS ten FROM FLOAT8_TBL;
+
+SELECT f1 AS five FROM FLOAT8_TBL
+  WHERE f1 BETWEEN -1e6 AND 1e6
+UNION CORRESPONDING
+SELECT f1 AS five FROM INT4_TBL
+  WHERE f1 BETWEEN 0 AND 1000000;
+
+SELECT * FROM FLOAT8_TBL
+UNION corresponding SELECT * FROM FLOAT8_TBL;
+
 --
 -- INTERSECT and EXCEPT
 --
@@ -112,6 +195,22 @@ SELECT q1 FROM int8_tbl EXCEPT ALL SELECT DISTINCT q2 FROM int8_tbl ORDER BY 1;
 
 SELECT q1 FROM int8_tbl EXCEPT ALL SELECT q1 FROM int8_tbl FOR NO KEY UPDATE;
 
+SELECT q2 FROM int8_tbl INTERSECT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+
+SELECT q2 FROM int8_tbl INTERSECT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+
+SELECT q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q1 AS q2 FROM int8_tbl ORDER BY 1;
+
+SELECT q1 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q2 AS q1 FROM int8_tbl;
+
 --
 -- Mixed types
 --
@@ -120,6 +219,10 @@ SELECT f1 FROM float8_tbl INTERSECT SELECT f1 FROM int4_tbl ORDER BY 1;
 
 SELECT f1 FROM float8_tbl EXCEPT SELECT f1 FROM int4_tbl ORDER BY 1;
 
+SELECT f1 FROM float8_tbl INTERSECT CORRESPONDING SELECT f1 FROM int4_tbl;
+
+SELECT f1 FROM float8_tbl EXCEPT CORRESPONDING SELECT f1 FROM int4_tbl ORDER BY 1;
+
 --
 -- Operator precedence and (((((extra))))) parentheses
 --
@@ -150,6 +253,15 @@ SELECT q1 FROM int8_tbl EXCEPT SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1;
 -- But this should work:
 SELECT q1 FROM int8_tbl EXCEPT (((SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1))) ORDER BY 1;
 
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2,q1 FROM int8_tbl
+ORDER BY q2,q1;
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q1) SELECT q2,q1 FROM int8_tbl
+ORDER BY q1;
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q2) SELECT q2,q1 FROM int8_tbl
+ORDER BY q2;
+
 --
 -- New syntaxes (7.1) permit new tests
 --
@@ -170,6 +282,13 @@ ORDER BY 1;
 -- This should fail, but it should produce an error cursor
 SELECT '3.4'::numeric UNION SELECT 'foo';
 
+SELECT '3.4'::numeric AS a UNION CORRESPONDING SELECT 'foo' AS a;
+
+SELECT a.f1 FROM (SELECT 'test' AS f1 FROM varchar_tbl) a
+UNION CORRESPONDING
+SELECT b.f1 FROM (SELECT f1 FROM varchar_tbl) b
+ORDER BY 1;
+
 --
 -- Test that expression-index constraints can be pushed down through
 -- UNION or UNION ALL
#34Tom Lane
tgl@sss.pgh.pa.us
In reply to: Pavel Stehule (#33)
Re: New CORRESPONDING clause design

Pavel Stehule <pavel.stehule@gmail.com> writes:

Is following use case defined in standard?

postgres=# SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3
UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS a,
0 AS x6, -1 AS x6
UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS a, -100 AS aa;
┌───┐
│ a │
╞═══╡
│ 1 │
│ 3 │
│ 6 │
└───┘
(3 rows)

It depends on order of implementation

if we do (T1 U T2) U T3 ---> then result is correct,
but if we do T1 U (T2 U T3) ---> than it should to fail

UNION ALL should associate left-to-right, just like most other binary
operators, so this looks fine to me. Did you check that you get an
error if you put in parens to force the other order?

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#35Pavel Stehule
pavel.stehule@gmail.com
In reply to: Tom Lane (#34)
Re: New CORRESPONDING clause design

2017-03-30 21:43 GMT+02:00 Tom Lane <tgl@sss.pgh.pa.us>:

Pavel Stehule <pavel.stehule@gmail.com> writes:

Is following use case defined in standard?

postgres=# SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3
UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS

a,

0 AS x6, -1 AS x6
UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS a, -100 AS aa;
┌───┐
│ a │
╞═══╡
│ 1 │
│ 3 │
│ 6 │
└───┘
(3 rows)

It depends on order of implementation

if we do (T1 U T2) U T3 ---> then result is correct,
but if we do T1 U (T2 U T3) ---> than it should to fail

UNION ALL should associate left-to-right, just like most other binary
operators, so this looks fine to me. Did you check that you get an
error if you put in parens to force the other order?

yes - it fails

postgres=# SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3
UNION ALL CORRESPONDING BY(a,b) (SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6,
-1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS a, -100 AS aa);
ERROR: column name "b" can not be used in CORRESPONDING BY list
LINE 1: ...b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(a,b) (SELECT...
^
HINT: UNION queries with a CORRESPONDING BY clause must contain column
names from both tables.
Time: 1,135 ms

Regards

Pavel

Show quoted text

regards, tom lane

#36Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#35)
1 attachment(s)
Re: New CORRESPONDING clause design

Hi

2017-03-30 21:55 GMT+02:00 Pavel Stehule <pavel.stehule@gmail.com>:

2017-03-30 21:43 GMT+02:00 Tom Lane <tgl@sss.pgh.pa.us>:

Pavel Stehule <pavel.stehule@gmail.com> writes:

Is following use case defined in standard?

postgres=# SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3
UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS

a,

0 AS x6, -1 AS x6
UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS a, -100 AS aa;
┌───┐
│ a │
╞═══╡
│ 1 │
│ 3 │
│ 6 │
└───┘
(3 rows)

It depends on order of implementation

if we do (T1 U T2) U T3 ---> then result is correct,
but if we do T1 U (T2 U T3) ---> than it should to fail

UNION ALL should associate left-to-right, just like most other binary
operators, so this looks fine to me. Did you check that you get an
error if you put in parens to force the other order?

yes - it fails

postgres=# SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3
UNION ALL CORRESPONDING BY(a,b) (SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6,
-1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS a, -100 AS aa);
ERROR: column name "b" can not be used in CORRESPONDING BY list
LINE 1: ...b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(a,b) (SELECT...
^
HINT: UNION queries with a CORRESPONDING BY clause must contain column
names from both tables.
Time: 1,135 ms

I fixed wrong my comment

I have no any other objections, I'll mark this patch as ready for commiter

Regards

Pavel

Show quoted text

Regards

Pavel

regards, tom lane

Attachments:

corresponding_clause_v12.patchtext/x-patch; charset=US-ASCII; name=corresponding_clause_v12.patchDownload
diff --git a/doc/src/sgml/queries.sgml b/doc/src/sgml/queries.sgml
index 30792f45f1..2d60718ff1 100644
--- a/doc/src/sgml/queries.sgml
+++ b/doc/src/sgml/queries.sgml
@@ -1601,6 +1601,9 @@ SELECT DISTINCT ON (<replaceable>expression</replaceable> <optional>, <replaceab
    <primary>EXCEPT</primary>
   </indexterm>
   <indexterm zone="queries-union">
+   <primary>CORRESPONDING</primary>
+  </indexterm>
+  <indexterm zone="queries-union">
    <primary>set union</primary>
   </indexterm>
   <indexterm zone="queries-union">
@@ -1617,9 +1620,9 @@ SELECT DISTINCT ON (<replaceable>expression</replaceable> <optional>, <replaceab
    The results of two queries can be combined using the set operations
    union, intersection, and difference.  The syntax is
 <synopsis>
-<replaceable>query1</replaceable> UNION <optional>ALL</optional> <replaceable>query2</replaceable>
-<replaceable>query1</replaceable> INTERSECT <optional>ALL</optional> <replaceable>query2</replaceable>
-<replaceable>query1</replaceable> EXCEPT <optional>ALL</optional> <replaceable>query2</replaceable>
+<replaceable>query1</replaceable> UNION <optional>ALL</optional> <optional>CORRESPONDING</optional> <optional>BY</optional> <replaceable>query2</replaceable>
+<replaceable>query1</replaceable> INTERSECT <optional>ALL</optional> <optional>CORRESPONDING</optional> <optional>BY</optional> <replaceable>query2</replaceable>
+<replaceable>query1</replaceable> EXCEPT <optional>ALL</optional> <optional>CORRESPONDING</optional> <optional>BY</optional> <replaceable>query2</replaceable>
 </synopsis>
    <replaceable>query1</replaceable> and
    <replaceable>query2</replaceable> are queries that can use any of
@@ -1659,14 +1662,31 @@ SELECT DISTINCT ON (<replaceable>expression</replaceable> <optional>, <replaceab
   </para>
 
   <para>
-   In order to calculate the union, intersection, or difference of two
-   queries, the two queries must be <quote>union compatible</quote>,
-   which means that they return the same number of columns and
-   the corresponding columns have compatible data types, as
-   described in <xref linkend="typeconv-union-case">.
+   <literal>EXCEPT</> returns all rows that are in the result of
+   <replaceable>query1</replaceable> but not in the result of
+   <replaceable>query2</replaceable>.  (This is sometimes called the
+   <firstterm>difference</> between two queries.)  Again, duplicates
+   are eliminated unless <literal>EXCEPT ALL</> is used.
   </para>
- </sect1>
 
+  <para>
+   <literal>CORRESPONDING</> returns all columns that are in both 
+   <replaceable>query1</> and <replaceable>query2</> with the same name.
+  </para>
+
+  <para>
+   <literal>CORRESPONDING BY</> returns all columns in the column list 
+   that are also in both <replaceable>query1</> and 
+   <replaceable>query2</> with the same name. The names in column list
+   must be unique.
+  </para>
+
+  <para>
+   The names of columns in result when <literal>CORRESPONDING</> or
+   <literal>CORRESPONDING BY</> clause is used must be unique in
+   <replaceable>query1</replaceable> and <replaceable>query2</replaceable>.
+  </para>
+ </sect1>
 
  <sect1 id="queries-order">
   <title>Sorting Rows</title>
diff --git a/doc/src/sgml/sql.sgml b/doc/src/sgml/sql.sgml
index 57396d7c24..f98c22e696 100644
--- a/doc/src/sgml/sql.sgml
+++ b/doc/src/sgml/sql.sgml
@@ -859,7 +859,7 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="PARAMETER">expression</replac
     [ WHERE <replaceable class="PARAMETER">condition</replaceable> ]
     [ GROUP BY <replaceable class="PARAMETER">expression</replaceable> [, ...] ]
     [ HAVING <replaceable class="PARAMETER">condition</replaceable> [, ...] ]
-    [ { UNION | INTERSECT | EXCEPT } [ ALL ] <replaceable class="PARAMETER">select</replaceable> ]
+    [ { UNION | INTERSECT | EXCEPT } [ ALL ] [ CORRESPONDING [ BY ( <replaceable class="PARAMETER">expression</replaceable> ) ] ] <replaceable class="PARAMETER">select</replaceable> ]
     [ ORDER BY <replaceable class="parameter">expression</replaceable> [ ASC | DESC | USING <replaceable class="parameter">operator</replaceable> ] [ NULLS { FIRST | LAST } ] [, ...] ]
     [ LIMIT { <replaceable class="PARAMETER">count</replaceable> | ALL } ]
     [ OFFSET <replaceable class="PARAMETER">start</replaceable> ]
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 1c88d601bd..11e0590eec 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2995,6 +2995,7 @@ _copySelectStmt(const SelectStmt *from)
 	COPY_NODE_FIELD(withClause);
 	COPY_SCALAR_FIELD(op);
 	COPY_SCALAR_FIELD(all);
+	COPY_NODE_FIELD(correspondingClause);
 	COPY_NODE_FIELD(larg);
 	COPY_NODE_FIELD(rarg);
 
@@ -3010,6 +3011,8 @@ _copySetOperationStmt(const SetOperationStmt *from)
 	COPY_SCALAR_FIELD(all);
 	COPY_NODE_FIELD(larg);
 	COPY_NODE_FIELD(rarg);
+	COPY_NODE_FIELD(correspondingColumns);
+	COPY_SCALAR_FIELD(hasCorrespondingBy);
 	COPY_NODE_FIELD(colTypes);
 	COPY_NODE_FIELD(colTypmods);
 	COPY_NODE_FIELD(colCollations);
@@ -4588,6 +4591,8 @@ _copyValue(const Value *from)
 				 (int) from->type);
 			break;
 	}
+	COPY_LOCATION_FIELD(location);
+
 	return newnode;
 }
 
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 5941b7a2bf..dd6598d85b 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1050,6 +1050,7 @@ _equalSelectStmt(const SelectStmt *a, const SelectStmt *b)
 	COMPARE_NODE_FIELD(withClause);
 	COMPARE_SCALAR_FIELD(op);
 	COMPARE_SCALAR_FIELD(all);
+	COMPARE_NODE_FIELD(correspondingClause);
 	COMPARE_NODE_FIELD(larg);
 	COMPARE_NODE_FIELD(rarg);
 
@@ -1063,6 +1064,8 @@ _equalSetOperationStmt(const SetOperationStmt *a, const SetOperationStmt *b)
 	COMPARE_SCALAR_FIELD(all);
 	COMPARE_NODE_FIELD(larg);
 	COMPARE_NODE_FIELD(rarg);
+	COMPARE_NODE_FIELD(correspondingColumns);
+	COMPARE_SCALAR_FIELD(hasCorrespondingBy);
 	COMPARE_NODE_FIELD(colTypes);
 	COMPARE_NODE_FIELD(colTypmods);
 	COMPARE_NODE_FIELD(colCollations);
@@ -2935,6 +2938,8 @@ _equalValue(const Value *a, const Value *b)
 			break;
 	}
 
+	COMPARE_LOCATION_FIELD(location);
+
 	return true;
 }
 
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 6e52eb7231..7102ea96c2 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -3444,6 +3444,8 @@ raw_expression_tree_walker(Node *node,
 					return true;
 				if (walker(stmt->lockingClause, context))
 					return true;
+				if (walker(stmt->correspondingClause, context))
+					return true;
 				if (walker(stmt->withClause, context))
 					return true;
 				if (walker(stmt->larg, context))
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index bbb63a4bfa..09c097857d 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2664,6 +2664,7 @@ _outSelectStmt(StringInfo str, const SelectStmt *node)
 	WRITE_NODE_FIELD(withClause);
 	WRITE_ENUM_FIELD(op, SetOperation);
 	WRITE_BOOL_FIELD(all);
+	WRITE_NODE_FIELD(correspondingClause);
 	WRITE_NODE_FIELD(larg);
 	WRITE_NODE_FIELD(rarg);
 }
@@ -2971,6 +2972,8 @@ _outSetOperationStmt(StringInfo str, const SetOperationStmt *node)
 	WRITE_BOOL_FIELD(all);
 	WRITE_NODE_FIELD(larg);
 	WRITE_NODE_FIELD(rarg);
+	WRITE_NODE_FIELD(correspondingColumns);
+	WRITE_BOOL_FIELD(hasCorrespondingBy);
 	WRITE_NODE_FIELD(colTypes);
 	WRITE_NODE_FIELD(colTypmods);
 	WRITE_NODE_FIELD(colCollations);
@@ -3148,6 +3151,7 @@ _outAExpr(StringInfo str, const A_Expr *node)
 static void
 _outValue(StringInfo str, const Value *value)
 {
+	/* NB: this isn't a complete set of fields */
 	switch (value->type)
 	{
 		case T_Integer:
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 474f221a75..6e284f9ef8 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -416,6 +416,8 @@ _readSetOperationStmt(void)
 	READ_BOOL_FIELD(all);
 	READ_NODE_FIELD(larg);
 	READ_NODE_FIELD(rarg);
+	READ_NODE_FIELD(correspondingColumns);
+	READ_BOOL_FIELD(hasCorrespondingBy);
 	READ_NODE_FIELD(colTypes);
 	READ_NODE_FIELD(colTypmods);
 	READ_NODE_FIELD(colCollations);
diff --git a/src/backend/nodes/value.c b/src/backend/nodes/value.c
index 5d2f96c103..72afc172f9 100644
--- a/src/backend/nodes/value.c
+++ b/src/backend/nodes/value.c
@@ -26,6 +26,7 @@ makeInteger(long i)
 
 	v->type = T_Integer;
 	v->val.ival = i;
+	v->location = -1;
 	return v;
 }
 
@@ -41,6 +42,7 @@ makeFloat(char *numericStr)
 
 	v->type = T_Float;
 	v->val.str = numericStr;
+	v->location = -1;
 	return v;
 }
 
@@ -56,6 +58,7 @@ makeString(char *str)
 
 	v->type = T_String;
 	v->val.str = str;
+	v->location = -1;
 	return v;
 }
 
@@ -71,5 +74,6 @@ makeBitString(char *str)
 
 	v->type = T_BitString;
 	v->val.str = str;
+	v->location = -1;
 	return v;
 }
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index e327e66f6b..f02066dd5b 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -91,7 +91,8 @@ static List *generate_setop_tlist(List *colTypes, List *colCollations,
 					 Index varno,
 					 bool hack_constants,
 					 List *input_tlist,
-					 List *refnames_tlist);
+					 List *refnames_tlist,
+					 bool no_corresponding);
 static List *generate_append_tlist(List *colTypes, List *colCollations,
 					  bool flag,
 					  List *input_tlists,
@@ -110,6 +111,7 @@ static Node *adjust_appendrel_attrs_mutator(Node *node,
 static Relids adjust_relid_set(Relids relids, Index oldrelid, Index newrelid);
 static List *adjust_inherited_tlist(List *tlist,
 					   AppendRelInfo *context);
+static List *make_corresponding_target(List *corresponding_list, List *subroot_list);
 
 
 /*
@@ -187,6 +189,24 @@ plan_set_operations(PlannerInfo *root)
 									   leftmostQuery->targetList,
 									   &top_tlist);
 	}
+	/*
+	 * If corresponding column specified, we take column names from it.
+	 */
+	else if (topop->correspondingColumns != NIL )
+	{
+		/*
+		 * Recurse on setOperations tree to generate paths for set ops. The
+		 * final output path should have just the column types shown as the
+		 * output from the top-level node, plus possibly resjunk working
+		 * columns (we can rely on upper-level nodes to deal with that).
+		 */
+		path = recurse_set_operations((Node *) topop, root,
+									  topop->colTypes, topop->colCollations,
+									  true, -1,
+									  topop->correspondingColumns,
+									  &top_tlist,
+									  NULL);
+	}
 	else
 	{
 		/*
@@ -252,6 +272,8 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 					   List **pTargetList,
 					   double *pNumGroups)
 {
+	SetOperationStmt *topop = (SetOperationStmt *) root->parse->setOperations;
+
 	if (IsA(setOp, RangeTblRef))
 	{
 		RangeTblRef *rtr = (RangeTblRef *) setOp;
@@ -316,23 +338,53 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 		path = (Path *) create_subqueryscan_path(root, rel, subpath,
 												 NIL, NULL);
 
-		/*
-		 * Figure out the appropriate target list, and update the
-		 * SubqueryScanPath with the PathTarget form of that.
-		 */
-		tlist = generate_setop_tlist(colTypes, colCollations,
+		if (topop->correspondingColumns != NIL )
+		{
+			List	    *correspondingTarget;
+
+			/*
+			 * make target list that only contains corresponding column
+			 * from sub-queries list ito use it for projection
+			 */
+			correspondingTarget = make_corresponding_target(
+											  topop->correspondingColumns,
+											  subroot->processed_tlist);
+
+			/*
+			 * Figure out the appropriate target list, and update the
+			 * SubqueryScanPath with the PathTarget form of that.
+			 */
+			tlist = generate_setop_tlist(colTypes, colCollations, flag,
+											  rtr->rtindex, true,
+											  correspondingTarget,
+											  refnames_tlist, false);
+
+			path = apply_projection_to_path(root, rel, path,
+					create_pathtarget(root, tlist));
+
+			/* Return the fully-fledged tlist to caller, too */
+			*pTargetList = tlist;
+
+		}
+		else
+		{
+			/*
+			 * Figure out the appropriate target list, and update the
+			 * SubqueryScanPath with the PathTarget form of that.
+			 */
+			tlist = generate_setop_tlist(colTypes, colCollations,
 									 flag,
 									 rtr->rtindex,
 									 true,
 									 subroot->processed_tlist,
-									 refnames_tlist);
+									 refnames_tlist, true);
 
-		path = apply_projection_to_path(root, rel, path,
+			path = apply_projection_to_path(root, rel, path,
 										create_pathtarget(root, tlist));
 
-		/* Return the fully-fledged tlist to caller, too */
-		*pTargetList = tlist;
-
+			/* Return the fully-fledged tlist to caller, too */
+			*pTargetList = tlist;
+		}
 		/*
 		 * Estimate number of groups if caller wants it.  If the subquery used
 		 * grouping or aggregation, its output is probably mostly unique
@@ -392,7 +444,7 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 												0,
 												false,
 												*pTargetList,
-												refnames_tlist);
+												refnames_tlist, true);
 			path = apply_projection_to_path(root,
 											path->parent,
 											path,
@@ -1004,7 +1056,8 @@ generate_setop_tlist(List *colTypes, List *colCollations,
 					 Index varno,
 					 bool hack_constants,
 					 List *input_tlist,
-					 List *refnames_tlist)
+					 List *refnames_tlist,
+					 bool no_corresponding)
 {
 	List	   *tlist = NIL;
 	int			resno = 1;
@@ -1026,8 +1079,8 @@ generate_setop_tlist(List *colTypes, List *colCollations,
 
 		rtlc = lnext(rtlc);
 
-		Assert(inputtle->resno == resno);
-		Assert(reftle->resno == resno);
+		Assert(!no_corresponding || inputtle->resno == resno);
+		Assert(!no_corresponding || reftle->resno == resno);
 		Assert(!inputtle->resjunk);
 		Assert(!reftle->resjunk);
 
@@ -2150,3 +2203,70 @@ adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node,
 	/* Now translate for this child */
 	return adjust_appendrel_attrs(root, node, appinfo);
 }
+
+/*
+ * generate target list from left target list with the order
+ * of right target list
+ */
+static List *
+make_corresponding_target(List *corresponding_list, List *subroot_list)
+{
+	Index internal = 0;
+	ListCell   *ltl;
+	ListCell   *rtl;
+	int			size;
+	int			i;
+	List *matchingColumns = NIL;
+	TargetEntry *simple_te_array;
+
+	size = list_length(corresponding_list) + 1;
+
+	/* Use array to find the order of corresponding columen */
+	simple_te_array = (TargetEntry *) palloc0(size * sizeof(TargetEntry));
+	foreach(ltl, corresponding_list)
+	{
+		foreach(rtl, subroot_list)
+		{
+			TargetEntry *ltle = (TargetEntry *) lfirst(ltl);
+			TargetEntry *rtle = (TargetEntry *) lfirst(rtl);
+
+			/* Names of the columns must be resolved before calling this method. */
+			Assert(ltle->resname != NULL);
+			Assert(rtle->resname != NULL);
+
+			/* If column names are the same, add it to array. */
+			if (strcmp(ltle->resname, rtle->resname) == 0)
+			{
+				simple_te_array[internal].xpr = rtle->xpr;
+				simple_te_array[internal].expr = rtle->expr;
+				simple_te_array[internal].resno = rtle->resno;
+				simple_te_array[internal].resname = rtle->resname;
+				simple_te_array[internal].ressortgroupref =
+						rtle->ressortgroupref;
+				simple_te_array[internal].resorigtbl = rtle->resorigtbl;
+				simple_te_array[internal].resorigcol = rtle->resorigcol;
+				simple_te_array[internal].resjunk = rtle->resjunk;
+				internal++;
+				continue;
+			}
+		}
+	}
+	/* traverse the array and make targetlist */
+	for (i = 0; i < internal; i++)
+	{
+		TargetEntry *tle = makeNode(TargetEntry);
+
+		tle->xpr = simple_te_array[i].xpr;
+		tle->expr = simple_te_array[i].expr;
+		tle->resno = simple_te_array[i].resno;
+		tle->resname = simple_te_array[i].resname;
+		tle->ressortgroupref = simple_te_array[i].ressortgroupref;
+		tle->resorigtbl = simple_te_array[i].resorigtbl;
+		tle->resorigcol = simple_te_array[i].resorigcol;
+		tle->resjunk = simple_te_array[i].resjunk;
+
+		matchingColumns = lappend(matchingColumns, tle);
+
+	}
+	return matchingColumns;
+}
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index f6025225be..65dc7d153c 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -76,10 +76,18 @@ static Query *transformCreateTableAsStmt(ParseState *pstate,
 						   CreateTableAsStmt *stmt);
 static void transformLockingClause(ParseState *pstate, Query *qry,
 					   LockingClause *lc, bool pushedDown);
+static void makeUnionDatatype(List *ltargetlist, List *rtargetlist,
+		SetOperationStmt *op, List **targetlist, ParseState *parentParseState,
+		const char *context);
 #ifdef RAW_EXPRESSION_COVERAGE_TEST
 static bool test_raw_expression_coverage(Node *node, void *context);
 #endif
-
+static List *CommonColumns(List *ltargetlist, List *rtargetlist, bool filtered,
+								 ParseState *pstate, const char *context);
+static List *FilterColumnsByNames(List *common_columns, List *filter,
+								 ParseState *pstate, const char *context);
+static List *FilterColumnsByTL(List *targetlist, List *filter, bool check_uniq,
+								 ParseState *pstate, const char *context);
 
 /*
  * parse_analyze
@@ -1653,7 +1661,37 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->targetList = NIL;
 	targetvars = NIL;
 	targetnames = NIL;
-	left_tlist = list_head(leftmostQuery->targetList);
+
+	/*
+	 * for corresponding clause limits top-level query targetlist to those
+	 * corresponding column list only
+	 */
+	if (sostmt->correspondingColumns != NIL )
+	{
+		left_tlist = list_head(sostmt->correspondingColumns);
+		/*
+		 * In the case of corresponding without by clause property across
+		 * the statement may differ
+		 */
+		if (!sostmt->hasCorrespondingBy)
+		{
+			Node *correspodning_node;
+			correspodning_node = sostmt->larg;
+			while (correspodning_node && IsA(correspodning_node, SetOperationStmt))
+			{
+				SetOperationStmt *op = (SetOperationStmt *) correspodning_node;
+				op->correspondingColumns = sostmt->correspondingColumns;
+				op->colTypes = sostmt->colTypes;
+				op->colTypmods = sostmt->colTypmods;
+				op->colCollations = sostmt->colCollations;
+				op->groupClauses = sostmt->groupClauses;
+
+				correspodning_node = op->larg;
+			}
+		}
+	}
+	else
+		left_tlist = list_head(leftmostQuery->targetList);
 
 	forthree(lct, sostmt->colTypes,
 			 lcm, sostmt->colTypmods,
@@ -1913,8 +1951,6 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 		SetOperationStmt *op = makeNode(SetOperationStmt);
 		List	   *ltargetlist;
 		List	   *rtargetlist;
-		ListCell   *ltl;
-		ListCell   *rtl;
 		const char *context;
 
 		context = (stmt->op == SETOP_UNION ? "UNION" :
@@ -1925,6 +1961,84 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 		op->all = stmt->all;
 
 		/*
+		 * If CORRESPONDING is specified, syntax and column name validities checked,
+		 * column filtering is done by a subquery later on.
+		 */
+		if (stmt->correspondingClause == NIL )
+		{
+			/* No CORRESPONDING clause, no operation needed for column filtering */
+			op->correspondingColumns = stmt->correspondingClause;
+			op->hasCorrespondingBy = false;
+		}
+		else
+		{
+			/*
+			 * CORRESPONDING clause, find matching column names from both tables.
+			 * If there are none then it is a syntax error.
+			 */
+			Query	   *largQuery;
+			Query	   *rargQuery;
+			List	   *matchingColumns;
+			List	   *rightCorrespondingColumns;
+
+			op->hasCorrespondingBy = linitial(stmt->correspondingClause) != NULL;
+
+			/* Analyze left query to resolve column names. */
+			largQuery = parse_sub_analyze((Node *) stmt->larg,
+										  pstate, NULL, false, false );
+
+			/* Analyze right query to resolve column names. */
+			rargQuery = parse_sub_analyze((Node *) stmt->rarg,
+										  pstate, NULL, false, false );
+
+			/* Find matching columns from both queries. */
+			matchingColumns = CommonColumns(largQuery->targetList,
+											rargQuery->targetList,
+											op->hasCorrespondingBy,
+											pstate,
+											context);
+
+			/*
+			 * If matchingColumns is empty, there is an error.
+			 * At least one column in the select lists must have the same name.
+			 */
+			if (matchingColumns == NIL)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("there is not any corresponding name"),
+	 errhint("%s queries with a CORRESPONDING clause must have at least one column with the same name",
+								 context),
+						 parser_errposition(pstate,
+									exprLocation((Node *)
+									linitial(largQuery->targetList)))));
+
+			/* Use column filter when it is known */
+			if (op->hasCorrespondingBy)
+				matchingColumns = FilterColumnsByNames(matchingColumns,
+													   stmt->correspondingClause,
+													   pstate,
+													   context);
+
+			op->correspondingColumns = matchingColumns;
+
+			/*
+			 * When we know matching columns, we can quickly create
+			 * corresponding target list for right target list. It is faster,
+			 * than using symmetry. Ensure unique columns when hasCorrespondingBy
+			 * is true - in this case, the uniq is not checked already.
+			 */
+			rightCorrespondingColumns = FilterColumnsByTL(rargQuery->targetList,
+														  matchingColumns,
+														  op->hasCorrespondingBy,
+														  pstate,
+														  context);
+
+			/* make union'd datatype of output column */
+			makeUnionDatatype(matchingColumns, rightCorrespondingColumns,
+								op, targetlist, pstate, context);
+		}
+
+		/*
 		 * Recursively transform the left child node.
 		 */
 		op->larg = transformSetOperationTree(pstate, stmt->larg,
@@ -1949,177 +2063,417 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 											 false,
 											 &rtargetlist);
 
-		/*
-		 * Verify that the two children have the same number of non-junk
-		 * columns, and determine the types of the merged output columns.
-		 */
-		if (list_length(ltargetlist) != list_length(rtargetlist))
-			ereport(ERROR,
-					(errcode(ERRCODE_SYNTAX_ERROR),
-				 errmsg("each %s query must have the same number of columns",
-						context),
-					 parser_errposition(pstate,
-										exprLocation((Node *) rtargetlist))));
-
-		if (targetlist)
-			*targetlist = NIL;
-		op->colTypes = NIL;
-		op->colTypmods = NIL;
-		op->colCollations = NIL;
-		op->groupClauses = NIL;
-		forboth(ltl, ltargetlist, rtl, rtargetlist)
+		if (op->correspondingColumns == NIL )
 		{
-			TargetEntry *ltle = (TargetEntry *) lfirst(ltl);
-			TargetEntry *rtle = (TargetEntry *) lfirst(rtl);
-			Node	   *lcolnode = (Node *) ltle->expr;
-			Node	   *rcolnode = (Node *) rtle->expr;
-			Oid			lcoltype = exprType(lcolnode);
-			Oid			rcoltype = exprType(rcolnode);
-			int32		lcoltypmod = exprTypmod(lcolnode);
-			int32		rcoltypmod = exprTypmod(rcolnode);
-			Node	   *bestexpr;
-			int			bestlocation;
-			Oid			rescoltype;
-			int32		rescoltypmod;
-			Oid			rescolcoll;
-
-			/* select common type, same as CASE et al */
-			rescoltype = select_common_type(pstate,
-											list_make2(lcolnode, rcolnode),
-											context,
-											&bestexpr);
-			bestlocation = exprLocation(bestexpr);
-			/* if same type and same typmod, use typmod; else default */
-			if (lcoltype == rcoltype && lcoltypmod == rcoltypmod)
-				rescoltypmod = lcoltypmod;
-			else
-				rescoltypmod = -1;
-
+			makeUnionDatatype(ltargetlist, rtargetlist, op, targetlist, pstate,
+					context);
 			/*
-			 * Verify the coercions are actually possible.  If not, we'd fail
-			 * later anyway, but we want to fail now while we have sufficient
-			 * context to produce an error cursor position.
-			 *
-			 * For all non-UNKNOWN-type cases, we verify coercibility but we
-			 * don't modify the child's expression, for fear of changing the
-			 * child query's semantics.
-			 *
-			 * If a child expression is an UNKNOWN-type Const or Param, we
-			 * want to replace it with the coerced expression.  This can only
-			 * happen when the child is a leaf set-op node.  It's safe to
-			 * replace the expression because if the child query's semantics
-			 * depended on the type of this output column, it'd have already
-			 * coerced the UNKNOWN to something else.  We want to do this
-			 * because (a) we want to verify that a Const is valid for the
-			 * target type, or resolve the actual type of an UNKNOWN Param,
-			 * and (b) we want to avoid unnecessary discrepancies between the
-			 * output type of the child query and the resolved target type.
-			 * Such a discrepancy would disable optimization in the planner.
-			 *
-			 * If it's some other UNKNOWN-type node, eg a Var, we do nothing
-			 * (knowing that coerce_to_common_type would fail).  The planner
-			 * is sometimes able to fold an UNKNOWN Var to a constant before
-			 * it has to coerce the type, so failing now would just break
-			 * cases that might work.
+			 * Verify that the two children have the same number of non-junk
+			 * columns, and determine the types of the merged output columns.
 			 */
-			if (lcoltype != UNKNOWNOID)
-				lcolnode = coerce_to_common_type(pstate, lcolnode,
-												 rescoltype, context);
-			else if (IsA(lcolnode, Const) ||
-					 IsA(lcolnode, Param))
-			{
-				lcolnode = coerce_to_common_type(pstate, lcolnode,
-												 rescoltype, context);
-				ltle->expr = (Expr *) lcolnode;
-			}
+			if (list_length(ltargetlist) != list_length(rtargetlist))
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("each %s query must have the same number of columns", context),
+						 parser_errposition(pstate,
+											exprLocation((Node *) rtargetlist))));
+		}
+
+		return (Node *) op;
+	}
+}
+
+/*
+ * Processes targetlists of two queries for columns with same names to use
+ * with UNION/INTERSECT/EXCEPT CORRESPONDING. filtered is true, when
+ * CORRESPONDING BY is used. When it is false, we can check uniq names
+ * in rtargetlist here.
+ */
+static List *
+CommonColumns(List *ltargetlist, List *rtargetlist, bool filtered,
+			  ParseState *pstate, const char *context)
+{
+	List	   *common_columns = NIL;
+	ListCell   *ltlc;
+	ListCell   *rtlc;
+	int			resno = 1;
+
+	foreach(ltlc, ltargetlist)
+	{
+		TargetEntry *lte = (TargetEntry *) lfirst(ltlc);
+		bool		found = false;
+
+		Assert(lte->resname != NULL);
 
-			if (rcoltype != UNKNOWNOID)
-				rcolnode = coerce_to_common_type(pstate, rcolnode,
-												 rescoltype, context);
-			else if (IsA(rcolnode, Const) ||
-					 IsA(rcolnode, Param))
+		foreach(rtlc, rtargetlist)
+		{
+			ListCell   *lc;
+			TargetEntry *rte = (TargetEntry *) lfirst(rtlc);
+
+			Assert(rte->resname != NULL);
+
+			if (strcmp(lte->resname, rte->resname) == 0)
 			{
-				rcolnode = coerce_to_common_type(pstate, rcolnode,
-												 rescoltype, context);
-				rtle->expr = (Expr *) rcolnode;
+				if (filtered)
+				{
+					/*
+					 * We found common column, but we don't know if it
+					 * is in CORRESPONDING BY list - so don't try do more
+					 * work here. The column list will be modified later,
+					 * so use shall copy here.
+					 */
+					common_columns = lappend(common_columns, lte);
+					break;
+				}
+
+				/* If same column name mentioned more than once it is syntax error . */
+				if (found)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("corresponding column \"%s\" is used more times", rte->resname),
+	 errhint("In %s queries with CORRESPONDING clause the corresponding column names must be unique.",
+									context),
+							 parser_errposition(pstate,
+												exprLocation((Node *) rte))));
+
+				found = true;
+
+				/* In this case, common_columns must be unique */
+				foreach(lc, common_columns)
+				{
+					TargetEntry *te = (TargetEntry *) lfirst(lc);
+
+					if (strcmp(te->resname, lte->resname) == 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("corresponding column \"%s\" is used more times", lte->resname),
+	 errhint("In %s queries with CORRESPONDING clause the corresponding column names must be unique.",
+										context),
+								 parser_errposition(pstate,
+													exprLocation((Node *) lte))));
+				}
+
+				/* When is not any other filter create final te */
+				common_columns = lappend(common_columns,
+										 makeTargetEntry(lte->expr,
+														 (AttrNumber) resno++,
+														 lte->resname,
+														 false));
 			}
+		}
+	}
 
-			/*
-			 * Select common collation.  A common collation is required for
-			 * all set operators except UNION ALL; see SQL:2008 7.13 <query
-			 * expression> Syntax Rule 15c.  (If we fail to identify a common
-			 * collation for a UNION ALL column, the curCollations element
-			 * will be set to InvalidOid, which may result in a runtime error
-			 * if something at a higher query level wants to use the column's
-			 * collation.)
-			 */
-			rescolcoll = select_common_collation(pstate,
-											  list_make2(lcolnode, rcolnode),
-										 (op->op == SETOP_UNION && op->all));
+	return common_columns;
+}
 
-			/* emit results */
-			op->colTypes = lappend_oid(op->colTypes, rescoltype);
-			op->colTypmods = lappend_int(op->colTypmods, rescoltypmod);
-			op->colCollations = lappend_oid(op->colCollations, rescolcoll);
+/*
+ * Returns filtered common columns list - filter is based on CORRESPONDING BY
+ * list Ensure CORRESPONDING BY list is unique. Result is in CORRESPONDING BY
+ * list order. Common columns list can hold duplicate columns.
+ */
+static List *
+FilterColumnsByNames(List *common_columns, List *filter,
+					 ParseState *pstate, const char *context)
+{
+	List	   *filtered_columns = NIL;
+	ListCell   *flc;
+	int			resno = 1;
 
-			/*
-			 * For all cases except UNION ALL, identify the grouping operators
-			 * (and, if available, sorting operators) that will be used to
-			 * eliminate duplicates.
-			 */
-			if (op->op != SETOP_UNION || !op->all)
+	Assert(common_columns != NIL);
+	Assert(filter != NIL);
+
+	foreach(flc, filter)
+	{
+		Value	   *strval = (Value *) lfirst(flc);
+		char	   *name = strVal(strval);
+		ListCell   *tlc;
+		bool		found = false;
+
+		foreach(tlc, common_columns)
+		{
+			TargetEntry   *tec = (TargetEntry *) lfirst(tlc);
+
+			if (strcmp(tec->resname, name) == 0)
 			{
-				SortGroupClause *grpcl = makeNode(SortGroupClause);
-				Oid			sortop;
-				Oid			eqop;
-				bool		hashable;
-				ParseCallbackState pcbstate;
-
-				setup_parser_errposition_callback(&pcbstate, pstate,
-												  bestlocation);
-
-				/* determine the eqop and optional sortop */
-				get_sort_group_operators(rescoltype,
-										 false, true, false,
-										 &sortop, &eqop, NULL,
-										 &hashable);
-
-				cancel_parser_errposition_callback(&pcbstate);
-
-				/* we don't have a tlist yet, so can't assign sortgrouprefs */
-				grpcl->tleSortGroupRef = 0;
-				grpcl->eqop = eqop;
-				grpcl->sortop = sortop;
-				grpcl->nulls_first = false;		/* OK with or without sortop */
-				grpcl->hashable = hashable;
-
-				op->groupClauses = lappend(op->groupClauses, grpcl);
+				ListCell   *lc;
+
+				/*
+				 * When "found" is true, then common_columns contains
+				 * duplicate columns. Raise exception then.
+				 */
+				if (found)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("corresponding column \"%s\" is used more times", name),
+	 errhint("In %s queries with CORRESPONDING BY clause the corresponding column names must be unique.",
+									context),
+							 parser_errposition(pstate,
+												exprLocation((Node *) tec))));
+
+				found = true;
+
+				/* result list should not to contains this name */
+				foreach(lc, filtered_columns)
+				{
+					TargetEntry   *te = (TargetEntry *) lfirst(lc);
+
+					/*
+					 * CORRESPONDING BY clause contains a column name that is
+					 * not in unique in this clause
+					 */
+					if (strcmp(te->resname, name) == 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("column name \"%s\" is not unique in CORRESPONDING BY clause", name),
+								 errhint("CORRESPONDING BY clause must contain unique column names only."),
+								 parser_errposition(pstate, strval->location)));
+				}
+
+				/* create te with correct resno */
+				filtered_columns = lappend(filtered_columns,
+										 makeTargetEntry(tec->expr,
+														 (AttrNumber) resno++,
+														 tec->resname,
+														 false));
 			}
+		}
 
-			/*
-			 * Construct a dummy tlist entry to return.  We use a SetToDefault
-			 * node for the expression, since it carries exactly the fields
-			 * needed, but any other expression node type would do as well.
-			 */
-			if (targetlist)
+		/*
+		 * CORRESPONDING BY clause contains a column name that is not
+		 * in common columns.
+		 */
+		if (!found)
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("column name \"%s\" can not be used in CORRESPONDING BY list", name),
+		 errhint("%s queries with a CORRESPONDING BY clause must contain column names from both tables.",
+									 context),
+					 parser_errposition(pstate, strval->location)));
+	}
+
+	return filtered_columns;
+}
+
+/*
+ * Prepare target list for right query of CORRESPONDING clause.
+ * When check_uniq is true, we should to check uniq names from
+ * filter in target list. When it is false, then uniquenes was
+ * checked in CommonColumns function and should not be checked
+ * here again.
+ */
+static List *
+FilterColumnsByTL(List *targetlist, List *filter, bool check_uniq,
+				  ParseState *pstate, const char *context)
+{
+	List	   *result = NIL;
+	ListCell   *lc;
+	int			resno = 1;
+
+	foreach(lc, filter)
+	{
+		TargetEntry *fte = (TargetEntry *) lfirst(lc);
+		ListCell	*tle;
+		bool		found = false;
+
+		foreach(tle, targetlist)
+		{
+			TargetEntry *te = (TargetEntry *) lfirst(tle);
+
+			if (strcmp(fte->resname, te->resname) == 0)
 			{
-				SetToDefault *rescolnode = makeNode(SetToDefault);
-				TargetEntry *restle;
-
-				rescolnode->typeId = rescoltype;
-				rescolnode->typeMod = rescoltypmod;
-				rescolnode->collation = rescolcoll;
-				rescolnode->location = bestlocation;
-				restle = makeTargetEntry((Expr *) rescolnode,
-										 0,		/* no need to set resno */
-										 NULL,
-										 false);
-				*targetlist = lappend(*targetlist, restle);
+				/* create te with correct resno */
+				result = lappend(result,
+								 makeTargetEntry(te->expr,
+												 (AttrNumber) resno++,
+												 te->resname,
+												 false));
+
+				if (!check_uniq)
+					break;
+
+				/*
+				 * When "found" is true, then targetlist contains
+				 * duplicate filtered columns. Raise exception then.
+				 */
+				if (found)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("corresponding column \"%s\" is used more times", te->resname),
+							 errhint("In %s queries with CORRESPONDING BY clause the corresponding column names must be unique.",
+									context),
+							 parser_errposition(pstate,
+												exprLocation((Node *) te))));
+
+				found = true;
 			}
 		}
+	}
 
-		return (Node *) op;
+	return result;
+}
+
+/*
+ * process right and left target list to set up union'd datatype
+ */
+static void
+makeUnionDatatype(List *ltargetlist, List *rtargetlist, SetOperationStmt *op,
+		List **targetlist, ParseState *pstate, const char *context)
+{
+	ListCell   *ltl;
+	ListCell   *rtl;
+
+	if (targetlist)
+		*targetlist = NIL;
+
+	op->colTypes = NIL;
+	op->colTypmods = NIL;
+	op->colCollations = NIL;
+	op->groupClauses = NIL;
+
+	forboth(ltl, ltargetlist, rtl, rtargetlist)
+	{
+		TargetEntry	   *ltle = (TargetEntry *) lfirst(ltl);
+		TargetEntry	   *rtle = (TargetEntry *) lfirst(rtl);
+		Node		   *lcolnode = (Node *) ltle->expr;
+		Node		   *rcolnode = (Node *) rtle->expr;
+		Oid			lcoltype = exprType(lcolnode);
+		Oid			rcoltype = exprType(rcolnode);
+		int32		lcoltypmod = exprTypmod(lcolnode);
+		int32		rcoltypmod = exprTypmod(rcolnode);
+		Node	   *bestexpr;
+		int			bestlocation;
+		Oid			rescoltype;
+		int32		rescoltypmod;
+		Oid			rescolcoll;
+
+		/* select common type, same as CASE et al */
+		rescoltype = select_common_type(pstate, list_make2(lcolnode, rcolnode),
+				context, &bestexpr);
+		bestlocation = exprLocation(bestexpr);
+		/* if same type and same typmod, use typmod; else default */
+		if (lcoltype == rcoltype && lcoltypmod == rcoltypmod)
+			rescoltypmod = lcoltypmod;
+		else
+			rescoltypmod = -1;
+
+		/*
+		 * Verify the coercions are actually possible.  If not, we'd fail
+		 * later anyway, but we want to fail now while we have sufficient
+		 * context to produce an error cursor position.
+		 *
+		 * For all non-UNKNOWN-type cases, we verify coercibility but we
+		 * don't modify the child's expression, for fear of changing the
+		 * child query's semantics.
+		 *
+		 * If a child expression is an UNKNOWN-type Const or Param, we
+		 * want to replace it with the coerced expression.  This can only
+		 * happen when the child is a leaf set-op node.  It's safe to
+		 * replace the expression because if the child query's semantics
+		 * depended on the type of this output column, it'd have already
+		 * coerced the UNKNOWN to something else.  We want to do this
+		 * because (a) we want to verify that a Const is valid for the
+		 * target type, or resolve the actual type of an UNKNOWN Param,
+		 * and (b) we want to avoid unnecessary discrepancies between the
+		 * output type of the child query and the resolved target type.
+		 * Such a discrepancy would disable optimization in the planner.
+		 *
+		 * If it's some other UNKNOWN-type node, eg a Var, we do nothing
+		 * (knowing that coerce_to_common_type would fail).  The planner
+		 * is sometimes able to fold an UNKNOWN Var to a constant before
+		 * it has to coerce the type, so failing now would just break
+		 * cases that might work.
+		 */
+		if (lcoltype != UNKNOWNOID)
+			lcolnode = coerce_to_common_type(pstate, lcolnode, rescoltype,
+					context);
+		else if (IsA(lcolnode, Const) || IsA(lcolnode, Param))
+		{
+			lcolnode = coerce_to_common_type(pstate, lcolnode, rescoltype,
+					context);
+			ltle->expr = (Expr *) lcolnode;
+		}
+
+		if (rcoltype != UNKNOWNOID)
+			rcolnode = coerce_to_common_type(pstate, rcolnode, rescoltype,
+					context);
+		else if (IsA(rcolnode, Const) || IsA(rcolnode, Param))
+		{
+			rcolnode = coerce_to_common_type(pstate, rcolnode, rescoltype,
+					context);
+			rtle->expr = (Expr *) rcolnode;
+		}
+
+		/*
+		 * Select common collation.  A common collation is required for
+		 * all set operators except UNION ALL; see SQL:2008 7.13 <query
+		 * expression> Syntax Rule 15c.  (If we fail to identify a common
+		 * collation for a UNION ALL column, the curCollations element
+		 * will be set to InvalidOid, which may result in a runtime error
+		 * if something at a higher query level wants to use the column's
+		 * collation.)
+		 */
+		rescolcoll = select_common_collation(pstate,
+				list_make2(lcolnode, rcolnode),
+				(op->op == SETOP_UNION && op->all));
+
+		/* emit results */
+		op->colTypes = lappend_oid(op->colTypes, rescoltype);
+		op->colTypmods = lappend_int(op->colTypmods, rescoltypmod);
+		op->colCollations = lappend_oid(op->colCollations, rescolcoll);
+
+		/*
+		 * For all cases except UNION ALL, identify the grouping operators
+		 * (and, if available, sorting operators) that will be used to
+		 * eliminate duplicates.
+		 */
+		if (op->op != SETOP_UNION || !op->all)
+		{
+			SortGroupClause *grpcl = makeNode(SortGroupClause);
+			Oid			sortop;
+			Oid			eqop;
+			bool		hashable;
+			ParseCallbackState pcbstate;
+
+			setup_parser_errposition_callback(&pcbstate, pstate, bestlocation);
+
+			/* determine the eqop and optional sortop */
+			get_sort_group_operators(rescoltype, false, true, false, &sortop,
+					&eqop, NULL, &hashable);
+
+			cancel_parser_errposition_callback(&pcbstate);
+
+			/* we don't have a tlist yet, so can't assign sortgrouprefs */
+			grpcl->tleSortGroupRef = 0;
+			grpcl->eqop = eqop;
+			grpcl->sortop = sortop;
+			grpcl->nulls_first = false; /* OK with or without sortop */
+			grpcl->hashable = hashable;
+
+			op->groupClauses = lappend(op->groupClauses, grpcl);
+		}
+
+		/*
+		 * Construct a dummy tlist entry to return.  We use a SetToDefault
+		 * node for the expression, since it carries exactly the fields
+		 * needed, but any other expression node type would do as well.
+		 */
+		if (targetlist)
+		{
+			SetToDefault   *rescolnode = makeNode(SetToDefault);
+			TargetEntry	   *restle;
+
+			rescolnode->typeId = rescoltype;
+			rescolnode->typeMod = rescoltypmod;
+			rescolnode->collation = rescolcoll;
+			rescolnode->location = bestlocation;
+
+			/* no need to set resno */
+			restle = makeTargetEntry((Expr *) rescolnode, 0,
+			NULL, false );
+			*targetlist = lappend(*targetlist, restle);
+		}
 	}
+
 }
 
 /*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9d53a29ad2..6e5257d30d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -166,7 +166,7 @@ static void insertSelectOptions(SelectStmt *stmt,
 								Node *limitOffset, Node *limitCount,
 								WithClause *withClause,
 								core_yyscan_t yyscanner);
-static Node *makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg);
+static Node *makeSetOp(SetOperation op, bool all, List *correspondingClause, Node *larg, Node *rarg);
 static Node *doNegate(Node *n, int location);
 static void doNegateFloat(Value *v);
 static Node *makeAndExpr(Node *lexpr, Node *rexpr, int location);
@@ -394,7 +394,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				relation_expr_list dostmt_opt_list
 				transform_element_list transform_type_list
 				TriggerTransitions TriggerReferencing
-				publication_name_list
+				publication_name_list opt_corresponding_clause
 
 %type <list>	group_by_list
 %type <node>	group_by_item empty_grouping_set rollup_clause cube_clause
@@ -614,7 +614,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
 	CLUSTER COALESCE COLLATE COLLATION COLUMN COLUMNS COMMENT COMMENTS COMMIT
 	COMMITTED CONCURRENTLY CONFIGURATION CONFLICT CONNECTION CONSTRAINT
-	CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE
+	CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY CORRESPONDING COST CREATE
 	CROSS CSV CUBE CURRENT_P
 	CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA
 	CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
@@ -3579,7 +3579,10 @@ columnList:
 
 columnElem: ColId
 				{
-					$$ = (Node *) makeString($1);
+					Value *v = makeString($1);
+
+					v->location = @1;
+					$$ = (Node *) v;
 				}
 		;
 
@@ -10878,20 +10881,26 @@ simple_select:
 					n->fromClause = list_make1($2);
 					$$ = (Node *)n;
 				}
-			| select_clause UNION all_or_distinct select_clause
+			| select_clause UNION all_or_distinct opt_corresponding_clause select_clause
 				{
-					$$ = makeSetOp(SETOP_UNION, $3, $1, $4);
+					$$ = makeSetOp(SETOP_UNION, $3, $4, $1, $5);
 				}
-			| select_clause INTERSECT all_or_distinct select_clause
+			| select_clause INTERSECT all_or_distinct opt_corresponding_clause select_clause
 				{
-					$$ = makeSetOp(SETOP_INTERSECT, $3, $1, $4);
+					$$ = makeSetOp(SETOP_INTERSECT, $3, $4, $1, $5);
 				}
-			| select_clause EXCEPT all_or_distinct select_clause
+			| select_clause EXCEPT all_or_distinct opt_corresponding_clause select_clause
 				{
-					$$ = makeSetOp(SETOP_EXCEPT, $3, $1, $4);
+					$$ = makeSetOp(SETOP_EXCEPT, $3, $4, $1, $5);
 				}
 		;
 
+opt_corresponding_clause:
+			CORRESPONDING BY '(' columnList ')'	{ $$ = $4; }
+			| CORRESPONDING							{ $$ = list_make1(NIL); }
+			| /*EMPTY*/								{ $$ = NIL; }
+			;
+
 /*
  * SQL standard WITH clause looks like:
  *
@@ -14232,7 +14241,6 @@ name_list:	name
 					{ $$ = lappend($1, makeString($3)); }
 		;
 
-
 name:		ColId									{ $$ = $1; };
 
 database_name:
@@ -14549,6 +14557,7 @@ unreserved_keyword:
 			| CONTINUE_P
 			| CONVERSION_P
 			| COPY
+			| CORRESPONDING
 			| COST
 			| CSV
 			| CUBE
@@ -15383,7 +15392,7 @@ insertSelectOptions(SelectStmt *stmt,
 }
 
 static Node *
-makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg)
+makeSetOp(SetOperation op, bool all, List *correspondingClause, Node *larg, Node *rarg)
 {
 	SelectStmt *n = makeNode(SelectStmt);
 
@@ -15391,6 +15400,7 @@ makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg)
 	n->all = all;
 	n->larg = (SelectStmt *) larg;
 	n->rarg = (SelectStmt *) rarg;
+	n->correspondingClause = correspondingClause;
 	return (Node *) n;
 }
 
diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
index 0d7a2b1e1b..b553d847d8 100644
--- a/src/backend/parser/parse_type.c
+++ b/src/backend/parser/parse_type.c
@@ -735,7 +735,8 @@ typeStringToTypeName(const char *str)
 		stmt->limitCount != NULL ||
 		stmt->lockingClause != NIL ||
 		stmt->withClause != NULL ||
-		stmt->op != SETOP_NONE)
+		stmt->op != SETOP_NONE ||
+		stmt->correspondingClause != NULL)
 		goto fail;
 	if (list_length(stmt->targetList) != 1)
 		goto fail;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index c2681ced2a..adf797d600 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -5442,6 +5442,30 @@ get_setop_query(Node *setOp, Query *query, deparse_context *context,
 		}
 		if (op->all)
 			appendStringInfoString(buf, "ALL ");
+		if (op->correspondingColumns != NIL )
+		{
+			if (op->hasCorrespondingBy)
+			{
+				const char *sep;
+				ListCell *l;
+				appendStringInfoString(buf, "CORRESPONDING BY(");
+				sep = "";
+
+				foreach(l, op->correspondingColumns)
+				{
+					TargetEntry *tle = (TargetEntry *) lfirst(l);
+
+					appendStringInfoString(buf, sep);
+					appendStringInfo(buf, "%s", tle->resname);
+					sep = ", ";
+				}
+				appendStringInfoChar(buf, ')');
+
+			}
+			else
+
+				appendStringInfoString(buf, "CORRESPONDING ");
+		}
 
 		/* Always parenthesize if RHS is another setop */
 		need_paren = IsA(op->rarg, SetOperationStmt);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3a71dd5b37..ed8ce2f2d0 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1460,6 +1460,7 @@ typedef struct SelectStmt
 	 */
 	List	   *distinctClause; /* NULL, list of DISTINCT ON exprs, or
 								 * lcons(NIL,NIL) for all (SELECT DISTINCT) */
+	List	   *correspondingClause;	/* CORRESPONDING BY  clauses*/
 	IntoClause *intoClause;		/* target for SELECT INTO */
 	List	   *targetList;		/* the target list (of ResTarget) */
 	List	   *fromClause;		/* the FROM clause */
@@ -1495,7 +1496,6 @@ typedef struct SelectStmt
 	bool		all;			/* ALL specified? */
 	struct SelectStmt *larg;	/* left child */
 	struct SelectStmt *rarg;	/* right child */
-	/* Eventually add fields for CORRESPONDING spec here */
 } SelectStmt;
 
 
@@ -1525,8 +1525,8 @@ typedef struct SetOperationStmt
 	bool		all;			/* ALL specified? */
 	Node	   *larg;			/* left child */
 	Node	   *rarg;			/* right child */
-	/* Eventually add fields for CORRESPONDING spec here */
-
+	List	   *correspondingColumns;	/* list of corresponding column names */
+	bool		hasCorrespondingBy;		/* has corresponding by cluase? */
 	/* Fields derived during parse analysis: */
 	List	   *colTypes;		/* OID list of output column type OIDs */
 	List	   *colTypmods;		/* integer list of output column typmods */
diff --git a/src/include/nodes/value.h b/src/include/nodes/value.h
index ede97b7bcd..bf3b6e9b68 100644
--- a/src/include/nodes/value.h
+++ b/src/include/nodes/value.h
@@ -47,6 +47,7 @@ typedef struct Value
 		long		ival;		/* machine integer */
 		char	   *str;		/* string */
 	}			val;
+	int			location;		/* token location, or -1 if unknown */
 } Value;
 
 #define intVal(v)		(((Value *)(v))->val.ival)
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index cd21a789d5..7f1c2554e3 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -97,6 +97,7 @@ PG_KEYWORD("content", CONTENT_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("continue", CONTINUE_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("conversion", CONVERSION_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("copy", COPY, UNRESERVED_KEYWORD)
+PG_KEYWORD("corresponding", CORRESPONDING, UNRESERVED_KEYWORD)
 PG_KEYWORD("cost", COST, UNRESERVED_KEYWORD)
 PG_KEYWORD("create", CREATE, RESERVED_KEYWORD)
 PG_KEYWORD("cross", CROSS, TYPE_FUNC_NAME_KEYWORD)
diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out
index c719262720..3bcb89ceca 100644
--- a/src/test/regress/expected/create_view.out
+++ b/src/test/regress/expected/create_view.out
@@ -1626,3 +1626,28 @@ select pg_get_viewdef('tt19v', true);
 set client_min_messages = warning;
 DROP SCHEMA temp_view_test CASCADE;
 DROP SCHEMA testviewschm2 CASCADE;
+-- views with corresponding clause
+create view view_corresponding_01 as select 1 as a, 2 as b union all corresponding select 3 as a, 4 as b;
+select * from view_corresponding_01;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+create view view_corresponding_02 as select 1 as a, 2 as b union all corresponding by (a,b) select 3 as a, 4 as b, 5 as c;
+select * from view_corresponding_02;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+create view view_corresponding_03 as select 1 as a, 2 as b union all corresponding by (b,a) select 3 as a, 4 as b, 5 as c;
+select * from view_corresponding_03;
+ b | a 
+---+---
+ 2 | 1
+ 4 | 3
+(2 rows)
+
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index d706f42b2d..3e36dc58f9 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -2345,6 +2345,23 @@ toyemp| SELECT emp.name,
     emp.location,
     (12 * emp.salary) AS annualsal
    FROM emp;
+view_corresponding_01| SELECT 1 AS a,
+    2 AS b
+UNION ALL CORRESPONDING
+ SELECT 3 AS a,
+    4 AS b;
+view_corresponding_02| SELECT 1 AS a,
+    2 AS b
+UNION ALL CORRESPONDING BY(a, b)
+ SELECT 3 AS a,
+    4 AS b,
+    5 AS c;
+view_corresponding_03| SELECT 1 AS b,
+    2 AS a
+UNION ALL CORRESPONDING BY(b, a)
+ SELECT 3 AS b,
+    4 AS a,
+    5 AS c;
 SELECT tablename, rulename, definition FROM pg_rules
 	ORDER BY tablename, rulename;
 pg_settings|pg_settings_n|CREATE RULE pg_settings_n AS
diff --git a/src/test/regress/expected/union.out b/src/test/regress/expected/union.out
index 4d697bada7..59d6c001ef 100644
--- a/src/test/regress/expected/union.out
+++ b/src/test/regress/expected/union.out
@@ -59,6 +59,87 @@ SELECT 1.1 AS two UNION SELECT 2.2 ORDER BY 1;
  2.2
 (2 rows)
 
+SELECT 1 AS two UNION CORRESPONDING SELECT 2 two UNION CORRESPONDING SELECT 2 two;
+ two 
+-----
+   2
+   1
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a) SELECT 4 a, 5 b, 6 c;
+ a 
+---
+ 1
+ 4
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b) SELECT 4 a, 5 b, 6 c;
+ a | b 
+---+---
+ 1 | 2
+ 4 | 5
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c) SELECT 4 a, 5 b, 6 c;
+ b | c 
+---+---
+ 2 | 3
+ 5 | 6
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY (c,b) SELECT 4 a, 5 b, 6 c, 8 d;
+ c | b 
+---+---
+ 3 | 2
+ 6 | 5
+(2 rows)
+
+-- CORRESPONDING column ordering, left clause's column ordering must be preserved.
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+SELECT 2 b, 1 a, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+ b | a | c 
+---+---+---
+ 2 | 1 | 3
+ 5 | 4 | 6
+(2 rows)
+
+-- CORRESPONDING BY column ordering, BY clause column ordering must be preserved.
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 4 a, 5 b, 6 c;
+ a | b | c 
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c, a) SELECT 4 a, 5 b, 6 c;
+ b | c | a 
+---+---+---
+ 2 | 3 | 1
+ 5 | 6 | 4
+(2 rows)
+
+-- should to fail
+SELECT 10 a, 20 b UNION ALL CORRESPONDING SELECT 10 c, 20 d;
+ERROR:  there is not any corresponding name
+LINE 1: SELECT 10 a, 20 b UNION ALL CORRESPONDING SELECT 10 c, 20 d;
+               ^
+HINT:  UNION queries with a CORRESPONDING clause must have at least one column with the same name
+SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (a,b) SELECT 10 c, 20 d;
+ERROR:  there is not any corresponding name
+LINE 1: SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (a,b) SELECT 10...
+               ^
+HINT:  UNION queries with a CORRESPONDING clause must have at least one column with the same name
+SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (x) SELECT 10 c, 20 d;
+ERROR:  there is not any corresponding name
+LINE 1: SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (x) SELECT 10 c...
+               ^
+HINT:  UNION queries with a CORRESPONDING clause must have at least one column with the same name
 -- Mixed types
 SELECT 1.1 AS two UNION SELECT 2 ORDER BY 1;
  two 
@@ -124,6 +205,147 @@ SELECT 1.1 AS two UNION (SELECT 2 UNION ALL SELECT 2) ORDER BY 1;
    2
 (2 rows)
 
+-- other corresponding clause tests,
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+-- when column is not in result, then the name should not be unique
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS b, 5 AS a;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+ 5 | 6
+(3 rows)
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x3;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x6 UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS b, 5 AS a;
+ a | b 
+---+---
+ 1 | 2
+ 3 | 4
+ 5 | 6
+(3 rows)
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS b, -100 AS a;
+ b 
+---
+ 2
+ 4
+ 6
+(3 rows)
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS a, -100 AS x9;
+ a 
+---
+ 1
+ 3
+ 6
+(3 rows)
+
+SELECT 0 AS a, 1 AS b, 0 AS c UNION ALL CORRESPONDING SELECT 2 AS a, 3 AS b, 10 AS c, 20 AS d UNION ALL CORRESPONDING SELECT 11 AS c, 21 AS d;
+ c  
+----
+  0
+ 10
+ 11
+(3 rows)
+
+SELECT 0 AS a, 1 AS b  UNION ALL CORRESPONDING SELECT 2 AS a, 3 AS b, 10 AS c, 20 AS d UNION ALL CORRESPONDING SELECT 11 AS c, 21 AS d, 4 AS a;
+ a 
+---
+ 0
+ 2
+ 4
+(3 rows)
+
+SELECT 0 AS a, 1 AS b  UNION ALL CORRESPONDING SELECT 2 AS a, 3 AS b, 10 AS c, 20 AS d UNION ALL CORRESPONDING SELECT 11 AS c, 21 AS d, 5 AS b;
+ b 
+---
+ 1
+ 3
+ 5
+(3 rows)
+
+-- should fail
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3;
+ERROR:  corresponding column "x3" is used more times
+LINE 1: SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 U...
+                                                          ^
+HINT:  In UNION queries with CORRESPONDING clause the corresponding column names must be unique.
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3, -10 AS x3;
+ERROR:  corresponding column "x3" is used more times
+LINE 1: ...ELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3, -10 AS x3;
+                                                             ^
+HINT:  In UNION queries with CORRESPONDING clause the corresponding column names must be unique.
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(a,b,x3) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3;
+ERROR:  corresponding column "x3" is used more times
+LINE 1: SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 U...
+                                                          ^
+HINT:  In UNION queries with CORRESPONDING BY clause the corresponding column names must be unique.
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING BY(a,b,x3) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3, -10 AS x3;
+ERROR:  corresponding column "x3" is used more times
+LINE 1: ...ELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3, -10 AS x3;
+                                                             ^
+HINT:  In UNION queries with CORRESPONDING BY clause the corresponding column names must be unique.
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x6 UNION ALL CORRESPONDING BY(a,b,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+ERROR:  column name "b" is not unique in CORRESPONDING BY clause
+LINE 1: ... 0 AS x3, -1 AS x6 UNION ALL CORRESPONDING BY(a,b,b) SELECT ...
+                                                             ^
+HINT:  CORRESPONDING BY clause must contain unique column names only.
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS bb, -100 AS aa;
+ERROR:  there is not any corresponding name
+LINE 1: SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 U...
+                        ^
+HINT:  UNION queries with a CORRESPONDING clause must have at least one column with the same name
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS xxx, -100 AS x9;
+ERROR:  there is not any corresponding name
+LINE 1: SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 U...
+                        ^
+HINT:  UNION queries with a CORRESPONDING clause must have at least one column with the same name
+SELECT 0 AS a, 1 AS b UNION ALL CORRESPONDING SELECT 2 AS a, 3 AS b, 10 AS c, 20 AS d UNION ALL CORRESPONDING SELECT 11 AS c, 21 AS d;
+ERROR:  there is not any corresponding name
+LINE 1: SELECT 0 AS a, 1 AS b UNION ALL CORRESPONDING SELECT 2 AS a,...
+               ^
+HINT:  UNION queries with a CORRESPONDING clause must have at least one column with the same name
 --
 -- Try testing from tables...
 --
@@ -258,6 +480,74 @@ ORDER BY 1;
  hi de ho neighbor
 (5 rows)
 
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+         five          
+-----------------------
+ -1.2345678901234e+200
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+                     0
+(5 rows)
+
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING BY(five)
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+         five          
+-----------------------
+ -1.2345678901234e+200
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+                     0
+(5 rows)
+
+SELECT f1 AS ten FROM FLOAT8_TBL
+UNION ALL CORRESPONDING
+SELECT f1 AS ten FROM FLOAT8_TBL;
+          ten          
+-----------------------
+                     0
+                -34.84
+               -1004.3
+ -1.2345678901234e+200
+ -1.2345678901234e-200
+                     0
+                -34.84
+               -1004.3
+ -1.2345678901234e+200
+ -1.2345678901234e-200
+(10 rows)
+
+SELECT f1 AS five FROM FLOAT8_TBL
+  WHERE f1 BETWEEN -1e6 AND 1e6
+UNION CORRESPONDING
+SELECT f1 AS five FROM INT4_TBL
+  WHERE f1 BETWEEN 0 AND 1000000;
+         five          
+-----------------------
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+                     0
+                123456
+(5 rows)
+
+SELECT * FROM FLOAT8_TBL
+UNION corresponding SELECT * FROM FLOAT8_TBL;
+          f1           
+-----------------------
+                     0
+ -1.2345678901234e-200
+                -34.84
+ -1.2345678901234e+200
+               -1004.3
+(5 rows)
+
 --
 -- INTERSECT and EXCEPT
 --
@@ -320,6 +610,63 @@ SELECT q1 FROM int8_tbl EXCEPT ALL SELECT DISTINCT q2 FROM int8_tbl ORDER BY 1;
 
 SELECT q1 FROM int8_tbl EXCEPT ALL SELECT q1 FROM int8_tbl FOR NO KEY UPDATE;
 ERROR:  FOR NO KEY UPDATE is not allowed with UNION/INTERSECT/EXCEPT
+SELECT q2 FROM int8_tbl INTERSECT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+        q2        
+------------------
+              123
+ 4567890123456789
+(2 rows)
+
+SELECT q2 FROM int8_tbl INTERSECT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+        q2        
+------------------
+              123
+ 4567890123456789
+ 4567890123456789
+(3 rows)
+
+SELECT q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+        q2         
+-------------------
+ -4567890123456789
+               456
+(2 rows)
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+        q2         
+-------------------
+ -4567890123456789
+               456
+(2 rows)
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q1 AS q2 FROM int8_tbl ORDER BY 1;
+        q2         
+-------------------
+ -4567890123456789
+               456
+  4567890123456789
+(3 rows)
+
+SELECT q1 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+ q1 
+----
+(0 rows)
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+        q1        
+------------------
+              123
+ 4567890123456789
+(2 rows)
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q2 AS q1 FROM int8_tbl;
+        q1        
+------------------
+              123
+ 4567890123456789
+ 4567890123456789
+(3 rows)
+
 --
 -- Mixed types
 --
@@ -338,6 +685,21 @@ SELECT f1 FROM float8_tbl EXCEPT SELECT f1 FROM int4_tbl ORDER BY 1;
  -1.2345678901234e-200
 (4 rows)
 
+SELECT f1 FROM float8_tbl INTERSECT CORRESPONDING SELECT f1 FROM int4_tbl;
+ f1 
+----
+  0
+(1 row)
+
+SELECT f1 FROM float8_tbl EXCEPT CORRESPONDING SELECT f1 FROM int4_tbl ORDER BY 1;
+          f1           
+-----------------------
+ -1.2345678901234e+200
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+(4 rows)
+
 --
 -- Operator precedence and (((((extra))))) parentheses
 --
@@ -424,6 +786,24 @@ SELECT q1 FROM int8_tbl EXCEPT (((SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1)))
  4567890123456789
 (2 rows)
 
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2,q1 FROM int8_tbl
+ORDER BY q2,q1;
+ q1 | q2 
+----+----
+(0 rows)
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q1) SELECT q2,q1 FROM int8_tbl
+ORDER BY q1;
+ q1 
+----
+(0 rows)
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q2) SELECT q2,q1 FROM int8_tbl
+ORDER BY q2;
+ q2 
+----
+(0 rows)
+
 --
 -- New syntaxes (7.1) permit new tests
 --
@@ -459,6 +839,22 @@ SELECT '3.4'::numeric UNION SELECT 'foo';
 ERROR:  invalid input syntax for type numeric: "foo"
 LINE 1: SELECT '3.4'::numeric UNION SELECT 'foo';
                                            ^
+SELECT '3.4'::numeric AS a UNION CORRESPONDING SELECT 'foo' AS a;
+ERROR:  invalid input syntax for type numeric: "foo"
+LINE 1: ...CT '3.4'::numeric AS a UNION CORRESPONDING SELECT 'foo' AS a...
+                                                             ^
+SELECT a.f1 FROM (SELECT 'test' AS f1 FROM varchar_tbl) a
+UNION CORRESPONDING
+SELECT b.f1 FROM (SELECT f1 FROM varchar_tbl) b
+ORDER BY 1;
+  f1  
+------
+ a
+ ab
+ abcd
+ test
+(4 rows)
+
 --
 -- Test that expression-index constraints can be pushed down through
 -- UNION or UNION ALL
diff --git a/src/test/regress/sql/create_view.sql b/src/test/regress/sql/create_view.sql
index d6f50d6105..f61a01323b 100644
--- a/src/test/regress/sql/create_view.sql
+++ b/src/test/regress/sql/create_view.sql
@@ -551,3 +551,13 @@ select pg_get_viewdef('tt19v', true);
 set client_min_messages = warning;
 DROP SCHEMA temp_view_test CASCADE;
 DROP SCHEMA testviewschm2 CASCADE;
+
+-- views with corresponding clause
+create view view_corresponding_01 as select 1 as a, 2 as b union all corresponding select 3 as a, 4 as b;
+select * from view_corresponding_01;
+
+create view view_corresponding_02 as select 1 as a, 2 as b union all corresponding by (a,b) select 3 as a, 4 as b, 5 as c;
+select * from view_corresponding_02;
+
+create view view_corresponding_03 as select 1 as a, 2 as b union all corresponding by (b,a) select 3 as a, 4 as b, 5 as c;
+select * from view_corresponding_03;
diff --git a/src/test/regress/sql/union.sql b/src/test/regress/sql/union.sql
index 48e6850798..731e886b9a 100644
--- a/src/test/regress/sql/union.sql
+++ b/src/test/regress/sql/union.sql
@@ -20,6 +20,35 @@ SELECT 1 AS three UNION SELECT 2 UNION ALL SELECT 2 ORDER BY 1;
 
 SELECT 1.1 AS two UNION SELECT 2.2 ORDER BY 1;
 
+SELECT 1 AS two UNION CORRESPONDING SELECT 2 two UNION CORRESPONDING SELECT 2 two;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY (c,b) SELECT 4 a, 5 b, 6 c, 8 d;
+
+-- CORRESPONDING column ordering, left clause's column ordering must be preserved.
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+
+SELECT 2 b, 1 a, 3 c UNION CORRESPONDING SELECT 4 a, 5 b, 6 c;
+
+-- CORRESPONDING BY column ordering, BY clause column ordering must be preserved.
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(a, b, c) SELECT 4 a, 5 b, 6 c;
+
+SELECT 1 a, 2 b, 3 c UNION CORRESPONDING BY(b, c, a) SELECT 4 a, 5 b, 6 c;
+
+-- should to fail
+SELECT 10 a, 20 b UNION ALL CORRESPONDING SELECT 10 c, 20 d;
+
+SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (a,b) SELECT 10 c, 20 d;
+
+SELECT 10 a, 20 b UNION ALL CORRESPONDING BY (x) SELECT 10 c, 20 d;
+
 -- Mixed types
 
 SELECT 1.1 AS two UNION SELECT 2 ORDER BY 1;
@@ -40,6 +69,37 @@ SELECT 1.1 AS three UNION SELECT 2 UNION ALL SELECT 2 ORDER BY 1;
 
 SELECT 1.1 AS two UNION (SELECT 2 UNION ALL SELECT 2) ORDER BY 1;
 
+-- other corresponding clause tests,
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6;
+
+-- when column is not in result, then the name should not be unique
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS b, 5 AS a;
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x3;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x6 UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS b, 5 AS a;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS b, -100 AS a;
+
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS a, -100 AS x9;
+SELECT 0 AS a, 1 AS b, 0 AS c UNION ALL CORRESPONDING SELECT 2 AS a, 3 AS b, 10 AS c, 20 AS d UNION ALL CORRESPONDING SELECT 11 AS c, 21 AS d;
+SELECT 0 AS a, 1 AS b  UNION ALL CORRESPONDING SELECT 2 AS a, 3 AS b, 10 AS c, 20 AS d UNION ALL CORRESPONDING SELECT 11 AS c, 21 AS d, 4 AS a;
+SELECT 0 AS a, 1 AS b  UNION ALL CORRESPONDING SELECT 2 AS a, 3 AS b, 10 AS c, 20 AS d UNION ALL CORRESPONDING SELECT 11 AS c, 21 AS d, 5 AS b;
+
+-- should fail
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3, -10 AS x3;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(a,b,x3) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3 UNION ALL CORRESPONDING BY(a,b,x3) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -5 AS x3, -10 AS x3;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x6 UNION ALL CORRESPONDING BY(a,b,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING BY(a,b) SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS bb, -100 AS aa;
+SELECT 0 AS x1, 1 AS a, 0 AS x2, 2 AS b, 0 AS x3, -1 AS x3 UNION ALL CORRESPONDING SELECT 4 AS b, 0 AS x4, 3 AS a, 0 AS x6, -1 AS x6 UNION ALL CORRESPONDING SELECT 0 AS x8, 6 AS xxx, -100 AS x9;
+SELECT 0 AS a, 1 AS b UNION ALL CORRESPONDING SELECT 2 AS a, 3 AS b, 10 AS c, 20 AS d UNION ALL CORRESPONDING SELECT 11 AS c, 21 AS d;
+
 --
 -- Try testing from tables...
 --
@@ -90,6 +150,29 @@ UNION
 SELECT TRIM(TRAILING FROM f1) FROM CHAR_TBL
 ORDER BY 1;
 
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+
+SELECT f1 AS five FROM FLOAT8_TBL
+UNION CORRESPONDING BY(five)
+SELECT f1 AS five FROM FLOAT8_TBL
+ORDER BY 1;
+
+SELECT f1 AS ten FROM FLOAT8_TBL
+UNION ALL CORRESPONDING
+SELECT f1 AS ten FROM FLOAT8_TBL;
+
+SELECT f1 AS five FROM FLOAT8_TBL
+  WHERE f1 BETWEEN -1e6 AND 1e6
+UNION CORRESPONDING
+SELECT f1 AS five FROM INT4_TBL
+  WHERE f1 BETWEEN 0 AND 1000000;
+
+SELECT * FROM FLOAT8_TBL
+UNION corresponding SELECT * FROM FLOAT8_TBL;
+
 --
 -- INTERSECT and EXCEPT
 --
@@ -112,6 +195,22 @@ SELECT q1 FROM int8_tbl EXCEPT ALL SELECT DISTINCT q2 FROM int8_tbl ORDER BY 1;
 
 SELECT q1 FROM int8_tbl EXCEPT ALL SELECT q1 FROM int8_tbl FOR NO KEY UPDATE;
 
+SELECT q2 FROM int8_tbl INTERSECT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+
+SELECT q2 FROM int8_tbl INTERSECT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl;
+
+SELECT q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q1 AS q2 FROM int8_tbl ORDER BY 1;
+
+SELECT q2 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q1 AS q2 FROM int8_tbl ORDER BY 1;
+
+SELECT q1 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT q2 AS q1 FROM int8_tbl;
+
+SELECT q1 FROM int8_tbl EXCEPT ALL CORRESPONDING SELECT DISTINCT q2 AS q1 FROM int8_tbl;
+
 --
 -- Mixed types
 --
@@ -120,6 +219,10 @@ SELECT f1 FROM float8_tbl INTERSECT SELECT f1 FROM int4_tbl ORDER BY 1;
 
 SELECT f1 FROM float8_tbl EXCEPT SELECT f1 FROM int4_tbl ORDER BY 1;
 
+SELECT f1 FROM float8_tbl INTERSECT CORRESPONDING SELECT f1 FROM int4_tbl;
+
+SELECT f1 FROM float8_tbl EXCEPT CORRESPONDING SELECT f1 FROM int4_tbl ORDER BY 1;
+
 --
 -- Operator precedence and (((((extra))))) parentheses
 --
@@ -150,6 +253,15 @@ SELECT q1 FROM int8_tbl EXCEPT SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1;
 -- But this should work:
 SELECT q1 FROM int8_tbl EXCEPT (((SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1))) ORDER BY 1;
 
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING SELECT q2,q1 FROM int8_tbl
+ORDER BY q2,q1;
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q1) SELECT q2,q1 FROM int8_tbl
+ORDER BY q1;
+
+SELECT q1,q2 FROM int8_tbl EXCEPT CORRESPONDING BY(q2) SELECT q2,q1 FROM int8_tbl
+ORDER BY q2;
+
 --
 -- New syntaxes (7.1) permit new tests
 --
@@ -170,6 +282,13 @@ ORDER BY 1;
 -- This should fail, but it should produce an error cursor
 SELECT '3.4'::numeric UNION SELECT 'foo';
 
+SELECT '3.4'::numeric AS a UNION CORRESPONDING SELECT 'foo' AS a;
+
+SELECT a.f1 FROM (SELECT 'test' AS f1 FROM varchar_tbl) a
+UNION CORRESPONDING
+SELECT b.f1 FROM (SELECT f1 FROM varchar_tbl) b
+ORDER BY 1;
+
 --
 -- Test that expression-index constraints can be pushed down through
 -- UNION or UNION ALL
#37Tom Lane
tgl@sss.pgh.pa.us
In reply to: Pavel Stehule (#36)
Re: New CORRESPONDING clause design

Pavel Stehule <pavel.stehule@gmail.com> writes:

[ corresponding_clause_v12.patch ]

I worked on this for awhile but eventually decided that it's not very
close to being committable. The main thing that's scaring me off is
a realization that there are a *lot* of places that assume that the
output columns of a set operation are one-for-one with the columns of
the inputs. One such place is the subquery qual pushdown logic in
allpaths.c, which this patch hasn't touched, resulting in

regression=# create table t1 (a int, b int, c int);
CREATE TABLE
regression=# create table t2 (a int, b int, c int);
CREATE TABLE
regression=# create view vv2 as
regression-# select * from t1 union all corresponding by (b,c) select * from t2;
CREATE VIEW
regression=# select * from vv2 where c = 44;
ERROR: wrong number of tlist entries

Another is the code that converts a UNION ALL subquery into an appendrel.
The best thing there might be to just reject CORRESPONDING in
is_simple_union_all, but this patch doesn't, which means we get the wrong
results from

regression=# create table t3 (b int, a int, c int);
CREATE TABLE
regression=# explain verbose select * from t1 union all corresponding select * from t3;
QUERY PLAN
--------------------------------------------------------------------
Append (cost=0.00..60.80 rows=4080 width=12)
-> Seq Scan on public.t1 (cost=0.00..30.40 rows=2040 width=12)
Output: t1.a, t1.b, t1.c
-> Seq Scan on public.t3 (cost=0.00..30.40 rows=2040 width=12)
Output: t3.b, t3.a, t3.c
(5 rows)

Notice it's failed to rearrange the columns to match.

There are also such assumptions in ruleutils.c. Trying to reverse-list
the above view gives

regression=# \d+ vv2
View "public.vv2"
Column | Type | Collation | Nullable | Default | Storage | Description
--------+---------+-----------+----------+---------+---------+-------------
b | integer | | | | plain |
c | integer | | | | plain |
View definition:
SELECT t1.a AS b,
t1.b AS c,
t1.c
FROM t1
UNION ALL CORRESPONDING BY (b, c)
SELECT t2.a AS b,
t2.b AS c,
t2.c
FROM t2;

which is obviously wrong. The reason for that is that the code is
trying to ensure that the SELECT's output column names match the
view's column names, so it sticks AS clauses on the select-list
expressions. If we go a little further:

regression=# alter table vv2 rename column c to ceetwo;
ALTER TABLE
regression=# \d+ vv2
View "public.vv2"
Column | Type | Collation | Nullable | Default | Storage | Description
--------+---------+-----------+----------+---------+---------+-------------
b | integer | | | | plain |
ceetwo | integer | | | | plain |
View definition:
SELECT t1.a AS b,
t1.b AS ceetwo,
t1.c
FROM t1
UNION ALL CORRESPONDING BY (b, c)
SELECT t2.a AS b,
t2.b AS ceetwo,
t2.c
FROM t2;

Now things are a *complete* mess, because this view definition would
successfully parse during a dump-and-reload, but it means something
else than it did before; the CORRESPONDING BY list has failed to track
the change in names of the columns.

In general, there's a lot of subtle logic in ruleutils.c about what we
need to do to ensure that reverse-listed views keep the same semantic
meaning in the face of column renamings or additions in their input
tables. CORRESPONDING is really scary in this situation because its
semantics depend on column names. I tried to break it by adding/renaming
columns of the input tables to see if I could get ruleutils to print
something that didn't mean what it meant before. I didn't succeed
immediately, but I am thinking it would be smart for us always to
reverse-list the construct as CORRESPONDING BY with an explicit list of
column names. We don't ever reverse-list a NATURAL JOIN as such,
preferring to use an explicit JOIN USING list, for closely related
reasons. (You might care to study the logic in ruleutils.c around the
usingNames list, which exists to ensure that JOIN USING doesn't get broken
by column renames. It's entirely likely that we're going to need
something equivalently complicated for CORRESPONDING BY. Or if we
don't, I'd want to have some comments in there that clearly explain
why we don't, because otherwise somebody will break it in future.)

I also found that WITH RECURSIVE fails immediately if you stick
CORRESPONDING into the recursive union step; eg in this lightly
modified version of a query from with.sql,

CREATE TEMPORARY TABLE tree(
id INTEGER PRIMARY KEY,
parent_id INTEGER REFERENCES tree(id)
);

WITH RECURSIVE t(id, path) AS (
select 1 as id, ARRAY[]::integer[] as path
UNION ALL CORRESPONDING
SELECT tree.id, t.path || tree.id as path
FROM tree JOIN t ON (tree.parent_id = t.id)
)
SELECT t1.*, t2.* FROM t AS t1 JOIN t AS t2 ON
(t1.path[1] = t2.path[1] AND
array_upper(t1.path,1) = 1 AND
array_upper(t2.path,1) > 1)
ORDER BY t1.id, t2.id;

ERROR: column t.id does not exist
LINE 5: FROM tree JOIN t ON (tree.parent_id = t.id)
^
HINT: Perhaps you meant to reference the column "tree.id".

I was expecting to find trouble if the CORRESPONDING had to rearrange
columns (breaking the 1-to-1 assumption), but it fails even without that.

There may well be other places in the system that are making similar
assumptions about 1-to-1 mapping between setop inputs and outputs.
This comment in plan_set_operations is pretty scary, for instance:
* Find the leftmost component Query. We need to use its column names for
* all generated tlists (else SELECT INTO won't work right).
because the column names in the component query certainly won't be
1-to-1 with upper tlists if CORRESPONDING is active.

I wonder whether it wouldn't be a smarter plan to abandon this
implementation approach and rely on inserting an explicit level of
sub-selects during the parse analysis transformation. That is,

SELECT * FROM foo UNION CORRESPONDING BY (x, y) SELECT * FROM bar

would get converted into

SELECT x, y FROM (SELECT * FROM foo) ss1
UNION
SELECT x, y FROM (SELECT * FROM bar) ss2

This isn't very pretty, but I would have a lot fewer worries
about how long we'd be finding bugs induced by the feature.

If we do stick with trying to represent CORRESPONDING BY explicitly
in the parse tree, I'm not very satisfied with the representation
you've chosen for SetOperationStmt.correspondingColumns. The patch
says

+ List *correspondingColumns; /* list of corresponding column names */

which is a flat out lie. debug_print_parse and some study of the code
showed me that it's actually a list of TargetEntry nodes containing
copies of the left-hand input's target expression trees. This seems
bulky, bizarre, and asymmetric; among other things it requires the planner
to re-derive the column matching that the parser had already figured out.
There certainly doesn't seem to be any value in carrying around an extra
copy of the left-hand expressions.

I'd be inclined to suggest that the best representation in
SetOperationStmt is two integer lists of the left-hand and right-hand
column numbers of the CORRESPONDING columns. This would probably
simplify matters greatly for the planner. It would mean that ruleutils.c
would have to do a bit more work to find out the column names to print
in CORRESPONDING BY; but as I showed above, just printing the names that
appeared at parse time is a doomed strategy anyway.

Also, just in passing --- while it may well be a good idea to add a
location field to struct Value, I'm not on board with doing so and
then using it in only one place. As I said earlier, I think that
CORRESPONDING's column name list should act like every other column
name list in the grammar, and that includes making use of error cursor
locations where appropriate. And changing struct Value has repercussions
way beyond that, for instance it's not clear why we'd keep A_Const as a
separate node type if Value has a location field. I think if you want
to push that forward, it would be a good idea to submit a separate
patch that just focuses on adding error cursor location reports
everywhere that adding locations to Values would enable.

I'll set this back to Waiting on Author, but I think the chances of
getting to a committable patch before the end of the commitfest are
about nil.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#38David Steele
david@pgmasters.net
In reply to: Tom Lane (#37)
Re: New CORRESPONDING clause design

On 4/1/17 1:54 PM, Tom Lane wrote:

I'll set this back to Waiting on Author, but I think the chances of
getting to a committable patch before the end of the commitfest are
about nil.

I think this is especially true now that another three days have passed.

This submission has been marked "Returned with Feedback". Please feel
free to resubmit to a future commitfest.

Regards,
--
-David
david@pgmasters.net

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers