Tải bản đầy đủ (.pdf) (34 trang)

Learning SQL Second Edition phần 3 potx

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (857.11 KB, 34 trang )

mysql> SELECT emp_id, fname, lname, start_date, title
-> FROM employee
-> WHERE title = 'Head Teller';
+ + + + + +
| emp_id | fname | lname | start_date | title |
+ + + + + +
| 6 | Helen | Fleming | 2008-03-17 | Head Teller |
| 10 | Paula | Roberts | 2006-07-27 | Head Teller |
| 13 | John | Blake | 2004-05-11 | Head Teller |
| 16 | Theresa | Markham | 2005-03-15 | Head Teller |
+ + + + + +
4 rows in set (1.17 sec)
In this case the where clause filtered out 14 of the 18 employee rows. This where clause
contains a single filter condition, but you can include as many conditions as required;
individual conditions are separated using operators such as and, or, and not (see Chap-
ter 4 for a complete discussion of the where clause and filter conditions). Here’s an
extension of the previous query that includes a second condition stating that only those
employees with a start date later than January 1, 2006 should be included:
mysql> SELECT emp_id, fname, lname, start_date, title
-> FROM employee
-> WHERE title = 'Head Teller'
-> AND start_date > '2006-01-01';
+ + + + + +
| emp_id | fname | lname | start_date | title |
+ + + + + +
| 6 | Helen | Fleming | 2008-03-17 | Head Teller |
| 10 | Paula | Roberts | 2006-07-27 | Head Teller |
+ + + + + +
2 rows in set (0.01 sec)
The first condition (title = 'Head Teller') filtered out 14 of 18 employee rows, and
the second condition (start_date > '2006-01-01') filtered out an additional 2 rows,


leaving 2 rows in the final result set. Let’s see what would happen if you change the
operator separating the two conditions from and to or:
mysql> SELECT emp_id, fname, lname, start_date, title
-> FROM employee
-> WHERE title = 'Head Teller'
-> OR start_date > '2006-01-01';
+ + + + + +
| emp_id | fname | lname | start_date | title |
+ + + + + +
| 2 | Susan | Barker | 2006-09-12 | Vice President |
| 4 | Susan | Hawthorne | 2006-04-24 | Operations Manager |
| 5 | John | Gooding | 2007-11-14 | Loan Manager |
| 6 | Helen | Fleming | 2008-03-17 | Head Teller |
| 7 | Chris | Tucker | 2008-09-15 | Teller |
| 8 | Sarah | Parker | 2006-12-02 | Teller |
| 9 | Jane | Grossman | 2006-05-03 | Teller |
| 10 | Paula | Roberts | 2006-07-27 | Head Teller |
| 12 | Samantha | Jameson | 2007-01-08 | Teller |
| 13 | John | Blake | 2004-05-11 | Head Teller |
The where Clause | 53
Download at WoweBook.Com
| 14 | Cindy | Mason | 2006-08-09 | Teller |
| 15 | Frank | Portman | 2007-04-01 | Teller |
| 16 | Theresa | Markham | 2005-03-15 | Head Teller |
| 17 | Beth | Fowler | 2006-06-29 | Teller |
| 18 | Rick | Tulman | 2006-12-12 | Teller |
+ + + + + +
15 rows in set (0.00 sec)
Looking at the output, you can see that all four head tellers are included in the result
set, along with any other employee who started working for the bank after January 1,

2006. At least one of the two conditions is true for 15 of the 18 employees in the
employee table. Thus, when you separate conditions using the and operator, all condi-
tions must evaluate to true to be included in the result set; when you use or, however,
only one of the conditions needs to evaluate to true for a row to be included.
So, what should you do if you need to use both and and or operators in your where
clause? Glad you asked. You should use parentheses to group conditions together. The
next query specifies that only those employees who are head tellers and began working
for the company after January 1, 2006 or those employees who are tellers and began
working after January 1, 2007 be included in the result set:
mysql> SELECT emp_id, fname, lname, start_date, title
-> FROM employee
-> WHERE (title = 'Head Teller' AND start_date > '2006-01-01')
-> OR (title = 'Teller' AND start_date > '2007-01-01');
+ + + + + +
| emp_id | fname | lname | start_date | title |
+ + + + + +
| 6 | Helen | Fleming | 2008-03-17 | Head Teller |
| 7 | Chris | Tucker | 2008-09-15 | Teller |
| 10 | Paula | Roberts | 2006-07-27 | Head Teller |
| 12 | Samantha | Jameson | 2007-01-08 | Teller |
| 15 | Frank | Portman | 2007-04-01 | Teller |
+ + + + + +
5 rows in set (0.00 sec)
You should always use parentheses to separate groups of conditions when mixing dif-
ferent operators so that you, the database server, and anyone who comes along later to
modify your code will be on the same page.
The group by and having Clauses
All the queries thus far have retrieved raw data without any manipulation. Sometimes,
however, you will want to find trends in your data that will require the database server
to cook the data a bit before you retrieve your result set. One such mechanism is the

group by clause, which is used to group data by column values. For example, rather
than looking at a list of employees and the departments to which they are assigned, you
might want to look at a list of departments along with the number of employees assigned
to each department. When using the group by clause, you may also use the having
54 | Chapter 3: Query Primer
Download at WoweBook.Com
clause, which allows you to filter group data in the same way the where clause lets you
filter raw data.
Here’s a quick look at a query that counts all the employees in each department and
returns the names of those departments having more than two employees:
mysql> SELECT d.name, count(e.emp_id) num_employees
-> FROM department d INNER JOIN employee e
-> ON d.dept_id = e.dept_id
-> GROUP BY d.name
-> HAVING count(e.emp_id) > 2;
+ + +
| name | num_employees |
+ + +
| Administration | 3 |
| Operations | 14 |
+ + +
2 rows in set (0.00 sec)
I wanted to briefly mention these two clauses so that they don’t catch you by surprise
later in the book, but they are a bit more advanced than the other four select clauses.
Therefore, I ask that you wait until Chapter 8 for a full description of how and when
to use group by and having.
The order by Clause
In general, the rows in a result set returned from a query are not in any particular order.
If you want your result set in a particular order, you will need to instruct the server to
sort the results using the order by clause:

