Hi

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

> Hi
>
> 2017-03-10 12:55 GMT+01:00 Pavel Stehule <pavel.steh...@gmail.com>:
>
>>
>>
>> 2017-03-10 10:13 GMT+01:00 Surafel Temesgen <surafel3...@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


>
> Regards
>
> Pavel
>
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
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to