harian untung99play.xyz

untung99play.xyz: MySQL By Examples for Beginners


Untung99 menawarkan beragam permainan yang menarik, termasuk slot online, poker, roulette, blackjack, dan taruhan olahraga langsung. Dengan koleksi permainan yang lengkap dan terus diperbarui, pemain memiliki banyak pilihan untuk menjaga kegembiraan mereka. Selain itu, Untung99 juga menyediakan bonus dan promosi menarik yang meningkatkan peluang kemenangan dan memberikan nilai tambah kepada pemain.

Berikut adalah artikel atau berita tentang Harian untung99play.xyz dengan judul untung99play.xyz: MySQL By Examples for Beginners yang telah tayang di untung99play.xyz terimakasih telah menyimak. Bila ada masukan atau komplain mengenai artikel berikut silahkan hubungi email kami di [email protected], Terimakasih.

Read “How to Install MySQL and Get Started” on how to install, customize, and get started with MySQL.

Summary of MySQL Commands Used in this Tutorial

For detailed syntax, check MySQL manual “SQL Statement Syntax” @ http://dev.mysql.com/doc/refman/5.5/en/sql-syntax.html.

An Example for the Beginners (But NOT for the dummies)

A MySQL database server contains many databases (or schemas). Each database consists of one or more tables. A table is made up of columns (or fields) and rows (records).

The SQL keywords and commands are NOT case-sensitive. For clarity, they are shown in uppercase. The names or identifiers (database names, table names, column names, etc.) are case-sensitive in some systems, but not in other systems. Hence, it is best to treat identifiers as case-sensitive.

SHOW DATABASES

You can use SHOW DATABASES to list all the existing databases in the server.

mysql> SHOW DATABASES;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| test               |
........

The databases “mysql“, “information_schema” and “performance_schema” are system databases used internally by MySQL. A “test” database is provided during installation for your testing.

Let us begin with a simple example – a product sales database. A product sales database typically consists of many tables, e.g., products, customers, suppliers, orders, payments, employees, among others. Let’s call our database “southwind” (inspired from Microsoft’s Northwind Trader sample database). We shall begin with the first table called “products” with the following columns (having data types as indicated) and rows:

Database: southwind
Table: products
productID
INT
productCode
CHAR(3)
name
VARCHAR(30)
quantity
INT
price
DECIMAL(10,2)
1001 PEN Pen Red 5000 1.23
1002 PEN Pen Blue 8000 1.25
1003 PEN Pen Black 2000 1.25
1004 PEC Pencil 2B 10000 0.48
1005 PEC Pencil 2H 8000 0.49

Creating and Deleting a Database – CREATE DATABASE and DROP DATABASE

You can create a new database using SQL command “CREATE DATABASE databaseName“; and delete a database using “DROP DATABASE databaseName“. You could optionally apply condition “IF EXISTS” or “IF NOT EXISTS” to these commands. For example,

mysql> CREATE DATABASE southwind;
Query OK, 1 row affected (0.03 sec)

mysql> DROP DATABASE southwind;
Query OK, 0 rows affected (0.11 sec)

mysql> CREATE DATABASE IF NOT EXISTS southwind;
Query OK, 1 row affected (0.01 sec)

mysql> DROP DATABASE IF EXISTS southwind;
Query OK, 0 rows affected (0.00 sec)

IMPORTANT: Use SQL DROP (and DELETE) commands with extreme care, as the deleted entities are irrecoverable. THERE IS NO UNDO!!!

SHOW CREATE DATABASE

The CREATE DATABASE commands uses some defaults. You can issue a “SHOW CREATE DATABASE databaseName” to display the full command and check these default values. We use \G (instead of ';') to display the results vertically. (Try comparing the outputs produced by ';' and \G.)

mysql> CREATE DATABASE IF NOT EXISTS southwind;

mysql> SHOW CREATE DATABASE southwind \G
*************************** 1. row ***************************
       Database: southwind
Create Database: CREATE DATABASE `southwind` /*!40100 DEFAULT CHARACTER SET latin1 */
Back-Quoted Identifiers (`name`)

Unquoted names or identifiers (such as database name, table name and column name) cannot contain blank and special characters, or crash with MySQL keywords (such as ORDER and DESC). You can include blanks and special characters or use MySQL keyword as identifier by enclosing it with a pair of back-quote, in the form of `name`.

For robustness, the SHOW command back-quotes all the identifiers, as illustrated in the above example.

Comments and Version Comments

MySQL multi-line comments are enclosed within /* and */; end-of-line comments begins with -- (followed by a space) or #.

The /*!40100 ...... */ is known as version comment, which will only be run if the server is at or above this version number 4.01.00. To check the version of your MySQL server, issue query “SELECT version()“.

Setting the Default Database – USE

The command “USE databaseName” sets a particular database as the default (or current) database. You can reference a table in the default database using tableName directly. But you need to use the fully-qualified databaseName.tableName to reference a table NOT in the default database.

In our example, we have a database named “southwind” with a table named “products“. If we issue “USE southwind” to set southwind as the default database, we can simply call the table as “products“. Otherwise, we need to reference the table as “southwind.products“.

To display the current default database, issue command “SELECT DATABASE()“.

Creating and Deleting a Table – CREATE TABLE and DROP TABLE

You can create a new table in the default database using command “CREATE TABLE tableName” and “DROP TABLE tableName“. You can also apply condition “IF EXISTS” or “IF NOT EXISTS“. To create a table, you need to define all its columns, by providing the columns’ name, type, and attributes.

Let’s create a table “products” in our database “southwind“.

Explanations

We define 5 columns in the table products: productID, productCode, name, quantity and price. The types are:

  • productID is INT UNSIGNED – non-negative integers.
  • productCode is CHAR(3) – a fixed-length alphanumeric string of 3 characters.
  • name is VARCHAR(30) – a variable-length string of up to 30 characters.
    We use fixed-length string for productCode, as we assume that the productCode contains exactly 3 characters. On the other hand, we use variable-length string for name, as its length varies – VARCHAR is more efficient than CHAR.
  • quantity is also INT UNSIGNED (non-negative integers).
  • price is DECIMAL(10,2) – a decimal number with 2 decimal places.
    DECIMAL is precise (represented as integer with a fix decimal point). On the other hand, FLOAT and DOUBLE (real numbers) are not precise and are approximated. DECIMAL type is recommended for currency.

The attribute “NOT NULL” specifies that the column cannot contain the NULL value. NULL is a special value indicating “no value”, “unknown value” or “missing value”. In our case, these columns shall have a proper value. We also set the default value of the columns. The column will take on its default value, if no value is specified during the record creation.

We set the column productID as the so-called primary key. Values of the primary-key column must be unique. Every table shall contain a primary key. This ensures that every row can be distinguished from other rows. You can specify a single column or a set of columns (e.g., firstName and lastName) as the primary key. An index is build automatically on the primary-key column to facilitate fast search. Primary key is also used as reference by other tables.

We set the column productID to AUTO_INCREMENT.
with default starting value of 1. When you insert a row with NULL (recommended) (or 0, or a missing value) for the AUTO_INCREMENT column, the maximum value of that column plus 1 would be inserted. You can also insert a valid value to an AUTO_INCREMENT column, bypassing the auto-increment.

Inserting Rows – INSERT INTO

Let’s fill up our “products” table with rows. We set the productID of the first record to 1001, and use AUTO_INCREMENT for the rest of records by inserting a NULL, or with a missing column value. Take note that strings must be enclosed with a pair of single quotes (or double quotes).