The order by clause is the mechanism for sorting your result set using either raw column
data or expressions based on column data.
For example, here’s another look at an earlier query against the account table:
mysql> SELECT open_emp_id, product_cd
-> FROM account;
+ + +
| open_emp_id | product_cd |
+ + +
| 10 | CHK |
| 10 | SAV |
| 10 | CD |
| 10 | CHK |
| 10 | SAV |
| 13 | CHK |
| 13 | MM |
| 1 | CHK |
| 1 | SAV |
| 1 | MM |
| 16 | CHK |
| 1 | CHK |
| 1 | CD |
The order by Clause | 55
Download at WoweBook.Com
| 10 | CD |
| 16 | CHK |
| 16 | SAV |
| 1 | CHK |
| 1 | MM |
| 1 | CD |
| 16 | CHK |

| 16 | BUS |
| 10 | BUS |
| 16 | CHK |
| 13 | SBL |
+ + +
24 rows in set (0.00 sec)
If you are trying to analyze data for each employee, it would be helpful to sort the results
by the open_emp_id column; to do so, simply add this column to the order by clause:
mysql> SELECT open_emp_id, product_cd
-> FROM account
-> ORDER BY open_emp_id;
+ + +
| open_emp_id | product_cd |
+ + +
| 1 | CHK |
| 1 | SAV |
| 1 | MM |
| 1 | CHK |
| 1 | CD |
| 1 | CHK |
| 1 | MM |
| 1 | CD |
| 10 | CHK |
| 10 | SAV |
| 10 | CD |
| 10 | CHK |
| 10 | SAV |
| 10 | CD |
| 10 | BUS |
| 13 | CHK |

| 13 | MM |
| 13 | SBL |
| 16 | CHK |
| 16 | CHK |
| 16 | SAV |
| 16 | CHK |
| 16 | BUS |
| 16 | CHK |
+ + +
24 rows in set (0.00 sec)
It is now easier to see what types of accounts each employee opened. However, it might
be even better if you could ensure that the account types were shown in the same order
for each distinct employee; you can accomplish this by adding the product_cd column
after the open_emp_id column in the order by clause:
56 | Chapter 3: Query Primer
Download at WoweBook.Com
mysql> SELECT open_emp_id, product_cd
-> FROM account
-> ORDER BY open_emp_id, product_cd;
+ + +
| open_emp_id | product_cd |
+ + +
| 1 | CD |
| 1 | CD |
| 1 | CHK |
| 1 | CHK |
| 1 | CHK |
| 1 | MM |
| 1 | MM |
| 1 | SAV |

| 10 | BUS |
| 10 | CD |
| 10 | CD |
| 10 | CHK |
| 10 | CHK |
| 10 | SAV |
| 10 | SAV |
| 13 | CHK |
| 13 | MM |
| 13 | SBL |
| 16 | BUS |
| 16 | CHK |
| 16 | CHK |
| 16 | CHK |
| 16 | CHK |
| 16 | SAV |
+ + +
24 rows in set (0.00 sec)
The result set has now been sorted first by employee ID and then by account type. The
order in which columns appear in your order by clause does make a difference.
Ascending Versus Descending Sort Order
When sorting, you have the option of specifying ascending or descending order via the
asc and desc keywords. The default is ascending, so you will need to add the desc
keyword, only if you want to use a descending sort. For example, the following query
lists all accounts sorted by available balance with the highest balance listed at the top:
mysql> SELECT account_id, product_cd, open_date, avail_balance
-> FROM account
-> ORDER BY avail_balance DESC;
+ + + + +
| account_id | product_cd | open_date | avail_balance |

+ + + + +
| 29 | SBL | 2004-02-22 | 50000.00 |
| 28 | CHK | 2003-07-30 | 38552.05 |
| 24 | CHK | 2002-09-30 | 23575.12 |
| 15 | CD | 2004-12-28 | 10000.00 |
| 27 | BUS | 2004-03-22 | 9345.55 |
The order by Clause | 57
Download at WoweBook.Com
| 22 | MM | 2004-10-28 | 9345.55 |
| 12 | MM | 2004-09-30 | 5487.09 |
| 17 | CD | 2004-01-12 | 5000.00 |
| 18 | CHK | 2001-05-23 | 3487.19 |
| 3 | CD | 2004-06-30 | 3000.00 |
| 4 | CHK | 2001-03-12 | 2258.02 |
| 13 | CHK | 2004-01-27 | 2237.97 |
| 8 | MM | 2002-12-15 | 2212.50 |
| 23 | CD | 2004-06-30 | 1500.00 |
| 1 | CHK | 2000-01-15 | 1057.75 |
| 7 | CHK | 2002-11-23 | 1057.75 |
| 11 | SAV | 2000-01-15 | 767.77 |
| 10 | CHK | 2003-09-12 | 534.12 |
| 2 | SAV | 2000-01-15 | 500.00 |
| 19 | SAV | 2001-05-23 | 387.99 |
| 5 | SAV | 2001-03-12 | 200.00 |
| 21 | CHK | 2003-07-30 | 125.67 |
| 14 | CHK | 2002-08-24 | 122.37 |
| 25 | BUS | 2002-10-01 | 0.00 |
+ + + + +
24 rows in set (0.05 sec)
Descending sorts are commonly used for ranking queries, such as “show me the top

