Listing 8.17 uses two simple subqueries as column expressions to list each biography, its price, the average price of all books not just biographies, and the difference between the price
Trang 1Using Subqueries as
Column Expressions
In Chapters 4, 5, and 6, you learned that the
items in a SELECT-clause list can be literals,
column names, or more-complex expressions
SQL also lets you to embed a subquery in a
SELECT-clause list
A subquery that’s used as a column
expres-sion must be a scalar subquery Recall from
Table 8.1 in “Subquery Syntax” earlier in this
chapter that a scalar subquery returns a single
value (that is, a one-row, one-column result)
In most cases, you’ll have to use an aggregate
function or restrictive WHEREconditions in
the subquery to guarantee that the subquery
returns only one row
The syntax for the SELECT-clause list is the
same as you’ve been using all along, except
that you can specify a parenthesized
sub-query as one of the column expressions in
the list, as the following examples show
Listing 8.17 uses two simple subqueries as
column expressions to list each biography,
its price, the average price of all books (not
just biographies), and the difference between
the price of the biography and the average
price of all books The aggregate function
AVG()guarantees that each subquery returns
a single value See Figure 8.17 for the result.
Remember that AVG()ignores nulls when
computing an average; see “Calculating an
Average with AVG()” in Chapter 6
Listing 8.17 List each biography, its price, the average
price of all books, and the difference between the price
of the biography and the average price of all books See Figure 8.17 for the result.
SELECT title_id, price, ( SELECT AVG(price) FROM titles )
AS "AVG(price)", price - ( SELECT AVG(price) FROM titles )
AS "Difference"
FROM titles WHERE type='biography';
Listing
title_id price AVG(price) Difference - - -T06 19.95 18.3875 1.5625 T07 23.95 18.3875 5.5625 T10 NULL 18.3875 NULL T12 12.99 18.3875 -5.3975
Figure 8.17 Result of Listing 8.17.
Trang 2Listing 8.18 uses correlated subqueries to
list all the authors of each book in one row, as you’d view them in a report or spreadsheet
See Figure 8.18 for the result Note that in
eachWHEREclause, SQL qualifies title_id
implicitly with the table alias tareferenced in the subquery’s FROMclause; see “Qualifying Column Names in Subqueries” earlier in this chapter For a more efficient way to imple-ment this query, see the Tips in this section
See Listing 15.8 in Chapter 15 for the reverse
of this query
Listing 8.18 List all the authors of each book in one
row See Figure 8.18 for the result.
SELECT title_id,
( SELECT au_id
FROM title_authors ta
WHERE au_order = 1
AND title_id = t.title_id )
AS "Author 1",
( SELECT au_id
FROM title_authors ta
WHERE au_order = 2
AND title_id = t.title_id )
AS "Author 2",
( SELECT au_id
FROM title_authors ta
WHERE au_order = 3
AND title_id = t.title_id )
AS "Author 3"
FROM titles t;
Listing
title_id Author 1 Author 2 Author 3
-T01 A01 NULL NULL
T02 A01 NULL NULL
T03 A05 NULL NULL
T04 A03 A04 NULL
T05 A04 NULL NULL
T06 A02 NULL NULL
T07 A02 A04 NULL
T08 A06 NULL NULL
T09 A06 NULL NULL
T10 A02 NULL NULL
Trang 3In Listing 8.19, I revisit Listing 7.30 in
“Creating Outer Joins with OUTER JOIN” in
Chapter 7, but this time, I’m using a
corre-lated subquery instead of an outer join to
list the number of books that each author
wrote (or cowrote) See Figure 8.19 for
the result
Listing 8.20 uses a correlated subquery to
list each author and the latest date on which
he or she published a book You should qualify every column name explicitly in a subquery that contains a join to make it clear which table is referenced (even when qualifiers are
unnecessary) See Figure 8.20 for the result.
Listing 8.19 List the number of books that each
author wrote (or cowrote), including authors who
have written no books See Figure 8.19 for the result.
SELECT au_id,
( SELECT COUNT(*)
FROM title_authors ta
WHERE ta.au_id = a.au_id )
AS "Num books"
FROM authors a
ORDER BY au_id;
Listing
au_id Num books
-
-A01 3
A02 4
A03 2
A04 4
A05 1
A06 3
A07 0
Figure 8.19 Result of Listing 8.19 Listing 8.20 List each author and the latest date on which he or she published a book See Figure 8.20 for the result SELECT au_id, ( SELECT MAX(pubdate) FROM titles t INNER JOIN title_authors ta ON ta.title_id = t.title_id WHERE ta.au_id = a.au_id ) AS "Latest pub date" FROM authors a; Listing au_id Latest pub date - -A01 2000-08-01 A02 2000-08-31 A03 2000-11-30 A04 2001-01-01 A05 2000-09-01 A06 2002-05-31 A07 NULL
Figure 8.20 Result of Listing 8.20.
Trang 4Listing 8.21 uses a correlated subquery to
compute the running total of all book sales
A running total, or running sum, is a com-mon calculation: For each book, I want to compute the sum of all sales of the books that precede the book Here, I’m defining
precede to mean those books whose title_id
comes before the current book’s title_id
alphabetically Note the use of table aliases
to refer to the same table in two contexts
The subquery returns the sum of sales for all books preceding the current book, which is denoted by t1.title_id See Figure 8.21 for
the result See also “Calculating Running Statistics” in Chapter 9
✔ Tips
■ You also can use a subquery in a FROM
clause In the Tips in “Aggregating Distinct Values with DISTINCT” in Chapter 6, I used
aFROMsubquery to replicate a distinct
aggregate function Listing 8.22 uses a
FROMsubquery to calculate the greatest number of titles written (or cowritten)
by any author See Figure 8.22 for the
result Note that the outer query uses
a table alias (ta) and column label (count_titles) to reference the inner query’s result See also the “Column Aliases and WHERE” sidebar in “Filtering Rows with WHERE” in Chapter 4
■ You also can use a subquery as a column expression in UPDATE,INSERT, and DELETE
statements (see Chapter 10) but not in
anORDER BYlist
continues on next page
Listing 8.21 Compute the running sum of all book
sales See Figure 8.21 for the result.
SELECT t1.title_id, t1.sales,
( SELECT SUM(t2.sales)
FROM titles t2
WHERE t2.title_id <= t1.title_id )
AS “Running total”
FROM titles t1;
Listing
title_id sales Running total
- -
-T01 566 566
T02 9566 10132
T03 25667 35799
T04 13001 48800
T05 201440 250240
T06 11320 261560
T07 1500200 1761760
T08 4095 1765855
T09 5000 1770855
T10 NULL 1770855
T11 94123 1864978
T12 100001 1964979
T13 10467 1975446
Figure 8.21 Result of Listing 8.21.
Listing 8.22 Calculate the greatest number of titles
written (or cowritten) by any author See Figure 8.22
for the result.
SELECT MAX(ta.count_titles) AS “Max titles”
FROM ( SELECT COUNT(*) AS count_titles
FROM title_authors
Listing
Trang 5■ Use CASEexpressions instead of correlated subqueries to implement Listing 8.18 more efficiently (see “Evaluating Conditional Values with CASE” in Chapter 5):
SELECT title_id,
MIN(CASE au_order WHEN 1
THEN au_id END)
AS “Author 1”, MIN(CASE au_order WHEN 2
THEN au_id END)
AS “Author 2”, MIN(CASE au_order WHEN 3
THEN au_id END)
AS “Author 3”
FROM title_authors
GROUP BY title_id
ORDER BY title_id ASC;
support subqueries; see the DBMS Tip in “Understanding Subqueries” earlier in this chapter
In Microsoft Access, you must increase
the precision of the average-price calcula-tion in Listing 8.17 Use the type-conversion function CDbl()to coerce the average price to a double-precision floating-point number; see the DBMS Tip in “Converting Data Types with CAST()” in Chapter 5 To run Listing 8.17, change both occurrences
ofAVG(price)toCDbl(AVG(price))
Trang 6Comparing a Subquery
Value by Using a
Comparison Operator
You can use a subquery as a filter in a WHERE
clause or HAVINGclause by using one of the
comparison operators (=,<>,<,<=,>, or >=)
The important characteristics of a subquery comparison test are:
◆ The comparison operators work the
same way as they do in other
compar-isons (refer to Table 4.2 in Chapter 4)
◆ The subquery can be simple or correlated (see “Simple and Correlated Subqueries” earlier in this chapter)
◆ The subquery’s SELECT-clause list
can include only one expression or
column name
◆ The compared values must have the
same data type or must be implicitly
convertible to the same type (see
“Converting Data Types with CAST()”
in Chapter 5)
◆ String comparisons are case insensitive
or case sensitive, depending on your
DBMS; see the DBMS Tip in “Filtering
Rows with WHERE” in Chapter 4
◆ The subquery must return a single value (a one-row, one-column result) A
sub-query that returns more than one value
will cause an error
◆ If the subquery result contains zero
Trang 7The hard part of writing these statements is
getting the subquery to return one value,
which you can guarantee several ways:
◆ Using an aggregate function on an
ungrouped table always returns a single
value (see Chapter 6)
◆ Using a join with the outer query based
on a key always returns a single value
To compare a subquery value:
◆ In the WHEREclause of a SELECT
state-ment, type:
WHERE test_expr op (subquery)
test_expr is a literal value, a column name,
an expression, or a subquery that returns
a single value; op is a comparison operator
(=,<>,<,<=,>, or >=); and subquery is a
scalar subquery that returns exactly one
column and zero or one rows
If the value returned by subquery satisfies
the comparison to test_expr, the
compar-ison condition evaluates to true The
comparison condition is false if the
sub-query value doesn’t satisfy the condition,
the subquery value is null, or the subquery
result is empty (has zero rows)
The same syntax applies to a HAVINGclause:
HAVING test_expr op (subquery)
Listing 8.23 tests the result of a simple
subquery for equality to list the authors
who live in the state in which Tenterhooks
Press is located Only one publisher is
named Tenterhooks Press, so the inner
WHEREcondition guarantees that the inner
query returns a single-valued result See
Figure 8.23 for the result.
Listing 8.23 List the authors who live in the state in
which the publisher Tenterhooks Press is located See Figure 8.23 for the result.
SELECT au_id, au_fname, au_lname, state FROM authors
WHERE state =
(SELECT state FROM publishers WHERE pub_name = 'Tenterhooks Press');
Listing
au_id au_fname au_lname state - - -A03 Hallie Hull CA A04 Klee Hull CA A06 Kellsey CA
Figure 8.23 Result of Listing 8.23.
Listing 8.24 List the authors who live in the state in
which the publisher XXX is located See Figure 8.24 for the result.
SELECT au_id, au_fname, au_lname, state FROM authors
WHERE state =
(SELECT state FROM publishers WHERE pub_name = ' XXX ');
Listing
au_id au_fname au_lname state - -
-Figure 8.24 Result of Listing 8.24 (an empty table).
Trang 8Listing 8.24 is the same as Listing 8.23
except for the name of the publisher No publisher named XXX exists, so the sub-query returns an empty table (zero rows)
The comparison evaluates to null, so the
final result is empty See Figure 8.24 for
the result
Listing 8.25 lists the books with
above-average sales Subqueries introduced with comparison operators often use aggregate functions to return a single value See
Figure 8.25 for the result.
To list the authors of the books with above-average sales, I’ve added an inner join to
Listing 8.25 (Listing 8.26 and Figure 8.26).
Listing 8.25 List the books with above-average sales.
See Figure 8.25 for the result.
SELECT title_id, sales
FROM titles
WHERE sales >
(SELECT AVG(sales)
FROM titles);
Listing
title_id sales
-
-T05 201440
T07 1500200
Figure 8.25 Result of Listing 8.25.
Listing 8.26 List the authors of the books with
above-average sales by using a join and a subquery See
Figure 8.26 for the result.
SELECT ta.au_id, ta.title_id
FROM titles t
INNER JOIN title_authors ta
ON ta.title_id = t.title_id
WHERE sales >
(SELECT AVG(sales)
FROM titles)
ORDER BY ta.au_id ASC, ta.title_id ASC;
Listing
au_id title_id
-
-A02 T07
Trang 9Recall from the introduction to this chapter
that you can use a subquery almost
any-where an expression is allowed, so this
syntax is valid:
WHERE (subquery) op (subquery)
The left subquery must return a single value
Listing 8.27 is equivalent to Listing 8.26,
but I’ve removed the inner join and instead
placed a correlated subquery to the left of
the comparison operator See Figure 8.27
for the result
You can include GROUP BYorHAVINGclauses
in a subquery if you know that the GROUP BY
orHAVINGclause itself returns a single value
Listing 8.28 lists the books priced higher
than the highest-priced biography See
Figure 8.28 for the result.
Listing 8.27 List the authors of the books with
above-average sales by using two subqueries See Figure 8.27 for the result.
SELECT au_id, title_id FROM title_authors ta WHERE
(SELECT AVG(sales) FROM titles t WHERE ta.title_id = t.title_id)
>
(SELECT AVG(sales) FROM titles) ORDER BY au_id ASC, title_id ASC;
Listing
au_id title_id - -A02 T07 A04 T05 A04 T07
Figure 8.27 Result of Listing 8.27.
Listing 8.28 List the books priced higher than the
highest-priced biography See Figure 8.28 for the result.
SELECT title_id, price FROM titles
WHERE price >
(SELECT MAX(price) FROM titles GROUP BY type HAVING type = 'biography');
Listing
title_id price - -T03 39.95
Trang 10Listing 8.29 uses a subquery in a HAVING
clause to list the publishers whose average sales exceed overall average sales Again, the subquery returns a single value (the average
of all sales) See Figure 8.29 for the result.
Listing 8.30 uses a correlated subquery to
list authors whose royalty share is less than the highest royalty share of any coauthor
of a book The outer query selects the rows of
title_authors(that is, of ta1) one by one
The subquery calculates the highest royalty share for each book being considered for selection in the outer query For each possible value of ta1, the DBMS evaluates the sub-query and puts the row being considered in the result if the royalty share is less than the
calculated maximum See Figure 8.30 for
the result
Listing 8.29 List the publishers whose average sales
exceed the overall average sales See Figure 8.29 for
the result.
SELECT pub_id, AVG(sales) AS "AVG(sales)"
FROM titles
GROUP BY pub_id
HAVING AVG(sales) >
(SELECT AVG(sales)
FROM titles);
Listing
pub_id AVG(sales)
-
-P03 506744.33
Figure 8.29 Result of Listing 8.29.
Listing 8.30 List authors whose royalty share is less
than the highest royalty share of any coauthor of a
book See Figure 8.30 for the result.
SELECT ta1.au_id, ta1.title_id,
ta1.royalty_share
FROM title_authors ta1
WHERE ta1.royalty_share <
(SELECT MAX(ta2.royalty_share)
FROM title_authors ta2
WHERE ta1.title_id = ta2.title_id);
Listing
au_id title_id royalty_share
- -
-A04 T04 0.40