Sub-queries, first introduced to PostgreSQL in version 6.3, add a tremendous amount of flexibility to your SQL statements. Sub-queries are often referred to as sub-selects, as they allow a SELECT statement to be executed arbitrarily within the body of another SQL statement. A sub-query is executed by enclosing it in a set of parentheses. Sub-queries are generally used to return a single row as an atomic value, though they may be used to compare values against multiple rows with the IN keyword.
Sub-queries are allowed at nearly any meaningful point in a SQL statement, including the target list, the WHERE clause, and so on. A simple sub-query could be used as a search condition. For example, between a pair of tables. Example 4-60 demonstrates such a use of a sub-query.
Example 4-60. A Simple Sub-Query
booktown=# SELECT title FROM books booktown-# WHERE author_id = (SELECT id FROM authors booktown(# WHERE last_name='Geisel' booktown(# AND first_name='Theodor Seuss'); title ----------------------------- The Cat in the Hat Bartholomew and the Oobleck (2 rows)
Example 4-60 uses the equal-to operator to compare the one row result of a sub-query on the authors table with the author_id column in the books table. In a single statement, the author identification number is acquired from the authors table by a WHERE clause specifying the name of Theodor Seuss Geisel, and the single identifier field returned is compared against the author_id column of the books table to return any books by Dr. Seuss.
Note that caution should be taken with this sort of sub-query: to use a normal value operator on the results of a sub-query, only one field must be returned. For example, if a more general sub-query were used to check for an author identifier, and several rows were found, you might see an error such as the following:
booktown=# SELECT title FROM books booktown-# WHERE author_id = (SELECT id FROM authors booktown(# WHERE last_name ~ 'G'); ERROR: More than one tuple returned by a subselect used as an expression.
Normal comparison operators cannot check for a single value being equal to multiple values, so a check for equivalence between the author_id column and multiple rows causes an error. This could be solved with a LIMIT 1 clause to ensure that the sub-query never returns more than a single row.
If you are interested in checking for the existence of a single value within a set of other values, this can be achieved by using the IN keyword as an operator upon the result set from a sub-query. Example 4-61 illustrates comparing a sub-query which produces several results (the authors whose names begin with A through E, see the section called Operators in Chapter 5 for more on the regular expression being employed) to the author_id column via the IN keyword.
Example 4-61. A Sub-query Using IN
booktown=# SELECT title FROM books booktown-# WHERE author_id IN (SELECT id FROM authors booktown(# WHERE last_name ~ '^[A-E]'); title ----------------------- 2001: A Space Odyssey Franklin in the Dark Goodnight Moon Little Women The Velveteen Rabbit Perl Cookbook (6 rows)
As a result of the use of IN, books from several authors are able to be found in the books table through a comparison against several rows from a sub-query. Note that while the IN keyword allows you to compare against multiple rows, the number of columns to be matched against must be identical.
If you wish to use IN to compare several columns, you may group column names together in the WHERE clause with parentheses immediately preceding IN. The number of columns grouped must be the same as those in the target list of the sub-query, and of the same data type for comparison.
Example 4-62 demonstrates a sub-query which targets the isbn column of the editions table, and an integer constant of 0, for each paperback book (with a type value of p). Those rows are then returned and compared against the isbn column and the stock column of the stock table with the IN keyword, effectively selecting any paperback book which is out of stock.
Example 4-62. A Multi-Column Sub-query Using IN
booktown=# SELECT isbn, cost, retail FROM stock booktown-# WHERE (isbn, stock) booktown-# IN (SELECT isbn, 0 FROM editions booktown(# WHERE type = 'p'); isbn | cost | retail ------------+-------+-------- 0394800753 | 16.00 | 16.95 0394900014 | 23.00 | 23.95 0451457994 | 17.00 | 22.95 (3 rows)