The JOIN clause

The JOIN clause combines data from two tables according to the rules of the specified JOIN type. The general JOIN syntax is:

<table1> <JOIN_type> <table2> [ <join_condition> ]

The syntax of JOIN clauses that process string matches is:

<table1> { [INNER] | { LEFT | RIGHT | FULL } [OUTER] } JOIN <table2> ON <boolean_expression>
<table1> { [INNER] | { LEFT | RIGHT | FULL } [OUTER] } JOIN <table2> USING ( <join_column_list> )
<table1> NATURAL { [INNER] | { LEFT | RIGHT | FULL } [OUTER] } JOIN <table2>

INNER and OUTER are optional words in all forms. The default value is INNER. However, if the LEFT, RIGHT, or FULL modifier is specified without INNER or OUTER, PostgreSQL executes the JOIN clause as OUTER JOIN.

The ON and USING clause specifies the JOIN condition. Also, the NATURAL keyword implicitly sets the JOIN condition. The condition determines, which rows of the two source tables corresponds to each other.

To see how different types of JOIN operate with data, create two tables:

CREATE TABLE books (
    id SERIAL PRIMARY KEY,
    title VARCHAR (100),
    cover_id INT
);

INSERT INTO books (title, cover_id) VALUES
('Hyperion', 1),
('1984', 2),
('War and Peace', 3),
('The Time Machine',3),
('Mrs. Dalloway',5 );

CREATE TABLE covers (
    id SERIAL PRIMARY KEY,
    name VARCHAR (50)
);

INSERT INTO covers (name) VALUES
('Paperback'),
('Hardcover'),
('Hardcover with dust jacket'),
('Leatherette cover');

The books table contains the cover_id field. This field is a foreign key that refers to the primary key in the covers table and serves for defining relationships between tables.

CROSS JOIN

CROSS JOIN resulted table contains all possible combinations of rows from table1 and table2 (the Cartesian product). The set of the joined table columns includes all table1 columns, followed by all table2 columns. If the tables have N and M rows respectively, the joined table has N * M rows. CROSS JOIN is equivalent to FROM table1 INNER JOIN table2 ON TRUE.

Example:

SELECT * FROM books CROSS JOIN covers;

The result:

 id |      title       | cover_id | id |            name
----+------------------+----------+----+----------------------------
  1 | Hyperion         |        1 |  1 | Paperback
  2 | 1984             |        2 |  1 | Paperback
  3 | War and Peace    |        3 |  1 | Paperback
  4 | The Time Machine |        3 |  1 | Paperback
  5 | Mrs. Dalloway    |        5 |  1 | Paperback
  1 | Hyperion         |        1 |  2 | Hardcover
  2 | 1984             |        2 |  2 | Hardcover
  3 | War and Peace    |        3 |  2 | Hardcover
  4 | The Time Machine |        3 |  2 | Hardcover
  5 | Mrs. Dalloway    |        5 |  2 | Hardcover
  1 | Hyperion         |        1 |  3 | Hardcover with dust jacket
  2 | 1984             |        2 |  3 | Hardcover with dust jacket
  3 | War and Peace    |        3 |  3 | Hardcover with dust jacket
  4 | The Time Machine |        3 |  3 | Hardcover with dust jacket
  5 | Mrs. Dalloway    |        5 |  3 | Hardcover with dust jacket
  1 | Hyperion         |        1 |  4 | Leatherette cover
  2 | 1984             |        2 |  4 | Leatherette cover
  3 | War and Peace    |        3 |  4 | Leatherette cover
  4 | The Time Machine |        3 |  4 | Leatherette cover
  5 | Mrs. Dalloway    |        5 |  4 | Leatherette cover
(20 rows)

INNER JOIN

The INNER JOIN examines each row in table1 and checks if table2 contains a row that satisfies the JOIN condition. If a corresponding row is found, the INNER JOIN creates a new row that contains columns from both tables and adds it to the result set.

Example:

SELECT books.id, books.title, covers.name cover FROM books
    INNER JOIN covers ON books.cover_id = covers.id;

The result contains book’s id, title, and cover type. The Mrs. Dalloway book is not included in the result since for its cover_id value (4) a corresponding row in covers is not found:

 id |      title       |           cover
----+------------------+----------------------------
  1 | Hyperion         | Paperback
  2 | 1984             | Hardcover
  3 | War and Peace    | Hardcover with dust jacket
  4 | The Time Machine | Hardcover with dust jacket

LEFT OUTER JOIN

The LEFT OUTER JOIN clause performs the INNER JOIN operation first. After that, each table1 row, which is excluded from the INNER JOIN result, is added to the LEFT OUTER JOIN result with null values in the table2 columns. The joined table always has at least one row for each table1 row.

Example:

SELECT books.id, books.title, covers.name cover FROM books
    LEFT OUTER JOIN covers ON books.cover_id = covers.id;

The result contains a row with the Mrs. Dalloway book, in which the covers.name column with the cover alias has the null value:

 id |      title       |           cover
----+------------------+----------------------------
  1 | Hyperion         | Paperback
  2 | 1984             | Hardcover
  3 | War and Peace    | Hardcover with dust jacket
  4 | The Time Machine | Hardcover with dust jacket
  5 | Mrs. Dalloway    |

RIGHT OUTER JOIN

The RIGHT OUTER JOIN clause performs the INNER JOIN operation first. After that, each table2 row, which is excluded from the INNER JOIN result, is added to the RIGHT OUTER JOIN result with null values in the table1 columns. The joined table always has one row for each table2 row.

Example:

SELECT books.id, books.title, covers.name cover FROM books
    RIGHT OUTER JOIN covers ON books.cover_id = covers.id;

The result contains a row with Leatherette cover, in which the books.id and books.title columns have null values:

 id |      title       |           cover
----+------------------+----------------------------
  1 | Hyperion         | Paperback
  2 | 1984             | Hardcover
  3 | War and Peace    | Hardcover with dust jacket
  4 | The Time Machine | Hardcover with dust jacket
    |                  | Leatherette cover

FULL OUTER JOIN

The FULL OUTER JOIN clause performs the INNER JOIN operation first. After that, each table1 row, which is excluded from the INNER JOIN result, is added to the FULL OUTER JOIN result with null values in the table2 columns. Then each table2 row, which is excluded from the INNER JOIN result, is added to the FULL OUTER JOIN result with null values in the table1 columns.

Example:

SELECT books.id, books.title, covers.name cover FROM books
    FULL OUTER JOIN covers ON books.cover_id = covers.id;

The result:

 id |      title       |           cover
----+------------------+----------------------------
  1 | Hyperion         | Paperback
  2 | 1984             | Hardcover
  3 | War and Peace    | Hardcover with dust jacket
  4 | The Time Machine | Hardcover with dust jacket
  5 | Mrs. Dalloway    |
    |                  | Leatherette cover

JOIN conditions

The JOIN condition determines, which rows from two source tables match. You can use the ON, USING, or NATURAL clause to specify the condition.

ON

The ON clause returns a boolean value. A row from table1 matches a row from table2 if the ON expression evaluates to true.

Create two tables for test examples:

CREATE TABLE table1(
    num INT PRIMARY KEY,
    name VARCHAR (25)
);

INSERT INTO table1 (num, name) VALUES
    (1, 'a'),
    (2, 'b'),
    (3, 'c');

CREATE TABLE table2(
    num INT PRIMARY KEY,
    value VARCHAR (25)
);

INSERT INTO table2 (num, value) VALUES
    (1, 'value1'),
    (3, 'value2'),
    (5, 'value3');

table1:

 num | name
-----+------
   1 | a
   2 | b
   3 | c

table2:

 num | value
-----+-------
   1 | value1
   3 | value2
   5 | value3

Execute the following query with the ON condition:

SELECT * FROM table1 INNER JOIN table2 ON table1.num = table2.num;

The result:

 num | name | num | value
