You are on page 1of 197

Using MySQL - Introducing SQL This is the first in a series of MySQL workshops designed to introduce MySQL and SQL

statements to a novice developer. What is MySQL and why are we using it? MySQL is a powerful Relational Database Management System (RDBMS) which we will use to learn the basic principles of database and data manipulation using Structured Query Language (SQL) statements. SQL is a database language that is used to retrieve, insert, delete and update stored data. This is achieved by constructing conditional statements that conform to a specific syntax (i.e. the strict order required of elements for a statement to work). Although it is assumed that most people reading this know what a database and SQL are (if not necessarily how to use them), there follows a little recap that it does no harm ;-) How does MySQL work? MySQL is a database server program and as such is installed on one machine, but can 'serve' the database to a variety of locations. To explain look at the following diagram.

The MySQL Server is installed on a Server and can be accessed directly via various client interfaces, which send SQL statements to the server and then display the results to a user. Some of these are: A Local Client - a program on the same machine as the server. An example of this is the command line MySQL client software we will be using in the rest of the MySQL workshops (although there are other programs including graphical interfaces).

A Scripting Language - can pass SQL queries to the server and display the result. A Remote Client - a programme on a different machine that can connect to the server and run SQL statements. You can also use two more indirect methods. Remote Login - You may be able to connect to the Server Machine to run one of its local clients. Web Browser - you can use a web browser and scripts that someone has written (we're going to use this method for the rest of the workshop). A bit about SQL Structured Query Langauge is cross between a math-like language and an English-like language that allows us to ask a database questions or tell it do do things. There is a structure to this language: it uses English phrases to define an action, but uses math-like symbols to make comparisons. For example:
SELECT * FROM table;

Where 'SELECT', 'FROM' and 'table' are in English, but '*' is a symbol that means all. It is important to learn SQL as it is common to almost all database programs and was developed specifically as a language used to retrieve, add and manipulate data in databases. You will find it not only here in MySQL, but underlying MS Access, MS SQL Server, and in every web-based database application. While it may seem confusing at first it is almost like telling a story or asking a question once you become comfortable with the syntax. A Bit About Database Structure Databases can be as complicated as you wish to make them... so lets start with simple and work out way up from there. A database can have many TABLEs holding data. Imagine a simple table of car information: CarID Manufacturer Year Car 1094 1095 1096 1097 Subaru Suzuki Toyota Volkswagen 91 95 97 95 Model AirCon CDMulti FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE

Legacy 2000 Vitatra 1600 Corolla 1300 Golf3 1600

If you look at the blue Cell we call this a 'FIELD' and it has a value of 'Suzuki'. This FIELD exists in the COLUMN named 'Manufacturer'. The 'Model' COLUMN is green in this example. All the FIELDs in the 'Model' COLUMN contain the same type of data (i.e. the model of the car). Whereas a ROW (in this case red) contains a series of FIELDs, one in each COLUMN, together comprising a record about one car. This record represents the real world uniqueness of each thing we are recording (in this case a car) and thus is given a unique number (in database language the 'Primary Key') with which to identify it. In our simple table each unique number is stored as a FIELD in the 'carID' COLUMN. Creating a First SQL Statement As we have yet to create a database it would be difficult to construct some simple SQL statements to explain the above without first getting involved in some MySQL server administration. However as we saw above there are many ways to interact with a database and thus I have created a database and a 'cars' table filled with car info and provided a web browser interface to accept SQL statements and return the results for you to experiment with. So open the web interface in a new browser window and switch between the two or print this out and work from it. The first SQL statement we will look at is the SELECT statement. The basic SELECT statement has the following syntax.
SELECT columns FROM table [WHERE (condition)]

The WHERE condition is in square brackets as it is optional. So using our 'cars' table we can start issuing commands and you should see the actual data being displayed. Note: As an SQL statement can span many lines of a script or when typing it in at a command line (this virtual workshop will 'format' the SQL statements over multiple lines to avoid overlapping and to aid readability). When using MySQL the statement is only deemed 'complete' when a semi-colon is typed at the end to signify that you have finished constructing your statement. So ensure you include a semi-colon....... To display all the data from the 'cars' table we would issue the following command (where '*' is the wildcard symbol for all).
SELECT *

FROM cars;

The result should be a large amount of data displayed (due to the quantity it may take a few moments to display). This is not very useful, but we can begin to restrict the output by including WHERE conditions. For example to display only the records that contain the data '95-98' in the 'Year' field, try the following command:
SELECT *

FROM cars

WHERE (Year = '95-98');

There are a couple of items of good practice that make life easier at this point. The first is that our conditions (the bit after WHERE) should be enclosed by brackets. This 'forces' the condition to be evaluated and is needed when you have nested conditions in complex queries, so you should to get into the habit of doing this from the beginning. Secondly, it is likely that you will at some point wish to display data from different tables using the same query - making it a good idea to also get into the habit of using a the full TABLE.COLUMN reference (as different tables sometimes have COLUMNs of the same name). For example if we use another select statement, perhaps all records that have 'Volvo' as 'Manufacturer', we are explicit that we mean the Manufacturer COLUMN in the cars TABLE.
SELECT *

FROM cars

WHERE (cars.Manufacturer = 'Volvo');

As hinted at above, conditions can be combined to achieve better filtering of results, the simplist being to use the 'AND' operator

SELECT *

FROM cars

WHERE ((cars.Year = '95-98')

AND (cars.Manufacturer = 'Volvo'));

This last statement should produce only one result and you can begin to see how using conditions can be useful in finding individual records. A Tiny Bit about Operators Operators are another tool that you can use within your SQL statement to refine your search for specific records.
SELECT *

FROM cars

WHERE ((cars.Year = '95-98')

AND (cars.Manufacturer = 'Volvo'));

The above statement uses the 'AND' operator (it can also be expressed as '&&') to combine two conditions. Both conditions have to be met in order for the record to be displayed. We can also use the 'OR' operator (can be expressed as '||' ) to ask for a record to be displayed if either condition is met.
SELECT *

FROM cars

WHERE ((cars.Year = '95-98')

OR (cars.Manufacturer = 'Volvo'));

The final operator we'll discuss here is the 'NOT' operator ('!' in case you were wondering), which is a bit more complicated. Rather than joining conditions together it becomes part of the condition, turning a positive into a negative. The following statement retrieves all records that do not contain 'Volvo' as 'Manufacturer'.
SELECT *

FROM cars

WHERE (cars.Manufacturer != 'Volvo');

As the 'NOT' operator has become part of the condition it can be used with another operator to combine positive and negative conditions. For example to retrieve the records that contain the data '95-98' in the 'Year' field but do not contain 'Volvo' as 'Manufacturer' enter the following.
SELECT *

FROM cars

WHERE ((cars.Year = '95-98')

AND (cars.Manufacturer != 'Volvo'));

There are also other operators, but they will be discussed in a later part of the MySQL Virtual Workshop series. Restricting Columns Before leaving our initial encounter with the SELECT statement we need to address one final component. In all the examples we have used so far the wildcard '*' has been used to retrieve all the COLUMNs. While this may be okay for a table that only has 7 COLUMNs, it would not work quite so well for a table with 20 COLUMNs. Thus it may be desirable to sometimes restrict which COLUMNs are returned. If we look again at the seven fields that make up the 'cars' table
+-------+--------------+-------+------+-----------+--------+---------+

| CarID | Manufacturer | Year | Car | Model

| AirCon | CDMulti |

+-------+--------------+-------+------+-----------+--------+---------+

We may only be interested in seeing the 'Manufacturer','Year','Car' and 'Model' fields and thus we would construct a statement like so:
SELECT cars.Manufacturer, cars.Year, cars.Car, cars.Model

FROM cars

WHERE (cars.Year = '95-98');

Which, as you can see, asks for only certain columns to be returned - each field separated by a comma.

Using MySQL - Data and Structures Workshop Requirements You should have completed the first workshop in this series. You should also have access to the MySQL command line client software. You can either ask your systems adminstrator for access to this client, alternatively you could install a local copy of MySQL - see Setting Up a Server for details. You should also have full permissions on a database (or the ability to create a new one), again check with your administrator if necessary. Finally you should have printed out and filled in the Data Collection Form........ from the experience of others it IS easier than doing this 'on the fly'. Important information to understand before beginning. A common problem when learning how to the MySQL Command Line Client is getting the syntax exactly right. There are several conventions which are used throughout the MySQL virtual workshops when displaying commands that you will have to type. $ - This preceding a command signifies that the command should be typed at the command line of your system. The '$' should not be included in any statement you type. mysql> - This preceding a command signifies the command should be typed at the MySQL client Command Line Interface. Again ignore this and only type what follows. There are also several other command prompts that you may encounter within the mysql client. -> This is a continuation line allowing you to enter a long statement over several lines. A statement is never executed until you issue a semi colon at the end of it. You may at some point require to type either an apostrophe (') or a double quote (") as part of your data entry. However as these are used as part of the SQL syntax the statement would fail. This is signified by either: '> MySQL is asking you for another apostrophe "> MySQL is asking you for another double quote. To successsfully execute a statement containing either of these characters they must be entered in pairs. Or

e.g. Keith's should be typed with two single apostrophes Keith''s and "Hello" should be typed""Hello"" with two sets of double quotes. The final convention that we use in this series is the 'Syntactic Example'. As each new element of SQL is introduced, the syntax will be given before an actual example to try. The syntactic command will contain generic references with angular brackets that will not work if typed. eg:
SELECT <field> FROM <table>

This means that the <field> and <table> parts of the statement should contain the actual names of a FIELD and TABLE. Connecting To Your Database In order to issue MySQL data manipulation statements, we must specify a username, a password and a database to use. This information will have been provided by your systems adminsistrator. If you are running MySQL yourself, then skip ahead to the section on Creating a Database in Part 6 - then come back to this point. Either way you should now have in your possession a database name, a valid password for that database and a password. For the purposes of these workshops the following example values will be used. Database Name Valid MySQL Username vworksDB vworks

Password mypass In order to log into MySQL correctly we must pass information to the MySQL client program when we start it. This is done with the following commands and syntax.
$ mysql -u <mysql_username> -p <password> <database name>

The name of the program (mysql) is followed by the User flag (-u), the Password (-p) and finally the name of the database. So to start our MySQL session you could enter (but don't just yet):
$ mysql -u vworks -p mypass vworksDB

However is it a REALLY bad idea to ever enter a password anywhere in plain text, so if we omit the password but still leave the password flag (-p), you will be prompted for it (again do not try this yet).
$ mysql -u vworks -p vworksDB

Enter password:

You could also omit the name of the database as well (wait 'til I say so).
$ mysql -u vworks -p

Enter password:

...and once logged in elect to 'use' a specific database. You could choose to do this if your user has privileges on different databases.
mysql> use vworksDB

Database changed

...but we wont do that either. Okay, you can now log in to the database using the second example above.
$ mysql -u vworks -p examples

Enter password:

You should now be looking at the MySQL Command Line Interface that is awaiting any statement you wish to type.
mysql>

In the previous MySQL Virtual Workshop we looked at issuing SQL SELECT statements containing conditions to retrieve data from a table. The natural progression is to start constructing other statements which INSERT, UPDATE and DELETE records from the table. However, before we attempt this we need to create a table in our database. Creating a Table Datatypes Creating a table requires you to have an understanding of MySQL datatypes (similar if not identical to other database datatypes) so that you can define the fields in the table. The datatypes below are some of the basic datatypes and are meant as a simple introduction. If you are planning a production database you should investigate the datatypes thoroughly. Datatype INT Description Numeric entry Example: id INT

VARCHAR(n Text string of characters up to n with name ) a maximim of 255 characters VARCHAR(20) CHAR(n) Text string with specific number (n) of characters. If the number of characters is less than 'n' then is padded by spaces (spaces are removed when data is retrieved). 255 Maximum. Holds between 255 - 65535 characters The date stored in the format YYYYMM-DD address CHAR(30)

TEXT DATE TIME

philosophy TEXT dob DATE

The time stored in the format tob TIME HH:MM:SS The syntax for defining a field and datatype (as used in the examples) is 'fieldname datatype'. So lets get started with making a database table. You should have completed the Data Collection form so we will create a table and begin to enter some of the data into your database. CREATE TABLE...... The syntax for creating a table is as follows:
mysql> CREATE TABLE <table_name> (

field1 datatype,

field2 datatype,

etc......

);

When split over multiple lines within the MySQL command line client the continuation indictor will be used.
mysql> CREATE TABLE <table_name> (

-> field1 datatype,

-> field2 datatype,

-> etc......);

REMINDER: the command statement will not be executed until you use a semicolon. Thus for the command to create a table based on the information in the data collection form you should enter the following (omitting the 'No. of Tracks' which we will add later):
mysql> CREATE TABLE cds (

-> cdID INT(3) auto_increment primary key,

-> artist VARCHAR(20),

-> title VARCHAR(30),

-> year INT(4),

-> label VARCHAR(20),

-> bought DATE );

Query OK, 0 rows affected (0.00 sec)

When entering these commands over several lines it is possible you may make a mistake and would want to cancel the command. This can be done by typing the '\c' command:
mysql> CREATE TABLE cds (

-> cdID IN(3)

-> \c

mysql>

You can check that you have created the table correctly by issuing a 'describe' command.
mysql> describe cds;

+--------+-------------+------+-----+---------+----------------+

| Field | Type

| Null | Key | Default | Extra

+--------+-------------+------+-----+---------+----------------+

| cdID | int(3)

| PRI | NULL | auto_increment |

| artist | varchar(20) | YES |

| NULL |

| title | varchar(30) | YES |

| NULL |

| year | int(4)

| YES |

| NULL |

| label | varchar(20) | YES |

| NULL |

| bought | date

| YES |

| NULL |

+--------+-------------+------+-----+---------+----------------+

6 rows in set (0.01 sec)

You will have noticed that when we created the first 'cdID' field we added a few other instuctions. These were 'auto_increment' and 'primary key'. These are used in the context of relational databases to identify the unique COLUMN in the TABLE (primary key) and to ensure that there cannot be duplicate numbers in the COLUMN by automatically numbering this FIELD (auto_increment). Now that the table has been created we are ready to start inserting data.

INSERTing data The SQL statement to INSERT data is again fairly straight forward once you get used to the syntax. The basic syntax for the INSERT statement is as follows.
INSERT INTO <table_name>

VALUES (

value1,

value2,

etc......

Which will work fine if you are inserting data into every field in a row. However if you only want to insert data into certain fields (columns) you will need to specify only these fields as part of the statement otherwise the number of data items will not match the number of available fields and MySQL will return an error.
INSERT INTO <table_name> (

column1,

column2,

....

) VALUES (

value1,

value2,

....

To illustrate this point we will try to insert the first example from the Data Collection form into the 'cds' table.
mysql> INSERT INTO cds

-> VALUES (

-> 'jamiroquai',

-> 'A Funk Odyssey',

-> '2001',

-> 'Sony Soho2',

-> '2001-09-13');

ERROR 1136: Column count doesn't match value count at row 1

As you can see we get an error telling us that the number of values we have tried to insert (5) does that match the number of columns that there are (6). This is because we have omitted the 'cdID' field which is used as the unique indentifer and thus gets generated automatically. In order for this to work we we would have to specify which 5 fields we want to insert our values into.
mysql> INSERT INTO cds (

-> artist, title, year, label, bought

-> ) VALUES (

-> 'jamiroquai',

-> 'A Funk Odyssey',

-> '2001',

-> 'Sony Soho2',

-> '2001-09-01');

Query OK, 1 row affected (0.02 sec)

Although MySQL tells us that this has worked, we can check for ourselves by issuing a SELECT query.
mysql> SELECT * FROM cds;

+------+------------+----------------+------+------------+------------+

| cdID | artist

| title

| year | label

| bought

+------+------------+----------------+------+------------+------------+

| 1 | jamiroquai | A Funk Odyssey | 2001 | Sony Soho2 | 2001-09-01 |

+------+------------+----------------+------+------------+------------+

1 row in set (0.00 sec)

Also notice that the 'cdID' field has a value of '1' that has been generated automatically by the 'auto_increment' property. Although we could enter all our data by explicitly declaring the fields then the values, there is another way we could do it. Our first attempt at the INSERT SQL statement failed because we were trying to add 5 values to a database that has 6 fields (columns) and we couldn't specify a 6th value for the 'cdID' field as that value is an automatically generated number. What we can do in the statement is acknowledge that a field exists, but admit we don't know what the value will be. This is done by substituting a question mark for a real value. If we enter the statement again.

mysql> INSERT INTO cds VALUES (

-> '?',

-> 'jamiroquai',

-> 'A Funk Odyssey',

-> '2001',

-> 'Sony Soho2',

-> '2001-09-01');

Query OK, 1 row affected (0.00 sec)

This time the INSERT statement succeeds. Another SELECT statement confirms this.
mysql> SELECT * FROM cds;

+------+------------+----------------+------+------------+------------+

| cdID | artist

| title

| year | label

| bought

+------+------------+----------------+------+------------+------------+

| 1 | jamiroquai | A Funk Odyssey | 2001 | Sony Soho2 | 2001-09-01 |

| 2 | jamiroquai | A Funk Odyssey | 2001 | Sony Soho2 | 2001-09-01 |

+------+------------+----------------+------+------------+------------+

1 row in set (0.00 sec)

Using MySQL - Manipulating Data Workshop Requirements You should have completed Parts One and Two of this series. You should also have access to the MySQL command line client software. You should also have full permissions on a database. Introduction In the previous MySQL Virtual Workshops we have looked at logging into MySQL, creating tables, adding and selecting data using SQL statements. In this Workshop we will continue to look at other SQL commands, specifically updating and deleting, and revisit the SELECT command to examine different operators and options. UPDATE-ing Records The UPDATE SQL statement is similar to the SELECT statement as there must be a WHERE condition to specify which record(s) to change.
UPDATE <table_name>

SET <column_name> = 'new_value'

WHERE (<column_name> = 'some_value');

So to change the 'title' in the first row of our 'cds' table from:
| 1 | jamiroquai | A Funk Odyssey | 2001 | Sony Soho2 | 2001-09-01 |

to:
| 1 | jamiroquai | Wrong Title | 2001 | Sony Soho2 | 2001-09-01 |

We would use the following statement:


mysql> UPDATE cds

-> SET cds.title = 'Wrong Title'

-> WHERE (cds.cdID = '1');

Query OK, 1 row affected (0.06 sec)

Rows matched: 1 Changed: 1 Warnings: 0

The value '1' in the 'cdID' column is the unique identifier that allows us to change values. You can issue a SELECT statement if you want to check the changes.
mysql> SELECT * FROM cds;

+------+------------+----------------+------+------------+------------+

| cdID | artist

| title

| year | label

| bought

+------+------------+----------------+------+------------+------------+

| 1 | jamiroquai | Wrong Title

| 2001 | Sony Soho2 | 2001-09-01 |

| 2 | jamiroquai | A Funk Odyssey | 2001 | Sony Soho2 | 2001-09-01 |

We could also change multiple columns using one statement

mysql> UPDATE cds SET cds.title = 'Other Title',

-> cds.artist = 'artist'

-> WHERE (cds.cdID = '1');

Query OK, 1 row affected (0.00 sec)

Rows matched: 1 Changed: 1 Warnings: 0

The DELETE statement By using the UPDATE statement we have altered the 'cds' table so that there are no longer duplicate records in rows 1 and 2. However we do have an incorrect record in row 1 and need to DELETE it. The syntax for deleting rows is:
DELETE FROM <table_name>

WHERE (<column_name> = 'some_value');

Thus to remove our incorrect record


mysql> DELETE FROM cds

-> WHERE (cds.cdID = '1');

Query OK, 1 row affected (0.00 sec)

NOTE: When a record is deleted all the fields are deleted INCLUDING the unique identifier, and the database does not renumber this auto_increment COLUMN. So it is important to remember that the ID is not the same as the row number, e.g. in our example row 2 has a cdID of 3, row 3 - cdID of 4 etc. SELECT Revisited In the first MySQL Virtual Workshop we looked at using the SELECT SQL statement to retrieve data from a table. This was fairly limited in scope as we could only retrieve data which matched a specific text string. In this section we will look at retrieving data based on other criteria. The first of these we will look at is Selecting a Number Range. We have created two numeric COLUMNs in our 'cds' TABLE: the 'cdID' and 'Year' COLUMNs. As each 'cdID' entry is unique, we will use the 'Year' COLUMN in the following examples. It is also important to note that while we may recognise an entry in the 'Year' COLUMN as an actual year MySQL only recognises it as a number. Simple stuff first to select all from one year
mysql> SELECT * FROM cds

-> WHERE (cds.year = 2001);

Note that as the 'year' COLUMN is numeric rather than a text string we do not need to enclose our search term in apostrophes (although if we did it would treat the number '2001' as a text string and find it anyway). If we wanted to expand our search term to include other years (as well as 2001) we need to use numeric operators. Numeric Operators Numeric operators are used to compare two numbers under several conditions. Operator = <> > Condition Equal to Not equal to Greater than

< >= <=

Less than Greater than or equal to Less than or equal to

Thus to display all cds that were released before 2001:


mysql> SELECT * FROM cds

-> WHERE (cds.year < 2001);

Or all cds in / after 1990:


mysql> SELECT * FROM cds

-> WHERE (cds.year >= 1990);

So we are able to select records by a column's numeric content. Other Operators There are two other operators so far unmentioned, the LIKE and BETWEEN operators. The LIKE operator is used when we want to match part of the data in a field by using the '%' wildcard. So if we wanted to search the 'title' field for all albums beginning with 'A' we would use the following statement.
mysql> SELECT * FROM cds

-> WHERE (cds.title LIKE 'A%');

The 'A' in the search string is followed by our wildcard character. To match the word 'the' in any title we would put a wildcard character before and afterwards in the SQL statement.
mysql> SELECT * FROM cds

-> WHERE (cds.title LIKE '%the%');

The BETWEEN statement is used to add more functionality to a condition, allowing us to select a range of values from a column.
SELECT <column_name> FROM <table_name>

WHERE (<column_name> BETWEEN value1 AND value2)

Thus we could select a numeric range


mysql> SELECT * FROM cds

-> WHERE (cds.year BETWEEN 1995 AND 1998);

Or we could select an alphanumeric range. To select all artists alphabetically between 'Elvis' and 'Michael Jackson'
mysql> SELECT * FROM cds

-> WHERE (cds.artist BETWEEN 'Elvis' AND 'Michael Jackson');

Which might return 'Frank Sinatra' or 'Led Zeppelin'. Working With Dates A typical entry for a date might look something like this.

mysql> SELECT cds.bought FROM cds;

+------------+

| bought |

+------------+

| 2001-09-01 |

+------------+

1 row in set (0.01 sec)

You could imagine trying to SELECT dates using 'Less Than' or 'More Than' would be difficult as a date is not a sequential decimal number. However that is why we specify the 'DATE' datatype when creating the field in the table, as this allows comparisons of this type. What actually happens is that the MySQL DBMS converts any date into the number of days since year '0' before doing any comparison. Try SELECTing cds that you bought prior to the year 2000
mysql> SELECT * FROM cds

-> WHERE (cds.bought < '2001-01-01');

And cds that were bought this century.


mysql> SELECT * FROM cds

-> WHERE (cds.bought >= '2001-01-01');

Other Tricks When Displaying Data Before leaving the SELECT command alone again for a while, we should touch upon a few popular ways to control output. DISTINCT The DISTINCT qualifier is used to return unique results and is included as part of the SELECT statement (i.e. before any conditions are applied). Let us image that our 'cds' table contained over 2000 rows. If we wanted to look at all the artists whose name started with 'E' we might construct a statement like so.
mysql> SELECT artist FROM cds

-> WHERE cds.artist LIKE 'E%';

This would return each row that had an artist whose name started with 'E'. If that artist has 10 cds in the database his name will be returned 10 times (once for each cd). We could avoid this by using the DISTINCT qualifier, which would ensure that each artist was returned only once.
mysql> SELECT DISTINCT cds.artist FROM cds

-> WHERE cds.artist LIKE 'E%';

Hierarchy of Conditions The conditions that control the filtering of the query exist in a rough (if not quite exact) hierarchal order:

WHERE

GROUP BY

HAVING

ORDER BY

LIMIT

If more than one of these conditions are to used in a query then it is recommended that this order be used, so that GROUP BY cannot go before the WHERE condition and LIMIT cannot go before HAVING etc. Notice that HAVING and ORDER BY are at the same level - this is because either one can go before the other. Breaking this hierarchy will generally result in errors. We have already looked at the WHERE condition extensively, so lets have a closer look at these conditions. GROUP BY When we issue a SELECT command we are shown the results in the order that the records were entered. We can change this by using the 'GROUP BY' directive, allowing us to display the data grouped by field. To return the results ordered by artist:
mysql> SELECT * FROM cds

-> GROUP BY cds.artist;

ORDER BY Similar to GROUP BY, this condition further allows control over the result set. ORDER BY controls the sequence of results. For example to display all the data from the 'cds' table ordered alphabetically by artist we would issue this command.

mysql> SELECT * FROM cds ORDER BY cds.artist;

You can also choose to sort the results in reverse by appending DESC to the condition
mysql> SELECT * FROM cds ORDER BY cds.artist DESC;

We can also use two or more ORDER BY conditions, for example if we wanted to list by artist then the date bought in descending order we could enter.
mysql> SELECT * FROM cds ORDER BY cds.artist, cds.bought DESC;

HAVING The HAVING condition is really just another WHERE condition that acts as a 'secondary constraint' on the result set. This works best when you are trying to apply a restrictive condition after a grouping has taken place. So for example:
mysql> SELECT * FROM cds

-> GROUP BY cds.artist

-> HAVING cds.title LIKE 'A%';

You should try if at possible to write queries using the WHERE condition rather than the HAVING condition as HAVING is un-optimised. Most people use HAVING to work out summaries of datareturned - such as the number or rows etc. LIMIT There will be times that you create a query that produces a large result set that you don't want to view or handle all at once. In these situations it is useful to use the LIMIT condition to restrict the number or records returned. For example to display the first 5 records that meet a query:
mysql> SELECT * FROM cds LIMIT 5;

To retrieve the rest of the records in batches we can also specify a starting point before the number or rows to return.
SELECT <fields> FROM <TABLE> LIMIT <starting_point>, <number_of_rows>

So to return rows 6-10 we would specify the start point of 5 and then ask to return 5 records.
mysql> SELECT * FROM cds LIMIT 5, 5

Or to start at rows 3-7:


mysql> SELECT * FROM cds LIMIT 2, 4

Using MySQL - Manipulating Structures Workshop Requirements You should have completed Parts One, Two and Three of this series. You should also have access to the MySQL command line client software. You should also have full permissions on a database. Introduction By this point you should be very familiar with SQL statements and manipulating data within database TABLEs. In this part of the Using MySQL series we will look at manipulating database tables rather than just the data within. Before beginning, ensure that you have opened your own MySQL database where you entered all the CD data. ADDing a Column When we created the 'cds' TABLE, based on the Data Collection Form, we omitted the 'tracks' field. It is time to correct this by adding a 'tracks' COLUMN to our 'cds' TABLE. This is done by using the ALTER statement. With the ALTER statement you can add, remove or change a table's columns with a general syntax of:
ALTER TABLE <table_name>

ADD/DROP <column_name> [datatype];

As we want to add a new COLUMN called 'tracks' to our 'cds' table we should enter this:
mysql> ALTER TABLE cds

-> ADD tracks INT(2);

Using the SELECT command we can confirm that there is now an empty or NULL 'tracks' column in every row.
mysql> SELECT cdID, artist, title, tracks

-> FROM cds;

+------+------------+----------------+--------+

| cdID | artist

| title

| tracks |

+------+------------+----------------+--------+

| 2 | jamiroquai | A Funk Odyssey | NULL |

Mini Exercise Using the UPDATE command learned in Part3 you can now add all the 'tracks' data to your 'cds' table. MODIFYing a TABLE As well as ADDing new COLUMNs we could also change an existing one by using MODIFY in the statement.
ALTER TABLE <table>

MODIFY <column> <new_definition>

So to change the newly added track COLUMN to hold 3 figure values we would enter:
mysql> ALTER TABLE cds

-> MODIFY tracks INT(3);

And to change this back to hold 2 figure values:

mysql> ALTER TABLE cds

-> MODIFY tracks INT(2);

Copying Tables Before we look at the other table manipulation commands it will be useful to make a copy of our 'cds' table (in fact we'll make several to illsutrate the process) so that we can mess about and still have all the data we have input. Copying tables in MySQL can sometimes be done in one statement, but some column attributes cannot be copied so it is usually necessary to use at least two statements. To gain a good understanding of what is going on let's break this process down into its parts. The Long Way The really long 'manual' way to copy a table is to create a new table indentical to the first, THEN copy all our inputted data into the new table and finally modify the new table to create the 'auto increment' and 'primary key' attributes on the 'cdID' field. So first we should double check the structure of the 'cds' table using the describe command.
mysql> DESCRIBE cds;

+--------+-------------+------+-----+---------+----------------+

| Field | Type

| Null | Key | Default | Extra

+--------+-------------+------+-----+---------+----------------+

| cdID | int(3)

| PRI | NULL | auto_increment |

| artist | varchar(20) | YES |

| NULL |

| title | varchar(30) | YES |

| NULL |

| year | int(4)

| YES |

| NULL |

| label | varchar(20) | YES |

| NULL |

| bought | date

| YES |

| NULL |

| tracks | int(2)

| YES |

| NULL |

+--------+-------------+------+-----+---------+----------------+

7 rows in set (0.00 sec)

We now need to make the new 'cds2' table that matches exactly.
mysql> CREATE TABLE cds2 (

-> cdID INT(3) auto_increment primary key,

-> artist VARCHAR(20),

-> title VARCHAR(30),

-> year INT(4),

-> label VARCHAR(20),

-> bought DATE,

-> tracks INT(2));

Query OK, 0 rows affected (0.00 sec)

Use the DESCRIBE command again, this time to check that the 'cds2' table matches the 'cds' table. Once that is complete, we now have to copy the data from one table to the other. This is done by mixing the INSERT and SELECT statements so that you are inserting into one table the selection from the other.
mysql> INSERT INTO cds2

-> SELECT * FROM cds;

Check that this has worked by issuing a select command on the 'cds2' table .
mysql> SELECT cds2.cdID, cds2.artist, cds2.title

-> FROM cds2;

+------+------------+----------------+

| cdID | artist

| title

+------+------------+----------------+

| 2 | jamiroquai | A Funk Odyssey |

There is a problem however. As can be seen in the DESCRIBE statement, the 'cdID' COLUMN in the new TABLE has been created without the auto_increment and primary key properties. We can use the ALTER TABLE.....MODIFY statement we learned above to add theauto_increment and primary key properties:
mysql> ALTER TABLE cds2

-> MODIFY cdID INT(3) auto_increment primary key;

The Short Way There is another way to achieve the same result. Rather than creating the new table theninserting the data from the original we can CREATE another table called 'cds3' with a SELECT statement:
mysql> CREATE TABLE cds3

-> SELECT * from cds;

A SELECT statement will confirm that this has worked.


mysql> SELECT cds3.cdID, cds3.artist, cds3.title

-> FROM cds3;

+------+------------+----------------+

| cdID | artist

| title

+------+------------+----------------+

| 2 | jamiroquai | A Funk Odyssey |

We still have to add the structural constraints.


mysql> ALTER TABLE cds3

-> MODIFY cdID INT(3) auto_increment primary key;

All in one statement We can make copy of the table in one statement if we first create the cdID field (with auto_increment and primary key) then insert the other fields as specified in the INSERT statement, which is a combination of boths methods.
mysql> CREATE TABLE cds4 (cdID INT(3) auto_increment primary key)

-> SELECT

-> cds.artist,

-> cds.title,

-> cds.year,

-> cds.label,

-> cds.bought,

-> cds.tracks

-> FROM cds;

DROP-ing Columns and Tables As we saw in a previous Virtual Workshop we can delete data from a table by using the DELETE command. This will leave the table structure intact and simply remove the data. If we want to delete a structural element of the database (a column or table) we have to use the DROPcommand.
DROP <element> <name>

So to remove a column from our 'cds2' table - lets say the newly added 'tracks' column - we would use the DROP and ALTER TABLE commands together (as we are altering a table).
mysql> ALTER TABLE cds2

-> DROP tracks;

Similarly if we wanted to remove the whole table we would issue the following:
mysql> DROP TABLE cds2;

It is important to be sure that you know exactly what want to do before you issue a DROP Command. If you created another 'cds3' table in the previous section remove it as well. You can check that a table has in fact gone by asking MySQL to display all the tables.
mysql> show tables;

+------------------+

| Tables_in_kbrown |

+------------------+

| cds

| cds3

| cds4

In order to tidy things up - you should also delete tables 'cds3' and 'cds4'.
mysql> DROP TABLE cds3;

mysql> DROP TABLE cds4;

Exporting and Importing Data The final thing that we will look at in this workshop is the exporting and importing of data. You may want to export data so that it can be imported into another package, or similarly we may wish to import data from a different package. IMPORTANT: In newer versions of MySQL these methods may be disabled. You should be able to turn this feature on by issuing the command:
mysql> --local-infile=1

Otherwise contact your systems administrator for advice. The version of MySQL that comes with the FoxSev distribution discussed in Setting Up a Server does allow this.

Exporting Data When we are exporting files from MySQL we need an area that the 'MySQL' package can write to AND that you can access to get your data. You may have to contact your systems administrator for advice on a directory. For the purpose of this section we will use Unix commands and the example directory '/tmp/vworks'. If you are a Windows users you will have to substitute Windows commands (i.e. 'dir' for 'ls' and 'type' for 'cat') and directory strucures (e.g. 'C:\temp\vworks' for '/tmp/vworks'). The command to export data to a text file is similar to the standard SELECT command except instead of displaying the data on screen it goes into a file.
SELECT <columns>

FROM <table_name>

INTO OUTFILE <file_name>

IMPORTANT: We do not want to output the 'cdID' column as this is only useful within the database and so we need to specify the other columns explicitly.
mysql> SELECT

-> cds.artist,

-> cds.title,

-> cds.year,

-> cds.label,

-> cds.bought,

-> cds.tracks

-> FROM cds INTO OUTFILE '/tmp/vworks/cds.txt';

In order to check that this has worked we need to exit MySQL to your system's command line (or you could open another 'shell' window). First Change Directory to '/tmp/vworks''.
$ cd /tmp/vworks

Then issue a 'list short' command.


$ ls

And you should see that your file is there, so far so good. Next we want to view the contents of the file and to do this we will use a 'cat' command.
$ cat cds.txt

You should see something like this..... (Thanks to Mark for these).
Jamiroquai A Funk Odyssey 2001 Sony soho2 2001-09-01 11

Abba Abbas Greatest Hits 1998 demon 2001-09-10 23

Various Now 49 2001 virgin 2001-09-10 40

westlife westlife 2000 jive 2000-06-09 13

Various Eurovision Song contest 2001 2001 EMI 2000-09-08 20

The space between each column is a 'tab'. This is because MySQL creates a 'tab delimited' file by default. It is important to know this if you are going to import the file to another database as it will likely ask for the delimited format to determine the differences between fields. Importing data Importing data is a very simliar process to exporting. The syntax for importing a file is:
LOAD DATA INFILE 'file_name.txt'

INTO TABLE tbl_name (field1, field2...etc)

For a quick example we will import our exported data back into the cds TABLE. It is important to specify the field names as we are not importing data into every column - the first column 'cdID' is automatically numbered by MySQL. So to do it for real:
mysql> LOAD DATA INFILE '/tmp/vworks/cds.txt'

-> INTO TABLE cds (artist, title, year, label, bought, tracks);

You can now check that this has worked by issuing a SELECT command, where you should see duplicate entries in your table.
mysql> SELECT *

-> FROM cds;

Using MySQL, Joins Workshop Requirements You should have completed Parts One, Two, Three and Four of this series.

You should also have access to the MySQL command line client software. You should also have full permissions on a database. Introduction In this Virtual Workshop we will look at retrieving data from a relational database structure, i.e. with multiple tables, using the SQL JOIN Syntax. Most databases have multiple tables to avoid repeating data. i.e. why enter the details of a customer over and over again. NOTE: For brevity I will only show 5 records in the examples in this page. Creating Another Table In order to see how JOINs work we need to create another table and populate it with data. So let's create a new table called 'genres' with the following properties. genreID genre Unique Identifier Music Genre 01 Heavy Metal Greasy Haired Bikers

Type of person boughtby that buys this music


mysql> CREATE TABLE genres (

-> genreID INT(2) auto_increment primary key,

-> genre VARCHAR(20),

-> boughtby VARCHAR(30)

->);

Insert the following genres into your genres table. The descriptions for the people that buy the music (boughtby) - I'll leave to you. You could also add other genres that are perhaps more relevant to your music collection. Pop Example INSERT:

Easy Listening 'Classic' Rock Heavy Metal Soul Seventies Eighties Hip Hop Jazz
-> 'Heavy Metal', -> '?', mysql> INSERT INTO genres VALUES (

-> 'Greasy Haired Bikers'

Guitar Bands
-> );

This could (depending on what you enter as descriptions) result in a table like this:
mysql> SELECT *

-> FROM genres;

+---------+----------------+------------------------------------------------+

| genreID | genre

| boughtby

+---------+----------------+------------------------------------------------+

1 | Pop

| Little girls and adults who should know better |

2 | Easy Listening | Crushed velvet wearing lounge lizards

3 | "Classic" Rock | Middle-aged men reliving their youth

4 | Heavy Metal | Wannabe Bikers - who work PT at safeway

5 | Soul

| White Boys in thin leather ties

6 | Seventies

| Those not born til 1980

7 | Eighties

| Those born in the '70's

8 | Hip Hop

| Middle-class Ghetto wannabes

9 | Jazz

| Those that *think* they are better

10 | Guitar Bands | Those stuck in 1996

+---------+----------------+------------------------------------------------+

10 rows in set (0.00 sec)

Adding a genre to the 'cds' table Next we need to add a 'genreID' column to our 'cds' table so we can store information about the genre of each cd. This will be an Integer (numeric) field as it will correspond to the unique identifier ('genreID') from the genres table.

mysql> ALTER TABLE cds

-> ADD genreID INT(2);

Check this has worked by using the describe command.


mysql> DESCRIBE cds;

+--------+-------------+------+-----+---------+----------------+

| Field | Type

| Null | Key | Default | Extra

+--------+-------------+------+-----+---------+----------------+

| cdID | int(3)

| PRI | NULL | auto_increment |

| artist | varchar(20) | YES |

| NULL |

| title | varchar(30) | YES |

| NULL |

| year | int(4)

| YES |

| NULL |

| label | varchar(20) | YES |

| NULL |

| bought | date

| YES |

| NULL |

| tracks | int(2)

| YES |

| NULL |

| genreID| int(2)

| YES |

| NULL |

+--------+-------------+------+-----+---------+----------------+

8 rows in set (0.00 sec)

IMPORTANT: As we now have a column called 'genreID' in both tables we need to distinguish which we are talking about. This is why we have been prefixing the column name with the table name to ensure we can tell the difference. For example 'cds.genreID' and 'genres.genreID' are easily distinguishable in our example. We are now ready to enter the genre type for each cd into our CDs table. If we decide that our 'Jamiroquai' album is a soul album we need the 'genres.genreID' number from our 'genres' table for the soul entry, i.e. '5'. We must UPDATE the Jamiroquai record in the CDs table so that the 'cds.genreID' column also has a value of '5'.
mysql> UPDATE cds

-> SET cds.genreID = 5

-> WHERE (cds.cdID = 2);

If we issue a SELECT all command we can see the effect this has.
mysql> select * from cds;

+------+-------------------+------------------+------+---------+

| cdid | artist

| title

| year | genreID |

+------+-------------------+------------------+------+---------+

| 2 | Jamiroquai

| A Funk Odyssey | 2001 |

5 |

Continue and enter the rest of the genres into the CDs table. Beginning with Joins Before starting with joins we should say a little about what exactly a join is. Basically it is the combining of two rows based on the comparative values in selected columns. This 'super-row' exists only for the duration of the query that creates it. We need to use joins to temporarily create 'complete' records from a database which may split related data across several tables (perhaps as a result of normalisation). Cross-Join Syntax:
SELECT <column_name>

FROM <table1>, <table2>

A cross-join between two tables takes the data from each row in table1 and joins it to the data from each row in table2. To give an example lets look at two very simple tables. id 1 2 3 1 animal Cat Dog Cow Cat id 1 2 Food Milk Bone

3 Grass A cross-join on these tables would produce the following result. 1 Milk

1 1 2 2 2 3 3

Cat Cat Dog Dog Dog Cow Cow

2 3 1 2 3 1 2

Bone Grass Milk Bone Grass Milk Bone

3 Cow 3 Grass Where every row from one table is joined to every row in the other table. We can also see the effect of this by using an SQL cross-join on our tables (although in this example we are asking only to display one column from each table).
mysql> SELECT cds.artist, genres.genre

-> FROM cds, genres;

You should see every artist associated with every genre. This is obviously not a very useful join, but does specify the need for a join that uses some kind of comparison between the two tables. The Equi-Join or Inner Join Syntax:
SELECT <column_name>

FROM <Table1>, <Table2>

WHERE (Table1.column = Table2.column)

In the equi-join the comparison we are making between two columns is that they match the same value. We can use this method to select certain fields from both tables and only the correct rows will be joined together. So if we were to use this join on the cds and genres tables in our own database (using the CDs that Mark provided in the last virtual workshop as an example).

mysql> SELECT cds.artist, cds.title, genres.genre

-> FROM cds, genres

-> WHERE (cds.genreID = genres.genreID);

+------------+------------------------------+----------------+

| artist

| title

| genre

+------------+------------------------------+----------------+

| Jamiroquai | A Funk Odyssey

| Soul

| Abba

| Abbas Greatest Hits

| Seventies

| Various | Now 49

| Pop

| westlife | westlife

| Pop

| Various | Eurovision Song contest 2001 | Easy Listening |

+------------+------------------------------+----------------+

5 rows in set (0.00 sec)

Obviously your data will be different as you should have different CDs and different genres. If we compare the cross-join and equi-join we can see that the equi-join is just the cross join with a very restrictive WHERE condition, that forces only the rows in the second table RELEVANT to the rows in the first table to be retrieved. This method is fine if all we want to do is look at normalised data in a temporarily flat database view. However if we want to filter this data, we have to start adding more conditions to our WHERE clause and it seems rather redundant to have to specify joining conditions as part of the WHERE condition every time. The Left Join The left join is a mechanism used to join tables before we add other conditions such as WHERE etc. Syntax:
SELECT <column_name>

FROM <Table1>

LEFT JOIN <Table2>

ON Table1.column = Table2.column

Without adding a WHERE clause the Left Join produces the same results as the equi-join example above.
mysql> SELECT cds.artist, cds.title, genres.genre

-> FROM cds

-> LEFT JOIN genres

-> ON cds.genreID = genres.genreID;

+-------------------+------------------------------+----------------+

| artist

| title

| genre

+-------------------+------------------------------+----------------+

| Jamiroquai

| A Funk Odyssey

| Soul

| Abba

| Abbas Greatest Hits

| Seventies

| Various

| Now 49

| Pop

| westlife

| westlife

| Pop

| Various

| Eurovision Song contest 2001 | Easy Listening |

+-------------------+------------------------------+----------------+

5 rows in set (0.00 sec)

An important thing to note with this particular join is that even if there are no records in the second table (in this case 'genres') data will still be displayed from the first table. Or in other words data from the LEFT of the join will be displayed and is where the term LEFT JOIN comes from. To demonstrate this, UPDATE a genreID from row four (cdID of 5, because we deleted one row in part three) of the cds table (in this case westlife) to a value that doesn't exist in the genres table.
mysql> UPDATE cds

SET cds.genreID = 100

WHERE (cds.cdID = 5);

...and run the query again....


mysql> SELECT cds.artist, cds.title, genres.genre

-> FROM cds

-> LEFT JOIN genres

-> ON cds.genreID = genres.genreID;

+-------------------+------------------------------+----------------+

| artist

| title

| genre

+-------------------+------------------------------+----------------+

| Jamiroquai

| A Funk Odyssey

| Soul

| Abba

| Abbas Greatest Hits

| Seventies

| Various

| Now 49

| Pop

| westlife

| westlife

| NULL

| Various

| Eurovision Song contest 2001 | Easy Listening |

+-------------------+------------------------------+----------------+

5 rows in set (0.00 sec)

The artist and title are still displayed even though there is no data in the genre (and thus NULL is shown). To further illustrate this we can issue a RIGHT JOIN which is a variation where all the data on the RIGHT side of the join (the second table) is returned regardless of the presence of data from the first table. Reset row four of the cds table to have the correct genreID value.
mysql> UPDATE cds

-> SET cds.genreID = 1

-> WHERE (cds.cdID = 5);

And run the RIGHT JOIN query including genres.genreID.


mysql> SELECT cds.artist, cds.title, genres.genreID, genres.genre

-> FROM cds

-> RIGHT JOIN genres

-> ON cds.genreID = genres.genreID;

+--------------------+------------------------------+---------+----------------+

| artist

| title

| genreID | genre

+--------------------+------------------------------+---------+----------------+

| Various

| Now 49

1 | Pop

| westlife

| westlife

1 | Pop

| Various

| Eurovision Song contest 2001 |

2 | Easy Listening |

| NULL

| NULL

3 | "Classic" Rock |

| NULL

| NULL

4 | Heavy Metal |

| Jamiroquai

| A Funk Odyssey

6 | Soul

| Abba

| Abbas Greatest Hits

6 | Seventies

| NULL

| NULL

7 | Eighties

| NULL

| NULL

8 | Hip Hop

| NULL

| NULL

9 | Jazz

| NULL

| NULL

10 | Guitar Bands |

+--------------------+------------------------------+---------+----------------+

19 rows in set (0.00 sec)

Note that where there aren't any cds in a genre then a NULL value is returned. This is because every record of the RIGHT side must be returned at least once by the RIGHT JOIN. Adding a WHERE Clause to our Join Now we have the join occurring out with the WHERE clause, we can begin to add other conditions. For example if we want to select only the pop CDs
mysql> SELECT cds.artist, cds.title, genres.genre

-> FROM cds

-> LEFT JOIN genres

-> ON cds.genreID = genres.genreID

-> WHERE genres.genre = 'pop';

+----------+----------+-------+

| artist | title | genre |

+----------+----------+-------+

| Various | Now 49 | Pop |

| westlife | westlife | Pop |

+----------+----------+-------+

2 rows in set (0.00 sec)

The USING Clause A variation on the Left Join is the 'USING' clause. You can use this if the columns you are carrying out the join on have the same name. Syntax:
SELECT <column_name>

FROM <Table1>

LEFT JOIN <Table2>

USING (<column_name>)

In our example we are joining the tables where cds.genreID is the same as genres.genreIDthus genreID is the name of a column in BOTH of tables we are using for the join.
mysql> SELECT cds.artist, cds.title, genres.genre

-> FROM cds

-> LEFT JOIN genres

-> USING (genreID);

+-------------------+------------------------------+----------------+

| artist

| title

| genre

+-------------------+------------------------------+----------------+

| Jamiroquai

| A Funk Odyssey

| Soul

| Abba

| Abbas Greatest Hits

| Seventies

| Various

| Now 49

| Pop

| westlife

| westlife

| Pop

| Various

| Eurovision Song contest 2001 | Easy Listening |

+-------------------+------------------------------+----------------+

5 rows in set (0.00 sec)

Mini Exercise Practice joining the 'CDs' and 'genre' tables to retrieve different data about the CDs, e.g.
mysql> SELECT cds.artist, genres.boughtby

-> FROM cds

-> LEFT JOIN genres

-> USING (genreID);

+-------------------+------------------------------------------------+

| artist

| boughtby

+-------------------+------------------------------------------------+

| Jamiroquai

| White Boys in thin leather ties

| Abba

| Those not born til 1980

| Various

| Little girls and adults who should know better |

| westlife

| Little girls and adults who should know better |

| Various

| Crushed velvet wearing lounge lizards

+-------------------+------------------------------------------------+

5 rows in set (0.00 sec)

Preparing to Join more than two tables It is also possible to join more than two tables. In order to do this, however, we will need to make a third table - this time an 'artist' table containing the artistID and name. This will use a lot of the techniques we've used in the previous workshops, so you may have to refer back to refresh your memory as to what you are doing, which will serve as more good revision. We are going to create our new table using a five stage process CREATE the new table INSERT our artist data using a SELECT DISTINCT query ALTER TABLE to add a new 'artistID' COLUMN to the cds TABLE UPDATE the cds.artistID fields to match the values assigned to an artist in the new table DROP the artist column from the cds TABLE We'll go through each step in more detail CREATE the new table A simple enough revision of what we've done before.
mysql> CREATE TABLE artists (

-> artistID int(3) auto_increment primary key,

-> Artist varchar(20)

-> );

INSERT our artist data using a SELECT DISTINCT query This uses similar syntax to that in Workshop Four. We are going to insert the artists from the cds table into the artist.Artist column.
mysql> INSERT INTO artists (artists.Artist)

-> SELECT DISTINCT cds.artist

-> FROM cds;

You can check this has worked by using a (by now) standard SELECT statement.
mysql> SELECT *

-> FROM artists;

ALTER TABLE to add a new 'artistID' COLUMN to the cds TABLE As we are going to refer to the artist by their artistID rather than their name, we need to create a column in the cds table to hold that ID.
mysql> ALTER TABLE cds

-> ADD artistID int(3);

Check this has worked with a describe statement.


mysql> DESCRIBE cds;

+----------+-------------+------+-----+---------+----------------+

| Field

| Type

| Null | Key | Default | Extra

+----------+-------------+------+-----+---------+----------------+

| cdID

| int(3)

| PRI | NULL | auto_increment |

| artist | varchar(20) | YES |

| NULL |

| title | varchar(30) | YES |

| NULL |

| year

| int(4)

| YES |

| NULL |

| label | varchar(20) | YES |

| NULL |

| bought | date

| YES |

| NULL |

| tracks | int(2)

| YES |

| NULL |

| genreID | int(2)

| YES |

| NULL |

| artistID | int(3)

| YES |

| NULL |

+----------+-------------+------+-----+---------+----------------+

9 rows in set (0.00 sec)

UPDATE the cds.artistID fields without Joins For those with access to older versions of MySQL this will have to be done by hand. If you are lucky enough to have MySQL 4.0 installed, then you could use UPDATE joins to speed up this stage, but for consistency between versions MySQL I will demonstrate the method common to older and newer versions of MySQL.
mysql> UPDATE cds

-> SET cds.artistID = 1

-> WHERE (cds.artist = 'Jamiroquai');

......etc usng all the artistIDs from the artists table. Check that this has worked with a few joins between the cds and artists TABLEs eg
mysql> SELECT artists.Artist, cds.title

-> FROM artists

-> LEFT JOIN cds

-> USING (artistID)

-> WHERE (cds.artistID = 1);

+------------+----------------+

| name

| title

+------------+----------------+

| jamiroquai | A Funk Odyssey |

+------------+----------------+

DROP the artist column from the cds TABLE As we can retrieve the artist name by using a join and the artistID we can remove the cds.artist column.
mysql> ALTER TABLE cds

-> DROP artist;

Query OK, 16 rows affected (0.15 sec)

Records: 16 Duplicates: 0 Warnings: 0

We are now ready to join all three tables together. Joining Three Tables Before we start with statements let's just recap what we can expect to do. We have a cds table that contains the foreign keys (i.e. values that correspond to primary keys in another table) called cds.genreID and cds.artistID which also exist in the genres and artists tables. A three table join can be achieved using another version of the Equi-Join or Inner Join where we can use the WHERE clause to limit the returned records based on comparing the artistID and the genreID.
mysql> SELECT artists.Artist, cds.title, genres.genre

-> FROM cds, artists, genres

-> WHERE (

-> (cds.artistID = artists.artistID)

-> AND (cds.genreID = genres.genreID)

-> );

+-----------------+------------------------------+----------------+

| name

| title

| genre

+-----------------+------------------------------+----------------+

| Jamiroquai

| A Funk Odyssey

| Soul

| Abba

| Abbas Greatest Hits

| Seventies

| Various

| Now 49

| Pop

| westlife

| westlife

| Pop

| Various

| Eurovision Song contest 2001 | Easy Listening |

+-----------------------------------------------------------------+

The problem with this is, once more, that we already have a fairly complex WHERE clause just to join ALL the records properly. Ideally what we want is to have a LEFT / RIGHT JOIN, but this is a problem as we cannot compare just two tables (Right vs Left). The solution to this is to use a series of joins. This is where one join is made, but rather than being used to evaluate and display the data from it, the result is passed to a second join and THEN the data can be displayed. A good way to think of this is that the first JOIN creates a virtual table (from joining tables one and two) which can then be joined to the third table. So let us first make a LEFT JOIN between the 'cds' and 'genres' tables.
mysql> SELECT cds.title, genres.genre

-> FROM cds

-> LEFT JOIN genres

-> ON cds.genreID = genres.genreID;

+------------------------------+----------------+

| title

| genre

+------------------------------+----------------+

| A Funk Odyssey

| Soul

| Abbas Greatest Hits

| Seventies

| Now 49

| Pop

| westlife

| Pop

| Eurovision Song contest 2001 | Easy Listening |

+------------------------------+----------------+

5 rows in set (0.02 sec)

Next we add another JOIN and SELECT the artists.Artist as well. The order in which you SELECT COLUMNs determines how your results will look, so we should place artists.Artist first.
mysql> SELECT artists.Artist, cds.title, genres.genre

-> FROM cds

-> LEFT JOIN genres

-> ON cds.genreID = genres.genreID

-> LEFT JOIN artists

-> ON cds.artistID = artists.artistID;

+------------+------------------------------+----------------+

| name

| title

| genre

+------------+------------------------------+----------------+

| Jamiroquai | A Funk Odyssey

| Soul

| Various | Now 49

| Pop

| westlife | westlife

| Pop

| Various | Eurovision Song contest 2001 | Easy Listening |

| Abba

| Abbas Greatest Hits

| Seventies

+------------+------------------------------+----------------+

5 rows in set (0.01 sec)

We can now add a WHERE clause to restrict the output.


mysql> SELECT artists.Artist, cds.title, genres.genre

-> FROM cds

-> LEFT JOIN genres

-> ON cds.genreID = genres.genreID

-> LEFT JOIN artists

-> ON cds.artistID = artists.artistID

-> WHERE (genres.genre = 'Pop');

+----------+----------+-------+

| name

| title | genre |

+----------+----------+-------+

| Various | Now 49 | Pop |

| westlife | westlife | Pop |

+----------+----------+-------+

2 rows in set (0.01 sec)

We could also join a fourth table, imagine we were to repeat the steps above to create a 'label' table. We could issue the a statement to join all four tables and display the results.
mysql> SELECT artists.Artist, cds.title, label.Label, genres.genre

-> FROM cds

-> LEFT JOIN genres

-> ON cds.genreID = genres.genreID

-> LEFT JOIN artists

-> ON cds.artistID = artists.artistID

-> LEFT JOIN label

-> ON cds.labelID = label.labelID;

+------------+------------------------------+------------+----------------+

| Artist

| title

| Label

| genre

+------------+------------------------------+------------+----------------+

| Jamiroquai | A Funk Odyssey

| Sony soho2 | Soul

| Various | Now 49

| virgin

| Pop

| westlife | westlife

| jive

| Pop

| Various | Eurovision Song contest 2001 | EMI

| Easy Listening |

| Abba

| Abbas Greatest Hits

| EMI

| Seventies

+------------+------------------------------+------------+----------------+

5 rows in set (0.01 sec)

This method of adding tables and performing JOINS will work only if one TABLE has all the foreign keys. Joins CAN be more complex mixing types etc, but I don't think it is necessary to go into that here. UPDATE and DELETE Joins Requires MySQL 4.0 or later. As MySQL 4.0 has been deemed to be stable enough for production use it is worth mentioning some of the functionality that has been added and significantly this includes UPDATE and DELETE joins. To find out the version of MySQL that you have access to you are usually told when you login at the command-line:
Welcome to the MySQL monitor. Commands end with ; or \g.

Your MySQL connection id is 2471 to server version: 4.0.12

Type 'help;' or '\h' for help. Type '\c' to clear the buffer.

mysql>

But you can also check from the command-line using a 'STATUS' command:

mysql> STATUS

--------------

mysql Ver 12.18 Distrib 4.0.12, for pc-linux (i586)

Connection id:

2563

Current database:

Current user:

kbrown@localhost

SSL:

Not in use

Current pager:

stdout

Using outfile:

''

Server version:

4.0.12

If you do indeed have access to MySQL 4.0 then you can continue with this workshop. UPDATE Joins Before looking at the syntax let's pause for a moment and think why we would want a to perform a Join as part of a UPDATE statement? Well the answer is to

update more than one table using only one statement or indeed to insert the values from one table into another. The syntax for UPDATE joins is very similar to a normal UPDATE statement only including one of the Joins specified above in much the same way as the select statement did. Thus to use an Equi-Join in the statement, we specify the tables together, what we want to SET and then use a WHERE condition to apply the constraint.
UPDATE <table1>, <table2>

SET <column_name> = 'new_value'

WHERE <conditions>

For Example let's temporarily change the values of the 'A Funk Odyssey' album so that it was recorded by 'George Clinton' (see bio if you're confused), and called 'The Funkship Odyssey' (one of the lesser known works ;-). Thus we have to update the 'cds' (to change the title) and the 'artists' table, use one condition to constrain the join (cds.artistID =artists.artistID) and then a final condition to only effect that row (and not all rows).
mysql> UPDATE cds, artists

-> SET

-> cds.title = 'The Funkship Odyssey',

-> artists.Artist = 'George Clinton'

-> WHERE (artists.artistID = cds.artistID)

-> AND (cds.cdID = '2');

Query OK, 2 rows affected (0.00 sec)

Rows matched: 2 Changed: 2 Warnings: 0

You can use a SELECT join described above to view how this has changed the values.
mysql> SELECT artists.Artist, cds.title

-> FROM artists

-> LEFT JOIN cds

-> USING (artistID)

-> WHERE (cds.artistID = 1);

+----------------+----------------------+

| name

| title

+----------------+----------------------+

| George Clinton | The Funkship Odyssey |

+----------------+----------------------+

1 row in set (0.00 sec)

Of course by replacing 'Jamiroquai' in the 'artists' table it now means that George has recorded all their albums and since George didn't record 'The Funkship Odyssey' we'd better put things back the way they were. This time we will use a LEFT JOIN to UPDATE the values. In a SELECT statement the LEFT JOIN allowed us to join the tables before applying any constraints and in an UPDATE join this is no different. First we make the LEFT JOIN, then use SET to provide new and finally use the WHERE condition to restrict which rows this effects.
mysql> UPDATE cds->LEFT JOIN artists

-> ON cds.artistID = artists.artistID

-> SET

-> cds.title = 'A Funk Odyssey',

-> artists.name = 'Jamiroquai'

-> WHERE (cds.cdID = '2');

Query OK, 2 rows affected (0.00 sec)

Rows matched: 2 Changed: 2 Warnings: 0

The other purpose of the UPDATE join is to set the value of a column in one table to that of a column in another table. This isn't particularly spectacular as any

column value in a table can be set to the value of another column in the same row and as any join results in a 'big virtual table' this can be done also. To illustrate let's recreate the artist field in the cds table that we deleted above and repopulate the column using an UPDATE join. First modify the cds table:
mysql> ALTER TABLE cds

-> ADD artist VARCHAR(20);

Next set a value of 'Unknown' for each row.


mysql> UPDATE cds

-> SET artist = 'Unknown';

Query OK, 9 rows affected (0.00 sec)

Rows matched: 9 Changed: 9 Warnings: 0

mysql> SELECT cds.artist, cds.title

-> FROM cds;

+---------+------------------------------+

| artists | title

+---------+------------------------------+

| Unknown | A Funk Odyssey

| Unknown | Now 49

| Unknown | westlife

| Unknown | Eurovision Song contest 2001 |

| Unknown | Abbas Greatest Hits

+---------+------------------------------+

5 rows in set (0.01 sec)

Next we will use an UPDATE Join to merge with the 'artists' table and SET the value of the ofcds.artist = artists.Artist. Let's only do one artist (Jamiroquai again) to see how this works.
mysql> UPDATE cds

-> LEFT JOIN artists

-> USING (artistID)

-> SET cds.artists = artists.Artist

-> WHERE (cds.cdID = '2');

Query OK, 1 row affected (0.02 sec)

Rows matched: 1 Changed: 1 Warnings: 0

mysql> SELECT cds.artists, cds.title

-> FROM cds;

+------------+------------------------------+

| artists | title

+------------+------------------------------+

| Jamiroquai | A Funk Odyssey

| Unknown

| Now 49

| Unknown

| westlife

| Unknown

| Eurovision Song contest 2001 |

| Unknown

| Abbas Greatest Hits

+------------+------------------------------+

5 rows in set (0.01 sec)

We can rerun the query without the final WHERE condition that constrains the row and all the artists will be correctly identified by the join.
mysql> UPDATE cds

-> LEFT JOIN artists

-> USING (artistID)

-> SET cds.artists = artists.name;

Query OK, 8 rows affected (0.02 sec)

Rows matched: 9 Changed: 8 Warnings: 0

mysql> SELECT cds.artists, cds.title

-> FROM cds;

+-------------+------------------------------+

| artists

| title

+-------------+------------------------------+

| Jamiroquai | A Funk Odyssey

| Various

| Now 49

| westlife | westlife

| Various

| Eurovision Song contest 2001 |

| Abba

| Abbas Greatest Hits

+-------------+------------------------------+

5 rows in set (0.01 sec)

Notice: These last two examples have included a USING clause as part of the Join. DELETE Joins The final Join that I am going to discuss in this workshop is the DELETE Join. This is pretty much a case of 'same again' so I'll only give a quick examples in which I'll delete Westlife who have an artistID = '3' (if only it were that easy ;->. To DELETE from just the 'cds' table include only the 'cds' table between DELETE and FROM (the join is made after FROM so both tables are needed there).

mysql> DELETE cds

-> FROM cds, artists

-> WHERE (cds.artistID = artists.artistID)

-> AND (cds.artistID = '3');

To DELETE from both tables:


mysql> DELETE cds, artist

-> FROM cds, artist

-> WHERE (cds.artistID = artists.artistID)

-> AND (cds.artistID = '3');

Phew.. I bet you're glad that's over. ;-)

Using MySQL, Administration Workshop Requirements You should have access to the MySQL command line client software. Various different PRIVILEGES on the MySQL Server Introduction In the other MySQL Virtual Workshops we have used commands that are pretty much applicable to anyone. This part of the MySQL series is aimed at giving a

rudimentary understanding of managing a MySQL database server. As such the task covered here are not really about manipulating data or database structures, but the actual databases themselves. Creating a Database In order to create a database you need to have the PRIVILEGES- this may be because you are the root user or you (or you systems administrator) has created an admin user that has ALL PRIVILEGES over all databases. In these examples a user called 'admin' has been created precisely for this purpose. Creating a database is fairly straightforward. Logging In A reminder of how to start the MySQL Client Software, and as we are not concerned with manipulating just one database we don't have to specify a database as part of our startup command.
$ mysql -u <username> -p

Enter password:

Create database command Next we are ready to enter the very simple command to create a database which is:
mysql> CREATE DATABASE <database>;

Let's imagine that we are going to create a 'vworks' database (those wishing to create a database for use with the VWs should use this). We would enter the command:
mysql> CREATE DATABASE vworks;

We can now check for the presence of this database by typing:


mysql> SHOW DATABASES;

+-----------+

| Database |

+-----------+

| mysql

| vworks |

+-----------+

2 rows in set (0.06 sec)

The other database listed ('mysql') is the internal database which MySQL uses to manage users, permissions etc. NOTE: Deleting or DROPing a database is similar to the DROP TABLE command issued in Part 4. e.g.
DROP DATABASE <database>

Granting Privileges on the new database Now that we have created a database, we need to decide who gets to use it. This is done by granting permissions for a user to use the database. This has a simplified syntax of:
GRANT <privileges>

ON <database>

TO <user>

[IDENTIFIED BY <password>]

[WITH GRANT OPTION]

Where the items in square brackets are optional. The most common use is to give ALL PRIVILEGES on a database to a local user who has to use a password to access the database (in this case vworks).
mysql> GRANT ALL PRIVILEGES

-> ON vworks.*

-> TO newuser@localhost

-> IDENTIFIED BY 'newpassword';

If you are creating a database for use with the rest of the Virtual Workshops you should use this statement, substituting your username and password of choice. There are some other options we will look at. To restrict the user to manipulating data (rather than table or database structures) the statement would be altered to:
mysql> GRANT SELECT,INSERT,UPDATE,DELETE

-> ON vworks.*

-> TO newuser@localhost

-> IDENTIFIED BY 'newpassword';

So that the user can only change the data using SELECT,INSERT,UPDATE or DELETE statements. If you wished to give a non-local user permissions on the database (for use with remote clients) then you could designate an IP or host address from which the user can connect:
mysql> GRANT ALL PRIVILEGES

-> ON vworks.*

-> TO newuser@192.168.0.2

-> IDENTIFIED BY 'newpassword';

Now a user on the machine '192.168.0.2' can connect to the database. To allow a user to connect from anywhere you would use a wildcard '%'
mysql> GRANT ALL PRIVILEGES

-> ON vworks.*

-> TO newuser@'%'

-> IDENTIFIED BY 'newpassword';

You could even decide that a user doesn't need a password if connecting from a certain machine.
mysql> GRANT ALL PRIVILEGES

-> ON vworks.*

-> TO newuser@192.168.0.2

But I think it is sometimes good to provide a password anyway. Finally we'll look at the WITH GRANT OPTION condition. This allows the user to give others privileges to that database:
mysql> GRANT ALL PRIVILEGES

-> ON vworks.*

-> TO newuser@localhost

-> IDENTIFIED BY 'newpassword'

-> WITH GRANT OPTION;

This would allow the user 'newuser' to log into the database and give their friend privileges to SELECT,INSERT,UPDATE or DELETE from the database.
mysql> GRANT SELECT,INSERT,UPDATE,DELETE

-> ON vworks.*

-> TO friend@localhost

-> IDENTIFIED BY 'friendpass';

The WITH GRANT OPTION usually signifies ownership although it is worth noting that no user can GRANT more privileges that they themselves possess. Revoking privileges Revoking privileges is almost identical to granting them as you simply substitute RE VOKE.... FROM for GRANT....TO and omit any passwords or other options. For example to REVOKE the privileges assigned to a user called 'badvworks':
mysql> REVOKE ALL PRIVILEGES

-> ON vworks.*

-> FROM badvworks@localhost;

Or just to remove UPDATE, INSERT and DELETE privileges to that data cannot be changed.
mysql> REVOKE INSERT,UPDATE,DELETE

-> ON vworks.*

-> FROM badvworks@localhost;

Backing Up Data There are several methods we can use to backup data. We are going to look at a couple of utilities that come with MySQL: mysqlhotcopy and mysqldump. mysqlhotcopy mysqlhotcopy is a command line utility written in Perl that backs up (to a location you specify) the files which make up a database. You could do this manually, but mysqlhotcopy has the advantage of combining several different commands that lock the tables etc to prevent data corruption. The syntax (as ever) first.

$ mysqlhotcopy -u <username> -p <database> /backup/location/

Which SHOULD copy all the tables (*.frm, *.MYI, *.MYD) into the new directory the script does require the DBI perl module though. To restore these backup files simply copy them back into your MySQL data directory. mysqldump This is my preferred method of backing up. This outputs the table structure and data in series of SQL commands stored in a text file. The simplified syntax is
$ mysqldump -u <username> -p <database> [<table>] > file.sql

So for example to back up a 'vworks' database which may have been created by completing the workshops:
$ mysqldump -u admin -p vworks > vworks.sql

After entering the password a 'vworks.sql' file should be created. When you look at this file you can actually see that the data and structures are stored as a series of SQL statements. e.g.:
-- MySQL dump 8.22

--

-- Host: localhost

Database: vworks

---------------------------------------------------------

-- Server version

3.23.52

--

-- Table structure for table 'artist'

--

CREATE TABLE artist (

artistID int(3) NOT NULL auto _increment,

name varchar(20) default NULL,

PRIMARY KEY (artistID)

) TYPE=MyISAM;

--

-- Dumping data for table 'artist'

--

INSERT INTO artist VALUES (1,'Jamiroquai');

INSERT INTO artist VALUES (2,'Various');

INSERT INTO artist VALUES (3,'westlife');

INSERT INTO artist VALUES (4,'Various');

INSERT INTO artist VALUES (5,'Abba');

And so on for the other tables. We could also have chosen to output just one table from the database, for example the artist table:
$ mysqldump -u admin -p vworks artist > artist.sql

We could even dump all the databases out (providing we have the permissions).
$ mysqldump -u admin -p --all-databases > alldb.sql

Restoring a Dump Restoring a dump depends on what you have actually dumped. For example to restore a database to a blank database (perhaps having transferred the sql file to another machine) it is fairly simple.
$ mysql -u admin -p vworks < vworks.sql

...or to add a non-existent table to a database...


$ mysql -u admin -p vworks < artist.sql

However, what happens if we want to restore data to an existing database (perhaps a nightly backup) ? Well we would have to add other options: The equivalent of overwriting the existing tables would be telling the dump to automatically drop any tables that exist before restoring the stored tables. This is done with the ' --add-drop-table ' option added to our statement.
$ mysqldump -u admin -p --add-drop-table vworks > vworks.sql

Then restore like normal:


$ mysql -u admin -p vworks < vworks.sql

The reverse might also be true. We may wish to create the database if it doesn't already exist. To do this we use the '--databases' option to specify the database we wish to back up (you can specify more than one).
$ mysqldump -u admin -p --databases vworks > vworksDB.sql

This will create additional SQL statements at the start of each database that CREATEs the dumped database (checking first to see if it does indeed exist) then USEing that database to import the table data into.
-- Current Database: vworks

--

CREATE DATABASE /*!32312 IF NOT EXISTS*/ vworks;

USE vworks;

Again we can resort like normal, but of course this time we can omit the database name.
$ mysql -u admin -p < vworksDB.sql

Optimising a dump There are a couple of options that are sometimes worth including when backing up and restoring large databases. The first option is '--opt', this is used override the mysql server's normal method of reading the whole result set into memory giving a faster dump. Example:
$ mysqldump -u admin -p --opt vworks > vworks.sql

The second option is '-a' or '-all' (either will do). Which also optimises the dump by creating mysql specific CREATE statements that speeds up the restore:
$ mysqldump -u admin -p --all vworks > vworks.sql

Using mysqldump to copy databases. It is possible to combine a dump and a restore on one line by using a pipe '|' to pass the output of the dump directly to mysql basically bypassing the file. This may initially seem a bit redundant, but we can use this method to copy a database to another server or even create a duplicate copy. For example to copy the 'vworks' database to a mysql server called 'remote.server.com':
$ mysqldump -u admin -p --databases vworks | \

> mysql -u backup -p MyPassword -h remote.server.com

Note: the"\" at the end of the first line means you wish to contine the command on another line before executing it. You may, in certain circumstances, wish to make a copy of live data so that you can test new scripts and 'real world' data. To do this you would need to duplicate a local database. First create the duplicate database:
mysql> CREATE DATABASE vworks2;

Then once appropriate privileges have been assigned we can copy the tables from the first table into the second.
$ mysqldump -u admin -p vworks | mysql -u backup -p MyPassword vworks2

Notice in both these examples the second half of the line (after the pipe) passes the password as part of the connection statement. This is because asking for two separate passwords at the same time breaks most shells. That is why I have used a 'backup' user who can be granted permissions and have them revoked as necessary. Miscellaneous Leftovers This final bit includes a few brief tricks that weren't really appropriate to include elsewhere, but are still worth noting. Remote Client Connection If you have set the privileges to allow remote connections to a database, you can connect from a remote command line client by using the -h flag:
$ mysql -u <username> -p -h <host>

For example to connect to a fictional vworks.keithjbrown.co.uk server:


$ mysql -u admin -p -h vworks.keithjbrown.co.uk

Non-Interactive Commands Sometimes you may wish to just do a quick look up on a table without the hassle of logging into the client, running the query then logging back out again. You can instead just type one line using the ' -e ' flag. For example:

$ mysql -u admin -p vworks -e 'SELECT cds.artist, cds.title FROM cds'

Enter password:

+------------+------------------------------+

| artist

| title

+------------+------------------------------+

| Jamiroquai | A Funk Odyssey

| Various | Now 49

| westlife | westlife

| Various | Eurovision Song contest 2001 |

| Abba

| Abbas Greatest Hits

+------------+------------------------------+

Using MySQL, Normalisation Workshop Requirements You should have experience of using databases and be familiar with retrieving data from multiple tables using SQL joins. It is also advised that you have undertaken parts 1 -5 of this MySQL series so that you are familiar with the examples used so far.

Introduction In the previous workshops in the MySQL series, I've hinted at elements of database theory (such as normalisation and foreign keys) without fully explaining these concepts and why people use them when building databases. This workshop aims to fill this gap, but from previous experience in trying to teach this material, I know that a lot of people when learning about normalisation have problems relating the theory to practice. This workshop aims to bridge this gap by providing numerous examples that illustrate both why we need to normalise at each step and also what effect is has on the data. It is not intended to be an indepth theoretical discussion of database theory, however that said we cannot normalise by studying the data alone and thus we will need to touch on a few other database concepts to fully understand what is going on. Unusually for these workshops I'm going to give figure numbers to the examples used and store them in external files. This is due to the size of some of the tables, and it may be worth printing these figures before continuing. Figure Figure Figure Figure Figure Figure One - Unnormalised Table - HTML | PDF Two - Problematic First Normal Form - HTML | PDF Three - Correct First Normal Form - HTML | PDF Four - Correct Second Normal Form - HTML | PDF Five - Partial Third Normal Form - HTML | PDF Six - Third Normal Form - HTML | PDF

What is normalisation? Normalisation is a process whereby the tables in a database are optimised to remove the potential for redundancy. Two main problems may arise if this is not done: Repeated data makes a database bigger. Multiple instances of the same values make maintaining the data more difficult and can create anomalies. I'm not going to develop the idea of specific anomalies much here, but to briefly illustrate an update anomaly imagine the problem of updating a customer address. If there are multiple instances of that customer in a database, any query designed to update the address will have to update them all. That may seem easy - as we've seen in previous workshops, SQL is quite capable of updating information based on field values. But if one of those field values (say an instance of customer name) has been input incorrectly, then the query would fail to update the address in that row. Normalisation aims to remove this redundancy by applying rules in a series of stages, splitting tables and creating relationships between unique identifiers (keys), to ensure that the database table structure is efficient, but data can still be accurately manipulated. It can also be used in conjunction with other database modeling techniques, such as Entity-Relationship diagrams, but for simple databases normalisation can sometimes be enough.

Before we begin looking at the stages of normalisation it is worth having a brief look at some of the relational database concepts that inform the process. Relational Databases Relational databases, such as MySQL, are so called as they rely on a series of relationships to connect data stored in different tables. For example in Workshop 5 we looked at joining tables based on a common ID number. This ID number was stored as the uniquely valued field (Primary Key) in one table (for example in the artists table), but was also present in another table (the cds table) acting as a reference (Foreign Key) that allowed data to be retrieved from both tables when the values matched. For ease of understanding we used unique numbers, but a key can have any value provided it conforms to a few rules. Terminology When were we were looking at the physical database in the other workshops we used certain terms such as TABLE, COLUMN, ROW and FIELD. Discussion of database theory uses different terms: Relation/ Entity - These are the same as a table. Attributes - These are similar to columns in that they describe the type of data stored. Domain - This is values within the same attribute (a collection of fields that exist in the same column). Tuple - This is a record similar to a row. As I'm trying to tie the theoretical discussion to the practical database experience that the first Virtual Workshops provide, I will use both sets of terms in this workshops for clarity and to reinforce their meanings. Different Keys The Primary Key is frequently used to identify a specific row (although other keys may exist) and all the other values are dependent on that value to be meaningfully identified. A primary key is usually one attribute (column) in a relation (table), but can be a combination of attributes (Composite Key). If we consider the following attributes some cds may have. Catalogue Artist Title No. The obvious Primary Key for a CDs table would be the Catalogue No. as each cd has already been given a unique identifier. But consider if we knew for a FACT that each artist could only release one CD per year (perhaps something a cold war communist five year plan would envisage ;-). CDs

Catalogue Artist Year Title No. By adding the Year attribute we could also use the combination of Artist and Year to identify the row, as together they form a Composite Key. Thus we have two potential or Candidate Keys.We would probably still choose the Catalogue No. as it is simpler to use one column. Consider a Radio station wishing to record how many times a day they played a CD. The combination of Catalogue No. and the date would be sufficient to identify that record in a new Play History table. Play History Catalogue Times Date No. Played In this situation the Catalogue No. number is a Primary Key in the CDs table but only part of the Composite Key in the Play History table as the date is required to form the whole key. An alternative could have been to create a new unique single-attribute Primary Key for the table. As we still need to know the catalogue number in order to work out how many times a DJ has played Britney, Christina or Pink (BBC Radio One is just rubbish isn't it? ;-). The catalogue number would become the Foreign Key in the Play History table, i.e. a Foreign Key is a attribute (column) in a table that refers to a Primary Key in another table. Play History Catalogue Times Date No. Played During the Normalisation process we will look at the rules that determine how tables (and thus Keys) are formed. HistoryID NULLs NULLs are used in databases to signify that no value exists, but are not really values themselves. Instead they are just an indictor that a particular field has no value. Some argue this is better that leaving a blank value or having a default value. I personally see little value in a blank field, but default values (such as inserting the current date if no date is offered) can be useful. The interesting point about NULLs is that no Primary Key can contain a NULL and thus it's useful when comparing Candidate Keys to see if one could potentially contain a NULL, thus disqualifying that key. Functional Dependency This is another important concept and describes the relationship that columns have to the Primary Key. If the value of a column can be determined by knowing the value of the Primary Key and no other, then that column is said to be functionally dependent. Or more simply, if we know the value of Primary Key we

can find out the value of any other dependent column. This dependency is often written as follows (where A is the Primary Key and B is the dependent column): AB So for the CDs table above we could express the dependency of the title column as either: Catalogue No Title (Artist, Year) Title This is important as we will see later as having a column that is NOT wholly dependent on the Primary Key causes redundancy. Having introduced some of the terminology of Normalisation we can begin to look at the stages. Describing relations (tables) When describing tables there is a way of expressing their structure that we will follow here. This includes the name of the relation, the attributes within the relation and which attributes are key. The format of this is: RELATION (attribute one, attribute two, attribute three etc) NOTE: The key attribute is underlined to signify its status and the relation is always UPPERCASE. For example consider how we might express the CDs table above: CDS (Catalogue No, Artist, Year, Title) Stages of Normalisation There are several stages of normalisation that a database structure can be subject to, each with rules that constrain the database further and each creating what is called a Normal Form. These are, in order: First Normal Form (1NF) Second Normal Form (2NF) Third Normal Form (3NF) Boyce Codd Normal Form (BCNF) Fourth Normal Form (4NF) Fifth Normal Form (5FN) or

As the database goes through each stage of normalisation it is said to be in the normal form of that stage, i.e. my database is 2NF and I need it to be 3NF. We are not going to take a detailed look at all of these Normal Forms as BCNF, 4NF and 5NF are probably overkill for small to medium databases, with the first 3 normal forms usually being sufficient. There is also one other stage - that of the Un-normalised Normal Form (UNF) which is the starting point for Normalisation. To begin our examination we must first create this Un-normalised data.

Our Scenario In order to look at database normalisation we are going to use a fictional CD library (quaint in the post-napster internet age I know). Monitoring who has which CD is done by logging the CDs in and out of a loans logbook that contains the following information (with sample data): Date Due Borrowed Back 1st Dec 8th Dec Borrower Artist David Findlay Britney Spears Pink Christina Aguilera Darius Christina Aguilera Will Young Title Britney Cant Take Me Home Stripped Dive In Stripped From Now On

8th Dec

15th Dec

Iain Brown

Loans Log Information is also stored about each borrower in a separate register: Borrower David Findlay Address Davies Lodge 23 Driven St Edinburgh EH5 APE 54 Home St, Edinburgh, EH23 8TF Telephone No. 0131 555 5579 E-mail david@keithjbrown.co.uk

Iain Brown

0131 555 5580

iain@keithjbrown.co.uk

Borrower Log Finally there is a ledger that stores information about the CDs: Artist Britney Spears Pink Title Britney Cant Take Me Home Year Label 2001 Jive 2002 LaFace No. of Copy Tracks 14 15 1 3

Christina Aguilera Darius Will Young CDs Log

Stripped Dive In From Now On

2002 RCA

20

1 2 2

2002 Mercury 13 2002 RCA 13

We can see that by keeping the information in different places there has been some common sense organisation to avoid repeatedly entering the same data. This is quite common, but doesn't really help up as we need to use the normalisation rules to determine our structure. Thus to begin with we'll merge the fields (attributes) into a one-table structure along with the same sample data (figure one). We have also created a column called 'Transaction No' which we use as a primary key to uniquely identify each lending transaction. We do this as there is no other simple method of identifying the row, as the obvious Composite Key is pretty complex (date borrowed, name, artist, title ), and thus assigning a unique 'Transaction No' is sensible at this time. Note: As I mentioned above, a modeling tool like Entity Relationship diagramming is good to use before normalisation. This would be essential on larger database projects as it would be pretty impossible to create one big flat database to begin normalising. It is also worth noting that although we have included sample data, we are normalising the database structure and not the data itself. It can be easier however to spot potential problems when using example data. So to begin the normalisation process, we use the Un-normalised data and apply our first rules. First Normal Form (1NF) For a relation (table) to conform be 1NF, it must obey the following rules: To ensure that all attributes (columns) are atomic - what this means is that any single field should only have a value for ONE thing. For example in a 'cars' database a field in a car may have a value 'Blue Ford Focus'. This field is not atomic as it contains more than one piece of information (colour, manufacturer and model). To demostrate the problem with this, try to imagine constructing a query to select all Ford cars. This would be difficult as the value 'Ford' is trapped in the middle of the field. We must therefore split this column into three (colour, manufacturer, model). There must be no repeating groups. This is perhaps most easily understood as there cannot be repeating columns (that which contain the same type of information). For example a cars field may contain a list of Ford cars (Focus, Mondeo, Puma). As we know we must also make this field atomic, but splitting the column into three (cars1, cars2, cars3) to hold these values isn't an option

this time as that would create three domains that contain similar data. In practice this would make querying the databases difficult as we would have to create a statement to match potential values in 3 columns (or four, or five or a hundred if there was a list of a hundred). Splitting the field so that each value was in a separate row would require the other values in the rows to be repeated. The solution is to change the structure of the database by extracting the repeating groups into a new relation. 1NF in practice Looking at the Un-normalised data we can see that there are non-atomic attributes in the following columns: Borrower Address The elements that make up the CD information (Artist, Title, Year, Label, No_of_Tracks and Copy) All of these attributes will have to be normalised so the domain contains only atomic values. To begin, let's look at the Borrower attribute. If you look at a section of the table in figure one, the borrower contains a forename and a surname expressed as a single borrower. Thus we should split this attribute in two. The following excerpt from the table illustrates how this would be implemented: TransactionNo. Forename Surname Address 1 David Findlay TeleNo

Davies 0131 Lodge 555 23 Driven 5579 St Edinburgh EH5 APE 54 Home 0131 St 555 Edinburgh 5580 EH23 8TF

Iain

Brown

Similarly the address should be broken down into different attributes. Anyone that has filled out an application form will be familiar with this structure. Address Line One Address Line Two (for the purposes of this workshop we'll stop at two address lines - in reality you may need more) City Postcode Once more we can see the effect that this has on an excerpt of the table:

TransactionNo. Forename Surname 1 2 David Iain Findlay Brown

Address_ Address_ City One Two Davies Lodge 54 Home St 23 Driven St

Postcode

Edinburgh EH5 APE Edinburgh EH23 8TF

The Repeating Group If we have a look at the remaining non-atomic attributes we can see that they are similar informational attributes about a CD. In order to make these attributes atomic we cannot repeat the trick of creating additional columns as each repeated column would hold values of a similar type. If we take the artist field for example: TransactionNo. 1 2 (Other Attributes) ... ... Artist_ One Britney Spears Darius Artist_T wo Pink Christina Aguilera Artist_Thr ee Christina Aguilera Will Young

If we split each original field into 3 we can see the value 'Christina Aguilera' appears twice, once in Artist_Two and once in Artist_Three. If we were searching to see who had borrowed CDs by Christina we'd have to create a query to search ALL the artist fields. Also if someone borrowed a 4th CD as part of a transaction we would have to create a fourth Artist column and so on. The other reason that repeating groups are not allowed to be split into columns is that we MUST know the number of columns required in order to create the database. The alternative, for someone who knew nothing about normalisation, would be to just create extra rows and copy the values in non-repeating attributes - that way we could make sure that each field contained atomic values as seen in figure two. This is a trap that we could fall into by looking at only the data. We have not actually changed the structure of the database and thus the problem of repeating groups still exists. This can be seen by the fact that the Primary Key is no longer unique. The correct solution is to remove the repeating groups (Artist, Title, Year, Label, No_of_Tracks and Copy) into a new 'Borrowed CDs' relation . TransactionNo Artist 1 1 Britney Spears Pink Title Britney Cant Year Label 2001 Jive 2002 LaFace No_of _Track Copy s 14 15 1 3

Take Me Home 1 2 2 2 Christina Stripped 2002 RCA Aguilera Darius Will Young Dive In From Now On 20 1 2 2 1

2002 Mercury 13 2002 RCA 13 20

Christina Stripped 2002 RCA Aguilera

Because we need to be able to join this data back with that left in the original table we also need to include the TransactionNo in this Borrowed CDs relation which will act (along with a qualifying value) as Key for this relation. The qualifying value, allowing us to identify each CD in the library, combined with the transaction number will be able to tell us the specific CD borrowed per transaction. Thus we next need to define the qualifying value which will be the least number of attributes required to uniquely identify the CD. This could be (Artist, Title) if we were sure that this was a unique indentifer for a CD, but we already can see that there are different copies of the same CD. We could try adding Copy to the Key (Artist, Title, Copy), but in practice having a single unique value for each CD is preferable. Thus we will also create a cdID attribute in this relation and use that to represent the uniqueness of a CD. After achieving First Normal Form our database has two relations as can be seen in figure threeor can be expressed as follows: LOANS (TransactionNo, Forename, Surname, AddressOne, AddressTwo, City, Postcode, TeleNo, E-mail, Date_Borrowed, Due_Back) BORROWED CDS (TransactionNo, cdID, Artist, Title, Year, Label, No_of_Tracks, Copy) Second Normal Form (2NF) The second normal form has one rule that must be followed: All non-key attributes must be dependant ON THE WHOLE KEY and not just one attribute of the key. This obviously only applies to Composite Keys and means that attributes in the relation that only contain information (and have no role in the structure of the database) must be functionally dependent on all parts of the Key. Another way of thinking about this is that if there are some attributes that are not dependent on all parts of the Key this means that they are in the wrong relation. For example: Imagine we have a cars_bought table where each row (record) contains information about the car that a customer bought, which has a Primary Key of (CustomerID, Model). Information about the car (such as engine size, year etc) could be derived from just one part of the Key (Model) and thus isn't

dependent on the whole key. Whereas if we wanted to find out the colour of the car the customer bought then we could search for the customer and this would give us the colour - assuming he has only bought one car. BUT what if the customer has ordered two cars, of different models and different colours. In order to find the colour of a specific car we would need to know the CustomerID AND the model number. Thus colour is dependent on the WHOLE Primary Key and not just a part of it. What this tells us is that colour is in the correct relation as it is specific to the customer and the model, whereas information about the car, which is dependent upon the Model alone, should be in a different table. 2NF in practice When removing the partial dependencies from our database we can only look at tables that have a Composite Key and not a sole attribute as the Primary Key. As the loans Relation has a Primary Key of 'TransactionNo' this table doesn't require to be changed in this Normal Form. The second relation (Borrowed CDs) DOES have a Composite Key so we must examine the attributes of this table to see if they are all dependent on the whole Key. We can see that ALL the CD information attributes are dependent on the cdID attribute rather than the whole Key. This is because they aren't really about the CDs borrowed and more about just the CDs bought and thus belong on their own relation - CDs Bought. cdID Artist 1 2 3 4 5 Britney Spears Pink Christina Aguilera Darius Will Young Title Britney Cant Take Me Home Stripped Dive In Year Label 2001 Jive 2002 LaFace 2002 RCA No_of_ Copy Tracks 14 15 20 1 3 1 2 2

2002 Mercury 13 13

From Now 2002 RCA On

CDs Bought And that means that we can remove all the rest of CD attributes from the Borrowed CDs table. TransactionNo cdID 1 1 1 2

1 2 2 2 Borrowed CDs

3 4 5 3

The database in the second normal form will now have the relations as shown in figure four and is expressed as: LOANS (TransactionNo, Forename, Surname, AddressOne, AddressTwo, City, Postcode, TeleNo, E-mail, Date_Borrowed, Due_Back) BORROWED CDS(TransactionNo, cdID, Artist, Title, Year, Label, No_of_Tracks, Copy) CDS BOUGHT(cdID, Artist, Title, Year, Label, No_of_Tracks, Copy) Third Normal Form (3NF) To achieve Third Normal Form no attribute must be dependent on a non-key attribute. This means that every informational attribute must be DIRECTLY dependent on the Primary Key and not on another column. If we again look at an imagined cars database, a customer table contains information such as address, city, postcode and also a column called shipping cost. The value of shipping cost changes in relation to where the car should be delivered, and thus is not directly dependent on the customer, but the city. Thus we would need to create another separate relation to hold the information about cities and shipping costs. 3NF in practice As is good practice we should examine every table in turn to see if the non-key attributes are indeed directly dependent on the Primary Key. If we look at the loans relation we can see that the columns AddressOne, AddressTwo, City, Postcode, and E-mail are not dependent on the TransactionNo, but rather a Composite Key of Forename and Surname. Thus they should be removed from this table into a new relationBorrowers. As there is a possibility that there could be more that one David Findlay or Iain Brown, this Composite Key isn't very effective so once more we will replace it with a unique numeric reference (BorrowerID). This will become the new Primary Key of Borrowers and a foreign Key in the loans relation. The effect that this has on the tables can be seen in figure five. The Borrowed CDs relation only has two attributes, which together make up the Primary Key and thus is not required to be normalised. The final table CDs Bought can however be nornailised further. Year, Label and No of Tracks are dependent on a Composite Key of Artist + Title. Copy is dependent on the cdID i.e. that which relates a particular physical CD that has been purchased, rather

than album details such as artist or label. Thus we can create another relation CD Releases with a releaseID that identifies an album of which there may be millions of copies. releaseID Artist 1 2 3 4 5 Britney Spears Pink Christina Aguilera Darius Title Britney Cant Take Me Home Stripped Dive In Year Label 2001 Jive 2002 LaFace 2002 RCA No_of_ Tracks 14 15 20

2002 Mercury 13 2002 RCA 13

Will Young From Now On

CD Releases Relation We of course have to replace these dependent attributes in the CDs Bought relation with the releaseID. cdID 1 2 3 4 5 releaseID Copy 1 2 3 4 5 1 3 1 2 2

CDs Bought Relation To further illustrate the difference between the CDs and the releases consider that we have bought two new CDs that will be available to borrow. One is new to our database (Robbie Williams - Escapology) whereas the Christina Aguilera Stripped release has been so popular we decided to buy another copy. Adding the Robbie album would mean adding data to two relations, CDs Bought and CD Releases, like so. releaseID Artist 6 Robbie Williams Title Year Label No_of_ Tracks

Escapology 2002

Chrysalis 14

CD Releases Relation

cdID 6

releaseID 6

Copy 12/12/2002

CDs Bought Relation Whereas we would only need to add information about the Christina Aguilera album to the CDs Bought relation, as its release information has already been entered into the CD Releasesrelation. cdID 7 releaseID 3 Copy 12/12/2002

CDs Bought Relation Thus after three levels of normalisation we have a table structure as shown in figure six and is expressed as so: LOANS (TransactionNo, BorrowerID, Date_Borrowed, Due_Back) BORROWER (BorrowerID, Forename, Surname, AddressOne, AddressTwo, City, Postcode, TeleNo, E-mail) BORROWED CDS (TransactionNo, cdID, Artist, Title, Year, Label, No_of_Tracks, Copy) CDS BOUGHT (cdID, releaseID, Copy) CD RELEASES (releaseID, Artist, Title, Year, Label, No_of_Tracks) Other Normal Forms As mentioned above there are more Normal Forms that we could use to normalise our database further. We are not going to do this, because in practice the anomalies that they are designed to remove are rare. We will however have a brief look at BCNF so that there is at least some understanding of that norm should you come across it. If you are confused or struggling with the above, it would perhaps be worth skipping past this section until you are more confident about your understanding of the first three Normal Forms. Boyce-Codd Normal Form (BCNF) This often referred to as a strong 3NF and states that each determinant must be a Candidate Key. In a relation there may be more than just the Primary Key from which we can derive the values of other attributes, i.e. from another attribute or combination of attributes. These are known as Candidate Keys and in 3NF it is possible to have only some of the attributes functionally dependent on them. In BCNF all attributes must depend on all Candidate Keys. Variations While the rules of Normalisation stay the same the practical implementation of them differs. The method I've used above is the one that I think is easiest to understand for the beginner, but there are a couple of others worth mentioning in

case you see them discussed elsewhere. It doesn't really matter which you use as the derived data structure is the same. Composite Key in the First Relation In the above example we had 1NF of : LOANS (TransactionNo, Forename, Surname, AddressOne, AddressTwo, City, Postcode, TeleNo, E-mail, Date_Borrowed, Due_Back) BORROWED CDS(TransactionNo, cdID, Artist, Title, Year, Label, No_of_Tracks, Copy) We are reminded that there was a Composite Key in the second relation (Borrowed CDs). We could have just as easily created the Composite Key in the first relation, where we remove (Artist, Title, Year, Label, No_of_Tracks, Copy) to a new relation called CDs Bought, create the Primary Key cdID for that relation and create a new attribute in the Loans relation that is a Foreign Key and the qualifying value for the TransactionNo. This would have duplicated the data in the non-repeating groups, which doesn't matter as we're normalising the database structure not the actual data, and that repetition is removed by the later Normal Forms. This would leave a 1NF of: LOANS (cdID, TransactionNo, Forename, Surname, AddressOne, AddressTwo, City, Postcode, TeleNo, E-mail, Date_Borrowed, Due_Back) CDS BOUGHT (cdID, Artist, Title, Year, Label, No_of_Tracks, Copy) This means that in to achieve 2NF we no longer look at the second relation, but instead the first relation to determine if the attributes are functionally dependent on the whole Composite Key. All the attributes are functionally dependent on the TransactionNo. not the whole Key, so we remove them to a new relation called Transaction Info. 2NF is thus expressed: LOANS (cdID, TransactionNo) TRANSACTION INFO (TransactionNo, Forename, Surname, AddressOne, AddressTwo, City, Postcode, TeleNo, E-mail, Date_Borrowed, Due_Back) CDS BOUGHT (cdID, Artist, Title, Year, Label, No_of_Tracks, Copy) We can see that this is the same data structure as the original method at 2NF, but we've just chosen different names due to the order in which a relation was first established. 1NF with only one relation This is another method that is quite popular. If we recall the rules for 1NF they are: To ensure that all attributes (columns) contain only atomic values There must be no repeating groups This says nothing about not repeating data or having to create new relations to resolve the problems. If we look at figure two, we can see that the data in the non-

repeating attributes has indeed been repeated. The problem we had with this is that there was no longer a Primary Key. We can however make a Composite Key by using the same cdID as we did above to represent the uniqueness, except this time in just one relation. So 1NF would be: LOANS (cdID, TransactionNo, Forename, Surname, AddressOne, AddressTwo, City, Postcode, TeleNo, E-mail, Date_Borrowed, Due_Back, Artist, Title, Year, Label, No_of_Tracks, Copy) The consequence of this is that to achieve 2NF would be a longer stage. Initially we can see (similar to previous examples) that the attributes (Artist, Title, Year, Label, No_of_Tracks, Copy) are dependant on only the cdID part of the key and thus can be removed to a new relation: LOANS (cdID, TransactionNo, Forename, Surname, AddressOne, AddressTwo, City, Postcode, TeleNo, E-mail, Date_Borrowed, Due_Back) CDS BOUGHT (cdID, Artist, Title, Year, Label, No_of_Tracks, Copy) This creates a structure identical to that of the 'Composite Key in the First Relation' example in 1NF, where the loan information attributes are only dependent on the Transaction No, and thus we have to split the relation again: LOANS (cdID, TransactionNo) TRANSACTION INFO (TransactionNo, Forename, Surname, AddressOne, AddressTwo, City, Postcode, TeleNo, E-mail, Date_Borrowed, Due_Back) CDS BOUGHT (cdID, Artist, Title, Year, Label, No_of_Tracks, Copy) The database structure is now 2NF. Without A Joining Table and cdID This produces the only truly normalised database schema in this workshop - and I'll explain why in a moment. But first what is a 'Joining Table'? This is a table that is an entirely Composite Key of attributes which are Foreign Keys that contain no informational data and thus its function is to join the Primary Keys of other tables together. For example: in each of the database structures we have produced so far there has been a relation with two attributes (cdID, TransactionNo) so that if we look up a Transaction No we can retrieve the cds by the corresponding cdIDs. In strict normalisation this table should not exist in this form, it has come into existence because we created an artificial key (cdID) to represent the Composite Key (Artist, Title, Copy). This is something that is done quite frequently in practice (why I've done so above), but is theoretically wrong. To explain let's normalise the structure again. Because we cannot replace the Composite Key elements with cdID in 1NF, we are left with a structure like so: LOANS (TransactionNo, Forename, Surname, AddressOne, AddressTwo, City, Postcode, TeleNo, E-mail, Date_Borrowed, Due_Back) BORROWED CDS (TransactionNo, Artist, Title, Copy, Year, Label, No_of_Tracks)

As we can see, the Composite Key for the Borrowed CDs relation is now a lot more complicated. When we achieve 2NF we would need to create a structure like so: LOANS (TransactionNo, Forename, Surname, AddressOne, AddressTwo, City, Postcode, TeleNo, E-mail, Date_Borrowed, Due_Back) BORROWED CDS (TransactionNo, Artist, Title, Copy) CDS BOUGHT (Artist, Title, Copy, Year, Label, No_of_Tracks) This has meant that any join we perform using SQL becomes much more complicated, as we are having to match more fields. Consider the following sample MySQL Inner Join to retrieve all the information about a bought cd. With cdID:
SELECT *

FROM cds_bought, cds_released

WHERE (cds_bought.cdID = cds_released.cdID);

Without cdID:
SELECT *

FROM cds_bought, cds_released

WHERE (cds_bought.Artist = cds_released.Artist)

AND (cds_bought.Title = cds_released.Title)

AND (cds_bought.Copy = cds_released.Copy);

As we observe, joining tables on three fields is more complicated than on one field.

NOTE: The names of tables and columns are slightly different as the physical implementation of any database is best done without spaces in the names, whereas in the theoretical design stage spaces aid readability. Thus to summarise, we have replaced the 'true' Composite Key with an artificial Primary Key, in order to make things easier in the physical implementation, BUT THERE IS NO RULE IN NORMALISATION THAT SAYS YOU SHOULD. Conclusion This has hopefully been a pretty straightforward guide to a complicated (for the novice anyway) subject. I first got into this whole web design game from a media standpoint rather than a technical computer standpoint. When I started using databases on the web, I had to become familiar with concepts that are second nature to those from a formal technical background, which was tricky at first due to the target audience of the literature. Thus I hope that this VW can be of some use to those coming to normalisation from other backgrounds as well as those in IT seeking a little clarification.

Using MySQL, Advanced Queries Workshop Requirements You should have completed Parts One, Two, Three, Four and Five of this series. You should also have access to the MySQL command line client software. You should also have full permissions on a database.

Introduction Earlier in this series when we looked at SQL statements we were primarily concerned with either retrieving, changing or deleting values within the database and also manipulating the database structures. SQL is more powerful than that and in this workshop I aim to introduce some of the advanced functions and queries that can be useful when building more complex applications. As One of the simplest manipulations is to define a new structural element (table or column) by aliasing an existing value. A common use for this is to create a shorthand reference to elements with long names to make the SQL statements shorter and reduce the chance of typos in the longer names.
SELECT <columns>

FROM <existing_table_name>

AS <new_table_name>

It is important to remember that the table hasn't actually been renamed, but instead the<new_table_name> is simply a reference that exists for the duration of the SQL statement. For example to see this working lets create a simple SELECT statement that retrieves the name column from the artists table (that we created in Part 5) using a reference 't1'.
mysql> SELECT t1.name

-> FROM artists

-> AS t1;

+----------------------+

| name

+----------------------+

| Jamiroquai

| Various

| westlife

| Various

| Abba

As we can see t1 is a lot easier to type that artists. To see the full benefit of this let's revisit one of the join statements from part 5. The existing statement is:
mysql> SELECT artists.Artist, cds.title, genres.genre FROM cds

-> LEFT JOIN genres ON cds.genreID = genres.genreID

-> LEFT JOIN artists ON cds.artistID = artists.artistID

-> WHERE (genres.genre = 'Pop');

Whereas our modified statement would look like so:

mysql> SELECT t2.Artist, t1.title, t3.genre FROM cds AS t1

-> LEFT JOIN genres AS t3 ON t1.genreID = t3.genreID

-> LEFT JOIN artists AS t2 ON t1.artistID = t2.artistID

-> WHERE (t3.genre = 'Pop');

Using aliases also has few other advantages including: Avoiding any reserved (used by MySQL) words. Allowing Multiple Joins to the same table Allowing Self-Joins Assigning the result of MySQL function to a temporary column name.

While there is not much more to be said about MySQL reserved words and MySQL functions are covered in Part 9 of this Virtual Workshop series, we will take a look at Self-Joins next. Practical Uses of As In the earlier Virtual Workshop on joins we looked at joining different tables together, we are going to extend the use of joins using AS to in allow multiple joins on the same table and Self-Joins. To illustrate how these work we will create a new 'producer' column in the cds table (the reasons for which will become apparent later) that we last modified in part 5. This will again hold a foreign key reference to a table holding details about the producer, but rather than creating a 'producer' table, we can just reuse the artist table as producers are artists too. Altering the database structures and adding data In order to include this new producer column (and include meaningful data) we need to alter the structure of the cds table.
mysql> ALTER TABLE cds

-> ADD producerID INT(3);

At this point you can if you want you can enter the appropriate producer information for your existing CDs entered. For use in this workshop we also need to enter the details of an album produced by the artist and an album produced by a outside party. In this case the famously self reliant Prince (or whatever he is calling himself this week) and The Beatles with and their producer the '5th Beatle' Sir George Martin. Artist Prince Title Year Label Bought Tracks Genre Producer 16 Soul Prince Sign of Warner 1987the 1987 Brothers 11-07 Times

The The 1994Classic George White 1990 Capitol 30 Beatles 07-20 Rock Martin Album This data has to be entered into the artists table first:
mysql> INSERT INTO artists

-> VALUES ('?','The Beatles');

Query OK, 1 row affected (0.00 sec)

mysql> INSERT INTO artists

-> VALUES ('?','Prince');

Query OK, 1 row affected (0.00 sec)

mysql> INSERT INTO artists

-> VALUES ('?','George Martin');

Query OK, 1 row affected (0.00 sec)

Then the get the artistIDs using a SELECT statement


mysql> SELECT *

-> FROM artists;

+----------+----------------------+

| artistID | name

+----------+----------------------+

-- snip --

17 | The Beatles

18 | Prince

19 | George Martin

+----------+----------------------+

19 rows in set (0.00 sec)

Next enter the label details in the label table.


mysql> INSERT INTO label

-> VALUES ('?','Warner Brothers');

Query OK, 1 row affected (0.00 sec)

mysql> INSERT INTO label

-> VALUES ('?','Capitol');

Query OK, 1 row affected (0.00 sec)

And getting those newly created label IDs as well.


mysql> SELECT *

-> FROM label;

+---------+-----------------+

| labelID | name

+---------+-----------------+

-- snip --

5 | Warner Brothers |

6 | Capitol

+---------+-----------------+

6 rows in set (0.02 sec)

We should also know the genreIDs as well (Classic Rock = 3; Soul = 5) and we can enter the cd details into the cds table (you can see why it is better to have a scripted interface to MySQL).
mysql> INSERT INTO cds (

-> title, year, bought, tracks, genreID, artistID, labelID, producerID

-> ) VALUES (

-> 'Sign of the Times','1987','1987-11-07','16','5','18','5','18'

-> );

Query OK, 1 row affected (0.08 sec)

mysql> INSERT INTO cds (

-> title, year, bought, tracks, genreID, artistID, labelID, producerID

-> ) VALUES (

-> 'The White Album','1990','1994-07-20','30','3','17','6','19'

-> );

Query OK, 1 row affected (0.01 sec)

This will now allow us to see some of the good things that you can do with AS. Multiple Joins to One Table. Consider the following SQL statement and resulting output.
mysql> SELECT cds.title, artists.name

-> FROM cds

-> LEFT JOIN artists

-> USING (artistID)

-> WHERE artists.artistID = 17;

+-----------------+-------------+

| title

| name

+-----------------+-------------+

| The White Album | The Beatles |

+-----------------+-------------+

1 row in set (0.00 sec)

This is fine, but if we wanted to include the producer details we need to join to a producer table, which we don't have because we decided that producers are artists and this creates a problem. We solve this problem by making ANOTHER join to the artists table only this time calling it the producers table using AS to create this alias.
LEFT JOIN artists AS producers ....etc

There is another small consideration. In order to complete the join to the newly aliased producers tale we have to construct the join without the USING clause. This is because we need to join the cds.producerID field on the producers.artistID field which obviously aren't the same name that USING requires. SO to construct the whole query.
mysql> SELECT cds.title, artists.name AS Artist, producers.name AS Producer

-> FROM cds

-> LEFT JOIN artists

-> USING (artistID)

-> LEFT JOIN artists AS producers

-> ON cds.producerID = producers.artistId

-> WHERE artists.artistID = 17;

+-----------------+-------------+---------------+

| title

| Artist

| Producer

+-----------------+-------------+---------------+

| The White Album | The Beatles | George Martin |

+-----------------+-------------+---------------+

1 row in set (0.01 sec)

NOTE: The artists.name and producers.name field has also been aliased to differentiate them in the output.

We can also use this method of aliasing tables to to join a table to itself or perform a Self-Join as it is known. The Self-Join A Self-Join (as the name suggests) is when a table is joined to itself and is made possible by aliasing the original table. The main reasons for using a Self-Join arise when you need to either compare the values in differ net rows in the same table. Unfortunately in the CDs examples we have used so far there is no obvious candidate to demonstrate the Self-Join correctly so I will use a different example this one time only. The classic example given when explaining Self-Joins (I read this in many different books when learning SQL) involves an 'Employees' table that stores the details of their supervisor as well. EmployeeID 023452 Name Ken Smith Salary ManagerID 45000 NULL 25000 023452

087652 Linda Jones Employees Table

In order to get the name of the Linda's manager you would have to join the table to itself (using an alias to create a 'Manager' table). The join would occur ON Employees.ManagerID = Manager.EmployeeID. Thus we can construct the SQL statement using AS like so:
SELECT

Employees.EmployeeID, Employees.Name, Employees.Salary, Manager.Name AS Manager

FROM Employees

LEFT JOIN Employees AS Manager

ON Employees.ManagerID = Manager.EmployeeID

WHERE (Employees.EmployeeID = '087652';

This would result in the following being output. EmployeeID 087652 Name Linda Jones Salary Manager 25000 Ken Smith

Aliasing Function output Using AS with built in functions is discussed in more depth in Part 9 of this workshop series, but worth covering quickly here. Consider a quick query to find the average number of tracks.
mysql> SELECT AVG(tracks)

-> FROM cds;

+-------------+

| AVG(tracks) |

+-------------+

22.1667 |

+-------------+

1 row in set (0.00 sec)

AVG(tracks) does tell us certain information, but this field would be difficult to use as part of a script and therefore we can use AS to give a more meaningful name.
mysql> SELECT AVG(tracks) AS AverageTracks

-> FROM cds;

+---------------+

| AverageTracks |

+---------------+

22.1667 |

+---------------+

1 row in set (0.03 sec

UNION Joins Union Joins allow the results of two queries to be combined into one outputted result set. This is done by having the 2 (or more) queries glued together by the UNION operator.
SELECT <fields>

FROM <table>

WHERE <condition>

UNION

SELECT <fields>

FROM <table>

WHERE <condition>

For example if you wanted to search for artist beginning with either P or G you would construct two statements that searched each phrase and use the UNION statement.
mysql> SELECT artists.name

-> FROM artists

-> WHERE (artists.name LIKE 'P%')

-> UNION

-> SELECT artists.name

-> FROM artists

-> WHERE (artists.name LIKE 'G%');

+-----------------+

| name

+-----------------+

| pop goes the 80 |

| Prince

| George Martin |

+-----------------+

3 rows in set (0.03 sec)

By now with the knowledge you possess you will have worked out that this could just have easily been done by using two where conditions.
mysql> SELECT artists.name

-> FROM artists

-> WHERE ((artists.name LIKE 'P%') || (artists.name LIKE 'G%'));

+-----------------+

| name

+-----------------+

| pop goes the 80 |

| Prince

| George Martin |

+-----------------+

3 rows in set (0.00 sec)

However UNION also allows you to combine the results from different tables not just the same one. To give a practical, but somewhat unrealistic in a 'real world' sense lets return to our CDs database and select all the genres and all the labels that start with letters A to M.
mysql> SELECT label.name

-> FROM label

-> WHERE (label.name BETWEEN 'A%' AND 'M%')

-> UNION

-> SELECT genres.genre

-> FROM genres

-> WHERE (genres.genre BETWEEN 'A%' AND 'M%');

+----------------+

| name

+----------------+

| jive

| EMI

| Capitol

| Easy Listening |

| Heavy Metal |

| Eighties

| Hip Hop

| Jazz

| Guitar Bands |

+----------------+

9 rows in set (0.04 sec)

A more practical example would be to imagine if we had in fact made a separate producers table rather than just alias the artists table. We could query both tables using UNION to produce a result set. Temporary Tables As there name suggests, temporary tables are fleeting in nature lasting only for the length of the MySQL session. Some of the reasons that you may wish to do this include. A large, busy site may create a temporary table copies (which are issued and die with a session for example) for each client to use, thus easing congestion in on the original table. MySQL does not yet support subselects. Using temporary tables can, in some instances, overcome that problem. Storing data during structural database changes. There are many other reasons that I could go into but let's just look at one cool trick. Creating the Temporary Table. The syntax for creating temporary tables is almost identical that used for creating a normal table. Except that there is an extra TEMPORARY clause.
CREATE TEMPORARY TABLE <table> (

field definitions

This will work by using a query to create the table as well, like so.
CREATE TEMPORARY TABLE <newtable>

SELECT * FROM <oldtable>

And it is here that the 'cool trick' comes in. I received an e-mail asking for help (as I often do) and I was able to provide a solution using a temporary table. The problem was related to how to remove duplicate rows from a database. This came about from a web form that fed into a database and users were submitting it multiple times, thus creating duplicated data. The correspondent wished to remove any duplicate entries. We can replicate this problem by creating a copy of the cds table with all the data duplicated. First make the duplicate cds table:
mysql> CREATE TABLE cdsdupe

-> SELECT * FROM cds;

Apply the same structural constraints to the table:


mysql> ALTER TABLE cdsdupe MODIFY cdID INT(3) auto_increment primary key;

Next insert the same data again only omitting the cdID which would cause a conflict with the existing cdIDs (this means using the longer method of specifying which fields to insert into and select from).
mysql> INSERT INTO cdsdupe

-> (title, year, bought, tracks, genreID, artistID, labelID, producerID)

-> SELECT cds.title, cds.year, cds.bought, cds.tracks, cds.genreID,

-> cds.artistID, cds.labelID, cds.producerID

-> FROM cds;

Query OK, 8 rows affected (0.00 sec)

Records: 8 Duplicates: 0 Warnings: 0

You can check that there are indeed duplicate entries using a standard select statement. So how do we solve this problem? We saw in an earlier workshop that the DISTINCT qualifier can be used to only output records one if there are duplicates - which is what we want. Thus if we create a temporary table using a DISTINCT qualifier in a SELECT statement we can filter out the duplicates.
mysql> CREATE TEMPORARY TABLE cdstemp

-> SELECT DISTINCT cdsdupe.title, cdsdupe.year, cdsdupe.bought,

-> cdsdupe.tracks, cdsdupe.genreID, cdsdupe.artistID,

-> cds.labelID, cds.producerID

-> FROM cdsdupe;

Query OK, 8 rows affected (0.03 sec)

Records: 8 Duplicates: 0 Warnings: 0

Again you can check this with a select statement. We next need to remove the data from the original table using a TRUNCATE statement
TRUNCATE TABLE <table_name>

This is better than a delete statement as it drops then recreates the table. This means that it is faster on large tables and more importantly can be used as part of a transaction.
mysql> TRUNCATE TABLE cdsdupe;

Query OK, 0 rows affected (0.03 sec)

We then use the temporary table to repopulate the cdsdupe table.


mysql> INSERT INTO cdsdupe

-> (title, year, bought, tracks, genreID, artistID, labelID, producerID)

-> SELECT cdstemp.title, cdstemp.year, cdstemp.bought, cdstemp.tracks,

-> cdstemp.genreID, cdstemp.artistID, cdstemp.labelID, cdstemp.producerID

-> FROM cdstemp;

Thus we have removed the duplicates from the cdsdupe table. Using Full Text Searches Full Text searches allow you to search for a phrase that can appear ANYWHERE in fields that you specify. This is a kind of 'super-wildcard' approach. To do this you first have to alter any table nominating fields to be searchable within brackets as part of a FULL TEXT defination.
ALTER TABLE <table> ADD FULLTEXT (fields)

To illustrate lets make our title field of the cds table searchable.

mysql> ALTER TABLE cds ADD FULLTEXT (title);

Query OK, 8 rows affected (0.36 sec)

Records: 8 Duplicates: 0 Warnings: 0

This done we could query the database using the MATCH() and AGAINST() functions, where the field is matched against the search term.
SELECT * FROM cds WHERE MATCH(fields) AGAINST ('search term')

So if we wanted to see all the cds with 'funk' in the title (just because there are bound to be some).
mysql> SELECT cdID, title, year, tracks

-> FROM cds

-> WHERE

-> MATCH(title) AGAINST ('hits');

+------+---------------------+------+--------+

| cdID | title

| year | tracks |

+------+---------------------+------+--------+

| 5 | Abbas Greatest Hits | 1998 |

24 |

+------+---------------------+------+--------+

1 row in set (0.02 sec)

The MATCH function also sorts multiple results be relevance with the first result being the most relevant. To demonstrate lets add another cds with hits in the title (The Hits by Prince).
mysql> INSERT INTO cds (

-> title, year, bought, tracks, genreID, artistID, labelID, producerID

-> ) VALUES (

-> 'The Hits','1993','1993-10-07','58','5','18','5','18'

-> );

Query OK, 1 row affected (0.08 sec)

Running the FULL TEXT query again will put the newly added CD top.
mysql> SELECT cdID, title, year, tracks

-> FROM cds

-> WHERE

-> MATCH(title) AGAINST ('hits');

+------+---------------------+------+--------+

| cdID | title

| year | tracks |

+------+---------------------+------+--------+

| 9 | The Hits

| 1993 |

58 |

| 5 | Abbas Greatest Hits | 1998 |

24 |

+------+---------------------+------+--------+

2 rows in set (0.00 sec)

We can ask MySQL to show us the rating as well by SELECTing the result of the match().
mysql> SELECT title, MATCH(title) AGAINST ('hits') AS Rating

-> FROM cds

-> WHERE MATCH(title) AGAINST ('hits');

+---------------------+-----------------+

| title

| Rating

+---------------------+-----------------+

| The Hits

| 1.238520026207 |

| Abbas Greatest Hits | 1.2109839916229 |

+---------------------+-----------------+

2 rows in set (0.01 sec)

You can see that The Hits (1.23....) has a higher rating than Abbas Greatest Hits (1.21.....) Conclusion As these workshops progress and you, the reader, become more confident with SQL statements I have to introduce more complex problems and solutions to keep you entertained ;-). This workshop has dealt with more complex queries but if you have not had enough yet, can I suggest investigating SubSelects available in the MySQL 4.1 alpha releases.

Using MySQL, Built-In Functions Workshop Requirements You should have completed Parts One, Two, Three, Four and Five of this series. You should also have access to the MySQL command line client software. You should also have full permissions on a database. Introduction Thus far when retrieving stored data we have simply displayed the results of any query. MySQL can do more that this and has many built in functions that can transform data to meet our requirements. These include: Date Functions - used to manipulate the display format of a date as well as calculate time. String Functions - can manipulate a text string Numeric Functions - can manipulate figures Summarising Functions - output meta results from a query There are also Control Functions that can be used to give conditionality to queries. Date Functions Before looking at the date functions in detail it is worth revisiting the various date datatypes to gain a better understanding of the limitations of date formatting. Date Datatypes There are 5 MySQL date datatypes these are: Datatype DATETIME DATE Format YYYY-MM-DD HH:MM:SS YYYY-MM-DD Info This stores both date and time. This only stores the date See Below This stores only the time

TIMESTAMP(leng Varies th) TIME HH:MM:SS

YEAR YYYY Stores only the year The timestamp datatype is somewhat different as it stores the time that a row was last changed. The format also varies according to the length. For example to store the same information as DATETIME, you would specify a length of 14 whereas to store the DATE you would specify a length of 8. Timestamp Format

Definition TIMESTAMP(2) TIMESTAMP(4) TIMESTAMP(6) TIMESTAMP(8) TIMESTAMP(10) TIMESTAMP(12) YY YYYY YYMMDD YYYYMMDD YYMMDDHHMM YYMMDDHHMMSS

TIMESTAMP(14) YYYYMMDDHHMMSS In the 'cds' table we have used the DATE for the 'bought' field.
mysql> SELECT cds.title, cds.bought

-> FROM cds;

+------------------------------+------------+

| title

| bought

+------------------------------+------------+

| A Funk Odyssey

| 2001-10-10 |

| Now 49

| 2001-10-15 |

| Eurovision Song contest 2001 | 2000-09-08 |

| Abbas Greatest Hits

| 2000-11-05 |

| Space Cowboy

| 2001-10-10 |

| Sign of the times

| 1987-11-07 |

| The White Album

| 1994-07-20 |

| The Hits

| 1993-10-07 |

| westlife

| 2000-06-09 |

+------------------------------+------------+

9 rows in set (0.02 sec)

So to begin with let's look at how we can manipulate these dates using MySQL's date functions. DATE_FORMAT() This function allows the developer to format the date anyway that they wish by specifying a sequence of format strings. A string is composed of the percentage symbol '%' followed by a letter that signifies how you wish to display part of the date. These are some of the more common strings to use: String Displays %d %D %m %M %b The numeric day of the month Example 01....10....17....24 etc

The day of the month with 1st, 2nd, 3rd.... etc a suffix The numeric month The Month name The Abbreviated Month Name 01....04....08....11 etc January....April....August etc Jan....Apr....Aug....Nov etc

%y %Y %W %a %H %h %p %i

Two digit year Four digit year Weekday name Abbreviated Weekday name Hour (24 hour clock) Hour (12 hour clock) AM or PM Minutes

98, 99, 00, 01, 02, 03 etc 1998, 2000, 2002, 2003 etc Monday.... Wednesday....Friday etc Mon....Wed....Fri etc 07....11....16....23 etc 07....11....04....11 etc AM....PM 01....16....36....49 etc

%s Seconds 01....16....36....49 etc There are more, but that should be enough for now. There are a couple of things to note. Upper and Lowercase letters in the string make a difference and also that when arranging these strings into a sequence you can intersperse 'normal' characters. For example: The sequence '%d/%m/%y', with forward slashes separating the strings, would be displayed as 01/06/03. The next stage is to use the function DATE_FORMAT() to convert a stored time to a format we want. Syntax:
DATE_FORMAT(date, sequence)

Thus to change the format of the cds.bought field to DD-MM-YYYY we specify the field as the date and the sequence as '%d-%m-%Y'.
DATE_FORMAT(cds.bought, '%d-%m-%Y')

This function is then incorporated into our SQL statement in place of the exiting cds.bought field.
mysql> SELECT cds.title, DATE_FORMAT(cds.bought, '%d-%m-%Y')

-> FROM cds;

+------------------------------+-------------------------------------+

| title

| DATE_FORMAT(cds.bought, '%d-%m-%Y') |

+------------------------------+-------------------------------------+

| A Funk Odyssey

| 10-10-2001

| Now 49

| 15-10-2001

| Eurovision Song contest 2001 | 08-09-2000

| Abbas Greatest Hits

| 05-11-2000

| Space Cowboy

| 10-10-2001

| Sign of the times

| 07-11-1987

| The White Album

| 20-07-1994

| The Hits

| 07-10-1993

| westlife

| 09-06-2000

+------------------------------+-------------------------------------+

9 rows in set (0.00 sec)

Dates can also be formatted in 'plain english'.


mysql> SELECT cds.title, DATE_FORMAT(cds.bought, '%W the %D of %M %Y')

-> FROM cds;

+------------------------------+-----------------------------------------------+

| title

| DATE_FORMAT(cds.bought, '%W the %D of %M %Y') |

+------------------------------+-----------------------------------------------+

| A Funk Odyssey

| Wednesday the 10th of October 2001

| Now 49

| Monday the 15th of October 2001

| Eurovision Song contest 2001 | Friday the 8th of September 2000

| Abbas Greatest Hits

| Sunday the 5th of November 2000

| Space Cowboy

| Wednesday the 10th of October 2001

| Sign of the times

| Saturday the 7th of November 1987

| The White Album

| Wednesday the 20th of July 1994

| The Hits

| Thursday the 7th of October 1993

| westlife

| Friday the 9th of June 2000

+------------------------------+-----------------------------------------------+

9 rows in set (0.01 sec)

Note: DATE_FORMAT() only works with datatypes that include the date. This means DATE, DATETIME and TIMESTAMP. There is a similar function called TIME_FORMAT() that works with TIME as well as DATETIME and TIMESTAMP. Extraction Functions As well as using DATE_FORMAT() there are other functions that allow you to extract specific information about a date (year, month, day etc). These include: Function Displays Example 01....10....17....24 etc Monday.... Wednesday....Friday etc 01....04....08....11 etc January....April....August etc 1998, 2000, 2002, 2003 etc 07....11....16....23 etc 01....16....36....49 etc 01....16....36....49 etc DAYOFMONTH(da The numeric day of te) the month DAYNAME(date) MONTH(date) The Name of the day The numeric month

MONTHNAME(dat The Month name e) YEAR(date) HOUR(time) MINUTE(time) SECOND(time) Four digit year Hour (24 hour clock) Minutes Seconds

DAYOFYEAR(date Numeric day of the 1.....366 ) year To give an example of one of these you can use DAYNAME() to work out which day you were born on. To do this you can specify the date directly to the function without referring to any tables or field. So for my birthday (20th July 1973):

mysql> SELECT DAYNAME('1973-07-20');

+-----------------------+

| DAYNAME('1973-07-20') |

+-----------------------+

| Friday

+-----------------------+

1 row in set (0.00 sec)

Or you could even SELECT two or three date items.


mysql> SELECT DAYNAME('1973-07-20'), MONTHNAME('1973-07-20'), YEAR('1973-07-20');

+-----------------------+-------------------------+--------------------+

| DAYNAME('1973-07-20') | MONTHNAME('1973-07-20') | YEAR('1973-07-20') |

+-----------------------+-------------------------+--------------------+

| Friday

| July

1973 |

+-----------------------+-------------------------+--------------------+

1 row in set (0.02 sec)

Getting the Current Date and Time There are three functions that you can use to get the current date and time. NOW() - which gets both date and time, CURDATE() which works with only the date and CURTIME() for the time.
mysql> SELECT NOW(), CURTIME(), CURDATE();

+---------------------+-----------+------------+

| NOW()

| CURTIME() | CURDATE() |

+---------------------+-----------+------------+

| 2003-06-02 19:44:51 | 19:44:51 | 2003-06-02 |

+---------------------+-----------+------------+

1 row in set (0.01 sec)

Changing Date Values There are two functions that allow you to add and subtract time to a date. These are DATE_ADD() and DATE_SUB(). Syntax:
DATE_ADD(date,INTERVAL expr type)

DATE_SUB(date,INTERVAL expr type)

The date - is a standard DATE or DATETIME value, next come the command INTERVALfollowed by the time period (expr) and finally what type period it is (Month, Day, Year etc). Therefore to work out the date 60 days in the future:
mysql> SELECT DATE_ADD(CURDATE(), INTERVAL 60 DAY);

+--------------------------------------+

| DATE_ADD(CURDATE(), INTERVAL 60 DAY) |

+--------------------------------------+

| 2003-08-01

+--------------------------------------+

1 row in set (0.00 sec)

Or 6 months in the past:


mysql> SELECT DATE_SUB(CURDATE(), INTERVAL 6 MONTH);

+---------------------------------------+

| DATE_SUB(CURDATE(), INTERVAL 6 MONTH) |

+---------------------------------------+

| 2002-12-02

+---------------------------------------+

1 row in set (0.00 sec)

We can also format this result as well using DATE_FORMAT() and using an alias to tidy up the title:
mysql> SELECT

-> DATE_FORMAT(DATE_SUB(CURDATE(), INTERVAL 6 MONTH), '%W the %D of %M %Y')

-> AS 'Six Months Ago';

+---------------------------------+

| Six Months Ago

+---------------------------------+

| Monday the 2nd of December 2002 |

+---------------------------------+

1 row in set (0.01 sec)

By now you should have the idea and thus I'm not going to carry on and extensively cover all the functions, the MySQL manual is probably the best place to look to see all the date functions. String Functions String values are can be explained as 'bits of text' and much like the date functions, the string functions allow us to manipulate these values before they are displayed. Although there are once more many different functions, I'm going to concentrate on the functions that fall into a few broad categories. Adding text to an existing value Changing Part of a String Extracting Text from a String Finding a piece of text in a string

Adding text to an existing value There are two simple ways to add more text to an existing value - either at the start or end of the text. Placing the text at either end is best achieved with the CONCAT() function. Syntax:
CONCAT(string1,string2,...)

Thus we can take an existing value (say string2) and place a new value (string1) at the beginning to get string1string2. To see this in action let's retrieve the title of The Beatles 'The White Album' (which was entered in part 8).
mysql> SELECT cds.title

-> FROM cds WHERE cdID='20';

+-----------------+

| title

+-----------------+

| The White Album |

+-----------------+

1 row in set (0.00 sec)

If we wanted to add the text "The Beatles' " at the beginning.


+-----------------------------------+

| CONCAT("The Beatles' ",cds.title) |

+-----------------------------------+

| The Beatles' The White Album

+-----------------------------------+

1 row in set (0.04 sec)

Or if we wanted to say "By The Beatles" at the end.


mysql> SELECT CONCAT(cds.title," By The Beatles")

-> FROM cds WHERE cdID='20';

+-------------------------------------+

| CONCAT(cds.title," By The Beatles") |

+-------------------------------------+

| The White Album By The Beatles

+-------------------------------------+

1 row in set (0.00 sec)

Changing Part of a String As well as add text we can replace it or overwrite it completely. To replace an instance of text within a string we can use the REPLACE() function. Syntax:
REPLACE(whole_string,to_be_replaced,replacement)

Therefore if we wanted to replace the word 'White' with the word 'Black' in the cds.title:
mysql> SELECT REPLACE(cds.title,'White','Black')

-> FROM cds WHERE cdID='20';

+------------------------------------+

| REPLACE(cds.title,'White','Black') |

+------------------------------------+

| The Black Album

+------------------------------------+

1 row in set (0.02 sec)

Or just to be silly (and to demonstrate that each occurrence in the string is changed) let's swap the 'e' for an 'a'.
mysql> SELECT REPLACE(cds.title,'e','a')

-> FROM cds WHERE cdID='20';

+----------------------------+

| REPLACE(cds.title,'e','a') |

+----------------------------+

| Tha Whita Album

+----------------------------+

1 row in set (0.00 sec)

Another Function we can use to add text is the INSERT() function that overwrites any text in the string from a start point for a certain length. Syntax:
INSERT(string,start_position,length,newstring)

In this case the crucial bits of information are the position to start (how many characters from the begriming) and the length. So again to replace 'White' (which starts at character 5 in the string) with 'Black' in the title we need to start at position 5 for a length of 5.
mysql> SELECT INSERT(cds.title,5,5,'Black')

-> FROM cds WHERE cdID='20';

+-------------------------------+

| INSERT(cds.title,5,5,'Black') |

+-------------------------------+

| The Black Album

+-------------------------------+

1 row in set (0.01 sec)

If we alter the position (say to 3) you can see that the exchange doesn't work properly.

mysql> SELECT INSERT(cds.title,3,5,'Black')

-> FROM cds WHERE cdID='20';

+-------------------------------+

| INSERT(cds.title,3,5,'Black') |

+-------------------------------+

| ThBlackte Album

+-------------------------------+

1 row in set (0.00 sec)

Similarly if we were to shorten the length to 3 (resetting the position to 5) not all of the word 'White' gets overwritten.
mysql> SELECT INSERT(cds.title,5,3,'Black')

-> FROM cds WHERE cdID='20';

+-------------------------------+

| INSERT(cds.title,5,3,'Black') |

+-------------------------------+

| The Blackte Album

+-------------------------------+

1 row in set (0.00 sec)

Thus using this knowledge we can insert text into the middle of a string by setting the length to '0' (so it doesn't overwrite anything). Let's make the title 'Black and White':
mysql> SELECT INSERT(cds.title,5,0,'Black and ')

-> FROM cds WHERE cdID='20';

+------------------------------------+

| INSERT(cds.title,5,0,'Black and ') |

+------------------------------------+

| The Black and White Album

+------------------------------------+

1 row in set (0.00 sec)

Extracting Text from a String. As well as adding text to a string we can also use functions to extract specific data from a string. To begin with lets look at three LEFT(), RIGHT() and MID(). Syntax:
LEFT(string,length)

RIGHT(string,length)

MID(string,start_position,length)

The first two, LEFT() and RIGHT(), are fairly straight forward. You specify the string and the length of the string to keep, relative to either the left or right depending on which function you are using. So to keep the words 'The' (which occupies 3 characters on the left) and 'Album' (5characters on the right) we would specify:
mysql> SELECT LEFT(cds.title,3), RIGHT(cds.title,5)

-> FROM cds WHERE cdID='20';

+-------------------+--------------------+

| LEFT(cds.title,3) | RIGHT(cds.title,5) |

+-------------------+--------------------+

| The

| Album

+-------------------+--------------------+

1 row in set (0.00 sec)

The MID() function is only slightly complex. You still specify the length, but also a starting position. So to keep the work 'White', you would start at position 5 and have a length of 5.
mysql> SELECT MID(cds.title,5,5)

-> FROM cds WHERE cdID='20';

+--------------------+

| MID(cds.title,5,5) |

+--------------------+

| White

+--------------------+

1 row in set (0.03 sec)

There is also another extraction function that is is worth mentioning; SUBSTRING(). Syntax:
SUBSTRING(string,position)

This returns all of the string after the position. Thus to return 'White Album' you would start at '5'.
mysql> SELECT SUBSTRING(cds.title,5)

-> FROM cds WHERE cdID='20';

+------------------------+

| SUBSTRING(cds.title,5) |

+------------------------+

| White Album

+------------------------+

1 row in set (0.00 sec)

Finding a piece of text in a string. In some of the string functions we have seen so far it has been necessary to provide a starting position as part of the function This position can be found using the LOCATE() function specifying the text to find (substring) as well as the string to search in. Syntax:
LOCATE(substring,string)

So to find the location of 'White':

mysql> SELECT LOCATE('White',cds.title)

-> FROM cds WHERE cdID='20';

+---------------------------+

| LOCATE('White',cds.title) |

+---------------------------+

5|

+---------------------------+

1 row in set (0.06 sec)

If a substring is not present then '0' is returned.


mysql> SELECT LOCATE('Black',cds.title)

-> FROM cds WHERE cdID='20';

+---------------------------+

| LOCATE('Black',cds.title) |

+---------------------------+

0|

+---------------------------+

1 row in set (0.00 sec)

It is also possible to automatically calculate the length of a piece of text using LENGTH(). Syntax:
LENGTH(string)

So with the word 'White'.


mysql> SELECT LENGTH('White');

+-----------------+

| LENGTH('White') |

+-----------------+

5|

+-----------------+

1 row in set (0.03 sec)

Therefore with these results who these can be combined with one of the other functions. For example with the MID() function.
mysql> SELECT MID(cds.title,LOCATE('White',cds.title),LENGTH('White'))

-> FROM cds WHERE cdID='20';

+----------------------------------------------------------+

| MID(cds.title,LOCATE('White',cds.title),LENGTH('White')) |

+----------------------------------------------------------+

| White

+----------------------------------------------------------+

1 row in set (0.01 sec)

The LENGTH() of 'White' is worked out, the position of 'White' is worked out using LOCATE() and these values are included within the MID() function. The result is that White is returned. Transforming Strings The final group of string functions this workshop will look at are those that transform the string in some way. The first two change the case of the string to either uppercase - UCASE() - or to lowercase - LCASE(). Syntax:

LCASE(string)

UCASE(string)

As you can imagine the usage of these are fairly straightforward.


mysql> SELECT LCASE(cds.title), UCASE(cds.title)

-> FROM cds WHERE cdID='20';

+------------------+------------------+

| LCASE(cds.title) | UCASE(cds.title) |

+------------------+------------------+

| the white album | THE WHITE ALBUM |

+------------------+------------------+

1 row in set (0.01 sec)

The last string function this workshop will examine is REVERSE(). Syntax:
REVERSE(string)

This rather obviously reverses the order of the letters. For example the alphabet.

mysql> SELECT REVERSE('abcdefghijklmnopqrstuvwxyz');

+---------------------------------------+

| REVERSE('abcdefghijklmnopqrstuvwxyz') |

+---------------------------------------+

| zyxwvutsrqponmlkjihgfedcba

+---------------------------------------+

1 row in set (0.00 sec)

Once more there are more string functions that the MySQL Manual documents. Numeric Functions Before talking about the specific numeric functions, it is probably worth mentioning that MySQL can perform simple math functions using mathematical operators. Operator + * / Examples:
mysql> SELECT 6+3;

Function Add Subtract Multiply Divide

+-----+

| 6+3 |

+-----+

| 9|

+-----+

1 row in set (0.00 sec)

mysql> SELECT 6-3;

+-----+

| 6-3 |

+-----+

| 3|

+-----+

1 row in set (0.01 sec)

mysql> SELECT 6*3;

+-----+

| 6*3 |

+-----+

| 18 |

+-----+

1 row in set (0.00 sec)

mysql> SELECT 6/3;

+------+

| 6/3 |

+------+

| 2.00 |

+------+

1 row in set (0.22 sec)

There are also other functions that serve a more specific math function and we shall have a look at a few of these. FLOOR() This reduces any number containing decimals to the lowest whole number. Syntax:
SELECT FLOOR(number)

Example:
mysql> SELECT FLOOR(4.84);

+-------------+

| FLOOR(4.84) |

+-------------+

4|

+-------------+

1 row in set (0.00 sec)

CEILING() Raises a number containing decimals to the highest whole number. Syntax:
SELECT CEILING(number)

Example:
mysql> SELECT CEILING(4.84);

+---------------+

| CEILING(4.84) |

+---------------+

5|

+---------------+

1 row in set (0.01 sec)

ROUND() This function, as you may have guessed, rounds the figures up or down to the nearest whole number (or to a specified number of decimal places). Syntax:

ROUND(number,[Decimal Places])

'Decimal Places' is optional and omitting it will mean that the figure is rounded to a whole number. Examples:
mysql> SELECT ROUND(14.537);

+---------------+

| ROUND(14.537) |

+---------------+

15 |

+---------------+

1 row in set (0.01 sec)

mysql> SELECT ROUND(14.537,2);

+-----------------+

| ROUND(14.537,2) |

+-----------------+

14.54 |

+-----------------+

1 row in set (0.00 sec)

TRUNCATE() This function, rather than rounding, simply shortens the number to a required decimal place. Syntax:
TRUNCATE(number,places)

Example:
mysql> SELECT TRUNCATE(14.537,2);

+--------------------+

| TRUNCATE(14.537,2) |

+--------------------+

14.53 |

+--------------------+

1 row in set (0.00 sec)

The interesting thing about truncate is that if you specify a negative number for the 'places', it replaces the existing numbers in those places with zeros. So for example 545 to '-2' becomes500.
mysql> SELECT TRUNCATE(545,-2);

+------------------+

| TRUNCATE(545,-2) |

+------------------+

500 |

+------------------+

1 row in set (0.00 sec)

Summary Functions The MySQL manual describes this group of functions as ' Functions for Use with GROUP BY Clauses' which I think is a little misleading as they can be used in queries where there are no GROUP BY clauses (see Part 3 for a refresher on GROUP BY). Thus is it is perhaps better (if probably not strictly correct) to think of them as functions that report information about a query (for example the number of rows), rather than simply display or manipulate directly the data retrieved. COUNT() This counts the number of times a row (or field) is returned. Syntax:

COUNT(field)

The most common usage for this is just to specify an asterisks as the field to count the number of rows (or in this case cds).
mysql> SELECT COUNT(*) as 'Number of Cds'

-> FROM cds;

+---------------+

| Number of Cds |

+---------------+

9|

+---------------+

1 row in set (0.00 sec)

You could also choose to count just one field:


mysql> SELECT COUNT(cds.title) as 'Number of Cds'

-> FROM cds;

+---------------+

| Number of Cds |

+---------------+

9|

+---------------+

1 row in set (0.00 sec)

There may be occasions that we would want to count the DISTINCT occurrences in a field. Let's look at the artist ID field to demonstrate.
mysql> SELECT COUNT(cds.artistID)

-> FROM cds;

+-----------------+

| COUNT(artist ID) |

+-----------------+

9|

+-----------------+

1 row in set (0.00 sec)

Now my database contain two artists that have 2 cds in the database (Various and Prince) each of whom have been counted twice. Thus we could try using DISTINCT as part of the query to find out how many artists have cds in the database.
mysql> SELECT DISTINCT COUNT(cds.artistID)

-> FROM cds;

+-----------------+

| COUNT(artist ID) |

+-----------------+

9|

+-----------------+

1 row in set (0.00 sec)

As we can see this doesn't work as each row is indeed unique and thus returns the total of cds again. However if we include DISTINCT as part of the COUNT() function it does work.
mysql> SELECT COUNT(DISTINCT cds.artistID)

-> FROM cds;

+--------------------------+

| COUNT(DISTINCT artist ID) |

+--------------------------+

7|

+--------------------------+

1 row in set (0.00 sec)

AVG() The next function we are going to look at is the AVG() which unsurprisingly is the average function. Syntax:
AVG(field)

Lets look that the tracks field and work out the average number of tracks per CD.
mysql> SELECT AVG(cds.tracks)

-> FROM cds;

+-------------+

| AVG(tracks) |

+-------------+

25.6667 |

+-------------+

1 row in set (0.01 sec)

As that is simply but AVG() can work out more that one value at a time when used with a GROUP BY clause.
mysql> SELECT cds.artistID,AVG(cds.tracks)

-> FROM cds

-> GROUP BY cds.artistID;

+----------+-------------+

| artist ID | AVG(tracks) |

+----------+-------------+

1|

11.5000 |

2|

40.0000 |

3|

17.0000 |

4|

23.0000 |

5|

24.0000 |

17 |

30.0000 |

18 |

37.0000 |

+----------+-------------+

7 rows in set (0.01 sec)

As you can see an average is produced for each artist (not that we know who they are yet) when the artist ID is specified in the GROUP BY clause. Although AVG() is fairly straight forward, lets take this opportunity to 'tidy things up' and see how it can work with other functions and also with a join. So first left's round the averages to one decimal place:
mysql> SELECT cds.artistID, ROUND(AVG(cds.tracks),1)

-> FROM cds

-> GROUP BY cds.artistID;

+----------+----------------------+

| artist ID | ROUND(AVG(tracks),1) |

+----------+----------------------+

1|

11.5 |

2|

40.0 |

3|

17.0 |

4|

23.0 |

5|

24.0 |

17 |

30.0 |

18 |

37.0 |

+----------+----------------------+

7 rows in set (0.04 sec)

...then perform a join on the artists table to retrieve and display the artists' names in place of the artist ID:
mysql> SELECT artists.Artist, ROUND(AVG(cds.tracks),1)

-> FROM cds

-> LEFT JOIN artists

-> USING (artist ID)

-> GROUP BY artists.Artist;

+-------------+--------------------------+

| Artist

| ROUND(AVG(cds.tracks),1) |

+-------------+--------------------------+

| Abba

24.0 |

| Jamiroquai |

11.5 |

| Prince

37.0 |

| The Beatles |

30.0 |

| Various

31.5 |

| westlife |

17.0 |

+-------------+--------------------------+

6 rows in set (0.01 sec)

MIN() and MAX() These functions are very similar and select the lowest and highest figure respectively from a result set. Syntax:
MIN(field)

MAX(field)

So a simple example would be to display the least number and most number of tracks that any cd in the database has.
mysql> SELECT MIN(cds.tracks), MAX(cds.tracks)

-> FROM cds;

+-----------------+-----------------+

| MIN(cds.tracks) | MAX(cds.tracks) |

+-----------------+-----------------+

11 |

58 |

+-----------------+-----------------+

1 row in set (0.00 sec)

SUM() The final summary function that we will look at is the SUM() function which adds rows of one field in the results set together. Syntax:
SUM(field)

So another simple example would be to add the total number of tracks in the CD collection.
mysql> SELECT SUM(tracks)

-> FROM cds;

+-------------+

| SUM(tracks) |

+-------------+

231 |

+-------------+

1 row in set (0.03 sec)

Control Functions The final set of functions that this workshop will look at are the control functions that allow us a degree of conditionality when returning result sets. IF() The IF() function is fairly straight forward and consists of 3 elements. A condition and values for the condition being evaluated either true or false. Syntax:
IF(condition,true_value,false_value)

So using a simple comparison (is a number greater than 10) to return either 'yup' or 'nope'.
mysql> SELECT IF(15>10,'Yup','Nope');

+------------------------+

| IF(15>10,'Yup','Nope') |

+------------------------+

| Yup

+------------------------+

1 row in set (0.03 sec)

mysql> SELECT IF(5>10,'Yup','Nope');

+-----------------------+

| IF(5>10,'Yup','Nope') |

+-----------------------+

| Nope

+-----------------------+

1 row in set (0.01 sec)

As well as returned a string value, a number can also be returned. Thus if we wanted to count how many cds had more that 15 tracks we could return '1' for a true match and a NULL value for a false match.
mysql> SELECT IF(cds.tracks>15,1,NULL)

-> FROM cds;

+--------------------------+

| IF(cds.tracks>15,1,NULL) |

+--------------------------+

NULL |

1|

1|

1|

NULL |

1|

1|

1|

1|

+--------------------------+

9 rows in set (0.00 sec)

We can then use COUNT() to give us a total as it ignores NULL values.

mysql> SELECT COUNT(IF(cds.tracks>15,1,NULL))

-> FROM cds;

+---------------------------------+

| COUNT(IF(cds.tracks>15,1,NULL)) |

+---------------------------------+

7|

+---------------------------------+

1 row in set (0.01 sec)

CASE Slightly more advanced from IF() is the CASE function that allows for than one comparison to be made. It is slightly different as the actual value is specified first, then a series of comparisons are made for a potential match that then returns a value. Syntax:
CASE actual_value

WHEN potential_value1 THEN return_value1

WHEN potential_value2 THEN return_value2...

etc

END

Thus if we were to evaluate numeric values and return their string values.
mysql> SELECT CASE 2

-> WHEN 1 THEN 'One'

-> WHEN 2 THEN 'Two'

-> WHEN 3 THEN 'Three'

-> END;

+--------------------------------------------------------------------+

| CASE 2 WHEN 1 THEN 'One' WHEN 2 THEN 'Two' WHEN 3 THEN 'Three' END |

+--------------------------------------------------------------------+

| Two

+--------------------------------------------------------------------+

1 row in set (0.01 sec)

mysql> SELECT CASE 1

-> WHEN 1 THEN 'One'

-> WHEN 2 THEN 'Two'

-> WHEN 3 THEN 'Three'

-> END;

+--------------------------------------------------------------------+

| CASE 1 WHEN 1 THEN 'One' WHEN 2 THEN 'Two' WHEN 3 THEN 'Three' END |

+--------------------------------------------------------------------+

| One

+--------------------------------------------------------------------+

1 row in set (0.00 sec)

mysql> SELECT CASE 3

-> WHEN 1 THEN 'One'

-> WHEN 2 THEN 'Two'

-> WHEN 3 THEN 'Three'

-> END;

+--------------------------------------------------------------------+

| CASE 3 WHEN 1 THEN 'One' WHEN 2 THEN 'Two' WHEN 3 THEN 'Three' END |

+--------------------------------------------------------------------+

| Three

+--------------------------------------------------------------------+

1 row in set (0.01 sec)

Notice that the actual_value is the only thing that changes in the statement. Again you could also return numeric values. Consider the following short example

in which I have competed in various competitions around Scotland (sport undetermined ;-).
mysql> SELECT * FROM comp;

+------+-----------+----------+

| id | location | position |

+------+-----------+----------+

| 1 | Aberdeen |

2|

| 2 | Edinburgh |

1|

| 3 | Glasgow |

1|

| 4 | Dundee

3|

+------+-----------+----------+

4 rows in set (0.00 sec)

I know that 10 points are awarded for 1st, 7 points for second and 5 points for third. I can use the case statement to work out the total points I have.
mysql> SELECT CASE comp.position

-> WHEN 1 THEN 10

-> WHEN 2 THEN 7

-> WHEN 3 THEN 5

-> END

-> AS points

-> FROM comp;

+--------+

| points |

+--------+

7|

10 |

10 |

5|

+--------+

4 rows in set (0.00 sec)

A SUM() function can be added to this statement to give us the total.


mysql> SELECT SUM(CASE comp.position

-> WHEN 1 THEN 10

-> WHEN 2 THEN 7

-> WHEN 3 THEN 5

-> END)

-> AS points

-> FROM comp;

+--------+

| points |

+--------+

32 |

+--------+

1 row in set (0.00 sec)

IFNULL() The Final function we will look at is IFNULL and unsurprisingly this is a very simple syntax that is similar to IF(). The difference is that that instead of there being TRUE and FALSE return values based on a condition, the original value is returned if it is not NULL and a different new value is returned if it is NULL. Syntax:
IFNULL(original_value, new_value)

To quickly demonstrate test one query with a NULL value and another with a real value.
mysql> SELECT IFNULL(NULL,'The value is Null');

+----------------------------------+

| IFNULL(NULL,'The value is Null') |

+----------------------------------+

| The value is Null

+----------------------------------+

1 row in set (0.00 sec)

mysql> SELECT IFNULL(10,'The value is Null');

+--------------------------------+

| IFNULL(10,'The value is Null') |

+--------------------------------+

| 10

+--------------------------------+

1 row in set (0.00 sec)

This is particularly useful for converting NULL values to actual numeric zeros. To illustrate let's set the track value from 'The White Album' to NULL.
mysql> UPDATE cds

-> SET cds.tracks = NULL

-> WHERE (cdID = "20");

Query OK, 1 row affected (0.03 sec)

Rows matched: 1 Changed: 1 Warnings: 0

mysql> SELECT cds.title, cds.tracks

-> FROM cds;

+------------------------------+--------+

| title

| tracks |

+------------------------------+--------+

| A Funk Odyssey

11 |

| Now 49

40 |

| Eurovision Song contest 2001 |

23 |

| Abbas Greatest Hits

24 |

| Space Cowboy

12 |

| Sign of the times

16 |

| The White Album

| NULL |

| The Hits

58 |

| westlife

17 |

+------------------------------+--------+

9 rows in set (0.00 sec)

So we can use the IFNULL() function to specify a zero in place of the NULL figure.
mysql> SELECT cds.title, IFNULL(cds.tracks,0)

-> FROM cds;

+------------------------------+----------------------+

| title

| IFNULL(cds.tracks,0) |

+------------------------------+----------------------+

| A Funk Odyssey

11 |

| Now 49

40 |

| Eurovision Song contest 2001 |

23 |

| Abbas Greatest Hits

24 |

| Space Cowboy

12 |

| Sign of the times

16 |

| The White Album

0|

| The Hits

58 |

| westlife

17 |

+------------------------------+----------------------+

9 rows in set (0.00 sec)

Conclusion I have by no means covered all the functions that MySQL possesses and even some of those discussed above have additional syntax elements that have been excluded from this workshop for reasons of brevity and 'keeping things simple'. That said I would wager that what has been covered would be enough 99% of the time. The final thing I would like to mention about functions is that if you want you can define your own to some degree using 'User Defined Functions', but I don't think these workshops are the correct place to discuss that either.

You might also like