exists This chapter covers the WHERE
clause’s EXISTS
, ANY
/SOME
, and ALL
predicates. The examples in this chapter use these tables:
Films
id | title | year | duration | genre_id |
---|---|---|---|---|
1 | Die Hard | 1988 | 132 | 7 |
2 | Casablanca | 1942 | 102 | 5 |
3 | The Conversation | 1974 | 113 | 1 |
4 | 1984 | 1956 | 90 | 4 |
5 | The Godfather | 1972 | 175 | 2 |
6 | Tinker, Tailor, Soldier, Spy | 2011 | 127 | 6 |
7 | The Birdcage | 1996 | 118 | 3 |
Genres
id | name |
---|---|
1 | thriller |
2 | crime |
3 | comedy |
4 | scifi |
5 | drama |
6 | espionage |
7 | action |
Directors
id | name |
---|---|
1 | Mike Nichols |
2 | Michael Anderson |
3 | John McTiernan |
4 | Tomas Alfredson |
5 | Francis Ford Coppola |
6 | Michael Curtiz |
EXISTS
exists The EXISTS
predicate takes this form:
exists exists exists The WHERE EXISTS
clause evaluates to true unless the resultset of subquery is empty. (WHERE NOT EXISTS
reverses this.) As with IN
, EXISTS
allows a subquery to return multiple rows, but also allows it to return multiple columns, since it evaluates whether or not the subquery returns any rows rather than comparing an input and output value as IN
does.
exists This query uses EXISTS
to find all the directors who have directed a sci-fi film:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
SELECT name FROM directors d WHERE EXISTS ( SELECT * FROM films WHERE director_id = d.id AND genre_id = ( SELECT id FROM genres WHERE name = 'scifi' ) ); |
It would be more common to use SELECT id
than SELECT *
, but SELECT *
demonstrates that multiple columns can be returned.
exists exists Syntactically, the essential difference between IN
and EXISTS
is that EXISTS
moves the specification of the input expression being evaluated into the subquery (directors.id
in the case of these examples).
exists You can do anything with a JOIN
clause that you can do with an EXISTS
predicate. For example, this is equivalent to the previous query:
1 2 3 4 5 6 7 |
SELECT name FROM directors d JOIN films f on d.id = f.director_id JOIN genres g on g.id = f.genre_id WHERE g.name = 'scifi' |
exists exists Again, it’s worth benchmarking the different ways (IN
/NOT IN
, joins and EXISTS
/NOT EXISTS
) to get a resultset.
ANY
, SOME
and ALL
The ANY
, SOME
and ALL
predicates take this form:
Where:
- expression is analogous to expression in the
IN
predicate. - operator is a valid comparison operator:
=, <, >, <=, >=, <>/!=
. - query is the resultset returned by a valid query, with the restriction that it must have exactly one column.
The WHERE ANY
clause evaluates to true if the result of (expression operator resultset_value) is true for any of the values in the resultset of query.
SOME
is an alias for ANY
; they are equivalent.
The WHERE ALL
clause evaluates to true if the result of (expression operator resultset_value) is true for all of the values in the resultset of query.
Replacing ANY
, SOME
and ALL
with IN
, EXISTS
or a Scalar Query
The ANY
, SOME
and ALL
predicates are rarely used, perhaps because they don’t need to be. Any statement using one of these can also be written using either IN
, EXISTS
or a scalar query.
Here are some logical equivalencies:
Expression | Equivalent | Explanation |
---|---|---|
= ANY | IN | A value that is equal to some member of a set is also in that set. |
< ANY | < max | A value that is less than any one member of a set is also less than the maximum value in the set. |
> ANY | > min | A value that is greater than any one member of a set is also greater than the minimum value in the set. |
<> ALL | NOT IN | If all members of a set are not equal to a value, the value is not in the set. |
< ALL | < min | A value that is less than all members of a set is also less than the minimum value in the set. |
> ALL | > max | A value that is greater than all members of a set is also greater than the maximum value in the set. |
In any of the above equivalencies, <=
can be substituted for <
, or >=
can be substituted for >
, and they will still apply.
Here’s one example. Suppose we have two tables with these structures:
Column | Type |
---|---|
id | integer |
first_name | text |
last_name | text |
phone | character varying(10) |
text |
Column | Type |
---|---|
id | integer |
name | text |
starts_at | timestamp without time zone |
base_price | numeric(4,2) |
Now (for some contrived reason), I would like a list of all customers whose email is longer than the longest event name that has an s
in it. This query will do the job:
1 2 3 4 5 6 7 8 9 |
SELECT last_name, first_name FROM customers WHERE length(email) > ALL ( SELECT length(name) FROM events WHERE name LIKE '%s%' ); |
However, so will this one:
1 2 3 4 5 6 7 8 9 |
SELECT last_name, first_name FROM customers WHERE length(email) > ( SELECT max(length(name)) FROM events WHERE name LIKE '%s%' ); |
In practice, max
is much more common than ALL
.