Whole document tree
3 Introduction to MySQL: A MySQL Tutorial
This chapter provides a tutorial introduction to MySQL by showing
how to use the
To see a list of options provided by shell> mysql --help
This chapter assumes that This chapter describes the entire process of setting up and using a database. If you are interested only in accessing an already-existing database, you may want to skip over the sections that describe how to create the database and the tables it contains. Because this chapter is tutorial in nature, many details are necessarily left out. Consult the relevant sections of the manual for more information on the topics covered here. 3.1 Connecting to and Disconnecting from the Server
To connect to the server, you'll usually need to provide a MySQL
user name when you invoke shell> mysql -h host -u user -p Enter password: ********
The
If that works, you should see some introductory information followed by a
shell> mysql -h host -u user -p Enter password: ******** Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 459 to server version: 3.22.20a-log Type 'help' for help. mysql>
The prompt tells you that
Some MySQL installations allow users to connect as the anonymous
(unnamed) user to the server running on the local host. If this is the case
on your machine, you should be able to connect to that server by invoking
shell> mysql
After you have connected successfully, you can disconnect any time by typing
mysql> QUIT Bye You can also disconnect by pressing Control-D.
Most examples in the following sections assume you are connected to the
server. They indicate this by the 3.2 Entering Queries
Make sure you are connected to the server, as discussed in the previous
section. Doing so will not in itself select any database to work with, but
that's okay. At this point, it's more important to find out a little about
how to issue queries than to jump right in creating tables, loading data
into them, and retrieving data from them. This section describes the basic
principles of entering commands, using several queries you can try out to
familiarize yourself with how
Here's a simple command that asks the server to tell you its version number
and the current date. Type it in as shown below following the mysql> SELECT VERSION(), CURRENT_DATE; +--------------+--------------+ | version() | CURRENT_DATE | +--------------+--------------+ | 3.22.20a-log | 1999-03-19 | +--------------+--------------+ 1 row in set (0.01 sec) mysql>
This query illustrates several things about
Keywords may be entered in any lettercase. The following queries are equivalent: mysql> SELECT VERSION(), CURRENT_DATE; mysql> select version(), current_date; mysql> SeLeCt vErSiOn(), current_DATE;
Here's another query. It demonstrates that you can use mysql> SELECT SIN(PI()/4), (4+1)*5; +-------------+---------+ | SIN(PI()/4) | (4+1)*5 | +-------------+---------+ | 0.707107 | 25 | +-------------+---------+ The commands shown thus far have been relatively short, single-line statements. You can even enter multiple statements on a single line. Just end each one with a semicolon: mysql> SELECT VERSION(); SELECT NOW(); +--------------+ | version() | +--------------+ | 3.22.20a-log | +--------------+ +---------------------+ | NOW() | +---------------------+ | 1999-03-19 00:15:33 | +---------------------+
A command need not be given all on a single line, so lengthy commands that
require several lines are not a problem. Here's a simple multiple-line statement: mysql> SELECT -> USER() -> , -> CURRENT_DATE; +--------------------+--------------+ | USER() | CURRENT_DATE | +--------------------+--------------+ | joesmith@localhost | 1999-03-18 | +--------------------+--------------+
In this example, notice how the prompt changes from
If you decide you don't want to execute a command that you are in the
process of entering, cancel it by typing mysql> SELECT -> USER() -> \c mysql>
Here, too, notice the prompt. It switches back to
The following table shows each of the prompts you may see and summarizes what
they mean about the state that
Multiple-line statements commonly occur by accident when you intend to
issue a command on a single line, but forget the terminating semicolon. In
this case, mysql> SELECT USER() ->
If this happens to you (you think you've entered a statement but the only
response is a mysql> SELECT USER() -> ; +--------------------+ | USER() | +--------------------+ | joesmith@localhost | +--------------------+
The mysql> SELECT * FROM my_table WHERE name = "Smith AND age < 30; ">
If you enter this
At this point, what do you do? The simplest thing is to cancel the command.
However, you cannot just type mysql> SELECT * FROM my_table WHERE name = "Smith AND age < 30; "> "\c mysql>
The prompt changes back to
It's important to know what the 3.3 Creating and Using a Database
Now that you know how to enter commands, it's time to access a database. Suppose you have several pets in your home (your menagerie) and you'd like to keep track of various types of information about them. You can do so by creating tables to hold your data and loading them with the desired information. Then you can answer different sorts of questions about your animals by retrieving data from the tables. This section shows you how to:
The menagerie database will be simple (deliberately), but it is not difficult
to think of real-world situations in which a similar type of database might
be used. For example, a database like this could be used by a farmer to keep
track of livestock, or by a veterinarian to keep track of patient records.
A menagerie distribution containing some of the queries and sample data used
in the following sections can be obtained from the MySQL Web site.
It's available in either
compressed
Use the mysql> SHOW DATABASES; +----------+ | Database | +----------+ | mysql | | test | | tmp | +----------+
The list of databases is probably different on your machine, but the
If the mysql> USE test Database changed
Note that
You can use the mysql> GRANT ALL ON menagerie.* TO your_mysql_name;
where 3.3.1 Creating and Selecting a DatabaseIf the administrator creates your database for you when setting up your permissions, you can begin using it. Otherwise, you need to create it yourself: mysql> CREATE DATABASE menagerie;
Under Unix, database names are case sensitive (unlike SQL keywords), so you
must always refer to your database as
Creating a database does not select it for use; you must do that explicitly.
To make mysql> USE menagerie Database changed
Your database needs to be created only once, but you must select it for use
each time you begin a shell> mysql -h host -u user -p menagerie Enter password: ********
Note that 3.3.2 Creating a Table
Creating the database is the easy part, but at this point it's empty, as
mysql> SHOW TABLES; Empty set (0.00 sec) The harder part is deciding what the structure of your database should be: what tables you will need and what columns will be in each of them.
You'll want a table that contains a record for each of your pets. This can
be called the How about age? That might be of interest, but it's not a good thing to store in a database. Age changes as time passes, which means you'd have to update your records often. Instead, it's better to store a fixed value such as date of birth. Then, whenever you need age, you can calculate it as the difference between the current date and the birth date. MySQL provides functions for doing date arithmetic, so this is not difficult. Storing birth date rather than age has other advantages, too:
You can probably think of other types of information that would be useful in
the
Use a mysql> CREATE TABLE pet (name VARCHAR(20), owner VARCHAR(20), -> species VARCHAR(20), sex CHAR(1), birth DATE, death DATE);
Animal sex can be represented in a variety of ways, for example,
The use of the
Now that you have created a table, mysql> SHOW TABLES; +---------------------+ | Tables in menagerie | +---------------------+ | pet | +---------------------+
To verify that your table was created the way you expected, use
a mysql> DESCRIBE pet; +---------+-------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +---------+-------------+------+-----+---------+-------+ | name | varchar(20) | YES | | NULL | | | owner | varchar(20) | YES | | NULL | | | species | varchar(20) | YES | | NULL | | | sex | char(1) | YES | | NULL | | | birth | date | YES | | NULL | | | death | date | YES | | NULL | | +---------+-------------+------+-----+---------+-------+
You can use 3.3.3 Loading Data into a Table
After creating your table, you need to populate it. The
Suppose your pet records can be described as shown below.
(Observe that MySQL expects dates in
Because you are beginning with an empty table, an easy way to populate it is to create a text file containing a row for each of your animals, then load the contents of the file into the table with a single statement.
You could create a text file `pet.txt' containing one record per line,
with values separated by tabs, and given in the order in which the columns
were listed in the
To load the text file `pet.txt' into the mysql> LOAD DATA LOCAL INFILE "pet.txt" INTO TABLE pet;
You can specify the column value separator and end of line marker explicitly
in the
When you want to add new records one at a time, the mysql> INSERT INTO pet -> VALUES ('Puffball','Diane','hamster','f','1999-03-30',NULL);
Note that string and date values are specified as quoted strings here. Also,
with
From this example, you should be able to see that there would be a lot more
typing involved to load
your records initially using several 3.3.4 Retrieving Information from a Table
The SELECT what_to_select FROM which_table WHERE conditions_to_satisfy
3.3.4.1 Selecting All Data
The simplest form of mysql> SELECT * FROM pet; +----------+--------+---------+------+------------+------------+ | name | owner | species | sex | birth | death | +----------+--------+---------+------+------------+------------+ | Fluffy | Harold | cat | f | 1993-02-04 | NULL | | Claws | Gwen | cat | m | 1994-03-17 | NULL | | Buffy | Harold | dog | f | 1989-05-13 | NULL | | Fang | Benny | dog | m | 1990-08-27 | NULL | | Bowser | Diane | dog | m | 1998-08-31 | 1995-07-29 | | Chirpy | Gwen | bird | f | 1998-09-11 | NULL | | Whistler | Gwen | bird | NULL | 1997-12-09 | NULL | | Slim | Benny | snake | m | 1996-04-29 | NULL | | Puffball | Diane | hamster | f | 1999-03-30 | NULL | +----------+--------+---------+------+------------+------------+
This form of There are are least a couple of ways to fix this:
As shown above, it is easy to retrieve an entire table. But typically you don't want to do that, particularly when the table becomes large. Instead, you're usually more interested in answering a particular question, in which case you specify some constraints on the information you want. Let's look at some selection queries in terms of questions about your pets that they answer. 3.3.4.2 Selecting Particular RowsYou can select only particular rows from your table. For example, if you want to verify the change that you made to Bowser's birth date, select Bowser's record like this: mysql> SELECT * FROM pet WHERE name = "Bowser"; +--------+-------+---------+------+------------+------------+ | name | owner | species | sex | birth | death | +--------+-------+---------+------+------------+------------+ | Bowser | Diane | dog | m | 1989-08-31 | 1995-07-29 | +--------+-------+---------+------+------------+------------+ The output confirms that the year is correctly recorded now as 1989, not 1998.
String comparisons are normally case insensitive, so you can specify the
name as
You can specify conditions on any column, not just mysql> SELECT * FROM pet WHERE birth >= "1998-1-1"; +----------+-------+---------+------+------------+-------+ | name | owner | species | sex | birth | death | +----------+-------+---------+------+------------+-------+ | Chirpy | Gwen | bird | f | 1998-09-11 | NULL | | Puffball | Diane | hamster | f | 1999-03-30 | NULL | +----------+-------+---------+------+------------+-------+ You can combine conditions, for example, to locate female dogs: mysql> SELECT * FROM pet WHERE species = "dog" AND sex = "f"; +-------+--------+---------+------+------------+-------+ | name | owner | species | sex | birth | death | +-------+--------+---------+------+------------+-------+ | Buffy | Harold | dog | f | 1989-05-13 | NULL | +-------+--------+---------+------+------------+-------+
The preceding query uses the mysql> SELECT * FROM pet WHERE species = "snake" OR species = "bird"; +----------+-------+---------+------+------------+-------+ | name | owner | species | sex | birth | death | +----------+-------+---------+------+------------+-------+ | Chirpy | Gwen | bird | f | 1998-09-11 | NULL | | Whistler | Gwen | bird | NULL | 1997-12-09 | NULL | | Slim | Benny | snake | m | 1996-04-29 | NULL | +----------+-------+---------+------+------------+-------+
mysql> SELECT * FROM pet WHERE (species = "cat" AND sex = "m") -> OR (species = "dog" AND sex = "f"); +-------+--------+---------+------+------------+-------+ | name | owner | species | sex | birth | death | +-------+--------+---------+------+------------+-------+ | Claws | Gwen | cat | m | 1994-03-17 | NULL | | Buffy | Harold | dog | f | 1989-05-13 | NULL | +-------+--------+---------+------+------------+-------+ 3.3.4.3 Selecting Particular Columns
If you don't want to see entire rows from your table, just name the columns
in which you're interested, separated by commas. For example, if you want to
know when your animals were born, select the mysql> SELECT name, birth FROM pet; +----------+------------+ | name | birth | +----------+------------+ | Fluffy | 1993-02-04 | | Claws | 1994-03-17 | | Buffy | 1989-05-13 | | Fang | 1990-08-27 | | Bowser | 1989-08-31 | | Chirpy | 1998-09-11 | | Whistler | 1997-12-09 | | Slim | 1996-04-29 | | Puffball | 1999-03-30 | +----------+------------+ To find out who owns pets, use this query: mysql> SELECT owner FROM pet; +--------+ | owner | +--------+ | Harold | | Gwen | | Harold | | Benny | | Diane | | Gwen | | Gwen | | Benny | | Diane | +--------+
However, notice that the query simply retrieves the mysql> SELECT DISTINCT owner FROM pet; +--------+ | owner | +--------+ | Benny | | Diane | | Gwen | | Harold | +--------+
You can use a mysql> SELECT name, species, birth FROM pet -> WHERE species = "dog" OR species = "cat"; +--------+---------+------------+ | name | species | birth | +--------+---------+------------+ | Fluffy | cat | 1993-02-04 | | Claws | cat | 1994-03-17 | | Buffy | dog | 1989-05-13 | | Fang | dog | 1990-08-27 | | Bowser | dog | 1989-08-31 | +--------+---------+------------+ 3.3.4.4 Sorting Rows
You may have noticed in the preceding examples that the result rows are
displayed in no particular order. However, it's often easier to examine
query output when the rows are sorted in some meaningful way. To sort a
result, use an Here are animal birthdays, sorted by date: mysql> SELECT name, birth FROM pet ORDER BY birth; +----------+------------+ | name | birth | +----------+------------+ | Buffy | 1989-05-13 | | Bowser | 1989-08-31 | | Fang | 1990-08-27 | | Fluffy | 1993-02-04 | | Claws | 1994-03-17 | | Slim | 1996-04-29 | | Whistler | 1997-12-09 | | Chirpy | 1998-09-11 | | Puffball | 1999-03-30 | +----------+------------+
To sort in reverse order, add the mysql> SELECT name, birth FROM pet ORDER BY birth DESC; +----------+------------+ | name | birth | +----------+------------+ | Puffball | 1999-03-30 | | Chirpy | 1998-09-11 | | Whistler | 1997-12-09 | | Slim | 1996-04-29 | | Claws | 1994-03-17 | | Fluffy | 1993-02-04 | | Fang | 1990-08-27 | | Bowser | 1989-08-31 | | Buffy | 1989-05-13 | +----------+------------+ You can sort on multiple columns. For example, to sort by type of animal, then by birth date within animal type with youngest animals first, use the following query: mysql> SELECT name, species, birth FROM pet ORDER BY species, birth DESC; +----------+---------+------------+ | name | species | birth | +----------+---------+------------+ | Chirpy | bird | 1998-09-11 | | Whistler | bird | 1997-12-09 | | Claws | cat | 1994-03-17 | | Fluffy | cat | 1993-02-04 | | Fang | dog | 1990-08-27 | | Bowser | dog | 1989-08-31 | | Buffy | dog | 1989-05-13 | | Puffball | hamster | 1999-03-30 | | Slim | snake | 1996-04-29 | +----------+---------+------------+
Note that the 3.3.4.5 Date CalculationsMySQL provides several functions that you can use to perform calculations on dates, for example, to calculate ages or extract parts of dates. To determine how many years old each of your pets is, compute the difference in the year part of the current date and the birth date, then subtract one if the current date occurs earlier in the calendar year than the birth date. The following query shows, for each pet, the birth date, the current date, and the age in years. mysql> SELECT name, birth, CURRENT_DATE, -> (YEAR(CURRENT_DATE)-YEAR(birth)) -> - (RIGHT(CURRENT_DATE,5)<RIGHT(birth,5)) -> AS age -> FROM pet; +----------+------------+--------------+------+ | name | birth | CURRENT_DATE | age | +----------+------------+--------------+------+ | Fluffy | 1993-02-04 | 2001-08-29 | 8 | | Claws | 1994-03-17 | 2001-08-29 | 7 | | Buffy | 1989-05-13 | 2001-08-29 | 12 | | Fang | 1990-08-27 | 2001-08-29 | 11 | | Bowser | 1989-08-31 | 2001-08-29 | 11 | | Chirpy | 1998-09-11 | 2001-08-29 | 2 | | Whistler | 1997-12-09 | 2001-08-29 | 3 | | Slim | 1996-04-29 | 2001-08-29 | 5 | | Puffball | 1999-03-30 | 2001-08-29 | 2 | +----------+------------+--------------+------+
Here,
The query works, but the result could be scanned more easily if the rows
were presented in some order. This can be done by adding an mysql> SELECT name, birth, CURRENT_DATE, -> (YEAR(CURRENT_DATE)-YEAR(birth)) -> - (RIGHT(CURRENT_DATE,5)<RIGHT(birth,5)) -> AS age -> FROM pet ORDER BY name; +----------+------------+--------------+------+ | name | birth | CURRENT_DATE | age | +----------+------------+--------------+------+ | Bowser | 1989-08-31 | 2001-08-29 | 11 | | Buffy | 1989-05-13 | 2001-08-29 | 12 | | Chirpy | 1998-09-11 | 2001-08-29 | 2 | | Claws | 1994-03-17 | 2001-08-29 | 7 | | Fang | 1990-08-27 | 2001-08-29 | 11 | | Fluffy | 1993-02-04 | 2001-08-29 | 8 | | Puffball | 1999-03-30 | 2001-08-29 | 2 | | Slim | 1996-04-29 | 2001-08-29 | 5 | | Whistler | 1997-12-09 | 2001-08-29 | 3 | +----------+------------+--------------+------+
To sort the output by mysql> SELECT name, birth, CURRENT_DATE, -> (YEAR(CURRENT_DATE)-YEAR(birth)) -> - (RIGHT(CURRENT_DATE,5)<RIGHT(birth,5)) -> AS age -> FROM pet ORDER BY age; +----------+------------+--------------+------+ | name | birth | CURRENT_DATE | age | +----------+------------+--------------+------+ | Chirpy | 1998-09-11 | 2001-08-29 | 2 | | Puffball | 1999-03-30 | 2001-08-29 | 2 | | Whistler | 1997-12-09 | 2001-08-29 | 3 | | Slim | 1996-04-29 | 2001-08-29 | 5 | | Claws | 1994-03-17 | 2001-08-29 | 7 | | Fluffy | 1993-02-04 | 2001-08-29 | 8 | | Fang | 1990-08-27 | 2001-08-29 | 11 | | Bowser | 1989-08-31 | 2001-08-29 | 11 | | Buffy | 1989-05-13 | 2001-08-29 | 12 | +----------+------------+--------------+------+
A similar query can be used to determine age at death for animals that have
died. You determine which animals these are by checking whether or not the
mysql> SELECT name, birth, death, -> (YEAR(death)-YEAR(birth)) - (RIGHT(death,5)<RIGHT(birth,5)) -> AS age -> FROM pet WHERE death IS NOT NULL ORDER BY age; +--------+------------+------------+------+ | name | birth | death | age | +--------+------------+------------+------+ | Bowser | 1989-08-31 | 1995-07-29 | 5 | +--------+------------+------------+------+
The query uses
What if you want to know which animals have birthdays next month? For this
type of calculation, year and day are irrelevant; you simply want to extract
the month part of the mysql> SELECT name, birth, MONTH(birth) FROM pet; +----------+------------+--------------+ | name | birth | MONTH(birth) | +----------+------------+--------------+ | Fluffy | 1993-02-04 | 2 | | Claws | 1994-03-17 | 3 | | Buffy | 1989-05-13 | 5 | | Fang | 1990-08-27 | 8 | | Bowser | 1989-08-31 | 8 | | Chirpy | 1998-09-11 | 9 | | Whistler | 1997-12-09 | 12 | | Slim | 1996-04-29 | 4 | | Puffball | 1999-03-30 | 3 | +----------+------------+--------------+
Finding animals with birthdays in the upcoming month is easy, too. Suppose
the current month is April. Then the month value is mysql> SELECT name, birth FROM pet WHERE MONTH(birth) = 5; +-------+------------+ | name | birth | +-------+------------+ | Buffy | 1989-05-13 | +-------+------------+
There is a small complication if the current month is December, of course.
You don't just add one to the month number (
You can even write the query so that it works no matter what the current
month is. That way you don't have to use a particular month number
in the query. mysql> SELECT name, birth FROM pet -> WHERE MONTH(birth) = MONTH(DATE_ADD(NOW(), INTERVAL 1 MONTH));
A different way to accomplish the same task is to add mysql> SELECT name, birth FROM pet -> WHERE MONTH(birth) = MOD(MONTH(NOW()), 12) + 1;
Note that 3.3.4.6 Working with
|
Fluffy | 1995-05-15 | litter | 4 kittens, 3 female, 1 male |
Buffy | 1993-06-23 | litter | 5 puppies, 2 female, 3 male |
Buffy | 1994-06-19 | litter | 3 puppies, 3 female |
Chirpy | 1999-03-21 | vet | needed beak straightened |
Slim | 1997-08-03 | vet | broken rib |
Bowser | 1991-10-12 | kennel | |
Fang | 1991-10-12 | kennel | |
Fang | 1998-08-28 | birthday | Gave him a new chew toy |
Claws | 1998-03-17 | birthday | Gave him a new flea collar |
Whistler | 1998-12-09 | birthday | First birthday |
Load the records like this:
mysql> LOAD DATA LOCAL INFILE "event.txt" INTO TABLE event;
Based on what you've learned from the queries you've run on the pet
table, you should be able to perform retrievals on the records in the
event
table; the principles are the same. But when is the
event
table by itself insufficient to answer questions you might ask?
Suppose you want to find out the ages of each pet when they had their
litters. The event
table indicates when this occurred, but to
calculate the age of the mother, you need her birth date. Because that is
stored in the pet
table, you need both tables for the query:
mysql> SELECT pet.name, (TO_DAYS(date) - TO_DAYS(birth))/365 AS age, remark -> FROM pet, event -> WHERE pet.name = event.name AND type = "litter"; +--------+------+-----------------------------+ | name | age | remark | +--------+------+-----------------------------+ | Fluffy | 2.27 | 4 kittens, 3 female, 1 male | | Buffy | 4.12 | 5 puppies, 2 female, 3 male | | Buffy | 5.10 | 3 puppies, 3 female | +--------+------+-----------------------------+
There are several things to note about this query:
FROM
clause lists two tables because the query needs to pull
information from both of them.
name
column. The query uses
WHERE
clause to match up records in the two tables based on the
name
values.
name
column occurs in both tables, you must be specific
about which table you mean when referring to the column. This is done
by prepending the table name to the column name.
You need not have two different tables to perform a join. Sometimes it is
useful to join a table to itself, if you want to compare records in a table
to other records in that same table. For example, to find breeding pairs
among your pets, you can join the pet
table with itself to pair up
males and females of like species:
mysql> SELECT p1.name, p1.sex, p2.name, p2.sex, p1.species -> FROM pet AS p1, pet AS p2 -> WHERE p1.species = p2.species AND p1.sex = "f" AND p2.sex = "m"; +--------+------+--------+------+---------+ | name | sex | name | sex | species | +--------+------+--------+------+---------+ | Fluffy | f | Claws | m | cat | | Buffy | f | Fang | m | dog | | Buffy | f | Bowser | m | dog | +--------+------+--------+------+---------+
In this query, we specify aliases for the table name in order to refer to the columns and keep straight which instance of the table each column reference is associated with.
What if you forget the name of a database or table, or what the structure of a given table is (for example, what its columns are called)? MySQL addresses this problem through several statements that provide information about the databases and tables it supports.
You have already seen SHOW DATABASES
, which lists the databases
managed by the server. To find out which database is currently selected,
use the DATABASE()
function:
mysql> SELECT DATABASE(); +------------+ | DATABASE() | +------------+ | menagerie | +------------+
If you haven't selected any database yet, the result is blank.
To find out what tables the current database contains (for example, when you're not sure about the name of a table), use this command:
mysql> SHOW TABLES; +---------------------+ | Tables in menagerie | +---------------------+ | event | | pet | +---------------------+
If you want to find out about the structure of a table, the DESCRIBE
command is useful; it displays information about each of a table's columns:
mysql> DESCRIBE pet; +---------+-------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +---------+-------------+------+-----+---------+-------+ | name | varchar(20) | YES | | NULL | | | owner | varchar(20) | YES | | NULL | | | species | varchar(20) | YES | | NULL | | | sex | char(1) | YES | | NULL | | | birth | date | YES | | NULL | | | death | date | YES | | NULL | | +---------+-------------+------+-----+---------+-------+
Field
indicates the column name, Type
is the data type for
the column, Null
indicates whether or not the column can contain
NULL
values, Key
indicates whether or not the column is
indexed, and Default
specifies the column's default value.
If you have indexes on a table,
SHOW INDEX FROM tbl_name
produces information about them.
Here are examples of how to solve some common problems with MySQL.
Some of the examples use the table shop
to hold the price of each
article (item number) for certain traders (dealers). Supposing that each
trader has a single fixed price per article, then (article
,
dealer
) is a primary key for the records.
Start the command line tool mysql
and select a database:
mysql your-database-name
(In most MySQL installations, you can use the database-name 'test').
You can create the example table as:
CREATE TABLE shop ( article INT(4) UNSIGNED ZEROFILL DEFAULT '0000' NOT NULL, dealer CHAR(20) DEFAULT '' NOT NULL, price DOUBLE(16,2) DEFAULT '0.00' NOT NULL, PRIMARY KEY(article, dealer)); INSERT INTO shop VALUES (1,'A',3.45),(1,'B',3.99),(2,'A',10.99),(3,'B',1.45),(3,'C',1.69), (3,'D',1.25),(4,'D',19.95);
Okay, so the example data is:
mysql> SELECT * FROM shop; +---------+--------+-------+ | article | dealer | price | +---------+--------+-------+ | 0001 | A | 3.45 | | 0001 | B | 3.99 | | 0002 | A | 10.99 | | 0003 | B | 1.45 | | 0003 | C | 1.69 | | 0003 | D | 1.25 | | 0004 | D | 19.95 | +---------+--------+-------+
``What's the highest item number?''
SELECT MAX(article) AS article FROM shop +---------+ | article | +---------+ | 4 | +---------+
``Find number, dealer, and price of the most expensive article.''
In ANSI SQL this is easily done with a sub-query:
SELECT article, dealer, price FROM shop WHERE price=(SELECT MAX(price) FROM shop)
In MySQL (which does not yet have sub-selects), just do it in two steps:
SELECT
statement.
SELECT article, dealer, price FROM shop WHERE price=19.95
Another solution is to sort all rows descending by price and only
get the first row using the MySQL specific LIMIT
clause:
SELECT article, dealer, price FROM shop ORDER BY price DESC LIMIT 1
NOTE: If there are several most expensive articles (for example, each 19.95)
the LIMIT
solution shows only one of them!
``What's the highest price per article?''
SELECT article, MAX(price) AS price FROM shop GROUP BY article +---------+-------+ | article | price | +---------+-------+ | 0001 | 3.99 | | 0002 | 10.99 | | 0003 | 1.69 | | 0004 | 19.95 | +---------+-------+
``For each article, find the dealer(s) with the most expensive price.''
In ANSI SQL, I'd do it with a sub-query like this:
SELECT article, dealer, price FROM shop s1 WHERE price=(SELECT MAX(s2.price) FROM shop s2 WHERE s1.article = s2.article);
In MySQL it's best do it in several steps:
This can easily be done with a temporary table:
CREATE TEMPORARY TABLE tmp ( article INT(4) UNSIGNED ZEROFILL DEFAULT '0000' NOT NULL, price DOUBLE(16,2) DEFAULT '0.00' NOT NULL); LOCK TABLES shop read; INSERT INTO tmp SELECT article, MAX(price) FROM shop GROUP BY article; SELECT shop.article, dealer, shop.price FROM shop, tmp WHERE shop.article=tmp.article AND shop.price=tmp.price; UNLOCK TABLES; DROP TABLE tmp;
If you don't use a TEMPORARY
table, you must also lock the 'tmp' table.
``Can it be done with a single query?''
Yes, but only by using a quite inefficient trick that I call the ``MAX-CONCAT trick'':
SELECT article, SUBSTRING( MAX( CONCAT(LPAD(price,6,'0'),dealer) ), 7) AS dealer, 0.00+LEFT( MAX( CONCAT(LPAD(price,6,'0'),dealer) ), 6) AS price FROM shop GROUP BY article; +---------+--------+-------+ | article | dealer | price | +---------+--------+-------+ | 0001 | B | 3.99 | | 0002 | A | 10.99 | | 0003 | C | 1.69 | | 0004 | D | 19.95 | +---------+--------+-------+
The last example can, of course, be made a bit more efficient by doing the splitting of the concatenated column in the client.
You can use MySQL user variables to remember results without having to store them in temporary variables in the client. See section 6.1.4 User Variables.
For example, to find the articles with the highest and lowest price you can do:
select @min_price:=min(price),@max_price:=max(price) from shop; select * from shop where price=@min_price or price=@max_price; +---------+--------+-------+ | article | dealer | price | +---------+--------+-------+ | 0003 | D | 1.25 | | 0004 | D | 19.95 | +---------+--------+-------+
You don't need foreign keys to join 2 tables.
The only thing MySQL doesn't do is CHECK
to make sure that
the keys you use really exist in the table(s) you're referencing and it
doesn't automatically delete rows from table with a foreign key
definition. If you use your keys like normal, it'll work just fine:
CREATE TABLE persons ( id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT, name CHAR(60) NOT NULL, PRIMARY KEY (id) ); CREATE TABLE shirts ( id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT, style ENUM('t-shirt', 'polo', 'dress') NOT NULL, color ENUM('red', 'blue', 'orange', 'white', 'black') NOT NULL, owner SMALLINT UNSIGNED NOT NULL REFERENCES persons, PRIMARY KEY (id) ); INSERT INTO persons VALUES (NULL, 'Antonio Paz'); INSERT INTO shirts VALUES (NULL, 'polo', 'blue', LAST_INSERT_ID()), (NULL, 'dress', 'white', LAST_INSERT_ID()), (NULL, 't-shirt', 'blue', LAST_INSERT_ID()); INSERT INTO persons VALUES (NULL, 'Lilliana Angelovska'); INSERT INTO shirts VALUES (NULL, 'dress', 'orange', LAST_INSERT_ID()), (NULL, 'polo', 'red', LAST_INSERT_ID()), (NULL, 'dress', 'blue', LAST_INSERT_ID()), (NULL, 't-shirt', 'white', LAST_INSERT_ID()); SELECT * FROM persons; +----+---------------------+ | id | name | +----+---------------------+ | 1 | Antonio Paz | | 2 | Lilliana Angelovska | +----+---------------------+ SELECT * FROM shirts; +----+---------+--------+-------+ | id | style | color | owner | +----+---------+--------+-------+ | 1 | polo | blue | 1 | | 2 | dress | white | 1 | | 3 | t-shirt | blue | 1 | | 4 | dress | orange | 2 | | 5 | polo | red | 2 | | 6 | dress | blue | 2 | | 7 | t-shirt | white | 2 | +----+---------+--------+-------+ SELECT s.* FROM persons p, shirts s WHERE p.name LIKE 'Lilliana%' AND s.owner = p.id AND s.color <> 'white'; +----+-------+--------+-------+ | id | style | color | owner | +----+-------+--------+-------+ | 4 | dress | orange | 2 | | 5 | polo | red | 2 | | 6 | dress | blue | 2 | +----+-------+--------+-------+
MySQL doesn't yet optimize when you search on two different
keys combined with OR
(Searching on one key with different OR
parts is optimized quite good):
SELECT field1_index, field2_index FROM test_table WHERE field1_index = '1' OR field2_index = '1'
The reason is that we haven't yet had time to come up with an efficient
way to handle this in the general case. (The AND
handling is,
in comparison, now completely general and works very well).
For the moment you can solve this very efficiently by using a
TEMPORARY
table. This type of optimization is also very good if
you are using very complicated queries where the SQL server does the
optimizations in the wrong order.
CREATE TEMPORARY TABLE tmp SELECT field1_index, field2_index FROM test_table WHERE field1_index = '1'; INSERT INTO tmp SELECT field1_index, field2_index FROM test_table WHERE field2_index = '1'; SELECT * from tmp; DROP TABLE tmp;
The above way to solve this query is in effect an UNION
of two queries.
The following shows an idea of how you can use the bit group functions to calculate the number of days per month a user has visited a web page.
CREATE TABLE t1 (year YEAR(4), month INT(2) UNSIGNED ZEROFILL, day INT(2) UNSIGNED ZEROFILL); INSERT INTO t1 VALUES(2000,1,1),(2000,1,20),(2000,1,30),(2000,2,2),(2000,2,23),(2000,2,23); SELECT year,month,BIT_COUNT(BIT_OR(1<<day)) AS days FROM t1 GROUP BY year,month; Which returns: +------+-------+------+ | year | month | days | +------+-------+------+ | 2000 | 01 | 3 | | 2000 | 02 | 2 | +------+-------+------+
The above calculates how many different days was used for a given year/month combination, with automatic removal of duplicate entries.
mysql
in Batch Mode
In the previous sections, you used mysql
interactively to enter
queries and view the results. You can also run mysql
in batch
mode. To do this, put the commands you want to run in a file, then
tell mysql
to read its input from the file:
shell> mysql < batch-file
If you need to specify connection parameters on the command line, the command might look like this:
shell> mysql -h host -u user -p < batch-file Enter password: ********
When you use mysql
this way, you are creating a script file, then
executing the script.
Why use a script? Here are a few reasons:
mysql
to execute it again.
shell> mysql < batch-file | more
shell> mysql < batch-file > mysql.out
cron
job. In this case, you must use batch mode.
The default output format is different (more concise) when you run
mysql
in batch mode than when you use it interactively. For
example, the output of SELECT DISTINCT species FROM pet
looks like
this when run interactively:
+---------+ | species | +---------+ | bird | | cat | | dog | | hamster | | snake | +---------+
But like this when run in batch mode:
species bird cat dog hamster snake
If you want to get the interactive output format in batch mode, use
mysql -t
. To echo to the output the commands that are executed, use
mysql -vvv
.
At Analytikerna and Lentus, we have been doing the systems and field work for a big research project. This project is a collaboration between the Institute of Environmental Medicine at Karolinska Institutet Stockholm and the Section on Clinical Research in Aging and Psychology at the University of Southern California.
The project involves a screening part where all twins in Sweden older than 65 years are interviewed by telephone. Twins who meet certain criteria are passed on to the next stage. In this latter stage, twins who want to participate are visited by a doctor/nurse team. Some of the examinations include physical and neuropsychological examination, laboratory testing, neuroimaging, psychological status assessment, and family history collection. In addition, data are collected on medical and environmental risk factors.
More information about Twin studies can be found at:
http://www.imm.ki.se/TWIN/TWINUKW.HTM
The latter part of the project is administered with a Web interface written using Perl and MySQL.
Each night all data from the interviews are moved into a MySQL database.
The following query is used to determine who goes into the second part of the project:
select concat(p1.id, p1.tvab) + 0 as tvid, concat(p1.christian_name, " ", p1.surname) as Name, p1.postal_code as Code, p1.city as City, pg.abrev as Area, if(td.participation = "Aborted", "A", " ") as A, p1.dead as dead1, l.event as event1, td.suspect as tsuspect1, id.suspect as isuspect1, td.severe as tsevere1, id.severe as isevere1, p2.dead as dead2, l2.event as event2, h2.nurse as nurse2, h2.doctor as doctor2, td2.suspect as tsuspect2, id2.suspect as isuspect2, td2.severe as tsevere2, id2.severe as isevere2, l.finish_date from twin_project as tp /* For Twin 1 */ left join twin_data as td on tp.id = td.id and tp.tvab = td.tvab left join informant_data as id on tp.id = id.id and tp.tvab = id.tvab left join harmony as h on tp.id = h.id and tp.tvab = h.tvab left join lentus as l on tp.id = l.id and tp.tvab = l.tvab /* For Twin 2 */ left join twin_data as td2 on p2.id = td2.id and p2.tvab = td2.tvab left join informant_data as id2 on p2.id = id2.id and p2.tvab = id2.tvab left join harmony as h2 on p2.id = h2.id and p2.tvab = h2.tvab left join lentus as l2 on p2.id = l2.id and p2.tvab = l2.tvab, person_data as p1, person_data as p2, postal_groups as pg where /* p1 gets main twin and p2 gets his/her twin. */ /* ptvab is a field inverted from tvab */ p1.id = tp.id and p1.tvab = tp.tvab and p2.id = p1.id and p2.ptvab = p1.tvab and /* Just the sceening survey */ tp.survey_no = 5 and /* Skip if partner died before 65 but allow emigration (dead=9) */ (p2.dead = 0 or p2.dead = 9 or (p2.dead = 1 and (p2.death_date = 0 or (((to_days(p2.death_date) - to_days(p2.birthday)) / 365) >= 65)))) and ( /* Twin is suspect */ (td.future_contact = 'Yes' and td.suspect = 2) or /* Twin is suspect - Informant is Blessed */ (td.future_contact = 'Yes' and td.suspect = 1 and id.suspect = 1) or /* No twin - Informant is Blessed */ (ISNULL(td.suspect) and id.suspect = 1 and id.future_contact = 'Yes') or /* Twin broken off - Informant is Blessed */ (td.participation = 'Aborted' and id.suspect = 1 and id.future_contact = 'Yes') or /* Twin broken off - No inform - Have partner */ (td.participation = 'Aborted' and ISNULL(id.suspect) and p2.dead = 0)) and l.event = 'Finished' /* Get at area code */ and substring(p1.postal_code, 1, 2) = pg.code /* Not already distributed */ and (h.nurse is NULL or h.nurse=00 or h.doctor=00) /* Has not refused or been aborted */ and not (h.status = 'Refused' or h.status = 'Aborted' or h.status = 'Died' or h.status = 'Other') order by tvid;
Some explanations:
concat(p1.id, p1.tvab) + 0 as tvid
id
and tvab
in
numerical order. Adding 0
to the result causes MySQL to
treat the result as a number.
id
tvab
1
or 2
.
ptvab
tvab
. When tvab
is 1
this is
2
, and vice versa. It exists to save typing and to make it easier for
MySQL to optimize the query.
This query demonstrates, among other things, how to do lookups on a
table from the same table with a join (p1
and p2
). In the example, this
is used to check whether a twin's partner died before the age of 65. If so,
the row is not returned.
All of the above exist in all tables with twin-related information. We
have a key on both id,tvab
(all tables), and id,ptvab
(person_data
) to make queries faster.
On our production machine (A 200MHz UltraSPARC), this query returns about 150-200 rows and takes less than one second.
The current number of records in the tables used above:
Table | Rows |
person_data | 71074 |
lentus | 5291 |
twin_project | 5286 |
twin_data | 2012 |
informant_data | 663 |
harmony | 381 |
postal_groups | 100 |
Each interview ends with a status code called event
. The query
shown below is used to display a table over all twin pairs combined by
event. This indicates in how many pairs both twins are finished, in how many
pairs one twin is finished and the other refused, and so on.
select t1.event, t2.event, count(*) from lentus as t1, lentus as t2, twin_project as tp where /* We are looking at one pair at a time */ t1.id = tp.id and t1.tvab=tp.tvab and t1.id = t2.id /* Just the sceening survey */ and tp.survey_no = 5 /* This makes each pair only appear once */ and t1.tvab='1' and t2.tvab='2' group by t1.event, t2.event;
The Contrib section includes programs that let you authenticate your users from a MySQL database and also let you log your log files into a MySQL table. See section D Contributed Programs.
You can change the Apache logging format to be easily readable by MySQL by putting the following into the Apache configuration file:
LogFormat \ "\"%h\",%{%Y%m%d%H%M%S}t,%>s,\"%b\",\"%{Content-Type}o\", \ \"%U\",\"%{Referer}i\",\"%{User-Agent}i\""
In MySQL you can do something like this:
LOAD DATA INFILE '/local/access_log' INTO TABLE table_name FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' ESCAPED BY '\\'
Go to the first, previous, next, last section, table of contents.