By Andrew Cumming, Gordon Russell
Book Price: $29.99 USD
£20.99 GBP
PDF Price: $23.99
Cover | Table of Contents
localhost called dbname with user scott and password tiger.localhost called dbname with user scott and password tiger.
INSERT statements to your SQL command-line utility. Here’s an example that runs on the Windows command prompt, but could also work on a Unix or Linux system if you used the appropriate SQL command-line utility (these are described later in this hack).
\ character (the line-continuation character) before the line break: C:>perl -pe "s/DATE //g; " < cmnd.sql | sqlcmd -U
SELECT commandSELECT statement includes a syntax error or your permissions are inadequate.INSERT
INTO
table
(
list
)
VALUES
(
list
) to add a single row to a table. You can also use INSERT
INTO
table
(
list
)
SELECT
stmt to insert several rows.
can include expressions and literal expressions in the
VALUES list.jim01 has borrowed the book bk002 from your library. This book is due back in 14 days. Add the number 14 to today’s date to get the due date:INSERT INTO libraryLoan(member,book,dueDate)
VALUES ('jim01', 'bk002', CURRENT_DATE + 14);
GetDate( ) in place of CURRENT_DATE. For Access, you can use Date( ).CURRENT_DATE
+
INTERVAL
'14'
DAY in place of CURRENT_DATE+14. Oracle, PostgreSQL, and MySQL will allow that.VALUES list can include more complex calculations, and these calculations may involve subqueries. Let’s say that when the book is returned you must impose a fine of 20 cents if the book is overdue. You can use a single INSERT statement to apply this fine:INSERT INTO libraryReturn(member,book,returnDate,fine)
VALUES ('jim01','bk002',CURRENT_DATE,
(SELECT 0.20 fine
FROM libraryLoan
WHERE member='jim01' AND book='bk002'
GROUP BY member, book
HAVING MAX(dueDate)<CURRENT_DATE))
/* The original lineup */ goalShooter = 'Camelia'; wingAttack = 'Rosie'; /* Swap goalShooter with wingAttack */ tmp = goalShooter; goalShooter = wingAttack; wingAttack = tmp;
UPDATE statement, you don’t need the temporary variable. The values on the right of the = are consistent throughout the whole UPDATE statement; it is as though all of the updates happened simultaneously rather than one after another. Here is the result of swapping the two positions in
Oracle; you will get the same result if you try it on SQL Server or on PostgreSQL (read on for MySQL):SQL> SELECT goalShooter,goalAttack,wingAttack FROM offenceTeam; GOALSHOOTER GOALATTACK WINGATTACK --------------------- --------------------- --------------------- Camelia Demi Rosie SQL> UPDATE offenceTeam 2 SET goalShooter = wingAttack, 3 wingAttack = goalShooter; 1 row updated. SQL> SELECT goalShooter,goalAttack,wingAttack FROM offenceTeam; GOALSHOOTER GOALATTACK WINGATTACK --------------------- --------------------- --------------------- Rosie Demi Camelia
($goalShooter,$wingAttack) = ($wingAttack,$goalShooter);
UPDATE statement might involve thousands of rows and might take several minutes to complete. If there were a failure during the update (if someone switched off the computer, for example), the system is guaranteed to roll back and none of the changes will be committed.words that contains a few thousand words. If you know some characters in some positions, you can use the
underscore (_) wildcard. For example, say you are looking for an eight-letter word with the following pattern:LIKE:
mysql> SELECT * FROM words WHERE word LIKE '_a_l_ _o_';
+------+----------+
| id | word |
+------+----------+
| 3823 | ballroom |
| 3826 | ballyhoo |
| 7255 | Carleton |
| 7480 | cauldron |
+------+----------+
4 rows in set (0.04 sec)
% and _ as the two wildcards. % is used to represent a string of any length and _ represents any single character. In Access, you use
* and
?, respectively.mysql> SELECT word FROM words -> WHERE word LIKE CONCAT('%',SUBSTR(word,1,3)) -> AND LENGTH(word) > 3; +---------------+ | word | +---------------+ | Ababa | | antiformant | | booboo | | Einstein | | entertainment | | Giorgio | | Ionicization | | murmur | | Oshkosh | | redeclared | | restores | | restructures | | Tsunematsu | | underground | +---------------+ 14 rows in set (0.09 sec)
SELECT word FROM words WHERE (word LIKE '%' + SUBSTRING(word,1,3)) AND LEN(word) > 3
SELECT word FROM words WHERE (word LIKE '*' + LEFT(word,3)) AND LEN(word) > 3
contract table contains two columns: income and overhead. You want to produce five more columns calculated from these two values. The output would look like Table 1-2.| Income | Overhead | Residual:grant minus overheads | Est:20% of residual | Admin:10% of residual | Rsrv:5% of residual |
|---|---|---|---|---|---|
| $1,000 | 20% | 800 | 160 | 80 | 40 |
| $2,000 | 10% | 1,800 | 360 | 180 | 90 |
| $1,000 | 50% | 500 | 100 | 50 | 25 |
mysql> SELECT income, -> overhead, -> (income-income*overhead/100) AS residual, -> 0.20*(income-income*overhead/100) AS Est, -> 0.10*(income-income*overhead/100) AS Admin, -> 0.05*(income-income*overhead/100) AS Rsrv -> FROM contract; +--------+----------+----------+------+-------+------+ | income | overhead | residual | Est | Admin | Rsrv | +--------+----------+----------+------+-------+------+ | 1000 | 20 | 800 | 160 | 80 | 40 | | 2000 | 10 | 1800 | 360 | 180 | 90 | | 1000 | 20 | 500 | 100 | 50 | 25 | +--------+----------+----------+------+-------+------+
income-income*overhead/100) over and over again.employee table that contains a column with the id of the department for each employee. If you need to see the name of the department for each employee, you can use a JOIN, as in: SELECT employee.name, department.name FROM employee JOIN department ON (employee.department=department.id)
JOIN is an INNER
JOIN, as shown in the preceding code. There are other kinds of JOINs, such as the LEFT
OUTER
JOIN, the FULL
OUTER
JOIN, and the CROSS
JOIN. You can find examples of each in this chapter.UNION to combine two tables, but unlike with a JOIN, a UNION appends the rows of two tables into one result. In a UNION, the two tables must have the same number of columns, and the corresponding columns must have compatible types.VIEW to name a query. If you have a
SELECT statement (possibly using a JOIN or a UNION) you can save it as a named VIEW. As much as possible the system will treat the view as though it were a base table; you can SELECT from it, or JOIN it to other tables or views. It is generally possible to UPDATE, DELETE from, and INSERT into a view (with some restrictions).| assetTag | Description | DateAcquired |
|---|---|---|
| 50430 | Desktop PC | 2004-07-02 |
| 50431 | 19-inch monitor | 2004-07-02 |
ALTER TABLE equipment ADD COLUMN office VARCHAR(20); UPDATE equipment SET office = 'Headquarters'
SELECT
*
FROM
t. Grab the whole table and treat it as a giant array. No need to learn much SQL, right? The problem is that this approach is inefficient.pagename and content. How can you do this efficiently in, say, Perl? The name of the page you want to display is stored in $p:my $sql = "SELECT pagename,content FROM page";
my $sth = $dbh->prepare($sql);
my $rsh = $sth->execute( );
while (my $row = $sth->fetchrow_hashref( ) ) {
print $row->{content} if ($row->{pagename} eq $p);
}
my $sql = "SELECT pagename,content FROM page where pagename = '".$p."'";
my $sth = $dbh->prepare($sql);
my $rsh = $sth->execute( );
my $row = $sth->fetchrow_hashref( );
print $row->{content} if $row;
$p could be set to something unexpected. For instance, rather than index.html it could be index'html. This would cause the query to fail with a syntax error.* on the SELECT line). You can filter rows using WHERE rules, but you can also use other clauses, such as HAVING.pagename, with previous versions of your pages also recorded so that you can implement version control. Table 2-2 shows an example.| Employee | Title |
|---|---|
| Gordon Russell | Lecturer |
| Andrew Cumming | Teaching fellow |
| Jim Smith | Technician |
| Title | Rank |
|---|---|
| Lecturer | LECT1 |
| Teaching fellow | LECT2 |
| Technician | TECH1 |
| Rank | Payment |
|---|---|
| LECT1 | 2000.00 |
| LECT2 | 3000.00 |
| TECH1 | 5000.00 |
| TECH2 | 6000.00 |
mysql> SELECT title FROM jobs WHERE employee = 'Andrew Cumming';
+-----------------+
| title |
+-----------------+
| Teaching Fellow |
+-----------------+
mysql> SELECT rank FROM ranks WHERE title = 'Teaching Fellow';
+-------+
| rank |
+-------+
| LECT2 |
+-------+
mysql> SELECT payment FROM salary WHERE rank = 'LECT2';
+---------+
| payment |
+---------+
| 3000.00 |
+---------+
orders table shown in Table 2-6.| customer | whn | totalitems |
|---|---|---|
| Jim | 2006-10-10 | 5 |
| Jim | 2006-10-11 | 3 |
| Jim | 2006-10-12 | 1 |
| Brian | 2006-10-10 | 7 |
totalitems:SELECT customer,whn,totalitems FROM orders o1 WHERE o1.whn = ( SELECT MAX(whn) FROM orders o2 WHERE o1.customer = o2.customer );
orders, so the preceding code may be slow to execute. In addition, older versions of MySQL cannot handle subqueries. To avoid using a subquery, you can use a HAVING clause with a self-join:SELECT o1.customer,o1.whn,o1.totalitems FROM orders o1 JOIN orders o2 on (o1.customer = o2.customer) GROUP BY o1.customer,o1.whn,o1.totalitems HAVING o1.whn = max(o2.whn)
+----------+------------+------------+ | customer | whn | totalitems | +----------+------------+------------+ | Brian | 2006-10-10 | 7 | | Jim | 2006-10-12 | 1 | +----------+------------+------------+ 2 rows in set (0.00 sec)
UPDATE example shown in many introductory textbooks is a simple operation whereby you raise everyone’s salary by $100:UPDATE employee SET salary = salary + 100
employee and disciplinary tables:mysql> SELECT * FROM employee; +----+----------+---------+ | id | name | salary | +----+----------+---------+ | 1 | Reginald | 5000.00 | | 2 | C J | 5000.00 | | 3 | Joan | 5000.00 | +----+----------+---------+ mysql> SELECT * FROM disciplinary; +------------+-----+ | whn | emp | +------------+-----+ | 2006-05-20 | 1 | | 2006-05-21 | 1 | | 2006-05-22 | 3 | +------------+-----+
UPDATE statement that updates the employee table while referencing the disciplinary table, but it is easier to do this in two stages. First, prepare a view that calculates the new values and then apply those changes with an UPDATE.newSalary view includes two columns: the primary key of the table to be updated (employee) and the new value of the salary. You can preview the result of this view before executing a simple UPDATE to transfer the new values into place.mysql> CREATE VIEW newSalary AS -> SELECT id, CASE WHEN COUNT(emp) = 0 THEN salary+100 -> WHEN COUNT(emp) > 1 THEN salary-100
JOINs: the
chain and the star, as shown in Figure 2-1 and Figure 2-2. Each is described in the following sections.
JOIN chain example. The reference from trip, shown in Table 2-7, to budget, shown in Table 2-8, is optional—users may put a NULL value in the budget field of a trip row. The link from budget to staff (shown in Table 2-9) is mandatory; every row of the budget table must have a value in the budgetHolder field. Therefore, you use an
OUTER
JOIN when querying across trip and budget and an
INNER
JOIN when querying across budget and staff.| tripID | description | budget |
|---|---|---|
| TR01 | Sicily | NULL |
| TR02 | Egypt | CTH22 |
| budgetId | description | budgetHolder(NOT NULL) |
|---|---|---|
| CT22 | Officer’s mess | ST02 |
| staffId | name | rank |
|---|---|---|
| ST01 | Yossarian | Captain |
| ST02 | Milo | Lieutenant |
CROSS
JOIN queries occur rarely, but when you need them you need to know how to handle them. A table that is used more than once is known as a
self-join. If there are no join conditions between the two instances of the same table, your query will produce every combination of rows possible. So, a table with a row containing 'A' and a row containing 'B', when joined with itself, will produce ('A','A'), ('A','B'), ('B','A'), and ('B','B'). The effect is to produce all combinations of rows.| teamname |
|---|
| Lions |
| Tigers |
| Wildcats |
| Toads |
| hometeam | awayteam | homescore | awayscore |
|---|---|---|---|
| Lions | Wildcats | 1 | 4 |
| Toads | Tigers | 3 | 5 |
| Wildcats | Tigers | 0 | 0 |
CROSS
JOIN:mysql> SELECT home.teamname Home, away.teamname Away -> FROM teams home CROSS JOIN teams away -> ; +----------+----------+ | Home | Away | +----------+----------+ | Lions | Lions | | Tigers | Lions | | Wildcats | Lions | | Toads | Lions | | Lions | Tigers | | Tigers | Tigers | | Wildcats | Tigers | | Toads | Tigers | | Lions | Wildcats | | Tigers | Wildcats | | Wildcats | Wildcats | | Toads | Wildcats | | Lions | Toads | | Tigers | Toads | | Wildcats | Toads | | Toads | Toads | +----------+----------+ 16 rows in set (0.00 sec)
LIKE and || or CONCAT (concatenation), are all that you need for everyday queries. But there are some more exotic facilities, such as full-text indexing and string hashing, that can make your code faster and smarter.story, which contains the author of a story and the story itself:CREATE TABLE story (
author varchar(100),
body varchar(1000)
);
INSERT INTO story (author,body) VALUES('Atzeni'
,'Many database systems, through the use of SQL,↵
are wonderful at collating...');
INSERT INTO story (author,body) VALUES('Adams'
,'The definitions involved in understanding SQL databases are big.↵
You may have thought the distance from your chair to the fridge↵
was big, but that''s peanuts compared to standard definitions.');
INSERT INTO story (author,body) VALUES('Russell and Cumming'↵
,'Often you must store large chunks of text in a table.');
SELECT author FROM story WHERE body LIKE '%database system%'
LIKE operator is case sensitive. If you want to do a case-insensitive search you can force the value into lowercase:SELECT author FROM story WHERE LOWER(body) LIKE '%database system%'
story, which contains the author of a story and the story itself:CREATE TABLE story (
author varchar(100),
body varchar(1000)
);
INSERT INTO story (author,body) VALUES('Atzeni'
,'Many database systems, through the use of SQL,↵
are wonderful at collating...');
INSERT INTO story (author,body) VALUES('Adams'
,'The definitions involved in understanding SQL databases are big.↵
You may have thought the distance from your chair to the fridge↵
was big, but that''s peanuts compared to standard definitions.');
INSERT INTO story (author,body) VALUES('Russell and Cumming'↵
,'Often you must store large chunks of text in a table.');
SELECT author FROM story WHERE body LIKE '%database system%'
LIKE operator is case sensitive. If you want to do a case-insensitive search you can force the value into lowercase:SELECT author FROM story WHERE LOWER(body) LIKE '%database system%'
LIKE clause forces the database system to do a linear scan of the text fields in order to find the words of interest, and therefore performance will be slow. What you really need is an index on the words in these text strings. The FULLTEXT construct supports this type of indexing. It has other advantages as well: it can use a natural language engine to aid the matching algorithm, and it can return the quality of the match (rather than just | name | floorcolor | ceilingcolor | wallcolor |
|---|---|---|---|
| Jim | RED | GREEN | YELLOW |
| Bob | YELLOW | BLUE | BLACK |
| Allan | BLUE | PINK | BLACK |
| George | BLUE | GREEN | OAK |
SELECT name FROM bedroom WHERE floorcolor = 'YELLOW' OR ceilingcolor = 'YELLOW' OR wallcolor = 'YELLOW'
OR increases the chances of creating careless errors in your queries. Instead, you could use CONCAT to do this in one line:SELECT name FROM bedroom WHERE CONCAT(floorcolor,ceilingcolor,wallcolor) like '%YELLOW%'
LIKE expression will not usually employ an index (if the wildcard is not near the start of the pattern, an index might be used). However, the performance hit will be noticeable only for large data sets.CONCAT approach, the colors related to George would become BLUEGREENOAK. If there actually was a color called GREENOAK, you could not be sure whether GREEN and OAK are in different columns or GREENOAK is in one column. To help avoid confusion, you can add a separator:SELECT name FROM bedroom
WHERE CONCAT(':',floorcolor,':',ceilingcolor,':',wallcolor,':')
like '%:YELLOW:%'
COALESCE or NVL; for example, COALESCE(floorcolor,'').COALESCE is the SQL92 standard way of doing this. In Oracle, you can also use w) and the hash value (h). You’ll need a type with a large number of bits for h: MySQL has BIGINT which, at 64 bits, is just big enough. Having an index on the hash value makes a big difference; an index on w is handy:CREATE TABLE dict (w VARCHAR(50) ,h BIGINT ,INDEX(w) ,INDEX(h) );
BIGINT data type. In Oracle, the ROWID data type has 64 bits.LOAD
DATA in MySQL. You can load the file into a temporary table for a little processing before putting the data into dict:mysql> CREATE TEMPORARY TABLE tmp(w VARCHAR(50), INDEX(w)); Query OK, 0 rows affected (0.00 sec) mysql> LOAD DATA LOCAL INFILE '/usr/share/dict/words' INTO TABLE tmp(w); Query OK, 483523 rows affected (3.87 sec) Records: 483523 Deleted: 0 Skipped: 0 Warnings: 0
http://www.gutenberg.org/etext/3201.mysql> UPDATE tmp SET w = REPLACE(REPLACE(LOWER(w),'''',''),'-','');| Email by account name | Email by domain name |
|---|---|
| Alan.K.Buccannan@rbs.co.uk | napier.ac.uk; i.rankin |
| complaints@sirius-cybernetics.com | napier.ac.uk; P.Bhardwaj |
| i.rankin@napier.ac.uk | rbs.co.uk; Alan.K.Buccannan |
| P.Bhardwaj@napier.ac.uk | rbs.co.uk; Scott.Kemmer |
| Scott.Kemmer@rbs.co.uk | sirius-cybernetics.com; complaints |
mysql> SELECT SUBSTRING(e FROM POSITION('@' IN e)+1) AS domain -> , SUBSTRING(e FROM 1 FOR POSITION('@' IN e)-1) AS account -> FROM email -> ORDER BY domain, account; +------------------------+------------------+ | domain | account | +------------------------+------------------+ | napier.ac.uk | i.rankin | | napier.ac.uk | P.Bhardwaj | | rbs.co.uk | Alan.K.Buccannan | | rbs.co.uk | Scott.Kemmer | | sirius-cybernetics.com | complaints | +------------------------+------------------+
@ character: POSITION('@' IN e)+1. For the account name you take characters starting at 1 until just before the @ character; the number of characters required is POSITION('@' IN e)-1.SUBSTRING and POSITION, including the words FROM, IN, and FOR, which are used to separate the parameters. In Oracle and SQL Server, these functions have different names.
CURRENT_TIMESTAMP to generate times whenever possible.
DATE type for days and a
TIMESTAMP type to represent a date and time. Examples of literals are DATE
'2006-05-20' and TIMESTAMP
'2006-06-18
10:09:05'. The ISO format used in both examples (the year followed by the month followed by the day) has the advantage of sorting correctly even when it’s represented as a string data type. It is also visibly different from both the American convention that puts the month first, and the European style that puts the day first.