Subqueries

Overview

A PostgreSQL subquery is a SQL query nested inside a parent query. A subquery can be nested inside a SELECT, INSERT, UPDATE, DELETE, SET, or DO statement or inside another subquery. A subquery is usually added within the WHERE clause. In the WHERE clause, you can use single-row comparison operators (>, <, =, and others) or multiple-row expressions (IN, ANY, ALL, and others). A subquery must be enclosed in parentheses.

For example, we have the following table books:

 id |                title                | author_id | public_year |  genre  | evaluation | price | total_sales
----+-------------------------------------+-----------+-------------+---------+------------+-------+-------------
  1 | Mrs. Dalloway                       |         1 |        1925 | novel   |       7.82 |   360 |     6212880
  2 | To the Lighthouse                   |         1 |        1927 | novel   |       8.45 |   440 |     7216000
  3 | To Kill a Mockingbird               |         2 |        1960 | novel   |       7.48 |   750 |    11574000
  4 | The Great Gatsby                    |         3 |        1925 | novel   |       9.23 |   900 |    11110500
  5 | The Lord of the Rings               |         4 |        1955 | fantasy |       9.49 |  1200 |     5472000
  6 | 1984                                |         5 |        1949 | sci-fi  |       8.17 |   520 |     9642880
  7 | The Hobbit, or There and Back Again |         4 |        1937 | fantasy |       9.32 |  1100 |    19679000
  8 | War and Peace                       |         6 |        1869 | novel   |       9.69 |  1500 |    32548500
  9 | Hyperion                            |         7 |        1989 | sci-fi  |       9.46 |   610 |     8411290
 10 | The Time Machine                    |         8 |        1895 | sci-fi  |       8.12 |   450 |     6444450

The query below displays all books of the same genre as a genre of the book with id = 1. A subquery SELECT genre FROM books WHERE id = 1 determines the genre.

SELECT id, title, genre
FROM books
WHERE genre = (SELECT genre FROM books WHERE id = 1);

The result:

 id |         title         | genre
----+-----------------------+-------
  1 | Mrs. Dalloway         | novel
  2 | To the Lighthouse     | novel
  3 | To Kill a Mockingbird | novel
  4 | The Great Gatsby      | novel
  8 | War and Peace         | novel

A subquery is executed before its parent query, and the subquery result is passed to the parent query. In this example, the subquery returns novel, and the parent query returns books of the novel genre. The subquery from the example is a scalar one.

Scalar subquery

A scalar subquery is a subquery that selects only one column or expression and returns one row, a single scalar value.

In the example below, the subquery SELECT MAX(total) FROM orders WHERE customer_id = 17 selects the maximum value of the total field for the customer with id = 17:

INSERT INTO order_statistic (customer_id, max_sum) VALUES (
   17,
   (SELECT MAX(total) FROM orders WHERE customer_id = 17)
)
RETURNING *;

The result:

 customer_id | max_sum
-------------+---------
          17 | 5000.00

Subquery with comparison operators

You can use subquery before or after comparison operators. This type of subqueries can return at most one value. The value can be the result of an arithmetic expression or a function. PostgreSQL compares the subquery result value with the value on the other side of the comparison operator. You can use the following comparison operators:

Operator Description

=

Equal to

>

Greater than

>=

Greater than or equal to

<

Less than

<=

Less than or equal to

!=

Not equal to

<>

Not equal to

<=>

Null-safe equal to. This operator performs an equality comparison, but returns 1 rather than null if both operands are null, and 0 rather than null if one operand is null

The following query displays books with the price higher than the average price:

SELECT id, title, price
FROM books
WHERE  price >
(SELECT AVG(price) FROM books);

The result:

 id |                title                | price
----+-------------------------------------+-------
  4 | The Great Gatsby                    |   900
  5 | The Lord of the Rings               |  1200
  7 | The Hobbit, or There and Back Again |  1100
  8 | War and Peace                       |  1500

Subquery with EXISTS

The EXISTS expression syntax:

EXISTS (<subquery>)

The EXISTS argument is a subquery or a SELECT statement. The subquery is executed to determine whether it returns any rows. If it returns at least one row, the result of EXISTS is true. If subquery returns no rows, the result of EXISTS is false.