INSERT INTO Syntax

We can use the INSERT INTO statement to insert a new row with all the column values, using the following syntax:

You need to list the values in the same order in which the columns are defined in the CREATE TABLE, separated by commas. For columns of string data type (CHAR, VARCHAR), enclosed the value with a pair of single quotes (or double quotes). For columns of numeric data type (INT, DECIMAL, FLOAT, DOUBLE), simply place the number.

You can also insert multiple rows in one INSERT INTO statement:

INSERT INTO tableName VALUES 
   (row1FirstColumnValue, ..., row1lastColumnValue),
   (row2FirstColumnValue, ..., row2lastColumnValue), 
   ...

To insert a row with values on selected columns only, use:

The remaining columns will receive their default value, such as AUTO_INCREMENT, default, or NULL.

Querying the Database – SELECT

The most common, important and complex task is to query a database for a subset of data that meets your needs – with the SELECT command. The SELECT command has the following syntax:

For examples,

SELECT without Table

You can also issue SELECT without a table. For example, you can SELECT an expression or evaluate a built-in function.

Comparison Operators

For numbers (INT, DECIMAL, FLOAT), you could use comparison operators: '=' (equal to), '<>' or '!=' (not equal to), '>' (greater than), '<'<> (less than), '>=' (greater than or equal to), '<='<> (less than or equal to), to compare two numbers. For example, price > 1.0, quantity <=>.

mysql> SELECT name, price FROM products WHERE price <1.0<>;
+-----------+-------+
| name      | price |
+-----------+-------+
| Pencil 2B |  0.48 |
| Pencil 2H |  0.49 |
+-----------+-------+
2 rows in set (0.00 sec)

mysql> SELECT name, quantity FROM products WHERE quantity <=>;
+-----------+----------+
| name      | quantity |
+-----------+----------+
| Pen Black |     2000 |
+-----------+----------+
1 row in set (0.00 sec)

CAUTION: Do not compare FLOATs (real numbers) for equality ('=' or '<>'), as they are not precise. On the other hand, DECIMAL are precise.

For strings, you could also use '=', '<>', '>', '<'<>, '>=', '<='<> to compare two strings (e.g., productCode = 'PEC'). The ordering of string depends on the so-called collation chosen. For example,

String Pattern Matching – LIKE and NOT LIKE

For strings, in addition to full matching using operators like '=' and '<>', we can perform pattern matching using operator LIKE (or NOT LIKE) with wildcard characters. The wildcard '_' matches any single character; '%' matches any number of characters (including zero). For example,

  • 'abc%' matches strings beginning with 'abc';
  • '%xyz' matches strings ending with 'xyz';
  • '%aaa%' matches strings containing 'aaa';
  • '___' matches strings containing exactly three characters; and
  • 'a_b%' matches strings beginning with 'a', followed by any single character, followed by 'b', followed by zero or more characters.

MySQL also support regular expression matching via the REGEXE operator.

Arithmetic Operators

You can perform arithmetic operations on numeric fields using arithmetic operators, as tabulated below:

Operator Description
+ Addition
Subtraction
* Multiplication
/ Division
DIV Integer Division
% Modulus (Remainder)
Logical Operators – AND, OR, NOT, XOR

You can combine multiple conditions with boolean operators AND, OR, XOR. You can also invert a condition using operator NOT. For examples,

mysql> SELECT * FROM products WHERE quantity >= 5000 AND name LIKE 'Pen %';
+-----------+-------------+----------+----------+-------+
| productID | productCode | name     | quantity | price |
+-----------+-------------+----------+----------+-------+
|      1001 | PEN         | Pen Red  |     5000 |  1.23 |
|      1002 | PEN         | Pen Blue |     8000 |  1.25 |
+-----------+-------------+----------+----------+-------+

mysql> SELECT * FROM products WHERE quantity >= 5000 AND price <1.24>;
+-----------+-------------+---------+----------+-------+
| productID | productCode | name    | quantity | price |
+-----------+-------------+---------+----------+-------+
|      1001 | PEN         | Pen Red |     5000 |  1.23 |
+-----------+-------------+---------+----------+-------+

mysql> SELECT * FROM products WHERE NOT (quantity >= 5000 AND name LIKE 'Pen %');
+-----------+-------------+-----------+----------+-------+
| productID | productCode | name      | quantity | price |
+-----------+-------------+-----------+----------+-------+
|      1003 | PEN         | Pen Black |     2000 |  1.25 |
|      1004 | PEC         | Pencil 2B |    10000 |  0.48 |
|      1005 | PEC         | Pencil 2H |     8000 |  0.49 |
+-----------+-------------+-----------+----------+-------+
IN, NOT IN

You can select from members of a set with IN (or NOT IN) operator. This is easier and clearer than the equivalent AND-OR expression.

mysql> SELECT * FROM products WHERE name IN ('Pen Red', 'Pen Black');
+-----------+-------------+-----------+----------+-------+
| productID | productCode | name      | quantity | price |
+-----------+-------------+-----------+----------+-------+
|      1001 | PEN         | Pen Red   |     5000 |  1.23 |
|      1003 | PEN         | Pen Black |     2000 |  1.25 |
+-----------+-------------+-----------+----------+-------+
BETWEEN, NOT BETWEEN

To check if the value is within a range, you could use BETWEEN ... AND ... operator. Again, this is easier and clearer than the equivalent AND-OR expression.

mysql> SELECT * FROM products 
       WHERE (price BETWEEN 1.0 AND 2.0) AND (quantity BETWEEN 1000 AND 2000);
+-----------+-------------+-----------+----------+-------+
| productID | productCode | name      | quantity | price |
+-----------+-------------+-----------+----------+-------+
|      1003 | PEN         | Pen Black |     2000 |  1.25 |
+-----------+-------------+-----------+----------+-------+
IS NULL, IS NOT NULL

NULL is a special value, which represent “no value”, “missing value” or “unknown value”. You can checking if a column contains NULL by IS NULL or IS NOT NULL. For example,

mysql> SELECT * FROM products WHERE productCode IS NULL;
Empty set (0.00 sec)

Using comparison operator (such as = or <>) to check for NULL is a mistake – a very common mistake. For example,

ORDER BY Clause

You can order the rows selected using ORDER BY clause, with the following syntax:

SELECT ... FROM tableName
WHERE criteria
ORDER BY columnA ASC|DESC, columnB ASC|DESC, ...

The selected row will be ordered according to the values in columnA, in either ascending (ASC) (default) or descending (DESC) order. If several rows have the same value in columnA, it will be ordered according to columnB, and so on. For strings, the ordering could be case-sensitive or case-insensitive, depending on the so-called character collating sequence used. For examples,

You can randomize the returned records via function RAND(), e.g.,

mysql> SELECT * FROM products ORDER BY RAND();
LIMIT Clause

A SELECT query on a large database may produce many rows. You could use the LIMIT clause to limit the number of rows displayed, e.g.,

To continue to the following records , you could specify the number of rows to be skipped, followed by the number of rows to be displayed in the LIMIT clause, as follows:

AS – Alias

You could use the keyword AS to define an alias for an identifier (such as column name, table name). The alias will be used in displaying the name. It can also be used as reference. For example,

Take note that the identifier “Unit Price” contains a blank and must be back-quoted.

Function CONCAT()

You can also concatenate a few columns as one (e.g., joining the last name and first name) using function CONCAT(). For example,