five account balances.” MySQL includes a limit clause that allows you to sort your
data and then discard all but the first X rows; see Appendix B for a discussion of the
limit clause, along with other non-ANSI extensions.
Sorting via Expressions
Sorting your results using column data is all well and good, but sometimes you might
need to sort by something that is not stored in the database, and possibly doesn’t appear
anywhere in your query. You can add an expression to your order by clause to handle
such situations. For example, perhaps you would like to sort your customer data by
the last three digits of the customer’s federal ID number (which is either a Social Security
number for individuals or a corporate ID for businesses):
mysql> SELECT cust_id, cust_type_cd, city, state, fed_id
-> FROM customer
-> ORDER BY RIGHT(fed_id, 3);
+ + + + + +
| cust_id | cust_type_cd | city | state | fed_id |
+ + + + + +
| 1 | I | Lynnfield | MA | 111-11-1111 |
| 10 | B | Salem | NH | 04-1111111 |
| 2 | I | Woburn | MA | 222-22-2222 |
| 11 | B | Wilmington | MA | 04-2222222 |
| 3 | I | Quincy | MA | 333-33-3333 |
| 12 | B | Salem | NH | 04-3333333 |
| 13 | B | Quincy | MA | 04-4444444 |
| 4 | I | Waltham | MA | 444-44-4444 |
| 5 | I | Salem | NH | 555-55-5555 |
| 6 | I | Waltham | MA | 666-66-6666 |
| 7 | I | Wilmington | MA | 777-77-7777 |
58 | Chapter 3: Query Primer
Download at WoweBook.Com
| 8 | I | Salem | NH | 888-88-8888 |

| 9 | I | Newton | MA | 999-99-9999 |
+ + + + + +
13 rows in set (0.24 sec)
This query uses the built-in function right() to extract the last three characters of the
fed_id column and then sorts the rows based on this value.
Sorting via Numeric Placeholders
If you are sorting using the columns in your select clause, you can opt to reference the
columns by their position in the select clause rather than by name. For example, if you
want to sort using the second and fifth columns returned by a query, you could do the
following:
mysql> SELECT emp_id, title, start_date, fname, lname
-> FROM employee
-> ORDER BY 2, 5;
+ + + + + +
| emp_id | title | start_date | fname | lname |
+ + + + + +
| 13 | Head Teller | 2004-05-11 | John | Blake |
| 6 | Head Teller | 2008-03-17 | Helen | Fleming |
| 16 | Head Teller | 2005-03-15 | Theresa | Markham |
| 10 | Head Teller | 2006-07-27 | Paula | Roberts |
| 5 | Loan Manager | 2007-11-14 | John | Gooding |
| 4 | Operations Manager | 2006-04-24 | Susan | Hawthorne |
| 1 | President | 2005-06-22 | Michael | Smith |
| 17 | Teller | 2006-06-29 | Beth | Fowler |
| 9 | Teller | 2006-05-03 | Jane | Grossman |
| 12 | Teller | 2007-01-08 | Samantha | Jameson |
| 14 | Teller | 2006-08-09 | Cindy | Mason |
| 8 | Teller | 2006-12-02 | Sarah | Parker |
| 15 | Teller | 2007-04-01 | Frank | Portman |
| 7 | Teller | 2008-09-15 | Chris | Tucker |

