A MySQL TutorialYou have all the background you need now; it's time to put MySQL to work! This section will help you familiarize yourself with MySQL by providing a tutorial for you to try. As you work through the tutorial, you will create a sample database and some tables and then interact with the database by adding, retrieving, deleting, and modifying information in the tables. During the process of working with the sample database, you will learn the following things:
This book uses
sampdb
as the sample database name, but you may need to use a different name as you work through the material. For example, someone else on your system already may be using the
Table names can be used exactly as shown in the examples, even if multiple users on your system have their own sample databases. In MySQL, it doesn't matter if other people use the same table
Obtaining the Sample Database DistributionThis tutorial refers at certain points to files from the "sample database distribution" (also known as the sampdb distribution, after the name of the sampdb database). These files contain queries and data that will help you set up the sample database. See Appendix A, "Obtaining and Installing Software," for instructions on getting the distribution. When you unpack it, it will create a directory named sampdb containing the files you'll need. I recommend that you change location into that directory whenever you're working through examples pertaining to the sample database. Preliminary RequirementsTo try the examples in this tutorial, a few preliminary requirements must be satisfied:
The required software includes the MySQL clients and a MySQL server. The client programs must be located on the machine where you'll be working. The server can be located on your machine, although that is not required. As long as you have permission to connect to it, the server can be located
In addition to the MySQL software, you'll need a MySQL account so that the server will allow you to connect and create your sample database and its tables. (If you already have a MySQL account, you can use that, but you may want to set up a separate account for use with the material in this book.)
At this point, we run into something of a chicken-and-egg problem. To set up a MySQL account to use for connecting to the server, it's necessary to connect to the server. Typically, this is done by connecting as the MySQL
root
% mysql -p -u root Enter password: ****** mysql> GRANT ALL ON sampdb.* TO 'sampadm'@'localhost' IDENTIFIED BY 'secret';
The
mysql
command includes a
-p
option to cause
mysql
to prompt for the
root
user's MySQL password. Enter the password where you see
******
in the example. (I assume that you have already set up a password for the MySQL
root
user and that you know what it is. If you haven't yet assigned a password, just press Enter at the
Enter password:
prompt. However, having no
root
password is
The GRANT statement just shown is appropriate if you'll be connecting to MySQL from the same machine where the server is running. It allows you to connect to the server using the name sampadm and the password secret and gives you complete access to the sampdb database. However, GRANT doesn't create the database; we'll get to that a bit later. If you don't plan to connect from the same host as the one where the server is running, change localhost to the name of the machine where you'll be working. For example, if you will connect to the server from the host asp.snake.net , the GRANT statement should look like this:
mysql>
GRANT ALL ON sampdb.* TO 'sampadm'@'asp.snake.net' IDENTIFIED BY 'secret';
If you don't have control over the server, ask your MySQL administrator to set up an account for you. Then substitute the MySQL username, password, and database name that the administrator
More information on the GRANT statement, setting up MySQL user accounts, and changing passwords can be found in Chapter 11, "General MySQL Administration." Establishing and Terminating Connections to the ServerTo connect to your server, invoke the mysql program from your shell (that is, from your UNIX prompt or from a DOS console under Windows). The command is as follows: % mysql options
I use
%
throughout this book to
The options part of the mysql command line might be empty, but more probably you'll have to issue a command that looks something like the following: % mysql -h host_name -p -u user_name You may not need to supply all those options when you invoke mysql , but it's likely that you'll have to specify at least a name and password. Here's what the options mean:
Suppose that my MySQL username and password are sampadm and secret . If the MySQL server is running on the same host, I can leave out the -h option and the mysql command to connect to the server looks like this: % mysql -p -u sampadm Enter password: ****** After I enter the command, mysql prints Enter password: to prompt for my password, and I type it in (the ****** indicates where I type secret ). If all goes well, mysql prints a greeting and a mysql> prompt indicating that it is waiting for me to issue queries. The full startup sequence is as follows: % mysql -p -u sampadm Enter password: ****** Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 7575 to server version: 4.0.4-log Type 'help;' or '\h' for help. Type '\c' to clear the buffer. mysql> To connect to a server running on some other machine, it's necessary to specify the hostname using an -h option. If that host is cobra.snake.net , the command looks like this:
%
mysql -h cobra.snake.net -p -u sampadm
In most of the examples that follow that show a mysql command line, I'm going to leave out the -h , -u , and -p options for brevity and assume that you'll supply whatever options are necessary. After you establish a connection to the server, you can terminate your session any time by typing QUIT :
mysql>
QUIT
Bye
You can also quit by typing \q or (on UNIX) by pressing Ctrl-D.
When you're just starting to learn MySQL, you'll probably consider its security system to be an annoyance because it makes it harder to do what you want. (You must obtain permission to create and access a database, and you must specify your name and password whenever you connect to the server.) However, after you move beyond the sample database used in this book to entering and using your own records, your perspective will change
There are ways to set up your account so you don't have to type in connection parameters each time you run mysql. These are discussed in the "Tips for Interacting with mysql" section later in this chapter. The most common method for simplifying the connection process is to store your connection parameters in an option file. You may want to skip ahead to that section right now to see how to set up such a file. Issuing QueriesAfter you're connected to the server, you're ready to issue queries. This section describes some general things you should know about interacting with mysql . To enter a query in mysql , just type it in. At the end of the query, type a semicolon character (' ; ') and press Enter. The semicolon tells mysql that the query is complete. After you've entered a query, mysql sends it to the server to be executed. The server processes the query and sends the results back to mysql , which displays the result for you. The following example shows a simple query that asks for the current date and time:
mysql>
SELECT NOW();
+---------------------+
NOW()
+---------------------+
2002-09-01 13:54:24
+---------------------+
1 row in set (0.00 sec)
mysql
displays the query result and a line that shows the number of rows the result consists of and the time elapsed during query processing. In
Because mysql waits for the semicolon as a statement terminator, you need not enter a query all on a single line. You can spread it over several lines if you want: mysql> SELECT NOW(), -> USER(), -> VERSION() -> ; +---------------------+-------------------+----------------+ NOW() USER() VERSION() +---------------------+-------------------+----------------+ 2002-09-01 13:54:37 sampadm@localhost 4.0.4-beta-log +---------------------+-------------------+----------------+
Note how the prompt changes from
mysql>
to
->
after you enter the first line of the query. That tells you that
mysql
thinks you're still entering the query, which is important feedback—if you forget the semicolon at the end of a query, the changed prompt helps you realize that
mysql
is still waiting for something.
For the most part, it doesn't matter whether you enter queries using uppercase, lowercase, or mixed case. The following queries are all equivalent: SELECT USER(); select user(); SeLeCt UsEr(); The examples in this book use uppercase for SQL keywords and function names, and lowercase for database, table, and column names. When you invoke a function in a query, there must be no space between the function name and the following parenthesis: mysql> SELECT NOW (); ERROR 1064: You have an error in your SQL syntax near '()' at line 1 mysql> SELECT NOW(); +---------------------+ NOW() +---------------------+ 2002-09-01 13:56:36 +---------------------+ These two queries look similar, but the first one fails because the parenthesis doesn't immediately follow the function name. Another way to terminate a query is to use \g rather than a semicolon:
mysql>
SELECT NOW()\g
+---------------------+
NOW()
+---------------------+
2002-09-01 13:56:47
+---------------------+
Or you can use \G , which displays the results in vertical format:
mysql>
SELECT NOW(), USER(), VERSION()\G
*************************** 1. row ***************************
NOW(): 2002-09-01 13:56:58
USER(): sampadm@localhost
VERSION(): 4.0.4-beta-log
For a query that generates short output lines, \G is not so useful, but if the lines are so long that they wrap around on your screen, \G can make the output easier to read.
If you've begun typing in a
mysql> SELECT NOW(), -> VERSION(), -> \c mysql> Notice how the prompt changes back to mysql> to indicate that mysql is ready for a new query. You can store queries in a file and tell mysql to read queries from the file rather than from the keyboard. Use your shell's input redirection facilities for this. For example, if I have queries stored in a file named myfile.sql , I can execute its contents as follows:
%
mysql < myfile.sql
You can call the file whatever you want. I use the .sql suffix as a convention to indicate that a file contains SQL statements.
Executing
mysql
this way is something that will come up in the "Adding New Records" section later in this chapter when we enter data into the
sampdb
database. It's a lot more
The remainder of this tutorial shows many queries that you can try out for yourself. These are indicated by the
mysql>
prompt before the query, and such examples are usually accompanied by the output of the query. You should be able to type in these queries as shown, and the resulting output should be the same. Queries that are shown without a prompt are intended simply to
Creating the DatabaseWe'll begin by creating the sampdb sample database and the tables within it, populating its tables, and performing some simple queries on the data contained in those tables. Using a database involves several steps:
Retrieving existing data is easily the most common operation performed on a database. The
To create a new database, connect to the server using mysql and then issue a CREATE DATABASE statement that specifies the database name:
mysql>
CREATE DATABASE sampdb;
You'll need to create the sampdb database before you can create any of the tables that will go in it or do anything with the contents of those tables. Does creating the database select it as the default (or current) database? No, it doesn't, as you can see by executing the following query:
mysql>
SELECT DATABASE();
+------------+
DATABASE()
+------------+
+------------+
To make sampdb the default database, issue a USE statement: mysql> USE sampdb; mysql> SELECT DATABASE(); +------------+ DATABASE() +------------+ sampdb +------------+ The other way to select a database is to name it on the command line when you invoke mysql :
%
mysql sampdb
That is, in fact, the usual way to name the database you want to use. If you need any connection parameters, specify them before the database name. For example, the following two commands allow the sampadm user to connect to the sampdb database on the local host and on cobra.snake.net : % mysql -p -u sampadm sampdb % mysql -h cobra.snake.net -p -u sampadm sampdb Unless specified otherwise, all the examples that follow assume that when you invoke mysql , you name the sampdb database on the command line to make it the current database. If you invoke mysql but forget to name the database on the command line, just issue a USE sampdb statement at the mysql> prompt. Creating TablesIn this section, we'll build the tables needed for the sampdb sample database. First, we'll consider the tables needed for the Historical League and then those for the grade-keeping project. This is the part where some database books start talking about Analysis and Design, Entity-Relationship Diagrams, Normalization Procedures, and other such stuff. There's a place for all that, but I prefer just to say we need to think a bit about what our database will look like—what tables it should contain, what the contents of each table should be, and some of the issues involved in deciding how to represent our data. The choices made here about data representation are not absolute. In other situations, you might well elect to represent similar data in a different way, depending on the requirements of your applications and the uses to which you intend to put your data. Tables for the Historical LeagueTable layout for the Historical League is pretty simple:
The president TableThe president table is simpler, so let's discuss it first. This table will contain some basic biographical information about each United States president:
The member TableThe member table for the Historical League membership list is similar to the president table in the sense that each record contains basic descriptive information for a single person. But each member record contains more columns:
Creating the Historical League TablesNow we're ready to create the Historical League tables. For this we use the CREATE TABLE statement, which has the following general form: CREATE TABLE tbl_name ( column_specs ); tbl_name indicates the name you want to give the table. column_specs provides the specifications for the columns in the table, as well as any indexes (if there are any). Indexes make lookups faster; we'll discuss them further in Chapter 4, "Query Optimization." For the president table, the CREATE TABLE statement looks like this:
CREATE TABLE president
(
last_name VARCHAR(15) NOT NULL,
first_name VARCHAR(15) NOT NULL,
suffix VARCHAR(5) NULL,
city VARCHAR(20) NOT NULL,
state VARCHAR(2) NOT NULL,
birth DATE NOT NULL,
death DATE NULL
);
If you want to type in that statement yourself, invoke mysql , making sampdb the current database:
%
mysql sampdb
Then enter the CREATE TABLE statement as just shown, including the trailing semicolon so that mysql can tell where the end of the statement is. To create the president table using a prewritten description, use the create_president.sql file from the sampdb distribution. This file is located in the sampdb directory that is created when you unpack the distribution. Change location into that directory and then run the following command:
%
mysql sampdb < create_president.sql
Whichever way you invoke
mysql
, specify any connection parameters you may need (hostname, username, or password) on the command line
Each column specification in the CREATE TABLE statement consists of the column name, the data type (the kind of values the column will hold), and possibly some column attributes.
The two column types used in the
president
table are
VARCHAR
and
DATE.VARCHAR(
n
)
means the column contains variable-length character (string) values, with a maximum length of
n
The other column type we've used is DATE . This type indicates, not surprisingly, that the column holds date values. However, what may be surprising to you is the format in which dates are represented. MySQL expects dates to be specified in ' CCYY-MM-DD ' format, where CC , YY , MM , and DD represent the century, year within the century, month, and date. This is the ANSI SQL standard for date representation (also known as ISO 8601 format). For example, a date of July 18, 2002 is specified in MySQL as '2002-07-18' , not as '07-18-2002' or '18-07-2002' . The only attributes we're using for the columns in the president table are NULL (values can be missing) and NOT NULL (values must be filled in). Most columns are NOT NULL because we'll always have a value for them. The two columns that can have NULL values are suffix (most names don't have one) and death (some presidents are still alive, so there is no date of death). For the member table, the CREATE TABLE statement looks like this:
CREATE TABLE member
(
member_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
PRIMARY KEY (member_id),
last_name VARCHAR(20) NOT NULL,
first_name VARCHAR(20) NOT NULL,
suffix VARCHAR(5) NULL,
expiration DATE NULL DEFAULT '0000-00-00',
email VARCHAR(100) NULL,
street VARCHAR(50) NULL,
city VARCHAR(50) NULL,
state VARCHAR(2) NULL,
zip VARCHAR(10) NULL,
phone VARCHAR(20) NULL,
interests VARCHAR(255) NULL
);
Type that statement into mysql or execute the following command to use the prewritten file from the sampdb distribution:
%
mysql sampdb < create_member.sql
In terms of column types, most columns of the member table except two are not very interesting because they are created as variable-length strings. The exceptions are member_id and expiration , which exist to hold sequence numbers and dates, respectively.
The primary consideration for the
member_id
membership number column is that each of its values should be unique to avoid confusion between members. An
AUTO_INCREMENT
column is useful here because then we can let MySQL generate unique numbers for us automatically when we add new members. Even though it just contains numbers, the declaration for
member_id
has several
The
PRIMARY KEY
clause indicates that the
member_id
column is indexed to allow fast lookups and that each value in the column must be unique. The latter property is desirable for member ID values, because it
If you don't understand that stuff about AUTO_INCREMENT and PRIMARY KEY , just think of them as giving us a magic way of generating an ID number for each member. It doesn't particularly matter what the values are, as long as they're unique. (When you're ready to learn more about how to declare and use AUTO_INCREMENT columns, Chapter 2, "Working with Data in MySQL," covers them in detail.)
The
expiration
column is a
DATE
. It has a default value of
'0000-00-00'
, which is a non-
NULL
value that means no legal date has been entered. The reason for this is that expiration can be
NULL
to indicate that a member has a lifetime membership. If we don't specify otherwise, a column that can contain
NULL
also has
NULL
as its default value. That's not desirable in this case; if you created a new member record but
Now that you've told MySQL to create a couple of tables, check to make sure that it did so as you expect. In mysql , issue the following query to see the structure of the president table:
mysql>
DESCRIBE president;
+------------+-------------+------+-----+------------+-------+
Field Type Null Key Default Extra
+------------+-------------+------+-----+------------+-------+
last_name varchar(15)
first_name varchar(15)
suffix varchar(5) YES NULL
city varchar(20)
state char(2)
birth date 0000-00-00
death date YES NULL
+------------+-------------+------+-----+------------+-------+
In some versions of MySQL, the results from DESCRIBE include additional information showing access privilege information. I've not shown that here because it makes the lines too long to display without wrapping around. The output looks pretty much as we'd expect, except that the information for the state column says its type is CHAR(2) . That's odd; wasn't it declared as VARCHAR(2) ? Yes, it was, but MySQL has silently changed the type from VARCHAR to CHAR . The reason for this has to do with efficiency of storage space for short character columns, which I won't go into here. If you want the details, check the discussion of the ALTER TABLE statement in Chapter 3, "MySQL SQL Syntax and Use." For our purposes here, there is no difference between the two types. The important thing is that the column stores two-character values. If you issue a DESCRIBE member query, mysql will show you similar information for the member table. DESCRIBE is useful when you forget the name of a column in a table or need to know its type or how wide it is and so on. It's also useful for finding out the order in which MySQL stores columns in table rows. That order is important when you use INSERT or LOAD DATA statements that expect column values to be listed in the default column order. The information produced by DESCRIBE can be obtained in different ways. It may be abbreviated as DESC or written as an EXPLAIN or SHOW statement. The following statements are all synonymous: DESCRIBE president; DESC president; EXPLAIN president; SHOW COLUMNS FROM president; SHOW FIELDS FROM president; These statements also allow you to restrict the output to particular columns. For example, you can add a LIKE clause at the end of a SHOW statement to display information only for column names that match a given pattern:
mysql>
SHOW COLUMNS FROM president LIKE '%name';
+------------+-------------+------+-----+---------+-------+
Field Type Null Key Default Extra
+------------+-------------+------+-----+---------+-------+
last_name varchar(15)
first_name varchar(15)
+------------+-------------+------+-----+---------+-------+
The ' % ' character used here is a special wildcard character that is described later in the "Pattern Matching" section. Similar restrictions can be used with DESCRIBE and EXPLAIN as well; for the exact syntax, see Appendix D, "SQL Syntax Reference." The SHOW statement has other forms that are useful for obtaining different types of information from MySQL. SHOW TABLES lists the tables in the current database, so with the two tables we've created so far in the sampdb database, the output looks like this:
mysql>
SHOW TABLES;
+------------------+
Tables_in_sampdb
+------------------+
member
president
+------------------+
SHOW DATABASES lists the databases that are managed by the server to which you're connected:
mysql>
SHOW DATABASES;
+-----------+
Database
+-----------+
menagerie
mysql
sampdb
test
+-----------+
The list of databases varies from server to server, but you should see at least sampdb and mysql . You created sampdb yourself, and the database named mysql holds the grant tables that control MySQL access privileges. The mysqlshow utility provides a command-line interface to the same kinds of information that the SHOW statement displays. With no arguments, mysqlshow displays a list of databases:
%
mysqlshow
+-------------+
Databases
+-------------+
menagerie
mysql
sampdb
test
+-------------+
With a database name, it shows the tables in the given database:
%
mysqlshow sampdb
Database: sampdb
+-----------+
Tables
+-----------+
member
president
+-----------+
With a database and table name, mysqlshow displays information about the columns in the table, much like the SHOW COLUMNS statement. Tables for the Grade-Keeping ProjectTo see what tables are required for the grade-keeping project, let's consider how you might write down scores when you use a paper-based gradebook. Figure 1.2 shows a page from your gradebook. The main body of this page is a matrix for recording scores. There is also other information necessary for making sense of the scores. Student names and ID numbers are listed down the side of the matrix. (For simplicity, only four students are shown.) Along the top of the matrix, you put down the dates when you give quizzes and tests. The figure shows that you've given quizzes on September 3, 6, 16, and 23, and tests on September 9 and October 1. Figure 1.2. Example gradebook.
To keep track of this kind of information using a database, we need a score table. What should records in this table contain? That's easy. For each row, we need student name, the date of the quiz or test, and the score. Figure 1.3 shows how some of the scores from the gradebook look when represented in a table like this. (Dates are written the way MySQL represents them, in ' CCYY-MM-DD ' format.) Figure 1.3. Initial score table layout.
However, there is a problem with setting up the table in this way because it
It's possible to distinguish scores by recording the type in each record, for example, by adding a column to the
score
table that contains '
T
' or '
Q
' for each row to indicate "test" or "quiz," as in Figure 1.4. This has the advantage of making the type of score explicit in the data. The
Figure 1.4. score table layout, revised to include score type.
Let's try an alternative representation. Instead of recording score types in the
score
table, we'll figure them out from the dates. We can keep a list of dates and use it to keep track of what kind of "grade event" (quiz or test) occurred on each date. Then we can determine whether any given score was from a quiz or a test by combining it with the information in our event list; just match the date in the
score
table record with the date in the
event
table to get the event type. Figure 1.5 shows this table layout and
Figure 1.5. score and event tables, linked on date.
This is much better than trying to infer the score type based on some guess; instead, we're deriving the type directly from data recorded explicitly in our database. It's also preferable to recording score types in the score table because we must record each type only one time, rather than once per score record. However, now we're combining information from multiple tables. If you're like me, when you first hear about this kind of thing, you think, "Yeah, that's a cute idea, but isn't it a lot of work to do all that looking up all the time; doesn't it just make things more complicated?" In a way, that's correct; it is more work. Keeping two lists of records is more complicated than keeping one list. But take another look at your gradebook (see Figure 1.2). Aren't you already keeping two sets of records? Consider the following facts:
In other words, even though you may not think about it as such, you're really not doing anything different with the gradebook than what I'm
The page in the gradebook illustrates something about the way we think of information and about the difficulty of figuring out how to put information in a database. We tend to integrate different kinds of information and interpret them as a whole. Databases don't work like that, which is one reason why they sometimes seem artificial and
One requirement imposed on the event table by the layout shown in Figure 1.5 is that the dates be unique because each date is used to link together records from the score and event tables. In other words, you cannot give two quizzes on the same day, or a quiz and a test. If you do, you'll have two sets of records in the score table and two records in the event table, all with the same date, and you won't be able to tell how to match score records with event records.
That problem will never come up if there is never more than one grade event per day. But is it really valid to assume that will never happen? It might seem so; after all, you don't consider yourself sadistic enough to give a quiz and a test on the same day. But I hope you'll pardon me if I'm skeptical. I've often
It's better to think about the possible problems in advance and anticipate how to handle them. So, let's suppose you might need to record two sets of scores for the same day sometimes. How can we handle that? As it turns out, this problem isn't so difficult to solve. With a minor change to the way we lay out our data, multiple events on a given date won't cause trouble:
The result of these changes is shown in Figure 1.6. Now you link together the
score
and
event
tables using the event ID rather than the date, and you use the
event
table to determine not just the type of each score but also the date on which it occurred. Also, it's no longer the date that must be unique in the
event
table, it's the event ID. This means you can have a
Figure 1.6. score and event tables, linked on event ID.
At this point, you reached a
This leads naturally to a question: "Would it be better not to use a database at all? Maybe MySQL isn't for me." As you might guess, I will answer that question in the negative, because otherwise this book will come to a quick end. But when you're thinking about how to do a job, it's not a bad idea to consider various alternatives and to ask whether you're better off using a database system such as MySQL or something else, such as a spreadsheet program:
On the other hand, if you want to look at just part of your data (quizzes only or tests only, for example), perform comparisons such as boys versus
Another point to consider is that the abstract and disconnected nature of your data as represented in a relational database is not really that big of a deal, anyway. It's necessary to think about that representation when setting up the database so that you don't lay out your data in a way that doesn't make sense for what you want to do with it. However, after you determine the representation, you're going to rely on the database engine to pull together and present your data in a way that is meaningful to you. You're not going to look at it as a bunch of disconnected pieces. For example, when you retrieve scores from the score table, you don't want to see event IDs; you want to see dates. That's not a problem. The database will look up dates from the event table based on the event ID and show them to you. You may also want to see whether the scores are for tests or quizzes. That's not a problem, either. The database will look up score types the same way—using the event ID. Remember, that's what a relational database system like MySQL is good at—relating one thing to another to pull out information from multiple sources to present you with what you really want to see. In the case of our grade-keeping data, MySQL does the thinking about pulling information together using event IDs so that you don't have to. Now, just to provide a little advance preview of how you'd tell MySQL to do this relating of one thing to another, suppose you want to see the scores for September 23, 2002. The query to pull out scores for an event given on a particular date looks like the following: SELECT score.name, event.date, score.score, event.type FROM score, event WHERE event.date = '2002-09-23' AND score.event_id = event.event_id; Pretty scary, huh? This query retrieves the student name, the date, score, and the type of score by joining (relating) score table records to event table records. The result looks like this: +--------+------------+-------+------+ name date score type +--------+----------- +-------+------+ Billy 2002-09-23 15 Q Missy 2002-09-23 14 Q Johnny 2002-09-23 17 Q Jenny 2002-09-23 19 Q +--------+------------+-------+------+ Notice anything familiar about the format of that information? You should; it's the same as the table layout shown in Figure 1.4. And you don't need to know the event ID to get this result. You specify the date you're interested in and let MySQL figure out which score records go with that date. So if you've been wondering whether all the abstraction and disconnectedness loses us anything when it comes to getting information out of the database in a form that's meaningful to us, it doesn't.
Of course, after looking at that query, you might be wondering something else, too. Namely, it looks kind of long and complicated; isn't writing something like that a lot of work to go to just to find the scores for a given date? Yes, it is. However, there are ways to avoid typing several lines of SQL each time you want to issue a query.
I've actually jumped the gun a little bit in showing you that query. It is, believe it or not, a little simpler than the one we're really going to use to pull out scores. The reason for this is that we need to make one more change to our table layout. Instead of recording student name in the score table, we'll use a unique student ID. (That is, we'll use the value from the "ID" column of your gradebook rather than from the "Name" column.) Then we create another table called student that contains name and student_id columns (Figure 1.7). Figure 1.7. student , score , and event tables, linked on student ID and event ID.
Why make this modification? For one thing, there might be two students with the same name. Using a unique student ID number helps you tell their scores apart. (This is exactly analogous to the way you can tell scores apart for a test and quiz given on the same day by using a unique event ID rather than the date.) After making this change to the table layout, the query we'll actually use to pull out scores for a given date becomes a little more complex: SELECT student.name, event.date, score.score, event.type FROM event, score, student WHERE event.date = '2002-09-23' AND event.event_id = score.event_id AND score.student_id = student.student_id;
If you're
You'll note from Figure 1.7 that I added something to the student table that wasn't in your gradebook; it contains a column for sex. This will allow for simple things like counting the number of boys and girls in the class or more complex things like comparing scores for boys and girls.
We're almost done designing the tables for the grade-keeping project. We need just one more table to record absences for attendance purposes. Its contents are relatively straightforward: a student ID number and a date (see Figure 1.8). Each row in the table indicates that the given student was absent on the given date. At the end of the grading period, we'll call on MySQL's counting
Figure 1.8. absence table.
Now that we know what our grade-keeping tables should look like, we're ready to create them. The CREATE TABLE statement for the student table is as follows:
CREATE TABLE student
(
name VARCHAR(20) NOT NULL,
sex ENUM('F','M') NOT NULL,
student_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
PRIMARY KEY (student_id)
);
Type that statement into mysql or execute the following command:
%
mysql sampdb < create_student.sql
The CREATE TABLE statement creates a table named student with three columns, name , sex , and student_id . name is a variable-length string column that can hold up to 20 characters. This name representation is simpler than the one used for the Historical League tables; it uses a single column rather than separate first name and last name columns. That's because I know in advance that no grade-keeping query examples will need to do anything that would work better with separate columns. (Yes, that's cheating. I admit it.)
sex
represents whether a student is a boy or a girl. It's an
ENUM
(enumeration) column, which means it can take on only one of the values listed in the column specification:
'F'
for
By the way, values in an ENUM column need not be just a single character. The type column could have been declared as something like ENUM('female','male') instead. student_id is an integer column that will contain unique student ID numbers. Normally, you'd probably get ID numbers for your students from a central source, such as the school office. We'll just make them up, using an AUTO_INCREMENT column that is declared in much the same way as the member_id column that is part of the member table created earlier. Note that if you really were going to get student ID numbers from the office rather than generating them automatically, you could declare the student_id column without the AUTO_INCREMENT attribute. But leave in the PRIMARY KEY clause to disallow duplicate IDs. The event table looks like this:
CREATE TABLE event
(
date DATE NOT NULL,
type ENUM('T','Q') NOT NULL,
event_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
PRIMARY KEY (event_id)
);
To create the table, type that statement into mysql or execute the following command:
%
mysql sampdb < create_event.sql
All the columns are declared as NOT NULL because none of them can be missing. The date column holds a standard MySQL DATE value, in ' CCYY-MM-DD ' (year-first) format.
type
represents score type. Like
sex
in the
student
table,
type
is an enumeration column. The
event_id is an AUTO_INCREMENT column declared as a PRIMARY KEY , similar to student_id in the student table. Using AUTO_INCREMENT allows us to generate unique event ID values easily. As with the student_id column in the student table, the particular values are less important than that they be unique. The score table looks like this:
CREATE TABLE score
(
student_id INT UNSIGNED NOT NULL,
event_id INT UNSIGNED NOT NULL,
PRIMARY KEY (event_id, student_id),
score INT NOT NULL
);
Type that statement into mysql or execute the following command:
%
mysql sampdb < create_score.sql
The
student_id
and
event_id
columns are
INT
(integer) columns indicating the student and event to which each score applies. By using them to link to the student and event tables, we'll be able to tell the student name and event date. We've also made the combination of the two columns a
PRIMARY KEY
. This ensures that we won't have duplicate scores for a student for a given quiz or test. Also, it'll be easier to change a score later. For example, when a score is entered incorrectly, we can clobber the old record when we put in the new records by using MySQL's
REPLACE
statement. It's not necessary to do a
DELETE
Note that it's the combination of event_id and student_id that is unique. In the score table, neither value is unique by itself. There will be multiple score records for each event_id value (one per student) and multiple records for each student_id value (one for each quiz and test). score is an integer column. That is, I'm assuming score values are always integers. If you wanted to allow scores such as 58.5 that have a fractional part, you'd use one of the floating-point column types, such as FLOAT or DECIMAL , instead. The absence table for attendance looks like this:
CREATE TABLE absence
(
student_id INT UNSIGNED NOT NULL,
date DATE NOT NULL,
PRIMARY KEY (student_id, date)
);
Type that statement into mysql or execute the following command:
%
mysql sampdb < create_absence.sql
The
student_id
and
date
columns are both declared as
NOT NULL
to disallow missing values. We make the combination of the two columns a primary key so that we don't
Adding New RecordsAt this point, our database and its tables have been created. Now we need to put some records into the tables. However, it's useful to know how to check what's in a table after you put something into it, so although retrieval is not covered in any detail until the next section, you should know at least that the following statement will show you the contents of any table tbl_name :
SELECT * FROM
tbl_name;
For example:
mysql>
SELECT * FROM student;
Empty set (0.00 sec)
Right now, mysql indicates that the table is empty, but you'll see a different result after trying the examples in this section. There are several ways to add data to a database. You can insert records into a table manually by issuing INSERT statements. You can also add records by reading them from a file, either in the form of pre-written INSERT statements that you feed to mysql or as raw data values that you load using the LOAD DATA statement or the mysqlimport utility.
This section demonstrates each method of inserting records into your tables. What you should do is play with all of them to familiarize yourself with them and to see how they work. After you're done trying out the
Let's start adding records by using INSERT , a SQL statement for which you specify the table into which you want to insert a row of data and the values to put in the row. The INSERT statement has several forms:
Another method for loading records into a table is to read them directly from a file. For example, if you have a file named insert_president.sql that contains INSERT statements for adding new records to the president table, you can execute those statements like this:
%
mysql sampdb < insert_president.sql
(The insert_president.sql file can be found in the sampdb distribution.) If you're already running mysql , you can use a SOURCE command to read the file:
mysql>
SOURCE insert_president.sql;
SOURCE
requires MySQL 3.23.9 or
If you have the records stored in a file as raw data values rather than as INSERT statements, you can load them with the LOAD DATA statement or with the mysqlimport utility. The LOAD DATA statement acts as a bulk loader that reads data from a file. Use it from within mysql :
mysql>
LOAD DATA LOCAL INFILE 'member.txt' INTO TABLE member;
Assuming that the member.txt data file is located in your current directory on the client host, this statement reads it and sends its contents to the server to be loaded into the member table. ( member.txt can be found in the sampdb distribution.) By default, the LOAD DATA statement assumes that column values are separated by tabs and that lines end with newlines (also known as linefeeds). It also assumes that the values are present in the order that columns are stored in the table. It's possible to read files in other formats or to specify a different column order. See the entry for LOAD DATA in Appendix D for details. LOAD DATA LOCAL won't work if your MySQL is older than version 3.22.15 because that's when the capability of reading files from the client was added to LOAD DATA . (Without the LOCAL keyword, the file must be located on the server host and you need a server access privilege that most MySQL users don't have.) In addition, as of MySQL 3.23.49, the LOCAL capability may be present but disabled by default. If the LOAD DATA statement results in an error, try again after invoking mysql with the --local-infile option—for example: % mysql --local-infile sampdb mysql> LOAD DATA LOCAL INFILE 'member.txt' INTO TABLE member; If that doesn't work, either, the server also needs to be told to allow LOCAL . See Chapter 11 for information on how to do this. The mysqlimport utility acts as a command line interface to LOAD DATA . You invoke mysqlimport from the shell, and it generates a LOAD DATA statement for you:
%
mysqlimport --local sampdb member.txt
This won't work if your MySQL is older than version 3.22.15 because the --local option requires LOAD DATA LOCAL . As with the mysql program, if you need to specify connection parameters, indicate them on the command line preceding the database name.
For the command just shown,
mysqlimport
generates a
LOAD DATA
statement to load
member.txt
into the
member
table. That's because
mysqlimport
determines the table name from the name of the data file using everything up to the first period of the filename as the table name. For example,
member.txt
and
president.txt
would be loaded into the
member
and
president
tables. This means you should choose your filenames
After you have tried the record-adding methods just described, you should re- create and load the sampdb database tables so that their contents are the same as what the next sections assume. From your shell, execute the following commands: % mysql sampdb < create_president.sql % mysql sampdb < insert_president.sql % mysql sampdb < create_member.sql % mysql sampdb < insert_member.sql % mysql sampdb < create_student.sql % mysql sampdb < insert_student.sql % mysql sampdb < create_score.sql % mysql sampdb < insert_score.sql % mysql sampdb < create_event.sql % mysql sampdb < insert_event.sql % mysql sampdb < create_absence.sql % mysql sampdb < insert_absence.sql If you don't want to type those commands individually (which is not unlikely), try the following under UNIX:
%
sh init_all_tables.sh sampdb
Or try the following under Windows:
C:\>
init_all_tables.bat sampdb
If you need to specify connection parameters, list them on the command line before the database name. Retrieving InformationOur tables are created and loaded with data now, so let's see what we can do with that data. The SELECT statement allows you to retrieve and display information from your tables, in as general or specific a manner as you like. You can display the entire contents of a table SELECT * FROM president; or you can select as little as a single column of a single row: SELECT birth FROM president WHERE last_name = 'Eisenhower';
The
SELECT
statement has several clauses (or parts), which you combine as necessary to retrieve the information in which you're interested. Each of these clauses can be simple or complex, so
SELECT
statements as a whole can be simple or complex. However, you may rest assured that you won't find any page-long queries that take an
The general form of SELECT is as follows: SELECT what to select FROM table or tables WHERE conditions that data must satisfy;
To write a
SELECT
statement, specify what you want to retrieve and then some optional clauses. The clauses just shown (
FROM
and
WHERE
) are the most common ones, although others can be specified as well, such as
The FROM clause is usually present, but it need not be if you don't need to name any tables. For example, the following query simply displays the values of some expressions. These can be calculated without reference to any table, so no FROM clause is necessary:
mysql>
SELECT 2+2, 'Hello, world', VERSION();
+-----+--------------+----------------+
2+2 Hello, world VERSION()
+-----+--------------+----------------+
4 Hello, world 4.0.4-beta-log
+-----+--------------+----------------+
When you do use a
FROM
clause to specify a table from which to retrieve data, you'll also indicate which columns you want to see. The most "generic" form of
SELECT
uses
*
as a column
mysql>
SELECT * FROM student;
+-----------+-----+------------+
name sex student_id
+-----------+-----+------------+
Megan F 1
Joseph M 2
Kyle M 3
Katie F 4
...
The columns are displayed in the order that MySQL stores them in the table. This is the same order in which the columns are listed when you issue a DESCRIBE student statement. (The … at the end of the example indicates that the query returns more rows than are shown.) You can explicitly name the column or columns you want to see. To select just student names, do the following:
mysql>
SELECT name FROM student;
+-----------+
name
+-----------+
Megan
Joseph
Kyle
Katie
...
If you name more than one column, separate them with commas. The following statement is equivalent to SELECT * FROM student , but names each column explicitly:
mysql>
SELECT name, sex, student_id FROM student;
+-----------+-----+------------+
name sex student_id
+-----------+-----+------------+
Megan F 1
Joseph M 2
Kyle M 3
Katie F 4
...
You can name columns in any order: SELECT name, student_id FROM student; SELECT student_id, name FROM student; You can even name a column more than once if you like, although generally that's kind of pointless. MySQL allows you to select columns from more than one table at a time. We'll get to this in the "Retrieving Information from Multiple Tables" later in this chapter. Column names are not case sensitive in MySQL, so the following queries are equivalent: SELECT name, student_id FROM student; SELECT NAME, STUDENT_ID FROM student; SELECT nAmE, sTuDeNt_Id FROM student; On the other hand, database and table names may be case sensitive; it depends on the file system used on the server host. Windows filenames are not case sensitive, so a server running on Windows does not treat database and table names as case sensitive. A server running on UNIX treats database and table names as case sensitive because UNIX filenames are case sensitive. (An exception to this occurs under Mac OS X; filenames on HFS+ file systems are not case sensitive, whereas filenames on UFS file systems are.) If you want to have MySQL treat database and table names as not case sensitive, see the "Operating System Constraints on Database and Table Naming" section in Chapter 10, "The MySQL Data Directory." Specifying Retrieval CriteriaTo restrict the set of records retrieved by the SELECT statement, use a WHERE clause that specifies criteria for selecting rows. You can select rows by looking for column values that satisfy various criteria. You can look for various types of values. For example, you can search for numbers:
mysql>
SELECT * FROM score WHERE score > 95;
+------------+----------+-------+
student_id event_id score
+------------+----------+-------+
5 3 97
18 3 96
1 6 100
5 6 97
11 6 98
16 6 98
+------------+----------+-------+
Or you can look for string values. (Note that string comparisons normally are not case sensitive.) mysql> SELECT last_name, first_name FROM president -> WHERE last_name='ROOSEVELT'; +-----------+-------------+ last_name first_name +-----------+-------------+ Roosevelt Theodore Roosevelt Franklin D. +-----------+-------------+ mysql> SELECT last_name, first_name FROM president -> WHERE last_name='roosevelt'; +-----------+-------------+ last_name first_name +-----------+-------------+ Roosevelt Theodore Roosevelt Franklin D. +-----------+-------------+ Or you can look for dates: mysql> SELECT last_name, first_name, birth FROM president -> WHERE birth < '1750-1-1'; +------------+------------+------------+ last_name first_name birth +------------+------------+------------+ Washington George 1732-02-22 Adams John 1735-10-30 Jefferson Thomas 1743-04-13 +------------+------------+------------+ It's also possible to search for a combination of values: mysql> SELECT last_name, first_name, birth, state FROM president -> WHERE birth < '1750-1-1' AND (state='VA' OR state='MA'); +------------+------------+------------+-------+ last_name first_name birth state +------------+------------+------------+-------+ Washington George 1732-02-22 VA Adams John 1735-10-30 MA Jefferson Thomas 1743-04-13 VA +------------+------------+------------+-------+ Expressions in WHERE clauses can use arithmetic operators (Table 1.1), comparison operators (Table 1.2), and logical operators (Table 1.3). You can also use parentheses to group parts of an expression. Operations can be performed using constants, table columns, and function calls. We will have occasion to use several of MySQL's functions in queries throughout this tutorial, but there are far too many to show here. See Appendix C, "Operator and Function Reference," for a complete list. Table 1.1. Arithmetic Operators
Table 1.2. Comparison Operators
Table 1.3. Logical Operators
When you're formulating a query that requires logical operators, take care not to confuse the meaning of the logical AND operator with the way we use "and" in everyday speech. Suppose you want to find "presidents born in Virginia and presidents born in Massachusetts." That question is phrased using "and," which seems to imply that you'd write the query as follows: mysql> SELECT last_name, first_name, state FROM president -> WHERE state='VA' AND state='MA'; Empty set (0.36 sec) It's clear from the empty result that the query doesn't work. Why not? Because what it really means is "Select presidents who were born both in Virginia and in Massachusetts," which makes no sense. In English, you might express the query using "and," but in SQL, you connect the two conditions with OR : mysql> SELECT last_name, first_name, state FROM president -> WHERE state='VA' OR state='MA'; +------------+-------------+-------+ last_name first_name state +------------+-------------+-------+ Washington George VA Adams John MA Jefferson Thomas VA Madison James VA Monroe James VA Adams John Quincy MA Harrison William H. VA Tyler John VA Taylor Zachary VA Wilson Woodrow VA Kennedy John F MA Bush George H.W. MA +------------+-------------+-------+ This disjunction between natural language and SQL is something to be aware of, not just when formulating your own queries, but also when writing queries for other people. It's best to listen carefully as they describe what they want to retrieve, but you don't necessarily want to transcribe their descriptions into SQL using the same logical operators. For the example just described, the proper English equivalent for the query is "Select presidents who were born either in Virginia or in Massachusetts." The NULL ValueThe NULL value is special. It means "no value," so you can't assess it against known values the way you can assess two known values against each other. If you attempt to use NULL with the usual arithmetic comparison operators, the result is undefined:
mysql>
SELECT NULL < 0, NULL = 0, NULL != 0, NULL > 0;
+----------+----------+-----------+----------+
NULL < 0 NULL = 0 NULL != 0 NULL > 0
+----------+----------+-----------+----------+
NULL NULL NULL NULL
+----------+----------+-----------+----------+
In fact, you can't even compare NULL against itself because the result of comparing two unknown values cannot be determined:
mysql>
SELECT NULL = NULL, NULL != NULL;
+-------------+--------------+
NULL = NULL NULL != NULL
+-------------+--------------+
NULL NULL
+-------------+--------------+
To perform searches for NULL values, you must use a special syntax. Instead of using = or != to test for equality or inequality, use IS NULL or IS NOT NULL . For example, you can find presidents who are still living using the following query because we have represented their death dates as NULL in the president table:
mysql>
SELECT last_name, first_name FROM president WHERE death IS NULL;
+-----------+-------------+
last_name first_name
+-----------+-------------+
Ford Gerald R
Carter James E.
Reagan Ronald W.
Bush George H.W.
Clinton William J.
Bush George W.
+-----------+-------------+
To find names that have a suffix part, use IS NOT NULL : mysql> SELECT last_name, first_name, suffix -> FROM president WHERE suffix IS NOT NULL; +-----------+------------+--------+ last_name first_name suffix +-----------+------------+--------+ Carter James E. Jr. +-----------+------------+--------+ MySQL 3.23 and up has a special MySQL-specific <=> comparison operator that is true even for NULL -to- NULL comparisons. The preceding two queries can be rewritten to use this operator as follows: mysql> SELECT last_name, first_name FROM president WHERE death <=> NULL; +-----------+-------------+ last_name first_name +-----------+-------------+ Ford Gerald R Carter James E. Reagan Ronald W. Bush George H.W. Clinton William J. Bush George W. +-----------+-------------+ mysql> SELECT last_name, first_name, suffix -> FROM president WHERE NOT (suffix <=> NULL); +-----------+------------+--------+ last_name first_name suffix +-----------+------------+--------+ Carter James E. Jr. +-----------+------------+--------+ Sorting Query Results
Every MySQL user notices sooner or later that if you create a table, load some records into it, and then issue a
SELECT * FROM
tbl_name
statement, the records tend to be retrieved in the same order in which they were inserted. That makes a certain intuitive sense and, as a result, it's natural to make the assumption that retrieval of records in insertion order is a principle that you can rely on. But you can't, because the assumption is incorrect. For example, if you delete and insert rows after loading the table initially, those actions likely will change the order in which the server returns the table's rows. (Deleting records puts "holes" of unused space in the table, which MySQL
The principle that you
can
rely on is this: There is no guarantee about the order in which the server will return rows, unless you specify one yourself. To do so, add an
ORDER BY
clause to the statement that defines the sort order you want. The following query returns president names, sorted lexically (
mysql> SELECT last_name, first_name FROM president -> ORDER BY last_name; +------------+---------------+ last_name first_name +------------+---------------+ Adams John Adams John Quincy Arthur Chester A. Buchanan James ...
You can specify whether to sort a column in
mysql> SELECT last_name, first_name FROM president -> ORDER BY last_name DESC; +------------+---------------+ last_name first_name +------------+---------------+ Wilson Woodrow Washington George Van Buren Martin Tyler John ... Ascending order is the default if you specify neither ASC nor DESC for a column name in an ORDER BY clause. Query output can be sorted on multiple columns, and each column can be sorted in ascending or descending order independently of any other. The following query retrieves rows from the president table, sorts them by reverse state of birth, and by last name within each state: mysql> SELECT last_name, first_name, state FROM president -> ORDER BY state DESC, last_name ASC; +------------+---------------+-------+ last_name first_name state +------------+---------------+-------+ Arthur Chester A. VT Coolidge Calvin VT Harrison William H. VA Jefferson Thomas VA Madison James VA Monroe James VA Taylor Zachary VA Tyler John VA Washington George VA Wilson Woodrow VA Eisenhower Dwight D. TX Johnson Lyndon B. TX ... If you sort a column that may contain NULL values, where will they appear in the sort order? It depends. As of MySQL 4.0.2, they'll always sort at the beginning, even if specify DESC . Prior to that, they sort at the beginning for ascending sorts and at the end for descending sorts. If you want to ensure that NULL values will appear at a given end of the sort order, add an extra sort column that distinguishes NULL from non- NULL values. For example, to sort presidents by death date but put living presidents (those with NULL death dates) at the beginning of the sort order, use the following query: mysql> SELECT last_name, first_name, death FROM president -> ORDER BY IF(death IS NULL,0,1), death; +------------+---------------+------------+ last_name first_name death +------------+---------------+------------+ Ford Gerald R NULL Carter James E. NULL Reagan Ronald W. NULL Bush George H.W. NULL Clinton William J. NULL Bush George W. NULL Washington George 1799-12-14 Adams John 1826-07-04 ... Truman Harry S. 1972-12-26 Johnson Lyndon B. 1973-01-22 Nixon Richard M 1994-04-22 +------------+---------------+------------+ To put living presidents at the end instead, use the following query: mysql> SELECT last_name, first_name, death FROM president -> ORDER BY IF(death IS NULL,1,0), death; +------------+---------------+------------+ last_name first_name death +------------+---------------+------------+ Washington George 1799-12-14 Adams John 1826-07-04 ... Truman Harry S. 1972-12-26 Johnson Lyndon B. 1973-01-22 Nixon Richard M 1994-04-22 Ford Gerald R NULL Carter James E. NULL Reagan Ronald W. NULL Bush George H.W. NULL Clinton William J. NULL Bush George W. NULL +------------+---------------+------------+
The
IF()
function
Limiting Query Results
When a query returns many rows, but you want to see only a few of them, the
LIMIT
clause is useful,
mysql> SELECT last_name, first_name, birth FROM president -> ORDER BY birth LIMIT 5; +------------+------------+------------+ last_name first_name birth +------------+------------+------------+ Washington George 1732-02-22 Adams John 1735-10-30 Jefferson Thomas 1743-04-13 Madison James 1751-03-16 Monroe James 1758-04-28 +------------+------------+------------+ If you sort in reverse order, using ORDER BY birth DESC , you'd get the five most recently born presidents instead: mysql> SELECT last_name, first_name, birth FROM president -> ORDER BY birth DESC LIMIT 5; +-----------+-------------+------------+ last_name first_name birth +-----------+-------------+------------+ Clinton William J. 1946-08-19 Bush George W. 1946-07-06 Carter James E. 1924-10-01 Bush George H.W. 1924-06-12 Kennedy John F 1917-05-29 +-----------+-------------+------------+ LIMIT also allows you to pull a section of records out of the middle of a result set. To do this, you must specify two values. The first value is the number of records to skip at the beginning of the result set, and the second is the number of records to return. The following query is similar to the previous one but returns 5 records after skipping the first 10: mysql> SELECT last_name, first_name, birth FROM president -> ORDER BY birth LIMIT 10, 5; +-----------+------------+------------+ last_name first_name birth +-----------+------------+------------+ Tyler John 1790-03-29 Buchanan James 1791-04-23 Polk James K. 1795-11-02 Fillmore Millard 1800-01-07 Pierce Franklin 1804-11-23 +-----------+------------+------------+
To pull a
mysql> SELECT last_name, first_name FROM president -> ORDER BY RAND() LIMIT 1; +-----------+------------+ last_name first_name +-----------+------------+ McKinley William +-----------+------------+ Ordering by the result of a formula like this works for MySQL 3.23.2 and later. Prior to that, you must use a workaround that involves generating an additional column containing random numbers. See the "Overriding Optimization" section in Chapter 4 for details. Calculating and Naming Output Column ValuesMost of the preceding queries have produced output by retrieving values from tables. MySQL also allows you to calculate an output column value as the result of an expression. Expressions can be simple or complex. The following query evaluates a simple expression (a constant) and a more complex expression involving several arithmetic operations and a couple of function calls:
mysql>
SELECT 17, FORMAT(SQRT(3*3+4*4),0);
+----+-------------------------+
17 FORMAT(SQRT(3*3+4*4),0)
+----+-------------------------+
17 5
+----+-------------------------+
Expressions can also refer to table columns: mysql> SELECT CONCAT(first_name,' ',last_name),CONCAT(city,', ',state) -> FROM president; +----------------------------------+-------------------------+ CONCAT(first_name,' ',last_name) CONCAT(city,', ',state) +----------------------------------+-------------------------+ George Washington Wakefield, VA John Adams Braintree, MA Thomas Jefferson Albemarle County, VA James Madison Port Conway, VA ... This query formats president names as a single string by concatenating first and last names separated by a space and formats birthplaces as the birth cities and states separated by a comma. When you use an expression to calculate a column value, the expression becomes the column's name and is used for its heading. That can lead to a very wide column if the expression is long (as the preceding query illustrates). To deal with this, you can assign the column a different name using the AS name construct. Such names are called column aliases. The output from the previous query can be made more meaningful as follows: mysql> SELECT CONCAT(first_name,' ',last_name) AS Name, -> CONCAT(city,', ',state) AS Birthplace -> FROM president; +-----------------------+-------------------------+ Name Birthplace +-----------------------+-------------------------+ George Washington Wakefield, VA John Adams Braintree, MA Thomas Jefferson Albemarle County, VA James Madison Port Conway, VA ... If the column alias contains spaces, you'll need to put it in quotes: mysql> SELECT CONCAT(first_name,' ',last_name) AS 'President Name', -> CONCAT(city,', ',state) AS 'Place of Birth' -> FROM president; +-----------------------+-------------------------+ President Name Place of Birth +-----------------------+-------------------------+ George Washington Wakefield, VA John Adams Braintree, MA Thomas Jefferson Albemarle County, VA James Madison Port Conway, VA ... Working with DatesThe principal thing to keep in mind when it comes to dates in MySQL is that it always represents them with the year first. July 27, 2002 is represented as '2002-07-27' . It is not represented as '07-27-2002' or as '27-07-2002' , as you might be more used to writing. MySQL provides several ways to perform operations on dates. Some of the things you can do are as follows:
Some examples of these operations follow. To look for particular dates, either by exact value or compared to another value, compare a DATE column to the value you're interested in: mysql> SELECT * FROM event WHERE date = '2002-10-01'; +------------+------+----------+ date type event_id +------------+------+----------+ 2002-10-01 T 6 +------------+------+----------+ mysql> SELECT last_name, first_name, death -> FROM president -> WHERE death >= '1970-01-01' AND death < '1980-01-01'; +-----------+------------+------------+ last_name first_name death +-----------+------------+------------+ Truman Harry S. 1972-12-26 Johnson Lyndon B. 1973-01-22 +-----------+------------+------------+ To test or retrieve parts of dates, you can use functions such as YEAR() , MONTH() , or DAYOFMONTH() . For example, I can find presidents who were born in the same month that I was (March) by looking for dates with a month value of 3: mysql> SELECT last_name, first_name, birth -> FROM president WHERE MONTH(birth) = 3; +-----------+------------+------------+ last_name first_name birth +-----------+------------+------------+ Madison James 1751-03-16 Jackson Andrew 1767-03-15 Tyler John 1790-03-29 Cleveland Grover 1837-03-18 +-----------+------------+------------+ The query can also be written in terms of the month name: mysql> SELECT last_name, first_name, birth -> FROM president WHERE MONTHNAME(birth) = 'March'; +-----------+------------+------------+ last_name first_name birth +-----------+------------+------------+ Madison James 1751-03-16 Jackson Andrew 1767-03-15 Tyler John 1790-03-29 Cleveland Grover 1837-03-18 +-----------+------------+------------+ To be more specific—down to the day—I can combine tests for MONTH() and DAYOFMONTH() to find presidents born on my birthday: mysql> SELECT last_name, first_name, birth -> FROM president WHERE MONTH(birth) = 3 AND DAYOFMONTH(birth) = 29; +-----------+------------+------------+ last_name first_name birth +-----------+------------+------------+ Tyler John 1790-03-29 +-----------+------------+------------+ This is the kind of query you'd use to generate one of those "these famous people have birthdays today" lists such as you see in the Entertainment section of your newspaper. However, for the current date, you don't have to plug in a specific day the way the previous query did. To check for presidents born today, no matter what day of the year today is, compare their birthdays to the month and day parts of CURDATE() , which always returns the current date: SELECT last_name, first_name, birth FROM president WHERE MONTH(birth) = MONTH(CURDATE()) AND DAYOFMONTH(birth) = DAYOFMONTH(CURDATE()); You can subtract one date from another, which allows you to find the interval between dates. For example, to determine which presidents lived the longest, subtract the birth date from the death date. To do this, convert birth and death to days using the TO_DAYS() function and take the difference: mysql> SELECT last_name, first_name, birth, death, -> TO_DAYS(death) - TO_DAYS(birth) AS age -> FROM president WHERE death IS NOT NULL -> ORDER BY age DESC LIMIT 5; +-----------+------------+------------+------------+-------+ last_name first_name birth death age +-----------+------------+------------+------------+-------+ Adams John 1735-10-30 1826-07-04 33119 Hoover Herbert C. 1874-08-10 1964-10-20 32943 Truman Harry S. 1884-05-08 1972-12-26 32373 Madison James 1751-03-16 1836-06-28 31150 Jefferson Thomas 1743-04-13 1826-07-04 30397 +-----------+------------+------------+------------+-------+ To convert age in days to approximate age in years, divide by 365 (the FLOOR() function used here chops off any fractional part from the age to produce an integer): mysql> SELECT last_name, first_name, birth, death, -> FLOOR((TO_DAYS(death) - TO_DAYS(birth))/365) AS age -> FROM president WHERE death IS NOT NULL -> ORDER BY age DESC LIMIT 5; +-----------+------------+------------+------------+------+ last_name first_name birth death age +-----------+------------+------------+------------+------+ Adams John 1735-10-30 1826-07-04 90 Hoover Herbert C. 1874-08-10 1964-10-20 90 Truman Harry S. 1884-05-08 1972-12-26 88 Madison James 1751-03-16 1836-06-28 85 Jefferson Thomas 1743-04-13 1826-07-04 83 +-----------+------------+------------+------------+------+
In this particular case, the age values happen to
mysql> SELECT last_name, first_name, birth, death, -> (YEAR(death) - YEAR(birth)) - IF(RIGHT(death,5) < RIGHT(birth,5),1,0) -> AS age -> FROM president WHERE death IS NOT NULL -> ORDER BY age DESC LIMIT 5; +-----------+------------+------------+------------+------+ last_name first_name birth death age +-----------+------------+------------+------------+------+ Adams John 1735-10-30 1826-07-04 90 Hoover Herbert C. 1874-08-10 1964-10-20 90 Truman Harry S. 1884-05-08 1972-12-26 88 Madison James 1751-03-16 1836-06-28 85 Jefferson Thomas 1743-04-13 1826-07-04 83 +-----------+------------+------------+------------+------+
The
IF()
expression used here
Taking a difference between dates is also useful for determining how far dates are from some reference date. That's how you can tell which Historical League members need to renew their memberships soon. Compute the difference between their expiration dates and the current date, and if it's less than some threshold value, a renewal will soon be needed. The following query finds memberships that are due for renewal within 60 days: SELECT last_name, first_name, expiration FROM member WHERE (TO_DAYS(expiration) - TO_DAYS(CURDATE())) < 60; To calculate one date from another, you can use DATE_ADD() or DATE_SUB() . These functions take a date and an interval and produce a new date, for example: mysql> SELECT DATE_ADD('1970-1-1', INTERVAL 10 YEAR); +----------------------------------------+ DATE_ADD('1970-1-1', INTERVAL 10 YEAR) +----------------------------------------+ 1980-01-01 +----------------------------------------+ mysql> SELECT DATE_SUB('1970-1-1', INTERVAL 10 YEAR); +----------------------------------------+ DATE_SUB('1970-1-1', INTERVAL 10 YEAR) +----------------------------------------+ 1960-01-01 +----------------------------------------+ A query shown earlier in this section selected presidents who died during the 1970s, using literal dates for the endpoints of the selection range. That query can be rewritten to use a literal starting date and an ending date calculated from the starting date and an interval: mysql> SELECT last_name, first_name, death -> FROM president -> WHERE death >= '1970-1-1' -> AND death < DATE_ADD('1970-1-1', INTERVAL 10 YEAR); +-----------+------------+------------+ last_name first_name death +-----------+------------+------------+ Truman Harry S. 1972-12-26 Johnson Lyndon B. 1973-01-22 +-----------+------------+------------+ The membership-renewal query can be written in terms of DATE_ADD() : SELECT last_name, first_name, expiration FROM member WHERE expiration < DATE_ADD(CURDATE(), INTERVAL 60 DAY);
Earlier in this chapter, a query was presented for determining which of a dentist's
SELECT last_name, first_name, last_visit FROM patient WHERE last_visit < DATE_SUB(CURDATE(),INTERVAL 6 MONTH);
That query may not have
Pattern MatchingMySQL supports pattern matching operations, which allows you to select records without supplying an exact comparison value. To perform a pattern match, you use special operators ( LIKE and NOT LIKE ), and you specify a string containing wild card characters. The character ' _ ' matches any single character, and ' % ' matches any sequence of characters (including an empty sequence). Pattern matches using LIKE or NOT LIKE are not case sensitive. The following pattern matches last names that begin with a ' W ' or ' w ' character: mysql> SELECT last_name, first_name FROM president -> WHERE last_name LIKE 'W%'; +------------+------------+ last_name first_name +------------+------------+ Washington George Wilson Woodrow +------------+------------+ On the other hand, the following pattern match is erroneous: mysql> SELECT last_name, first_name FROM president -> WHERE last_name = 'W%'; Empty set (0.00 sec) The query demonstrates a common error, which is to use a pattern with an arithmetic comparison operator. The only way for such a comparison to succeed is for the column to contain exactly the string 'W%' or 'w%' . This pattern matches last names that contain ' W ' or ' w ' anywhere in the name, not just at the beginning: mysql> SELECT last_name, first_name FROM president -> WHERE last_name LIKE '%W%'; +------------+------------+ last_name first_name +------------+------------+ Washington George Wilson Woodrow Eisenhower Dwight D. +------------+------------+ This pattern matches last names that contain exactly four characters: mysql> SELECT last_name, first_name FROM president -> WHERE last_name LIKE '____'; +-----------+-------------+ last_name first_name +-----------+-------------+ Polk James K. Taft William H. Ford Gerald R Bush George H.W. Bush George W. +-----------+-------------+ MySQL also provides another form of pattern matching based on regular expressions, which are described in the section that discusses the REGEXP operator in Appendix C.
Setting and Using SQL
|