The subquery can refer to variables from the parent query.

The subquery is executed until at least one row is returned. Since the result depends on whether any rows are returned, the output list is not significant. A common coding convention is to write all EXISTS tests in the form EXISTS (SELECT 1 WHERE …​).

For example, we have the table authors in addition to table books mentioned above:

 id |        name         |    country
----+---------------------+---------------
  2 | Harper Lee          | USA
  3 | F. Scott Fitzgerald | USA
  4 | J.R.R. Tolkien      | Great Britain
  5 | George Orwell       | Great Britain
  6 | Leo Tolstoy         | Russia
  7 | Dan Simmons         | USA
  8 | Herbert Wells       | Great Britain

Execute the following query:

SELECT id, title, author_id
FROM books b
WHERE EXISTS (SELECT 1 FROM authors WHERE id = b.author_id);

The result contains books whose authors are in the authors table:

 id |                title                | author_id
----+-------------------------------------+-----------
  4 | The Great Gatsby                    |         3
  6 | 1984                                |         5
  7 | The Hobbit, or There and Back Again |         4
  5 | The Lord of the Rings               |         4
  8 | War and Peace                       |         6
  3 | To Kill a Mockingbird               |         2
  9 | Hyperion                            |         7
 10 | The Time Machine                    |         8

Subquery with IN

The IN expression syntax:

<in_expression> IN (<in_subquery>)

in_subquery must return one column. The in_expression is evaluated and compared against each row of the subquery result. The IN result is true if any equal subquery row is found. The result is false if no equal row is found, or in_subquery returns no rows. The following query selects books of American authors:

SELECT id, title FROM books
WHERE author_id IN
    (SELECT id FROM authors WHERE country='USA');

The result:

 id |         title
----+-----------------------
  3 | To Kill a Mockingbird
  4 | The Great Gatsby
  9 | Hyperion

Note, if in_expression returns null or if there are no equal subquery values and at least one subquery row returns null, the IN result is null, not false.

The left part of the IN expression can be a row constructor:

<row_constructor> IN (<in_subquery>)

in_subquery must return the same number of columns as the row_constructor expression contains.

Example:

SELECT ROW( books.id, books.title) FROM books
WHERE author_id IN
  (SELECT id FROM authors WHERE country='USA');

The result:

             row
-----------------------------
 (3,"To Kill a Mockingbird")
 (4,"The Great Gatsby")
 (9,Hyperion)

IN is equivalent to = ANY.

Subquery with NOT IN

The NOT IN expression syntax:

<not_in_expression> NOT IN (<not_in_subquery>)

not_in_subquery must return one column. The not_in_expression is evaluated and compared to each row of the subquery result. The NOT IN result is true if only unique subquery rows are found, or not_in_subquery returns no rows. The result is false if any equal row is found.

The following query selects books of non-American authors:

SELECT id, title FROM books
WHERE author_id NOT IN
  (SELECT id FROM authors WHERE country='USA');

The result:

 id |                title
----+-------------------------------------
  1 | Mrs. Dalloway
  2 | To the Lighthouse
  5 | The Lord of the Rings
  7 | The Hobbit, or There and Back Again
  8 | War and Peace
  6 | 1984
 10 | The Time Machine

NOT IN is equivalent to <> ALL.

The left part of the NOT IN expression can be a row constructor:

<row_constructor> NOT IN (<not_in_subquery>)

not_in_subquery must return the same number of columns as the row_constructor expression contains.

Example:

SELECT ROW( books.id, books.title) FROM books
WHERE author_id NOT IN
  (SELECT id FROM authors WHERE country='USA');

The result:

                    row
-------------------------------------------
 (1,"Mrs. Dalloway")
 (2,"To the Lighthouse")
 (5,"The Lord of the Rings")
 (7,"The Hobbit, or There and Back Again")
 (8,"War and Peace")
 (6,1984)
 (10,"The Time Machine")

Subquery with ANY/SOME

SOME is a synonym for ANY. The ANY/SOME expression syntax:

<expression> <operator> ANY (<subquery>)
<expression> <operator> SOME (<subquery>)

subquery must return one column. The expression is evaluated and compared to each row of the subquery result using operator. operator must return a boolean result. The result of ANY is true if any true result is obtained. The result is false if no true result is found, or subquery returns no rows.