-----+------+-----+--------
   1 | a    |   1 | value1
   3 | c    |   3 | value2

The JOIN condition can also contain additional clauses that do not relate directly to JOIN.

Example:

SELECT * FROM table1 INNER JOIN table2
    ON table1.num = table2.num AND table2.value = 'value1';

The result:

 num | name | num | value
-----+------+-----+--------
   1 | a    |   1 | value1

USING

You can utilize the USING clause, when columns in both tables have the same names. USING takes a comma-separated list of the column names and creates a JOIN condition that includes an equality comparison for each column. For example, joining table1 and table2 with USING (field1, field2) produces the JOIN condition ON table1.field1 = table2.field1 AND table1.field2 = table2.field2.

The result of the JOIN clause with USING contains a single column for each of the listed column pairs in the listed order, followed by all remaining columns from table1 and table2.

Example:

SELECT * FROM table1 INNER JOIN table2 USING (num);

The result:

 num | name | value
-----+------+--------
   1 | a    | value1
   3 | c    | value2

NATURAL

The NATURAL clause creates a USING list. It includes all matching column names that both input tables contain. As with USING, the result of the JOIN clause with NATURAL contains a single column for each of the listed column pairs in the listed order, followed by all remaining columns from table1 and table2.

Example:

SELECT * FROM table1 NATURAL INNER JOIN table2;

The result:

 num | name | value
-----+------+--------
   1 | a    | value1
   3 | c    | value2

If tables do not have columns with matching names, the NATURAL JOIN clause produces the same result as CROSS JOIN.

Combine JOIN clauses

You can chain JOIN clauses of all types or nest JOIN to another JOIN. In the default order, JOIN clauses are executed from left to right. You can enclose JOIN clauses in parentheses to specify the join order.

Create table3 in addition to table1 and table2 for test examples:

CREATE TABLE table3(
    num INT PRIMARY KEY,
    name VARCHAR (25)
);

INSERT INTO table3 (id, value) VALUES
    (1, 'value1'),
    (2, 'value4');
  table1            table2            table3

 num | name        num | value        id | value
-----+------      -----+------      -----+------
   1 | a             1 | value1        1 | value1
   2 | b             3 | value2        2 | value4
   3 | c             5 | value3

Execute a JOIN query without and with parentheses and compare results.

Run a query without parentheses:

SELECT * FROM table1 CROSS JOIN table2 INNER JOIN table3 USING(value);

First, PostgreSQL executes the CROSS JOIN clause SELECT * FROM table1 CROSS JOIN table2. Its result is:

 num | name | num | value
-----+------+-----+--------
   1 | a    |   1 | value1
   1 | a    |   3 | value2
   1 | a    |   5 | value3
   2 | b    |   1 | value1
   2 | b    |   3 | value2
   2 | b    |   5 | value3
   3 | c    |   1 | value1
   3 | c    |   3 | value2
   3 | c    |   5 | value3

After that, PostgreSQL executes INNER JOIN on the CROSS JOIN resulted table and table3. Since USING contains the value field, this field is the first column in the resulted table.

The result:

 value  | num | name | num | id
--------+-----+------+-----+----
 value1 |   1 | a    |   1 |  1
 value1 |   2 | b    |   1 |  1
 value1 |   3 | c    |   1 |  1

Add parentheses to the query:

SELECT * FROM table1 CROSS JOIN (table2 INNER JOIN table3 USING(value));

First, PostgreSQL executes the INNER JOIN clause SELECT * FROM table2 INNER JOIN table3 USING(value). Its result is:

 value  | num | id
--------+-----+----
 value1 |   1 |  1

After that, PostgreSQL executes CROSS JOIN on the table1 and the INNER JOIN resulted table.

The result:

 num | name | value  | num | id
-----+------+--------+-----+----
   1 | a    | value1 |   1 |  1
   2 | b    | value1 |   1 |  1
   3 | c    | value1 |   1 |  1
Found a mistake? Seleсt text and press Ctrl+Enter to report it