| 18 | Teller | 2006-12-12 | Rick | Tulman |
| 11 | Teller | 2004-10-23 | Thomas | Ziegler |
| 3 | Treasurer | 2005-02-09 | Robert | Tyler |
| 2 | Vice President | 2006-09-12 | Susan | Barker |
+ + + + + +
18 rows in set (0.00 sec)
You might want to use this feature sparingly, since adding a column to the select clause
without changing the numbers in the order by clause can lead to unexpected results.
Personally, I may reference columns positionally when writing ad hoc queries, but I
always reference columns by name when writing code.
The order by Clause | 59
Download at WoweBook.Com
Test Your Knowledge
The following exercises are designed to strengthen your understanding of the select
statement and its various clauses. Please see Appendix C for solutions.
Exercise 3-1
Retrieve the employee ID, first name, and last name for all bank employees. Sort by last
name and then by first name.
Exercise 3-2
Retrieve the account ID, customer ID, and available balance for all accounts whose
status equals 'ACTIVE' and whose available balance is greater than $2,500.
Exercise 3-3
Write a query against the account table that returns the IDs of the employees who
opened the accounts (use the account.open_emp_id column). Include a single row for
each distinct employee.
Exercise 3-4
Fill in the blanks (denoted by <#>) for this multi-data-set query to achieve the results
shown here:
mysql> SELECT p.product_cd, a.cust_id, a.avail_balance
-> FROM product p INNER JOIN account <1>

-> ON p.product_cd = <2>
-> WHERE p.<3> = 'ACCOUNT'
-> ORDER BY <4>, <5>;
+ + + +
| product_cd | cust_id | avail_balance |
+ + + +
| CD | 1 | 3000.00 |
| CD | 6 | 10000.00 |
| CD | 7 | 5000.00 |
| CD | 9 | 1500.00 |
| CHK | 1 | 1057.75 |
| CHK | 2 | 2258.02 |
| CHK | 3 | 1057.75 |
| CHK | 4 | 534.12 |
| CHK | 5 | 2237.97 |
| CHK | 6 | 122.37 |
| CHK | 8 | 3487.19 |
| CHK | 9 | 125.67 |
| CHK | 10 | 23575.12 |
| CHK | 12 | 38552.05 |
| MM | 3 | 2212.50 |
60 | Chapter 3: Query Primer
Download at WoweBook.Com
| MM | 4 | 5487.09 |
| MM | 9 | 9345.55 |
| SAV | 1 | 500.00 |
| SAV | 2 | 200.00 |
| SAV | 4 | 767.77 |
| SAV | 8 | 387.99 |
+ + + +

21 rows in set (0.09 sec)
Test Your Knowledge | 61
Download at WoweBook.Com
Download at WoweBook.Com
CHAPTER 4
Filtering
Sometimes you will want to work with every row in a table, such as:
• Purging all data from a table used to stage new data warehouse feeds
• Modifying all rows in a table after a new column has been added
• Retrieving all rows from a message queue table
In cases like these, your SQL statements won’t need to have a where clause, since you
don’t need to exclude any rows from consideration. Most of the time, however, you
will want to narrow your focus to a subset of a table’s rows. Therefore, all the SQL data
statements (except the insert statement) include an optional where clause to house
filter conditions used to restrict the number of rows acted on by the SQL statement.
Additionally, the select statement includes a having clause in which filter conditions
pertaining to grouped data may be included. This chapter explores the various types
of filter conditions that you can employ in the where clauses of select, update, and
delete statements; we explore the use of filter conditions in the having clause of a
select statement in Chapter 8.
Condition Evaluation
A where clause may contain one or more conditions, separated by the operators and and
or. If multiple conditions are separated only by the and operator, then all the conditions
must evaluate to true for the row to be included in the result set. Consider the following
where clause:
WHERE title = 'Teller' AND start_date < '2007-01-01'
Given these two conditions, only tellers who began working for the bank prior to 2007
will be included (or, to look at it another way, any employee who is either not a teller
or began working for the bank in 2007 or later will be removed from consideration).
Although this example uses only two conditions, no matter how many conditions are

in your where clause, if they are separated by the and operator they must all evaluate to
true for the row to be included in the result set.
63
Download at WoweBook.Com
If all conditions in the where clause are separated by the or operator, however, only
one of the conditions must evaluate to true for the row to be included in the result set.
Consider the following two conditions:
WHERE title = 'Teller' OR start_date < '2007-01-01'
There are now various ways for a given employee row to be included in the result set:
• The employee is a teller and was employed prior to 2007.
• The employee is a teller and was employed after January 1, 2007.
• The employee is something other than a teller but was employed prior to 2007.
Table 4-1 shows the possible outcomes for a where clause containing two conditions
separated by the or operator.
Table 4-1. Two-condition evaluation using or
Intermediate result Final result
WHERE true OR true True
WHERE true OR false True
WHERE false OR true True
WHERE false OR false False
In the case of the preceding example, the only way for a row to be excluded from the
result set is if the employee is not a teller and was employed on or after January 1, 2007.
Using Parentheses
If your where clause includes three or more conditions using both the and and or oper-
ators, you should use parentheses to make your intent clear, both to the database server
and to anyone else reading your code. Here’s a where clause that extends the previous
example by checking to make sure that the employee is still employed by the bank:
WHERE end_date IS NULL
AND (title = 'Teller' OR start_date < '2007-01-01')
There are now three conditions; for a row to make it to the final result set, the first

condition must evaluate to true, and either the second or third condition (or both)
must evaluate to true. Table 4-2 shows the possible outcomes for this where clause.
Table 4-2. Three-condition evaluation using and, or
Intermediate result Final result
WHERE true AND (true OR true) True
WHERE true AND (true OR false) True
WHERE true AND (false OR true) True
WHERE true AND (false OR false) False
64 | Chapter 4: Filtering
Download at WoweBook.Com
Intermediate result Final result
WHERE false AND (true OR true) False
WHERE false AND (true OR false) False
WHERE false AND (false OR true) False
WHERE false AND (false OR false) False
As you can see, the more conditions you have in your where clause, the more combi-
nations there are for the server to evaluate. In this case, only three of the eight combi-
nations yield a final result of true.
Using the not Operator
Hopefully, the previous three-condition example is fairly easy to understand. Consider
the following modification, however:
WHERE end_date IS NULL
AND NOT (title = 'Teller' OR start_date < '2007-01-01')
Did you spot the change from the previous example? I added the not operator after the
and operator on the second line. Now, instead of looking for nonterminated employees
who either are tellers or began working for the bank prior to 2007, I am looking for
nonterminated employees who both are nontellers and began working for the bank in
2007 or later. Table 4-3 shows the possible outcomes for this example.
Table 4-3. Three-condition evaluation using and, or, and not
Intermediate result Final result

WHERE true AND NOT (true OR true) False
WHERE true AND NOT (true OR false) False
WHERE true AND NOT (false OR true) False
WHERE true AND NOT (false OR false) True
WHERE false AND NOT (true OR true) False
WHERE false AND NOT (true OR false) False
WHERE false AND NOT (false OR true) False
WHERE false AND NOT (false OR false) False
While it is easy for the database server to handle, it is typically difficult for a person to
evaluate a where clause that includes the not operator, which is why you won’t en-
counter it very often. In this case, you can rewrite the where clause to avoid using the
not operator:
WHERE end_date IS NULL
AND title != 'Teller' AND start_date >= '2007-01-01'
Condition Evaluation | 65
Download at WoweBook.Com
While I’m sure that the server doesn’t have a preference, you probably have an easier
time understanding this version of the where clause.
Building a Condition
Now that you have seen how the server evaluates multiple conditions, let’s take a step
back and look at what comprises a single condition. A condition is made up of one or
more expressions coupled with one or more operators. An expression can be any of the
following:
• A number
• A column in a table or view
• A string literal, such as 'Teller'
• A built-in function, such as concat('Learning', ' ', 'SQL')
• A subquery
• A list of expressions, such as ('Teller', 'Head Teller', 'Operations Manager')
The operators used within conditions include:

• Comparison operators, such as =, !=, <, >, <>, LIKE, IN, and BETWEEN
• Arithmetic operators, such as +, −, *, and /
The following section demonstrates how you can combine these expressions and op-
erators to manufacture the various types of conditions.
Condition Types
There are many different ways to filter out unwanted data. You can look for specific
values, sets of values, or ranges of values to include or exclude, or you can use various
pattern-searching techniques to look for partial matches when dealing with string data.
The next four subsections explore each of these condition types in detail.
Equality Conditions
A large percentage of the filter conditions that you write or come across will be of the
form 'column = expression' as in:
title = 'Teller'
fed_id = '111-11-1111'
amount = 375.25
dept_id = (SELECT dept_id FROM department WHERE name = 'Loans')
Conditions such as these are called equality conditions because they equate one ex-
pression to another. The first three examples equate a column to a literal (two strings
and a number), and the fourth example equates a column to the value returned from
66 | Chapter 4: Filtering
Download at WoweBook.Com
a subquery. The following query uses two equality conditions; one in the on clause (a
join condition), and the other in the where clause (a filter condition):
mysql> SELECT pt.name product_type, p.name product
-> FROM product p INNER JOIN product_type pt
-> ON p.product_type_cd = pt.product_type_cd
-> WHERE pt.name = 'Customer Accounts';
+ + +
| product_type | product |
+ + +

| Customer Accounts | certificate of deposit |
| Customer Accounts | checking account |
| Customer Accounts | money market account |
| Customer Accounts | savings account |
+ + +
4 rows in set (0.08 sec)
This query shows all products that are customer account types.
Inequality conditions
Another fairly common type of condition is the inequality condition, which asserts that
two expressions are not equal. Here’s the previous query with the filter condition in the
where clause changed to an inequality condition:
mysql> SELECT pt.name product_type, p.name product
-> FROM product p INNER JOIN product_type pt
-> ON p.product_type_cd = pt.product_type_cd
-> WHERE pt.name <> 'Customer Accounts';
+ + +
| product_type | product |
+ + +
| Individual and Business Loans | auto loan |
| Individual and Business Loans | business line of credit |
| Individual and Business Loans | home mortgage |
| Individual and Business Loans | small business loan |
+ + +
4 rows in set (0.00 sec)
This query shows all products that are not customer account types. When building
inequality conditions, you may choose to use either the != or <> operator.
Data modification using equality conditions
Equality/inequality conditions are commonly used when modifying data. For example,
let’s say that the bank has a policy of removing old account rows once per year. Your
task is to remove rows from the account table that were closed in 2002. Here’s one way

to tackle it:
DELETE FROM account
WHERE status = 'CLOSED' AND YEAR(close_date) = 2002;
This statement includes two equality conditions: one to find only closed accounts, and
another to check for those accounts closed in 2002.
Condition Types | 67
Download at WoweBook.Com
When crafting examples of delete and update statements, I try to write
each statement such that no rows are modified. That way, when you
execute the statements, your data will remain unchanged, and your
output from select statements will always match that shown in this
book.
Since MySQL sessions are in auto-commit mode by default (see Chap-
ter 12), you would not be able to roll back (undo) any changes made to
the example data if one of my statements modified the data. You may,
of course, do whatever you want with the example data, including wip-
ing it clean and rerunning the scripts I have provided, but I try to leave
it intact.
Range Conditions
Along with checking that an expression is equal to (or not equal to) another expression,
you can build conditions that check whether an expression falls within a certain range.
This type of condition is common when working with numeric or temporal data. Con-
sider the following query:
mysql> SELECT emp_id, fname, lname, start_date
-> FROM employee
-> WHERE start_date < '2007-01-01';
+ + + + +
| emp_id | fname | lname | start_date |
+ + + + +
| 1 | Michael | Smith | 2005-06-22 |

| 2 | Susan | Barker | 2006-09-12 |
| 3 | Robert | Tyler | 2005-02-09 |
| 4 | Susan | Hawthorne | 2006-04-24 |
| 8 | Sarah | Parker | 2006-12-02 |
| 9 | Jane | Grossman | 2006-05-03 |
| 10 | Paula | Roberts | 2006-07-27 |
| 11 | Thomas | Ziegler | 2004-10-23 |
| 13 | John | Blake | 2004-05-11 |
| 14 | Cindy | Mason | 2006-08-09 |
| 16 | Theresa | Markham | 2005-03-15 |
| 17 | Beth | Fowler | 2006-06-29 |
| 18 | Rick | Tulman | 2006-12-12 |
+ + + + +
13 rows in set (0.15 sec)
This query finds all employees hired prior to 2007. Along with specifying an upper limit
for the start date, you may also want to specify a lower range for the start date:
mysql> SELECT emp_id, fname, lname, start_date
-> FROM employee
-> WHERE start_date < '2007-01-01'
-> AND start_date >= '2005-01-01';
+ + + + +
| emp_id | fname | lname | start_date |
+ + + + +
68 | Chapter 4: Filtering
Download at WoweBook.Com
| 1 | Michael | Smith | 2005-06-22 |
| 2 | Susan | Barker | 2006-09-12 |
| 3 | Robert | Tyler | 2005-02-09 |
| 4 | Susan | Hawthorne | 2006-04-24 |
| 8 | Sarah | Parker | 2006-12-02 |

| 9 | Jane | Grossman | 2006-05-03 |
| 10 | Paula | Roberts | 2006-07-27 |
| 14 | Cindy | Mason | 2006-08-09 |
| 16 | Theresa | Markham | 2005-03-15 |
| 17 | Beth | Fowler | 2006-06-29 |
| 18 | Rick | Tulman | 2006-12-12 |
+ + + + +
11 rows in set (0.00 sec)
This version of the query retrieves all employees hired in 2005 or 2006.
The between operator
When you have both an upper and lower limit for your range, you may choose to use
a single condition that utilizes the between operator rather than using two separate
conditions, as in:
mysql> SELECT emp_id, fname, lname, start_date
-> FROM employee
-> WHERE start_date BETWEEN '2005-01-01' AND '2007-01-01';
+ + + + +
| emp_id | fname | lname | start_date |
+ + + + +
| 1 | Michael | Smith | 2005-06-22 |
| 2 | Susan | Barker | 2006-09-12 |
| 3 | Robert | Tyler | 2005-02-09 |
| 4 | Susan | Hawthorne | 2006-04-24 |
| 8 | Sarah | Parker | 2006-12-02 |
| 9 | Jane | Grossman | 2006-05-03 |
| 10 | Paula | Roberts | 2006-07-27 |
| 14 | Cindy | Mason | 2006-08-09 |
| 16 | Theresa | Markham | 2005-03-15 |
| 17 | Beth | Fowler | 2006-06-29 |
| 18 | Rick | Tulman | 2006-12-12 |

+ + + + +
11 rows in set (0.03 sec)
When using the between operator, there are a couple of things to keep in mind. You
should always specify the lower limit of the range first (after between) and the upper
limit of the range second (after and). Here’s what happens if you mistakenly specify the
upper limit first:
mysql> SELECT emp_id, fname, lname, start_date
-> FROM employee
-> WHERE start_date BETWEEN '2007-01-01' AND '2005-01-01';
Empty set (0.00 sec)
As you can see, no data is returned. This is because the server is, in effect, generating
two conditions from your single condition using the <= and >= operators, as in:
Condition Types | 69
Download at WoweBook.Com
mysql> SELECT emp_id, fname, lname, start_date
-> FROM employee
-> WHERE start_date >= '2007-01-01'
-> AND start_date <= '2005-01-01';
Empty set (0.00 sec)
Since it is impossible to have a date that is both greater than January 1, 2007 and less
than January 1, 2005, the query returns an empty set. This brings me to the second
pitfall when using between, which is to remember that your upper and lower limits are
inclusive, meaning that the values you provide are included in the range limits. In this
case, I want to specify 2005-01-01 as the lower end of the range and 2006-12-31 as the
upper end, rather than 2007-01-01. Even though there probably weren’t any employees
who started working for the bank on New Year’s Day 2007, it is best to specify exactly
what you want.
Along with dates, you can also build conditions to specify ranges of numbers. Numeric
ranges are fairly easy to grasp, as demonstrated by the following:
mysql> SELECT account_id, product_cd, cust_id, avail_balance

-> FROM account
-> WHERE avail_balance BETWEEN 3000 AND 5000;
+ + + + +
| account_id | product_cd | cust_id | avail_balance |
+ + + + +
| 3 | CD | 1 | 3000.00 |
| 17 | CD | 7 | 5000.00 |
| 18 | CHK | 8 | 3487.19 |
+ + + + +
3 rows in set (0.10 sec)
All accounts with between $3,000 and $5,000 of an available balance are returned.
Again, make sure that you specify the lower amount first.
String ranges
While ranges of dates and numbers are easy to understand, you can also build condi-
tions that search for ranges of strings, which are a bit harder to visualize. Say, for ex-
ample, you are searching for customers having a Social Security number that falls within
a certain range. The format for a Social Security number is “XXX-XX-XXXX,” where X
is a number from 0 to 9, and you want to find every customer whose Social Security
number lies between “500-00-0000” and “999-99-9999.” Here’s what the statement
would look like:
mysql> SELECT cust_id, fed_id
-> FROM customer
-> WHERE cust_type_cd = 'I'
-> AND fed_id BETWEEN '500-00-0000' AND '999-99-9999';
+ + +
| cust_id | fed_id |
+ + +
| 5 | 555-55-5555 |
| 6 | 666-66-6666 |
70 | Chapter 4: Filtering

Download at WoweBook.Com
| 7 | 777-77-7777 |
| 8 | 888-88-8888 |
| 9 | 999-99-9999 |
+ + +
5 rows in set (0.01 sec)
To work with string ranges, you need to know the order of the characters within your
character set (the order in which the characters within a character set are sorted is called
a collation).
Membership Conditions
In some cases, you will not be restricting an expression to a single value or range of
values, but rather to a finite set of values. For example, you might want to locate all
accounts whose product code is either 'CHK', 'SAV', 'CD', or 'MM':
mysql> SELECT account_id, product_cd, cust_id, avail_balance
-> FROM account
-> WHERE product_cd = 'CHK' OR product_cd = 'SAV'
-> OR product_cd = 'CD' OR product_cd = 'MM';
+ + + + +
| account_id | product_cd | cust_id | avail_balance |
+ + + + +
| 1 | CHK | 1 | 1057.75 |
| 2 | SAV | 1 | 500.00 |
| 3 | CD | 1 | 3000.00 |
| 4 | CHK | 2 | 2258.02 |
| 5 | SAV | 2 | 200.00 |
| 7 | CHK | 3 | 1057.75 |
| 8 | MM | 3 | 2212.50 |
| 10 | CHK | 4 | 534.12 |
| 11 | SAV | 4 | 767.77 |
| 12 | MM | 4 | 5487.09 |

| 13 | CHK | 5 | 2237.97 |
| 14 | CHK | 6 | 122.37 |
| 15 | CD | 6 | 10000.00 |
| 17 | CD | 7 | 5000.00 |
| 18 | CHK | 8 | 3487.19 |
| 19 | SAV | 8 | 387.99 |
| 21 | CHK | 9 | 125.67 |
| 22 | MM | 9 | 9345.55 |
| 23 | CD | 9 | 1500.00 |
| 24 | CHK | 10 | 23575.12 |
| 28 | CHK | 12 | 38552.05 |
+ + + + +
21 rows in set (0.28 sec)
While this where clause (four conditions or‘d together) wasn’t too tedious to generate,
imagine if the set of expressions contained 10 or 20 members. For these situations, you
can use the in operator instead:
SELECT account_id, product_cd, cust_id, avail_balance
FROM account
WHERE product_cd IN ('CHK','SAV','CD','MM');
Condition Types | 71
Download at WoweBook.Com
With the in operator, you can write a single condition no matter how many expressions
are in the set.
Using subqueries
Along with writing your own set of expressions, such as ('CHK','SAV','CD','MM'), you
can use a subquery to generate a set for you on the fly. For example, all four product
types used in the previous query have a product_type_cd of 'ACCOUNT', so why not use
a subquery against the product table to retrieve the four product codes instead of ex-
plicitly naming them:
mysql> SELECT account_id, product_cd, cust_id, avail_balance

-> FROM account
-> WHERE product_cd IN (SELECT product_cd FROM product
-> WHERE product_type_cd = 'ACCOUNT');
+ + + + +
| account_id | product_cd | cust_id | avail_balance |
+ + + + +
| 3 | CD | 1 | 3000.00 |
| 15 | CD | 6 | 10000.00 |
| 17 | CD | 7 | 5000.00 |
| 23 | CD | 9 | 1500.00 |
| 1 | CHK | 1 | 1057.75 |
| 4 | CHK | 2 | 2258.02 |
| 7 | CHK | 3 | 1057.75 |
| 10 | CHK | 4 | 534.12 |
| 13 | CHK | 5 | 2237.97 |
| 14 | CHK | 6 | 122.37 |
| 18 | CHK | 8 | 3487.19 |
| 21 | CHK | 9 | 125.67 |
| 24 | CHK | 10 | 23575.12 |
| 28 | CHK | 12 | 38552.05 |
| 8 | MM | 3 | 2212.50 |
| 12 | MM | 4 | 5487.09 |
| 22 | MM | 9 | 9345.55 |
| 2 | SAV | 1 | 500.00 |
| 5 | SAV | 2 | 200.00 |
| 11 | SAV | 4 | 767.77 |
| 19 | SAV | 8 | 387.99 |
+ + + + +
21 rows in set (0.11 sec)
The subquery returns a set of four values, and the main query checks to see whether

the value of the product_cd column can be found in the set that the subquery returned.
Using not in
Sometimes you want to see whether a particular expression exists within a set of ex-
pressions, and sometimes you want to see whether the expression does not exist. For
these situations, you can use the not in operator:
72 | Chapter 4: Filtering
Download at WoweBook.Com
mysql> SELECT account_id, product_cd, cust_id, avail_balance
-> FROM account
-> WHERE product_cd NOT IN ('CHK','SAV','CD','MM');
+ + + + +
| account_id | product_cd | cust_id | avail_balance |
+ + + + +
| 25 | BUS | 10 | 0.00 |
| 27 | BUS | 11 | 9345.55 |
| 29 | SBL | 13 | 50000.00 |
+ + + + +
3 rows in set (0.09 sec)
This query finds all accounts that are not checking, savings, certificate of deposit, or
money market accounts.
Matching Conditions
So far, you have been introduced to conditions that identify an exact string, a range of
strings, or a set of strings; the final condition type deals with partial string matches.
You may, for example, want to find all employees whose last name begins with T. You
could use a built-in function to strip off the first letter of the lname column, as in:
mysql> SELECT emp_id, fname, lname
-> FROM employee
-> WHERE LEFT(lname, 1) = 'T';
+ + + +
| emp_id | fname | lname |

+ + + +
| 3 | Robert | Tyler |
| 7 | Chris | Tucker |
| 18 | Rick | Tulman |
+ + + +
3 rows in set (0.01 sec)
While the built-in function left() does the job, it doesn’t give you much flexibility.
Instead, you can use wildcard characters to build search expressions, as demonstrated
in the next section.
Using wildcards
When searching for partial string matches, you might be interested in:
• Strings beginning/ending with a certain character
• Strings beginning/ending with a substring
• Strings containing a certain character anywhere within the string
• Strings containing a substring anywhere within the string
• Strings with a specific format, regardless of individual characters
You can build search expressions to identify these and many other partial string
matches by using the wildcard characters shown in Table 4-4.
Condition Types | 73
Download at WoweBook.Com
Table 4-4. Wildcard characters
Wildcard character Matches
_ Exactly one character
%
Any number of characters (including 0)
The underscore character takes the place of a single character, while the percent sign
can take the place of a variable number of characters. When building conditions that
utilize search expressions, you use the like operator, as in:
mysql> SELECT lname
-> FROM employee

-> WHERE lname LIKE '_a%e%';
+ +
| lname |
+ +
| Barker |
| Hawthorne |
| Parker |
| Jameson |
+ +
4 rows in set (0.00 sec)
The search expression in the previous example specifies strings containing an a in the
second position and followed by an e at any other position in the string (including the
last position). Table 4-5 shows some more search expressions and their interpretations.
Table 4-5. Sample search expressions
Search expression Interpretation
F% Strings beginning with F
%t Strings ending with t
%bas% Strings containing the substring 'bas'
_ _t_ Four-character strings with a t in the third position
_ _ _-_ _-_ _ _ _ 11-character strings with dashes in the fourth and seventh positions
You could use the last example in Table 4-5 to find customers whose federal ID matches
the format used for Social Security numbers, as in:
mysql> SELECT cust_id, fed_id
-> FROM customer
-> WHERE fed_id LIKE '___-__-____';
+ + +
| cust_id | fed_id |
+ + +
| 1 | 111-11-1111 |
| 2 | 222-22-2222 |

| 3 | 333-33-3333 |
| 4 | 444-44-4444 |
| 5 | 555-55-5555 |
74 | Chapter 4: Filtering
Download at WoweBook.Com
| 6 | 666-66-6666 |
| 7 | 777-77-7777 |
| 8 | 888-88-8888 |
| 9 | 999-99-9999 |
+ + +
9 rows in set (0.02 sec)
The wildcard characters work fine for building simple search expressions; if your needs
are a bit more sophisticated, however, you can use multiple search expressions, as
demonstrated by the following:
mysql> SELECT emp_id, fname, lname
-> FROM employee
-> WHERE lname LIKE 'F%' OR lname LIKE 'G%';
+ + + +
| emp_id | fname | lname |
+ + + +
| 5 | John | Gooding |
| 6 | Helen | Fleming |
| 9 | Jane | Grossman |
| 17 | Beth | Fowler |
+ + + +
4 rows in set (0.00 sec)
This query finds all employees whose last name begins with F or G.
Using regular expressions
If you find that the wildcard characters don’t provide enough flexibility, you can use
regular expressions to build search expressions. A regular expression is, in essence, a

search expression on steroids. If you are new to SQL but have coded using programming
languages such as Perl, then you might already be intimately familiar with regular ex-
pressions. If you have never used regular expressions, then you may want to consult
Jeffrey E.F. Friedl’s Mastering Regular Expressions ( />9780596528126/) (O’Reilly), since it is far too large a topic to try to cover in this book.
Here’s what the previous query (find all employees whose last name starts with F or
G) would look like using the MySQL implementation of regular expressions:
mysql> SELECT emp_id, fname, lname
-> FROM employee
-> WHERE lname REGEXP '^[FG]';
+ + + +
| emp_id | fname | lname |
+ + + +
| 5 | John | Gooding |
| 6 | Helen | Fleming |
| 9 | Jane | Grossman |
| 17 | Beth | Fowler |
+ + + +
4 rows in set (0.00 sec)
The regexp operator takes a regular expression ('^[FG]' in this example) and applies it
to the expression on the lefthand side of the condition (the column lname). The query
Condition Types | 75
Download at WoweBook.Com
now contains a single condition using a regular expression rather than two conditions
using wildcard characters.
Oracle Database and Microsoft SQL Server also support regular expressions. With
Oracle Database, you would use the regexp_like function instead of the regexp operator
shown in the previous example, whereas SQL Server allows regular expressions to be
used with the like operator.
Null: That Four-Letter Word
I put it off as long as I could, but it’s time to broach a topic that tends to be met with

fear, uncertainty, and dread: the null value. Null is the absence of a value; before an
employee is terminated, for example, her end_date column in the employee table should
be null. There is no value that can be assigned to the end_date column that would make
sense in this situation. Null is a bit slippery, however, as there are various flavors of null:
Not applicable
Such as the employee ID column for a transaction that took place at an ATM
machine
Value not yet known
Such as when the federal ID is not known at the time a customer row is created
Value undefined
Such as when an account is created for a product that has not yet been added to
the database
Some theorists argue that there should be a different expression to cover
each of these (and more) situations, but most practitioners would agree
that having multiple null values would be far too confusing.
When working with null, you should remember:
• An expression can be null, but it can never equal null.
• Two nulls are never equal to each other.
To test whether an expression is null, you need to use the is null operator, as dem-
onstrated by the following:
mysql> SELECT emp_id, fname, lname, superior_emp_id
-> FROM employee
-> WHERE superior_emp_id IS NULL;
+ + + + +
| emp_id | fname | lname | superior_emp_id |
+ + + + +
| 1 | Michael | Smith | NULL |
+ + + + +
1 row in set (0.00 sec)
76 | Chapter 4: Filtering

Download at WoweBook.Com
This query returns all employees who do not have a boss (wouldn’t that be nice?).
Here’s the same query using = null instead of is null:
mysql> SELECT emp_id, fname, lname, superior_emp_id
-> FROM employee
-> WHERE superior_emp_id = NULL;
Empty set (0.01 sec)
As you can see, the query parses and executes but does not return any rows. This is a
common mistake made by inexperienced SQL programmers, and the database server
will not alert you to your error, so be careful when constructing conditions that test for
null.
If you want to see whether a value has been assigned to a column, you can use the is
not null operator, as in:
mysql> SELECT emp_id, fname, lname, superior_emp_id
-> FROM employee
-> WHERE superior_emp_id IS NOT NULL;
+ + + + +
| emp_id | fname | lname | superior_emp_id |
+ + + + +
| 2 | Susan | Barker | 1 |
| 3 | Robert | Tyler | 1 |
| 4 | Susan | Hawthorne | 3 |
| 5 | John | Gooding | 4 |
| 6 | Helen | Fleming | 4 |
| 7 | Chris | Tucker | 6 |
| 8 | Sarah | Parker | 6 |
| 9 | Jane | Grossman | 6 |
| 10 | Paula | Roberts | 4 |
| 11 | Thomas | Ziegler | 10 |
| 12 | Samantha | Jameson | 10 |

| 13 | John | Blake | 4 |
| 14 | Cindy | Mason | 13 |
| 15 | Frank | Portman | 13 |
| 16 | Theresa | Markham | 4 |
| 17 | Beth | Fowler | 16 |
| 18 | Rick | Tulman | 16 |
+ + + + +
17 rows in set (0.00 sec)
This version of the query returns the other 17 employees who, unlike Michael Smith,
have a boss.
Before putting null aside for a while, it would be helpful to investigate one more po-
tential pitfall. Suppose that you have been asked to identify all employees who are
not managed by Helen Fleming (whose employee ID is 6). Your first instinct might be
to do the following:
mysql> SELECT emp_id, fname, lname, superior_emp_id
-> FROM employee
-> WHERE superior_emp_id != 6;

Null: That Four-Letter Word | 77
Download at WoweBook.Com

×