mysql> SELECT CONCAT(productCode, ' - ', name) AS `Product Description`, price FROM products;
+---------------------+-------+
| Product Description | price |
+---------------------+-------+
| PEN - Pen Red       |  1.23 |
| PEN - Pen Blue      |  1.25 |
| PEN - Pen Black     |  1.25 |
| PEC - Pencil 2B     |  0.48 |
| PEC - Pencil 2H     |  0.49 |
+---------------------+-------+

Producing Summary Reports

To produce a summary report, we often need to aggregate related rows.

DISTINCT

A column may have duplicate values, we could use keyword DISTINCT to select only distinct values. We can also apply DISTINCT to several columns to select distinct combinations of these columns. For examples,

GROUP BY Clause

The GROUP BY clause allows you to collapse multiple records with a common value into groups. For example,

GROUP BY by itself is not meaningful. It is used together with GROUP BY aggregate functions (such as COUNT(), AVG(), SUM()) to produce group summary.

GROUP BY Aggregate Functions: COUNT, MAX, MIN, AVG, SUM, STD, GROUP_CONCAT

We can apply GROUP BY Aggregate functions to each group to produce group summary report.

The function COUNT(*) returns the rows selected; COUNT(columnName) counts only the non-NULL values of the given column. For example,

Besides COUNT(), there are many other GROUP BY aggregate functions such as AVG(), MAX(), MIN() and SUM(). For example,

HAVING clause

HAVING is similar to WHERE, but it can operate on the GROUP BY aggregate functions; whereas WHERE operates only on columns.

WITH ROLLUP

The WITH ROLLUP clause shows the summary of group summary, e.g.,

Modifying Data – UPDATE

To modify existing data, use UPDATE ... SET command, with the following syntax:

UPDATE tableName SET columnName = NULL, ... WHERE criteria

For example,

CAUTION: If the WHERE clause is omitted in the UPDATE command, ALL ROWS will be updated. Hence, it is a good practice to issue a SELECT query, using the same criteria, to check the result set before issuing the UPDATE. This also applies to the DELETE statement in the following section.

Deleting Rows – DELETE FROM

Use the DELELE FROM command to delete row(s) from a table, with the following syntax:

For example,

Beware that "DELETE FROM tableName” without a WHERE clause deletes ALL records from the table. Even with a WHERE clause, you might have deleted some records unintentionally. It is always advisable to issue a SELECT command with the same WHERE clause to check the result set before issuing the DELETE (and UPDATE).

Loading/Exporting Data from/to a Text File

There are several ways to add data into the database: (a) manually issue the INSERT commands; (b) run the INSERT commands from a script; or (c) load raw data from a file using LOAD DATA or via mysqlimport utility.

LOAD DATA LOCAL INFILE … INTO TABLE …

Besides using INSERT commands to insert rows, you could keep your raw data in a text file, and load them into the table via the LOAD DATA command. For example, use a text editor to CREATE a NEW FILE called “products_in.csv“, under “d:\myProject” (for Windows) or “Documents” (for Mac), containing the following records, where the values are separated by ','. The file extension of “.csv” stands for Comma-Separated Values text file.

\N,PEC,Pencil 3B,500,0.52
\N,PEC,Pencil 4B,200,0.62
\N,PEC,Pencil 5B,100,0.73
\N,PEC,Pencil 6B,500,0.47

You can load the raw data into the products table as follows:

mysql> SELECT * FROM products;
+-----------+-------------+-----------+----------+-------+
| productID | productCode | name      | quantity | price |
+-----------+-------------+-----------+----------+-------+
|      1007 | PEC         | Pencil 3B |      500 |  0.52 |
|      1008 | PEC         | Pencil 4B |      200 |  0.62 |
|      1009 | PEC         | Pencil 5B |      100 |  0.73 |
|      1010 | PEC         | Pencil 6B |      500 |  0.47 |
+-----------+-------------+-----------+----------+-------+

Notes:

  • You need to provide the path (absolute or relative) and the filename. Use Unix-style forward-slash '/' as the directory separator, instead of Windows-style back-slash '\'.
  • The default line delimiter (or end-of-line) is '\n' (Unix-style). If the text file is prepared in Windows, you need to include LINES TERMINATED BY '\r\n'.
  • The default column delimiter is “tab” (in a so-called TSV file – Tab-Separated Values). If you use another delimiter, e.g. ',', include COLUMNS TERMINATED BY ','.
  • You need to use \N for NULL.
mysqlimport Utility Program

You can also use the mysqlimport utility program to load data from a text file.

-- SYNTAX
> mysqlimport -u username -p --local databaseName tableName.tsv
   -- The raw data must be kept in a TSV (Tab-Separated Values) file with filename the same as tablename

-- EXAMPLES
-- Create a new file called "products.tsv" containing the following record,
--  and saved under "d:\myProject" (for Windows) or "Documents" (for Mac)
-- The values are separated by tab (not spaces).
\N  PEC  Pencil 3B  500  0.52
\N  PEC  Pencil 4B  200  0.62
\N  PEC  Pencil 5B  100  0.73
\N  PEC  Pencil 6B  500  0.47

(For Windows)
> cd path-to-mysql-bin
> mysqlimport -u root -p --local southwind d:/myProject/products.tsv

(For Macs)
$ cd /usr/local/mysql/bin
$ ./mysqlimport -u root -p --local southwind ~/Documents/products.tsv
SELECT … INTO OUTFILE …

Complimenting LOAD DATA command, you can use SELECT ... INTO OUTFILE fileName FROM tableName to export data from a table to a text file. For example,

(For Windows)
mysql> SELECT * FROM products INTO OUTFILE 'd:/myProject/products_out.csv' 
         COLUMNS TERMINATED BY ','
         LINES TERMINATED BY '\r\n';

(For Macs)
mysql> SELECT * FROM products INTO OUTFILE '~/Documents/products_out.csv'
         COLUMNS TERMINATED BY ',';

Running a SQL Script

Instead of manually entering each of the SQL statements, you can keep many SQL statements in a text file, called SQL script, and run the script. For example, use a programming text editor to prepare the following script and save as “load_products.sql” under “d:\myProject” (for Windows) or “Documents” (for Mac).

DELETE FROM products;
INSERT INTO products VALUES (2001, 'PEC', 'Pencil 3B', 500, 0.52),
                            (NULL, 'PEC', 'Pencil 4B', 200, 0.62),
                            (NULL, 'PEC', 'Pencil 5B', 100, 0.73),
                            (NULL, 'PEC', 'Pencil 6B', 500, 0.47);
SELECT * FROM products;

You can run the script either:

  1. via the “source” command in a MySQL client. For example, to restore the southwind backup earlier:
  2. via the “batch mode” of the mysql client program, by re-directing the input from the script:
    (For Windows)
    > cd path-to-mysql-bin
    > mysql -u root -p southwind 
    
    (For Macs)
    $ cd /usr/local/mysql/bin
    $ ./mysql -u root -p southwind <~\documents\load_products.sql<>

More Than One Tables

Our example so far involves only one table “products“. A practical database contains many related tables.

Products have suppliers. If each product has one supplier, and each supplier supplies only one product (known as one-to-one relationship), we can simply add the supplier’s data (name, address, phone number) into the products table. Suppose that each product has one supplier, and a supplier may supply zero or more products (known as one-to-many relationship). Putting the supplier’s data into the products table results in duplication of data. This is because one supplier may supply many products, hence, the same supplier’s data appear in many rows. This not only wastes the storage but also easily leads to inconsistency (as all duplicate data must be updated simultaneously). The situation is even more complicated if one product has many suppliers, and each supplier can supply many products, in a many-to-many relationship.

