A small new feature in SQL:2016 allows attaching a table alias to a
JOIN/USING construct:
<named columns join> ::=
USING <left paren> <join column list> <right paren>
[ AS <join correlation name> ]
(The part in brackets is new.)
This seems quite useful, and it seems the code would already support
this if we allow the grammar to accept this syntax.
Patch attached.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
From e1c9e7b7a12f0f7aba8f5c88a7909a61171dee27 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <[email protected]>
Date: Mon, 17 Jun 2019 15:35:32 +0200
Subject: [PATCH] Allow an alias to be attached directly to a JOIN ... USING
This allows something like
SELECT ... FROM t1 JOIN t2 USING (a, b, c) AS x
instead of requiring parentheses for the alias like
SELECT ... FROM (t1 JOIN t2 USING (a, b, c)) AS x
per SQL:2016 feature F404 "Range variable for common column names".
The parse analysis guts already support this, so this patch only
has to adjust the grammar a bit.
---
doc/src/sgml/ref/select.sgml | 2 +-
src/backend/catalog/sql_features.txt | 2 +-
src/backend/parser/gram.y | 69 ++++++++++++++++++----------
src/test/regress/expected/join.out | 18 ++++++++
src/test/regress/sql/join.sql | 5 ++
5 files changed, 69 insertions(+), 27 deletions(-)
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 06d611b64c..628b67a11a 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -59,7 +59,7 @@
[ LATERAL ] <replaceable class="parameter">function_name</replaceable> ( [
<replaceable class="parameter">argument</replaceable> [, ...] ] ) AS (
<replaceable class="parameter">column_definition</replaceable> [, ...] )
[ LATERAL ] ROWS FROM( <replaceable
class="parameter">function_name</replaceable> ( [ <replaceable
class="parameter">argument</replaceable> [, ...] ] ) [ AS ( <replaceable
class="parameter">column_definition</replaceable> [, ...] ) ] [, ...] )
[ WITH ORDINALITY ] [ [ AS ] <replaceable
class="parameter">alias</replaceable> [ ( <replaceable
class="parameter">column_alias</replaceable> [, ...] ) ] ]
- <replaceable class="parameter">from_item</replaceable> [ NATURAL ]
<replaceable class="parameter">join_type</replaceable> <replaceable
class="parameter">from_item</replaceable> [ ON <replaceable
class="parameter">join_condition</replaceable> | USING ( <replaceable
class="parameter">join_column</replaceable> [, ...] ) ]
+ <replaceable class="parameter">from_item</replaceable> [ NATURAL ]
<replaceable class="parameter">join_type</replaceable> <replaceable
class="parameter">from_item</replaceable> [ ON <replaceable
class="parameter">join_condition</replaceable> | USING ( <replaceable
class="parameter">join_column</replaceable> [, ...] ) [ AS <replaceable
class="parameter">alias</replaceable> ] ]
<phrase>and <replaceable class="parameter">grouping_element</replaceable> can
be one of:</phrase>
diff --git a/src/backend/catalog/sql_features.txt
b/src/backend/catalog/sql_features.txt
index ae874f38ee..15188ee970 100644
--- a/src/backend/catalog/sql_features.txt
+++ b/src/backend/catalog/sql_features.txt
@@ -264,7 +264,7 @@ F401 Extended joined table 02 FULL OUTER JOIN
YES
F401 Extended joined table 04 CROSS JOIN YES
F402 Named column joins for LOBs, arrays, and multisets
YES
F403 Partitioned joined tables NO
-F404 Range variable for common column names NO
+F404 Range variable for common column names YES
F411 Time zone specification YES differences regarding
literal interpretation
F421 National character YES
F431 Read-only scrollable cursors YES
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 8311b1dd46..844a6eab82 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -435,7 +435,7 @@ static Node *makeRecursiveViewSelect(char *relname, List
*aliases, Node *query);
%type <list> locked_rels_list
%type <boolean> all_or_distinct
-%type <node> join_outer join_qual
+%type <node> join_outer
%type <jtype> join_type
%type <list> extract_list overlay_list position_list
@@ -488,7 +488,7 @@ static Node *makeRecursiveViewSelect(char *relname, List
*aliases, Node *query);
%type <ival> sub_type opt_materialized
%type <value> NumericOnly
%type <list> NumericOnly_list
-%type <alias> alias_clause opt_alias_clause
+%type <alias> alias_clause opt_alias_clause opt_alias_clause_for_join_using
%type <list> func_alias_clause
%type <sortby> sortby
%type <ielem> index_elem
@@ -11967,20 +11967,28 @@ joined_table:
n->quals = NULL;
$$ = n;
}
- | table_ref join_type JOIN table_ref join_qual
+ | table_ref join_type JOIN table_ref ON a_expr
{
JoinExpr *n = makeNode(JoinExpr);
n->jointype = $2;
n->isNatural = false;
n->larg = $1;
n->rarg = $4;
- if ($5 != NULL && IsA($5, List))
- n->usingClause = (List *) $5;
/* USING clause */
- else
- n->quals = $5; /* ON clause */
+ n->quals = $6;
+ $$ = n;
+ }
+ | table_ref join_type JOIN table_ref USING '('
name_list ')' opt_alias_clause_for_join_using
+ {
+ JoinExpr *n = makeNode(JoinExpr);
+ n->jointype = $2;
+ n->isNatural = false;
+ n->larg = $1;
+ n->rarg = $4;
+ n->usingClause = $7;
+ n->alias = $9;
$$ = n;
}
- | table_ref JOIN table_ref join_qual
+ | table_ref JOIN table_ref ON a_expr
{
/* letting join_type reduce to empty
doesn't work */
JoinExpr *n = makeNode(JoinExpr);
@@ -11988,10 +11996,19 @@ joined_table:
n->isNatural = false;
n->larg = $1;
n->rarg = $3;
- if ($4 != NULL && IsA($4, List))
- n->usingClause = (List *) $4;
/* USING clause */
- else
- n->quals = $4; /* ON clause */
+ n->quals = $5;
+ $$ = n;
+ }
+ | table_ref JOIN table_ref USING '(' name_list ')'
opt_alias_clause_for_join_using
+ {
+ /* letting join_type reduce to empty
doesn't work */
+ JoinExpr *n = makeNode(JoinExpr);
+ n->jointype = JOIN_INNER;
+ n->isNatural = false;
+ n->larg = $1;
+ n->rarg = $3;
+ n->usingClause = $6;
+ n->alias = $8;
$$ = n;
}
| table_ref NATURAL join_type JOIN table_ref
@@ -12048,6 +12065,21 @@ opt_alias_clause: alias_clause
{ $$ = $1; }
| /*EMPTY*/
{ $$ = NULL; }
;
+/*
+ * The alias clause after JOIN ... USING only accepts the AS ColId spelling,
+ * per SQL standard. (The grammar could parse the other variants, but they
+ * don't seem to be useful, and it might lead to parser problems in the
+ * future.)
+ */
+opt_alias_clause_for_join_using:
+ AS ColId
+ {
+ $$ = makeNode(Alias);
+ $$->aliasname = $2;
+ }
+ | /*EMPTY*/
{ $$ = NULL; }
+ ;
+
/*
* func_alias_clause can include both an Alias and a coldeflist, so we make it
* return a 2-element list that gets disassembled by calling production.
@@ -12090,19 +12122,6 @@ join_outer: OUTER_P
{ $$ = NULL; }
| /*EMPTY*/
{ $$ = NULL; }
;
-/* JOIN qualification clauses
- * Possibilities are:
- * USING ( column list ) allows only unqualified column names,
- * which must match between
tables.
- * ON expr allows more general qualifications.
- *
- * We return USING as a List node, while an ON-expr will not be a List.
- */
-
-join_qual: USING '(' name_list ')' { $$ =
(Node *) $3; }
- | ON a_expr
{ $$ = $2; }
- ;
-
relation_expr:
qualified_name
diff --git a/src/test/regress/expected/join.out
b/src/test/regress/expected/join.out
index 07e631d45e..425028e811 100644
--- a/src/test/regress/expected/join.out
+++ b/src/test/regress/expected/join.out
@@ -1621,6 +1621,24 @@ SELECT '' AS "xxx", *
| 4 | 1 | one | 2
(4 rows)
+-- test aliases
+SELECT * FROM J1_TBL JOIN J2_TBL USING (i) WHERE J1_TBL.t = 'one'; -- ok
+ i | j | t | k
+---+---+-----+----
+ 1 | 4 | one | -1
+(1 row)
+
+SELECT * FROM J1_TBL JOIN J2_TBL USING (i) AS x WHERE J1_TBL.t = 'one'; --
error
+ERROR: invalid reference to FROM-clause entry for table "j1_tbl"
+LINE 1: ...CT * FROM J1_TBL JOIN J2_TBL USING (i) AS x WHERE J1_TBL.t =...
+ ^
+HINT: There is an entry for table "j1_tbl", but it cannot be referenced from
this part of the query.
+SELECT * FROM J1_TBL JOIN J2_TBL USING (i) AS x WHERE x.t = 'one'; -- ok
+ i | j | t | k
+---+---+-----+----
+ 1 | 4 | one | -1
+(1 row)
+
--
-- NATURAL JOIN
-- Inner equi-join on all columns with the same name
diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql
index bf6d5c3ae4..7ff7f30d7f 100644
--- a/src/test/regress/sql/join.sql
+++ b/src/test/regress/sql/join.sql
@@ -126,6 +126,11 @@ CREATE TABLE J2_TBL (
FROM J1_TBL t1 (a, b, c) JOIN J2_TBL t2 (a, b) USING (b)
ORDER BY b, t1.a;
+-- test aliases
+SELECT * FROM J1_TBL JOIN J2_TBL USING (i) WHERE J1_TBL.t = 'one'; -- ok
+SELECT * FROM J1_TBL JOIN J2_TBL USING (i) AS x WHERE J1_TBL.t = 'one'; --
error
+SELECT * FROM J1_TBL JOIN J2_TBL USING (i) AS x WHERE x.t = 'one'; -- ok
+
--
-- NATURAL JOIN
base-commit: 91acff7a538e6e6a8175450a38c7fa1d9a290011
--
2.22.0