= ANY is equivalent to IN.

Note, if operator does not return true and at least one subquery row returns null, the ANI result is null, not false.

Rewrite the example with IN as follows:

SELECT id, title FROM books
  WHERE author_id = ANY
  (SELECT id FROM authors
      WHERE country='USA');

The result is the same:

 id |         title
----+-----------------------
  3 | To Kill a Mockingbird
  4 | The Great Gatsby
  9 | Hyperion

The left part of the ANY/SOME expression can be a row constructor:

<row_constructor> <operator> ANY (<subquery>)
<row_constructor> <operator> SOME (<subquery>)

subquery must return the same number of columns as the row_constructor expression contains.

Example:

SELECT ROW( books.id, books.title) FROM books
WHERE author_id = ANY
  (SELECT id FROM authors WHERE country='USA');

The result:

             row
-----------------------------
 (3,"To Kill a Mockingbird")
 (4,"The Great Gatsby")
 (9,Hyperion)

Subquery with ALL

<all_expression> <operator> ALL (<all_subquery>)

all_subquery must return one column. The all_expression is evaluated and compared to each row of the subquery result using operator. operator must return a boolean result. The ALL result is true if all rows returns true or all_subquery returns no rows. The result is false if any false result is found. The result is null if no comparison with a subquery row returns false, and at least one comparison returns null.

<> ALL is equivalent to NOT IN.

Rewrite the example with NOT IN as follows:

SELECT id, title FROM books
WHERE author_id <> ALL
 (SELECT id FROM authors WHERE country='USA');

The result is the same:

 id |                title
----+-------------------------------------
  1 | Mrs. Dalloway
  2 | To the Lighthouse
  5 | The Lord of the Rings
  7 | The Hobbit, or There and Back Again
  8 | War and Peace
  6 | 1984
 10 | The Time Machine

The left part of the ALL expression can be a row constructor:

<row_constructor> <operator> ALL (<all_subquery>)

subquery must return the same number of columns as the row_constructor expression contains.

Example:

SELECT ROW( books.id, books.title) FROM books
WHERE author_id <> ALL
  (SELECT id FROM authors WHERE country='USA');

The result:

                    row
-------------------------------------------
 (1,"Mrs. Dalloway")
 (2,"To the Lighthouse")
 (5,"The Lord of the Rings")
 (7,"The Hobbit, or There and Back Again")
 (8,"War and Peace")
 (6,1984)
 (10,"The Time Machine")

Single-row comparison

The single-row comparison has the following syntax:

<row_constructor> <operator> (<subquery>)

subquery must return the same number of columns as the row_constructor expression contains. subquery cannot return more than one row. If it returns no rows, the result is null. You can use = , >, <, >=, <=, <>, !=, <=> comparison operators. The row_constructor is evaluated and compared to the single subquery result row using operator.

Example:

SELECT last_name FROM employees
  WHERE ROW(department_id, manager_id) =
    (SELECT department_id, manager_id FROM departments
      WHERE location  = "Moscow");

The result:

 last_name
------------
 Ivanov

Subquery in the FROM clause

An SQL-statement with subquery in the FROM clause has the following syntax:

SELECT <expression> FROM (<subquery>) AS <alias>

Every table in the FROM clause must have an alias. All columns in the subquery SELECT list must have unique names.

The following query shows books published after 1900 with a percentage of total sales:

SELECT book_id, book_title, percentage
FROM
  (SELECT id AS book_id, title AS book_title, public_year AS year, ROUND(total_sales/118311500*100,2) AS percentage FROM books) AS b
WHERE year > 1900;

The result:

 book_id |             book_title              | percentage
---------+-------------------------------------+------------
       1 | Mrs. Dalloway                       |       5.25
       2 | To the Lighthouse                   |       6.10
       3 | To Kill a Mockingbird               |       9.78
       4 | The Great Gatsby                    |       9.39
       5 | The Lord of the Rings               |       4.63
       6 | 1984                                |       8.15
       7 | The Hobbit, or There and Back Again |      16.63
       9 | Hyperion                            |       7.11
Found a mistake? Seleсt text and press Ctrl+Enter to report it