One-To-Many Relationship

Suppose that each product has one supplier, and each supplier supplies one or more products. We could create a table called suppliers to store suppliers’ data (e.g., name, address and phone number). We create a column with unique value called supplierID to identify every suppliers. We set supplierID as the primary key for the table suppliers (to ensure uniqueness and facilitate fast search).

To relate the suppliers table to the products table, we add a new column into the products table – the supplierID. We then set the supplierID column of the products table as a foreign key references the supplierID column of the suppliers table to ensure the so-called referential integrity.

Database: southwind
Table: suppliers
supplierID
INT
name
VARCHAR(3)
phone
CHAR(8)
501 ABC Traders 88881111
502 XYZ Company 88882222
503 QQ Corp 88883333

Database: southwind
Table: products
productID
INT
productCode
CHAR(3)
name
VARCHAR(30)
quantity
INT
price
DECIMAL(10,2)
supplierID
INT
(Foreign Key)

2001 PEC Pencil 3B 500 0.52 501
2002 PEC Pencil 4B 200 0.62 501
2003 PEC Pencil 5B 100 0.73 501
2004 PEC Pencil 6B 500 0.47 502

We need to first create the suppliers table, because the products table references the suppliers table. The suppliers table is known as the parent table; while the products table is known as the child table in this relationship.

mysql> USE southwind;

mysql> DROP TABLE IF EXISTS suppliers;

mysql> CREATE TABLE suppliers (
         supplierID  INT UNSIGNED  NOT NULL AUTO_INCREMENT, 
         name        VARCHAR(30)   NOT NULL DEFAULT '', 
         phone       CHAR(8)       NOT NULL DEFAULT '',
         PRIMARY KEY (supplierID)
       );

mysql> DESCRIBE suppliers;
+------------+------------------+------+-----+---------+----------------+
| Field      | Type             | Null | Key | Default | Extra          |
+------------+------------------+------+-----+---------+----------------+
| supplierID | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| name       | varchar(30)      | NO   |     |         |                |
| phone      | char(8)          | NO   |     |         |                |
+------------+------------------+------+-----+---------+----------------+

mysql> INSERT INTO suppliers VALUE
          (501, 'ABC Traders', '88881111'), 
          (502, 'XYZ Company', '88882222'), 
          (503, 'QQ Corp', '88883333');

mysql> SELECT * FROM suppliers;
+------------+-------------+----------+
| supplierID | name        | phone    |
+------------+-------------+----------+
|        501 | ABC Traders | 88881111 |
|        502 | XYZ Company | 88882222 |
|        503 | QQ Corp     | 88883333 |
+------------+-------------+----------+
ALTER TABLE

Instead of deleting and re-creating the products table, we shall use “ALTER TABLE” to add a new column supplierID into the products table.

mysql> ALTER TABLE products
       ADD COLUMN supplierID INT UNSIGNED NOT NULL;
Query OK, 4 rows affected (0.13 sec)
Records: 4  Duplicates: 0  Warnings: 0

mysql> DESCRIBE products;
+-------------+------------------+------+-----+------------+----------------+
| Field       | Type             | Null | Key | Default    | Extra          |
+-------------+------------------+------+-----+------------+----------------+
| productID   | int(10) unsigned | NO   | PRI | NULL       | auto_increment |
| productCode | char(3)          | NO   |     |            |                |
| name        | varchar(30)      | NO   |     |            |                |
| quantity    | int(10) unsigned | NO   |     | 0          |                |
| price       | decimal(10,2)    | NO   |     | 9999999.99 |                |
| supplierID  | int(10) unsigned | NO   |     | NULL       |                |
+-------------+------------------+------+-----+------------+----------------+

Next, we shall add a foreign key constraint on the supplierID columns of the products child table

to the suppliers parent table, to ensure that every supplierID in the products table always refers to a valid supplierID in the suppliers table – this is called referential integrity.

Before we can add the foreign key, we need to set the supplierID of the existing records in the products table to a valid supplierID in the table (say supplierID=501).

SELECT with JOIN

SELECT command can be used to query and join data from two related tables. For example, to list the product’s name (in products table) and supplier’s name (in suppliers table), we could join the two table via the two common supplierID columns:

In the above query result, two of the columns have the same heading “name“. We could create aliases for headings.

The database diagram is as illustrated. The link indicates a one-to-many relationship between products and suppliers.

Many-To-Many Relationship

Suppose that a product has many suppliers; and a supplier supplies many products in a so-called many-to-many relationship. The above solution breaks. You cannot include the supplierID in the products table, as you cannot determine the number of suppliers, and hence, the number of columns needed for the supplierIDs. Similarly, you cannot include the productID in the suppliers table, as you cannot determine the number of products.

To resolve this problem, you need to create a new table, known as a junction table (or joint table), to provide the linkage. Let’s call the junction table products_suppliers, as illustrated.

Database: southwind
Table: products_suppliers
productID
INT
(Foreign Key)

supplierID
INT
(Foreign Key)

2001 501
2002 501
2003 501
2004 502
2001 503

Database: southwind
Table: suppliers
supplierID
INT
name
VARCHAR(30)
phone
CHAR(8)
501 ABC Traders 88881111
502 XYZ Company 88882222
503 QQ Corp 88883333

Database: southwind
Table: products
productID
INT
productCode
CHAR(3)
name
VARCHAR(30)
quantity
INT
price
DECIMAL(10,2)
2001 PEC Pencil 3B 500 0.52
2002 PEC Pencil 4B 200 0.62
2003 PEC Pencil 5B 100 0.73
2004 PEC Pencil 6B 500 0.47

Let’s create the products_suppliers table. The primary key of the table consists of two columns: productID and supplierID, as their combination uniquely identifies each rows. This primary key is defined to ensure uniqueness. Two foreign keys are defined to set the constraint to the two parent tables.

Next, remove the supplierID column from the products table. (This column was added to establish the one-to-many relationship. It is no longer needed in the many-to-many relationship.)

Before this column can be removed, you need to remove the foreign key that builds on this column. To remove a key in MySQL, you need to know its constraint name, which was generated by the system. To find the constraint name, issue a “SHOW CREATE TABLE products” and take note of the foreign key’s constraint name in the clause “CONSTRAINT constraint_name FOREIGN KEY ....“. You can then drop the foreign key using “ALTER TABLE products DROP FOREIGN KEY constraint_name

mysql> SHOW CREATE TABLE products \G
Create Table: CREATE TABLE `products` (
  `productID`   int(10) unsigned  NOT NULL AUTO_INCREMENT,
  `productCode` char(3)           NOT NULL DEFAULT '',
  `name`        varchar(30)       NOT NULL DEFAULT '',
  `quantity`    int(10) unsigned  NOT NULL DEFAULT '0',
  `price`       decimal(7,2)      NOT NULL DEFAULT '99999.99',
  `supplierID`  int(10) unsigned   NOT NULL DEFAULT '501',
  PRIMARY KEY (`productID`),
  KEY `supplierID` (`supplierID`),
  CONSTRAINT `products_ibfk_1` FOREIGN KEY (`supplierID`) 
     REFERENCES `suppliers` (`supplierID`)
) ENGINE=InnoDB AUTO_INCREMENT=1006 DEFAULT CHARSET=latin1

mysql> ALTER TABLE products DROP FOREIGN KEY products_ibfk_1;

mysql> SHOW CREATE TABLE products \G

Now, we can remove the column redundant supplierID column.

mysql> ALTER TABLE products DROP supplierID;

mysql> DESC products;
Querying

Similarly, we can use SELECT with JOIN to query data from the 3 tables, for examples,

mysql> 

The database diagram is as follows. Both products and suppliers tables exhibit a one-to-many relationship to the junction table. The many-to-many relationship is supported via the junction table.

One-to-one Relationship

Suppose that some products have optional data (e.g., photo, comment). Instead of keeping these optional data in the products table, it is more efficient to create another table called product_details, and link it to products with a one-to-one relationship, as illustrated.

mysql> 

mysql> DESCRIBE product_details;
+-----------+------------------+------+-----+---------+-------+
| Field     | Type             | Null | Key | Default | Extra |
+-----------+------------------+------+-----+---------+-------+
| productID | int(10) unsigned | NO   | PRI | NULL    |       |
| comment   | text             | YES  |     | NULL    |       |
+-----------+------------------+------+-----+---------+-------+

mysql> SHOW CREATE TABLE product_details \G
*************************** 1. row ***************************
       Table: product_details
Create Table: CREATE TABLE `product_details` (
  `productID`  int(10) unsigned  NOT NULL,
  `comment`    text,
  PRIMARY KEY (`productID`),
  CONSTRAINT `product_details_ibfk_1` FOREIGN KEY (`productID`) REFERENCES `products` (`productID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1

Backup and Restore

Backup: Before we conclude this example, let’s run the mysqldump utility program to dump out (backup) the entire southwind database.

(For Windows)
-- Start a NEW "cmd"
> cd path-to-mysql-bin
> mysqldump -u root -p --databases southwind > "d:\myProject\backup_southwind.sql"

(For Macs)
-- Start a NEW "terminal"
$ cd /usr/local/mysql/bin
$ ./mysqldump -u root -p --databases southwind > ~/Documents/backup_southwind.sql

Study the output file, which contains CREATE DATABASE, CREATE TABLE and INSERT statements to re-create the tables dumped.

The SYNTAX for the mysqldump utility program is as follows:

-- Dump selected databases with --databases option
> mysqldump -u username -p --databases database1Name [database2Name ...] > backupFile.sql
-- Dump all databases in the server with --all-databases option, except mysql.user table (for security)
> mysqldump -u root -p --all-databases --ignore-table=mysql.user > backupServer.sql

-- Dump all the tables of a particular database
> mysqldump -u username -p databaseName > backupFile.sql
-- Dump selected tables of a particular database
> mysqldump -u username -p databaseName table1Name [table2Name ...] > backupFile.sql

Restore: The utility mysqldump produces a SQL script (consisting of CREATE TABLE and INSERT commands to re-create the tables and loading their data). You can restore from the backup by running the script either:

  1. via the “source” command in an interactive client. For example, to restore the southwind backup earlier:
  2. via the “batch mode” of the mysql client program by re-directing the input from the script:

More on Primary Key, Foreign Key and Index

Primary Key

In the relational model, a table shall not contain duplicate rows, because that would create ambiguity in retrieval. To ensure uniqueness, each table should have a column (or a set of columns), called primary key, that uniquely identifies every record of the table. For example, an unique number customerID can be used as the primary key for the customers table; productCode for products table; isbn for books table. A primary key is called a simple key if it is a single column; it is called a composite key if it is made up of several columns. Most RDBMSs build an index on the primary key to facilitate fast search. The primary key is often used to relate to other tables.

Foreign Key

A foreign key of a child table is used to reference the parent table. Foreign key constraint can be imposed to ensure so-called referential integrity – values in the child table must be valid values in the parent table.

We define the foreign key when defining the child table, which references a parent table, as follows:

You can specify the reference action for UPDATE and DELETE via the optional ON UPDATE and ON DELETE clauses:

  1. RESTRICT (default): disallow DELETE or UPDATE of the parent’s row, if there are matching rows in child table.
  2. CASCADE: cascade the DELETE or UPDATE action to the matching rows in the child table.
  3. SET NULL: set the foreign key value in the child table to NULL (if NULL is allowed).
  4. NO ACTION: a SQL term which means no action on the parent’s row. Same as RESTRICT in MySQL, which disallows DELETE or UPDATE (do nothing).

Try deleting a record in the suppliers (parent) table that is referenced by products_suppliers (child) table, e.g.,

The record cannot be deleted as the default “ON DELETE RESTRICT” constraint was imposed.

Indexes (or Keys)

Indexes (or Keys) can be created on selected column(s) to facilitate fast search. Without index, a “SELECT * FROM products WHERE productID=x” needs to match with the productID column of all the records in the products table. If productID column is indexed (e.g., using a binary tree), the matching can be greatly improved (via the binary tree search).

You should index columns which are frequently used in the WHERE clause; and as JOIN columns.

The drawback about indexing is cost and space. Building and maintaining indexes require computations and memory spaces. Indexes facilitate fast search but deplete the performance on modifying the table (INSERT/UPDATE/DELETE), and need to be justified. Nevertheless, relational databases are typically optimized for queries and retrievals, but NOT for updates.

In MySQL, the keyword KEY is synonym to INDEX.

In MySQL, indexes can be built on:

  1. a single column (column-index)
  2. a set of columns (concatenated-index)
  3. on unique-value column (UNIQUE INDEX or UNIQUE KEY)
  4. on a prefix of a column for strings (VARCHAR or CHAR), e.g., first 5 characters.

There can be more than one indexes in a table. Index are automatically built on the primary-key column(s).

You can build index via CREATE TABLE, CREATE INDEX or ALTER TABLE.

More SQL

Sub-Query

Results of one query can be used in another SQL statement. Subquery is useful if more than one tables are involved.

SELECT with Subquery

In the previous many-to-many product sales example, how to find the suppliers that do not supply any product? You can query for the suppliers that supply at least one product in the products_suppliers table, and then query the suppliers table for those that are not in the previous result set.

mysql> SELECT suppliers.name from suppliers
       WHERE suppliers.supplierID
          NOT IN (SELECT DISTINCT supplierID from products_suppliers);

Can you do this without sub-query?

A subquery may return a scalar, a single column, a single row, or a table. You can use comparison operator (e.g., '=', '>') on scalar, IN or NOT IN for single row or column, EXISTS or NOT EXIST to test for empty set.

INSERT|UPDATE|DELETE with Subquery

You can also use a subquery with other SQL statements such as INSERT, DELETE, or UPDATE. For example,

Working with Date and Time

Date and time are of particular interest for database applications. This is because business records often carry date/time information (e.g., orderDate, deliveryDate, paymentDate, dateOfBirth), as well as the need to time-stamp the creation and last-update of the records for auditing and security.

With date/time data types, you can sort the results by date, search for a particular date or a range of dates, calculate the difference between dates, compute a new date by adding/subtracting an interval from a given date.

Date By Example

Let’s begin with Date (without Time) with the following example. Take note that date value must be written as a string in the format of 'yyyy-mm-dd', e.g., '2012-01-31'.

Date/Time Functions

MySQL provides these built-in functions for getting the current date, time and datetime:

  • NOW(): returns the current date and time in the format of 'YYYY-MM-DD HH:MM:SS'.
  • CURDATE() (or CURRENT_DATE(), or CURRENT_DATE): returns the current date in the format of 'YYYY-MM-DD'.
  • CURTIME() (or CURRENT_TIME(), or CURRENT_TIME): returns the current time in the format of 'HH:MM:SS'.

For examples,

mysql> select now(), curdate(), curtime();
+---------------------+------------+-----------+
| now()               | curdate()  | curtime() |
+---------------------+------------+-----------+
| 2012-10-19 19:53:20 | 2012-10-19 | 19:53:20  |
+---------------------+------------+-----------+
SQL Date/Time Types

MySQL provides these date/time data types:

  • DATETIME: stores both date and time in the format of 'YYYY-MM-DD HH:MM:SS'. The valid range is '1000-01-01 00:00:00' to '9999-12-31 23:59:59'. You can set a value using the valid format (e.g., '2011-08-15 00:00:00'). You could also apply functions NOW() or CURDATE() (time will be set to '00:00:00'), but not CURTIME().
  • DATE: stores date only in the format of 'YYYY-MM-DD'. The range is '1000-01-01' to '9999-12-31'. You could apply CURDATE() or NOW() (the time discarded) on this field.
  • TIME: stores time only in the format of 'HH:MM:SS'. You could apply CURTIME() or NOW() (the date discarded) for this field.
  • YEAR(4|2): in 'YYYY' or 'YY'. The range of years is 1901 to 2155. Use DATE type for year outside this range. You could apply CURDATE() to this field (month and day discarded).
  • TIMESTAMP: similar to DATETIME but stored the number of seconds since January 1, 1970 UTC (Unix-style). The range is '1970-01-01 00:00:00' to '2037-12-31 23:59:59'.
    The differences between DATETIME and TIMESTAMP are:

    1. the range,
    2. support for time zone,
    3. TIMESTAMP column could be declared with DEFAULT CURRENT_TIMESTAMP to set the default value to the current date/time. (All other data types’ default, including DATETIME, must be a constant and not a function return value). You can also declare a TIMESTAMP column with “ON UPDATE CURRENT_TIMESTAMP” to capture the timestamp of the last update.


The date/time value can be entered manually as a string literal (e.g., '2010-12-31 23:59:59' for DATAETIME). MySQL will issue a warning and insert all zeros (e.g., '0000-00-00 00:00:00' for DATAETIME), if the value of date/time to be inserted is invalid or out-of-range. '0000-00-00' is called a “dummy” date.

More Date/Time Functions

Reference: MySQL’s “Date and Time Functions” @ http://dev.mysql.com/doc/refman/5.5/en/date-and-time-functions.html.

There are many date/time functions:

  • Extracting part of a date/time: YEAR(), MONTH(), DAY(), HOUR(), MINUTE(), SECOND(), e.g.,
    mysql> SELECT YEAR(NOW()), MONTH(NOW()), DAY(NOW()), HOUR(NOW()), MINUTE(NOW()), SECOND(NOW());
    +-------------+--------------+------------+-------------+---------------+---------------+
    | YEAR(NOW()) | MONTH(NOW()) | DAY(NOW()) | HOUR(NOW()) | MINUTE(NOW()) | SECOND(NOW()) |
    +-------------+--------------+------------+-------------+---------------+---------------+
    |        2012 |           10 |         24 |          11 |            54 |     45        |
    +-------------+--------------+------------+-------------+---------------+---------------+
  • Extracting information: DAYNAME() (e.g., 'Monday'), MONTHNAME() (e.g., 'March'), DAYOFWEEK() (1=Sunday, …, 7=Saturday), DAYOFYEAR() (1-366), …
    mysql> SELECT DAYNAME(NOW()), MONTHNAME(NOW()), DAYOFWEEK(NOW()), DAYOFYEAR(NOW());
    +----------------+------------------+------------------+------------------+
    | DAYNAME(NOW()) | MONTHNAME(NOW()) | DAYOFWEEK(NOW()) | DAYOFYEAR(NOW()) |
    +----------------+------------------+------------------+------------------+
    | Wednesday      | October          |                4 |              298 |
    +----------------+------------------+------------------+------------------+
  • Computing another date/time: DATE_SUB(date, INTERVAL expr unit), DATE_ADD(date, INTERVAL expr unit), TIMESTAMPADD(unit, interval, timestamp), e.g.,
    mysql> SELECT DATE_ADD('2012-01-31', INTERVAL 5 DAY);
    2012-02-05
    
    mysql> SELECT DATE_SUB('2012-01-31', INTERVAL 2 MONTH);
    2011-11-30
  • Computing interval: DATEDIFF(end_date, start_date), TIMEDIFF(end_time, start_time), TIMESTAMPDIFF(unit, start_timestamp, end_timestamp), e.g.,
    mysql> SELECT DATEDIFF('2012-02-01', '2012-01-28');
    4
    
    mysql> SELECT TIMESTAMPDIFF(DAY, '2012-02-01', '2012-01-28');
    -4
  • Representation: TO_DAYS(date) (days since year 0), FROM_DAYS(day_number), e.g.,
    mysql> SELECT TO_DAYS('2012-01-31');
    734898
    
    mysql> SELECT FROM_DAYS(734899);
    2012-02-01
  • Formatting: DATE_FORMAT(date, formatSpecifier), e.g.,
  1. Create a table with various date/time columns. Only the TIMESTAMP column can have the DEFAULT CURRENT_TIMESTAMP and ON UPDATE CURRENT_TIMESTAMP.
    mysql> CREATE TABLE IF NOT EXISTS `datetime_arena` (
              `description`  VARCHAR(50)  DEFAULT NULL,
              `cDateTime`    DATETIME     DEFAULT '1000-01-01  00:00:00',
              `cDate`        DATE         DEFAULT '1000-01-01 ',
              `cTime`        TIME         DEFAULT '00:00:00',
              `cYear`        YEAR         DEFAULT '0000',
              `cYear2`       YEAR(2)      DEFAULT '0000',
              `cTimeStamp`   TIMESTAMP    DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
           );
    
    mysql> DESCRIBE `datetime_arena`;
    +-------------+-------------+------+-----+---------------------+-----------------------------+
    | Field       | Type        | Null | Key | Default             | Extra                       |
    +-------------+-------------+------+-----+---------------------+-----------------------------+
    | description | varchar(50) | YES  |     | NULL                |                             |
    | cDateTime   | datetime    | YES  |     | 1000-01-01 00:00:00 |                             |
    | cDate       | date        | YES  |     | 1000-01-01          |                             |
    | cTime       | time        | YES  |     | 00:00:00            |                             |
    | cYear       | year(4)     | YES  |     | 0000                |                             |
    | cYear2      | year(4)     | YES  |     | 0000                |                             |
    | cTimeStamp  | timestamp   | NO   |     | CURRENT_TIMESTAMP   | on update CURRENT_TIMESTAMP |
    +-------------+-------------+------+-----+---------------------+-----------------------------+

    Notes:

    • Don’t use year(2) anymore.
    • From MySQL 5.7, the supported range for datetime is '1000-01-01 00:00:00' to '9999-12-31 23:59:59'.
  2. Insert values manually using string literals.
    mysql> INSERT INTO `datetime_arena` 
              (`description`, `cDateTime`, `cDate`, `cTime`, `cYear`, `cYear2`)
           VALUES 
              ('Manual Entry', '2001-01-01 23:59:59', '2002-02-02', '12:30:30', '2004', '05');
    
    mysql> SELECT * FROM `datetime_arena` WHERE description='Manual Entry';
    +--------------+---------------------+------------+----------+-------+--------+---------------------+
    | description  | cDateTime           | cDate      | cTime    | cYear | cYear2 | cTimeStamp          |
    +--------------+---------------------+------------+----------+-------+--------+---------------------+
    | Manual Entry | 2001-01-01 23:59:59 | 2002-02-02 | 12:30:30 |  2004 |     05 | 2010-04-08 14:44:37 |
    +--------------+---------------------+------------+----------+-------+--------+---------------------+
  3. Checking the on-update for TIMSTAMP.
    mysql> UPDATE `datetime_arena` SET `cYear2`='99' WHERE description='Manual Entry';
    
    mysql> SELECT * FROM `datetime_arena` WHERE description='Manual Entry';
    +--------------+---------------------+------------+----------+-------+--------+---------------------+
    | description  | cDateTime           | cDate      | cTime    | cYear | cYear2 | cTimeStamp          |
    +--------------+---------------------+------------+----------+-------+--------+---------------------+
    | Manual Entry | 2001-01-01 23:59:59 | 2002-02-02 | 12:30:30 |  2004 |     99 | 2010-04-08 14:44:48 |
    +--------------+---------------------+------------+----------+-------+--------+---------------------+
  4. Insert values using MySQL built-in functions now(), curdate(), curtime().
    mysql> INSERT INTO `datetime_arena` 
              (`description`, `cDateTime`, `cDate`, `cTime`, `cYear`, `cYear2`)
           VALUES 
              ('Built-in Functions', now(), curdate(), curtime(), now(), now());
    
    mysql> SELECT * FROM `datetime_arena` WHERE description='Built-in Functions';
    +--------------------+---------------------+------------+----------+-------+--------+---------------------+
    | description        | cDateTime           | cDate      | cTime    | cYear | cYear2 | cTimeStamp          |
    +--------------------+---------------------+------------+----------+-------+--------+---------------------+
    | Built-in Functions | 2010-04-08 14:45:48 | 2010-04-08 | 14:45:48 |  2010 |     10 | 2010-04-08 14:45:48 |
    +--------------------+---------------------+------------+----------+-------+--------+---------------------+
  5. Insert invalid or out-of-range values. MySQL replaces with all zeros.
    mysql> INSERT INTO `datetime_arena`
              (`description`, `cDateTime`, `cDate`, `cTime`, `cYear`, `cYear2`)
           VALUES 
              ('Error Input', '2001-13-31 23:59:59', '2002-13-31', '12:61:61', '99999', '999');
    
    mysql> SELECT * FROM `datetime_arena` WHERE description='Error Input';
    +-------------+---------------------+------------+----------+-------+--------+---------------------+
    | description | cDateTime           | cDate      | cTime    | cYear | cYear2 | cTimeStamp          |
    +-------------+---------------------+------------+----------+-------+--------+---------------------+
    | Error Input | 0000-00-00 00:00:00 | 0000-00-00 | 00:00:00 |  0000 |     00 | 2010-04-08 14:46:10 |
    +-------------+---------------------+------------+----------+-------+--------+---------------------+

    Note: Might not work in MySQL 5.7?!

  6. An useful built-in function INTERVAL can be used to compute a future date, e.g.,
    mysql> SELECT `cDate`, `cDate` + INTERVAL 30 DAY, `cDate` + INTERVAL 1 MONTH FROM `datetime_arena`;
    +------------+---------------------------+----------------------------+
    | cDate      | `cDate` + INTERVAL 30 DAY | `cDate` + INTERVAL 1 MONTH |
    +------------+---------------------------+----------------------------+
    | 2002-02-02 | 2002-03-04                | 2002-03-02                 |
    | 2010-04-08 | 2010-05-08                | 2010-05-08                 |
    | 0000-00-00 | NULL                      | NULL                       |
    +------------+---------------------------+----------------------------+

View

A view is a virtual table that contains no physical data. It provide an alternative way to look at the data.

mysql> DROP VIEW IF EXISTS patient_view;

mysql> CREATE VIEW patient_view
       AS
       SELECT 
          patientID AS ID, 
          name AS Name, 
          dateOfBirth AS DOB,
          TIMESTAMPDIFF(YEAR, dateOfBirth, NOW()) AS Age
       FROM patients
       ORDER BY Age, DOB;

mysql> SELECT * FROM patient_view WHERE Name LIKE 'A%';
+------+---------+------------+------+
| ID   | Name    | DOB        | Age  |
+------+---------+------------+------+
| 1003 | Ali     | 2011-01-30 |    1 |
| 1001 | Ah Teck | 1991-12-31 |   20 |
+------+---------+------------+------+

mysql> SELECT * FROM patient_view WHERE age >= 18;
+------+---------+------------+------+
| ID   | Name    | DOB        | Age  |
+------+---------+------------+------+
| 1001 | Ah Teck | 1991-12-31 |   20 |
+------+---------+------------+------+

Transactions

A atomic transaction is a set of SQL statements that either ALL succeed or ALL fail. Transaction is important to ensure that there is no partial update to the database, given an atomic of SQL statements. Transactions are carried out via COMMIT and ROLLBACK.

If you start another mysql client and do a SELECT during the transaction (before the commit or rollback), you will not see the changes.

Alternatively, you can also disable the so-called autocommit mode, which is set by default and commit every single SQL statement.

A transaction groups a set of operations into a unit that meets the ACID test:

  1. Atomicity: If all the operations succeed, changes are committed to the database. If any of the operations fails, the entire transaction is rolled back, and no change is made to the database. In other words, there is no partial update.
  2. Consistency: A transaction transform the database from one consistent state to another consistent state.
  3. Isolation: Changes to a transaction are not visible to another transaction until they are committed.
  4. Durability: Committed changes are durable and never lost.

User Variables

In MySQL, you can define user variables via:

  1. @varname :=value in a SELECT command, or
  2. SET @varname := value or SET @varname = value command.

For examples,

mysql> SELECT @ali_dob := dateOfBirth FROM patients WHERE name = 'Ali';
mysql> SELECT name WHERE dateOfBirth <@ali_dob;

mysql> SET @today := CURDATE();
mysql> SELECT name FROM patients WHERE nextVisitDate = @today;

More on JOIN

INNER JOIN

In an inner join of two tables, each row of the first table is combined (joined) with every row of second table. Suppose that there are n1 rows in the first table and n2 rows in the second table, INNER JOIN produces all combinations of n1×n2 rows – it is known as Cartesian Product or Cross Product.

You can impose constrain by using the ON clause, for example,

mysql> SELECT *
       FROM t1 INNER JOIN t2 ON t1.id = t2.id;
+----+------------+----+------------+
| id | desc       | id | desc       |
+----+------------+----+------------+
|  2 | ID 2 in t1 |  2 | ID 2 in t2 |
|  3 | ID 3 in t1 |  3 | ID 3 in t2 |
+----+------------+----+------------+

Take note that the following are equivalent:

OUTER JOIN – LEFT JOIN and RIGHT JOIN

INNER JOIN with constrain (ON or USING) produces rows that are found in both tables. On the other hand, OUTER JOIN can produce rows that are in one table, but not in another table. There are two kinds of OUTER JOINs: LEFT JOIN produces rows that are in the left table, but may not in the right table; whereas RIGHT JOIN produces rows that are in the right table but may not in the left table.

In a LEFT JOIN, when a row in the left table does not match with the right table, it is still selected but by combining with a “fake” record of all NULLs for the right table.

mysql> SELECT *
       FROM t1 LEFT JOIN t2 ON t1.id = t2.id;
+----+------------+------+------------+
| id | desc       | id   | desc       |
+----+------------+------+------------+
|  1 | ID 1 in t1 | NULL | NULL       |
|  2 | ID 2 in t1 |    2 | ID 2 in t2 |
|  3 | ID 3 in t1 |    3 | ID 3 in t2 |
+----+------------+------+------------+

mysql> SELECT *
       FROM t1 LEFT JOIN t2 USING (id);
+----+------------+------------+
| id | desc       | desc       |
+----+------------+------------+
|  1 | ID 1 in t1 | NULL       |
|  2 | ID 2 in t1 | ID 2 in t2 |
|  3 | ID 3 in t1 | ID 3 in t2 |
+----+------------+------------+

mysql> SELECT *
       FROM t1 RIGHT JOIN t2 ON t1.id = t2.id;
+------+------------+----+------------+
| id   | desc       | id | desc       |
+------+------------+----+------------+
|    2 | ID 2 in t1 |  2 | ID 2 in t2 |
|    3 | ID 3 in t1 |  3 | ID 3 in t2 |
| NULL | NULL       |  4 | ID 4 in t2 |
+------+------------+----+------------+

mysql> SELECT *
       FROM t1 RIGHT JOIN t2 USING (id);
+----+------------+------------+
| id | desc       | desc       |
+----+------------+------------+
|  2 | ID 2 in t2 | ID 2 in t1 |
|  3 | ID 3 in t2 | ID 3 in t1 |
|  4 | ID 4 in t2 | NULL       |
+----+------------+------------+

As the result, LEFT JOIN ensures that the result set contains every row on the left table. This is important, as in some queries, you are interested to have result on every row on the left table, with no match in the right table, e.g., searching for items without supplier. For example,

mysql> SELECT t1.id, t1.desc
       FROM t1 LEFT JOIN t2 USING (id)
       WHERE t2.id IS NULL;
+----+------------+
| id | desc       |
+----+------------+
|  1 | ID 1 in t1 |
+----+------------+

Take note that the followings are equivalent:

Exercises

Rental System

Peter runs a small car rental company with 10 cars and 5 trucks. He engages you to design a web portal to put his operation online.

For the initial phase, the web portal shall provide these basic functions:

  1. Maintaining the records of the vehicles and customers.
  2. Inquiring about the availability of vehicle, and
  3. Reserving a vehicle for rental.

A customer record contains his/her name, address and phone number.

A vehicle, identified by the vehicle registration number, can be rented on a daily basis. The rental rate is different for different vehicles. There is a discount of 20% for rental of 7 days or more.

A customer can rental a vehicle from a start date to an end date. A special customer discount, ranging from 0-50%, can be given to preferred customers.

Database

The initial database contains 3 tables: vehicles, customers, and rental_records. The rental_records is a junction table supporting many-to-many relationship between vehicles and customers.

Exercises
  1. Customer 'Tan Ah Teck' has rented 'SBA1111A' from today for 10 days. (Hint: You need to insert a rental record. Use a SELECT subquery to get the customer_id. Use CURDATE() (or NOW()) for today; and DATE_ADD(CURDATE(), INTERVAL x unit) to compute a future date.)
    INSERT INTO rental_records VALUES
       (NULL,
        'SBA1111A', 
        (SELECT customer_id FROM customers WHERE name='Tan Ah Teck'),
        CURDATE(),
        DATE_ADD(CURDATE(), INTERVAL 10 DAY),
        NULL);
  2. Customer 'Kumar' has rented 'GA5555E' from tomorrow for 3 months.
  3. List all rental records (start date, end date) with vehicle’s registration number, brand, and customer name, sorted by vehicle’s categories followed by start date.
    SELECT
       r.start_date  AS `Start Date`,
       r.end_date    AS `End Date`,
       r.veh_reg_no  AS `Vehicle No`,
       v.brand       AS `Vehicle Brand`,
       c.name        AS `Customer Name`
    FROM rental_records AS r
       INNER JOIN vehicles  AS v USING (veh_reg_no)
       INNER JOIN customers AS c USING (customer_id)
    ORDER BY v.category, start_date;
  4. List all the expired rental records (end_date before CURDATE()).
  5. List the vehicles rented out on '2012-01-10' (not available for rental), in columns of vehicle registration no, customer name, start date and end date. (Hint: the given date is in between the start_date and end_date.)
  6. List all vehicles rented out today, in columns registration number, customer name, start date, end date.
  7. Similarly, list the vehicles rented out (not available for rental) for the period from '2012-01-03' to '2012-01-18'. (Hint: start_date is inside the range; or end_date is inside the range; or start_date is before the range and end_date is beyond the range.)
  8. List the vehicles (registration number, brand and description) available for rental (not rented out) on '2012-01-10' (Hint: You could use a subquery based on a earlier query).
  9. Similarly, list the vehicles available for rental for the period from '2012-01-03' to '2012-01-18'.
  10. Similarly, list the vehicles available for rental from today for 10 days.
  11. Foreign Key Test:
    1. Try deleting a parent row with matching row(s) in child table(s), e.g., delete 'GA6666F' from vehicles table (ON DELETE RESTRICT).
    2. Try updating a parent row with matching row(s) in child table(s), e.g., rename 'GA6666F' to 'GA9999F' in vehicles table. Check the effects on the child table rental_records (ON UPDATE CASCADE).
    3. Remove 'GA6666F' from the database (Hints: Remove it from child table rental_records; then parent table vehicles.)
  12. Payments: A rental could be paid over a number of payments (e.g., deposit, installments, full payment). Each payment is for one rental. Create a new table called payments. Need to create columns to facilitate proper audit check (such as create_date, create_by, last_update_date, last_update_by, etc.)
  13. Staff: Keeping track of staff serving the customers. Create a new staff table. Assume that each transaction is handled by one staff, we can add a new column called staff_id in the rental_records table,
Advanced Exercises
  1. Adding Photo: We could store photo in MySQL using data type of BLOB (Binary Large Object) (up to 64KB), MEDIUMBLOB (up to 16MBytes), LONGBOLB (up to 4GBytes). For example,

    You can conveniently load and view the photo via graphical tools such as MySQL Workbench. To load a image in MySQL Workbench ⇒ right-click on the cell ⇒ Load Value From File ⇒ Select the image file. To view the image ⇒ right-click on the BLOB cell ⇒ Open Value in Editor ⇒ choose “Image” pane.

    I also include a Java program for reading and writing image BLOB from/to the database, based on this example: “TestImageBLOB.java“.

  2. VIEW: Create a VIEW called rental_prices on the rental_records with an additional column called price. Show all the records of the VIEW.

    It is probably easier to compute the price using a program/procedure, instead of inside the view.

  3. From the payments table, create a view to show the outstanding balance.
  4. Define more views.
  5. FUNCTION: Write a function to compute the rental price.
  6. Define more procedures and functions.
  7. TRIGGER: Write a trigger for the created_date and created_by columns of the payments table.
  8. Define more triggers.
  9. Implement discount on weekday (Monday to Friday, except public holiday): Need to set up a new table called public_hoilday with columns date and description. Use function DAYOFWEEK (1=Sunday, …, 7=Saturday) to check for weekday or weekend.

Product Sales Database

[TODO] Explanation

Link to MySQL References & Resources