Just somewhere to keep my notes while I'm playing.

Showing posts with label sql. Show all posts
Showing posts with label sql. Show all posts

Saturday, July 25, 2009

Reading from a file

I have started playing with a new database, that will allow the costing of simple recipes. Here's the schema (so far):



DROP TABLE IF EXISTS recipe.ingredient;
DROP TABLE IF EXISTS recipe.item;
DROP TABLE IF EXISTS recipe.unit;

CREATE TABLE recipe.unit (
id INT NOT NULL,
name VARCHAR(10) NOT NULL,
PRIMARY KEY (id)
) TYPE = INNODB;

CREATE TABLE recipe.item (
code VARCHAR(10) NOT NULL,
name VARCHAR(50) NOT NULL,
unitid INT NOT NULL,
cost DECIMAL(10,2) NOT NULL,
PRIMARY KEY (code),
FOREIGN KEY (unitid) REFERENCES unit(id)
) TYPE = INNODB;

CREATE TABLE recipe.ingredient (
recipe_code VARCHAR(10) NOT NULL,
ingred_code VARCHAR(10) NOT NULL,
quantity DECIMAL(10,2) NOT NULL,
FOREIGN KEY (recipe_code) REFERENCES item (code),
FOREIGN KEY (ingred_code) REFERENCES item (code)
) TYPE = INNODB;



First thing to do is to populate the unit table.


C:\Documents and Settings\Tim\My Documents\DB\Recipe\sql>more unit.txt
1,litre
2,each
3,gram

C:\Documents and Settings\Tim\My Documents\DB\Recipe\sql>

mysql> LOAD DATA LOCAL INFILE 'unit.txt' REPLACE INTO TABLE recipe.unit FIELDS TERMINATED BY ',' LINES TERMINATED BY '\r\n'
-> ;
Query OK, 3 rows affected (0.02 sec)
Records: 3 Deleted: 0 Skipped: 0 Warnings: 0

mysql> select * from unit;
+----+-------+
| id | name |
+----+-------+
| 1 | litre |
| 2 | each |
| 3 | gram |
+----+-------+
3 rows in set (0.00 sec)

mysql>


Note: the stupid 'LINES TERMINATED' option used because I'm running on a stupid OS that uses Character Return Line Feeds for it's line termination.

Then the PHP script to load some raw materials:


C:\Documents and Settings\Tim\My Documents\DB\Recipe\php>more rawingred.txt
EGG,Egg,2,15
FLOUR,Flour,3,0.01
MILK,Milk,1,75

C:\Documents and Settings\Tim\My Documents\DB\Recipe\php>more rawingredload.php
<?php
function openMyDB($dbname)
{
include("DBInfo.inc");
$con = mysql_connect($mydbhost,$mydbuser,$mydbpassword);
if (!$con) {
die('Could not connect: ' . mysql_error());
}
mysql_select_db($dbname, $con);
return $con;
}

$con = openMyDB("recipe");

$file=fopen("rawingred.txt","r") or exit("Unable to open file!");
while(!feof($file)) {
$ingred = fgets($file);
list($code, $name, $unitid, $cost) = split(',', $ingred);
echo "$code\t$name\t$unitid\t$cost\n";
$sql="INSERT INTO item (code, name, unitid, cost)
VALUES ('$code','$name','$unitid','$cost')";
if (!mysql_query($sql,$con)) {
die('Error: ' . mysql_error());
}
echo "1 record added\n";
}
fclose($file);

?>
C:\Documents and Settings\Tim\My Documents\DB\Recipe\php>php rawingredload.php
EGG Egg 2 15

1 record added
FLOUR Flour 3 0.01

1 record added
MILK Milk 1 75
1 record added


C:\Documents and Settings\Tim\My Documents\DB\Recipe\php>

mysql> select * from item;
+-------+-------+--------+-------+
| code | name | unitid | cost |
+-------+-------+--------+-------+
| EGG | Egg | 2 | 15.00 |
| FLOUR | Flour | 3 | 0.01 |
| MILK | Milk | 1 | 75.00 |
+-------+-------+--------+-------+
3 rows in set (0.00 sec)

mysql>

Wednesday, July 22, 2009

Compare the Perl with PHP

I had this idea that I should write exactly the same code in PHP that I had just written in Perl, just to compare.

<?php
function openMyDB()
{
include("DBInfo.inc");
$con = mysql_connect($mydbhost,$mydbuser,$mydbpassword);
if (!$con) {
die('Could not connect: ' . mysql_error());
}
mysql_select_db("cddb", $con);
return $con;
}

$con = openMyDB();

mysql_select_db("cddb", $con);

print "Name\t\tTitle\n";
$result = mysql_query("SELECT artist.name, cd.title FROM artist, cdtable AS cd WHERE artist.id = cd.artid");
while ($row = mysql_fetch_array($result)) {
echo $row['name'] . "\t" . $row['title'] . "\n" ;
}
mysql_free_result($result);
mysql_close($con);
?>

D:\Database\CDDB\Reports>php CDRep.php
Name Title
Phatfish Guaranteed
Phatfish 15
Bethany Dillon Waking Up
Bethany Dillon Bethany Dillon
Bethany Dillon Imagination
Nick Drake Made To Love Magic
Nick Drake Five Leaves Left
Nick Drake Bryter Layter

D:\Database\CDDB\Reports>

So, which do you prefer?

Please note that:

include("DBInfo.inc");

is my own code (for what it's worth) that we've seen in previous examples.

D:\Database\CDDB\Reports>more DBInfo.inc
<?php
$mydbhost="localhost";
$mydbuser="myuser";
$mydbpassword="mypassword";
?>

D:\Database\CDDB\Reports>

A Perl Script that accesses MySQL

Last year I posted a simple Perl script that accesses a PostgreSQL database using the DBI connector. I decided that I should do the same for MySQL. I'm using Active State Perl on Windows XP. Amusingly, this version does not include the MySQL connectors as default, but it's easy enough to add using perl ppm (Perl's Perl Package Manager).

I want a simple script that lists out artists and their CDs in my database.


#!/usr/bin/perl
use strict;
use DBI();
my $dbh1 = DBI->connect("DBI:mysql:database=cddb;host=localhost","uuuu", "xxxxxx", { raiseError => 1, AutoCommit => 0 })
|| die "Database connection not made: $DBI::errstr";
my $sql = "SELECT artist.name, cd.title FROM artist, cdtable AS cd WHERE artist.id = cd.artid";
my $sth = $dbh1->prepare($sql);

$sth->execute();

my( $name, $title );
$sth->bind_columns( \$name, \$title );

print "Name\t\tTitle\n";
while( $sth->fetch() ) {
print "$name\t$title\n";
}
$dbh1->disconnect() if($dbh1)

D:\Documents and Settings\timj\perl>c:\Perl\bin\perl cd.pl
Name Title
Phatfish Guaranteed
Phatfish 15
Bethany Dillon Waking Up
Bethany Dillon Bethany Dillon
Bethany Dillon Imagination
Nick Drake Made To Love Magic
Nick Drake Five Leaves Left
Nick Drake Bryter Layter

D:\Documents and Settings\timj\perl>


That's about as simple as it get, I'm sure that there is far more to it than that, but this will do for now!
Tim

Monday, July 20, 2009

Creating a View

Let's pretend that we regularly want to be able to select all our publishers with the CDs we have on file for them. We saw in the last post that the SQL is:

SELECT c.title, p.name FROM cdtable c RIGHT OUTER JOIN publisher p ON c.pubid = p.id;

We can create a view to do this:

mysql> CREATE VIEW V_Publisher as
-> SELECT c.title, p.name FROM cdtable c RIGHT OUTER JOIN publisher p ON c.pubid = p.id;
Query OK, 0 rows affected (0.00 sec)

mysql>
mysql> describe V_Publisher;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| title | varchar(50) | YES | | NULL | |
| name | varchar(20) | NO | | NULL | |
+-------+-------------+------+-----+---------+-------+
2 rows in set (0.00 sec)

mysql>


We can the simply select from this view:

mysql> select * from V_Publisher;
+--------------------+----------------+
| title | name |
+--------------------+----------------+
| Waking Up | EMI |
| Bethany Dillon | EMI |
| Imagination | EMI |
| Guaranteed | SONY |
| 15 | SONY |
| Made To Love Magic | Island Records |
| Five Leaves Left | Island Records |
| Bryter Layter | Island Records |
| NULL | Apple Records |
+--------------------+----------------+
9 rows in set (0.00 sec)

mysql>

Inner and Outer Joins

To Demonstrate inner and outer joins, the first thing I need to do is to add some data in to the database that isn't referenced by another table. So here I'm adding a publisher that I have no CDs for. Alas, no Beatles in my collection!


mysql> insert into publisher (name) values ('Apple Records');
Query OK, 1 row affected (0.00 sec)

A normal join would not tell us about Apple Records, as there is nothing to report.

mysql> SELECT c.title, p.name FROM cdtable c, publisher p
-> where c.pubid = p.id;
+--------------------+----------------+
| title | name |
+--------------------+----------------+
| Waking Up | EMI |
| Bethany Dillon | EMI |
| Imagination | EMI |
| Guaranteed | SONY |
| 15 | SONY |
| Made To Love Magic | Island Records |
| Five Leaves Left | Island Records |
| Bryter Layter | Island Records |
+--------------------+----------------+
8 rows in set (0.00 sec)

mysql>

We get the same results when we code an Inner Join,

mysql> SELECT c.title, p.name FROM cdtable c INNER JOIN publisher p ON c.pubid = p.id;
+--------------------+----------------+
| title | name |
+--------------------+----------------+
| Waking Up | EMI |
| Bethany Dillon | EMI |
| Imagination | EMI |
| Guaranteed | SONY |
| 15 | SONY |
| Made To Love Magic | Island Records |
| Five Leaves Left | Island Records |
| Bryter Layter | Island Records |
+--------------------+----------------+
8 rows in set (0.00 sec)

mysql>

When we code a righthand outer join on Publisher, we see Apple, but with no CDs of course.

mysql> SELECT c.title, p.name FROM cdtable c RIGHT OUTER JOIN publisher p ON c.pubid = p.id;
+--------------------+----------------+
| title | name |
+--------------------+----------------+
| Waking Up | EMI |
| Bethany Dillon | EMI |
| Imagination | EMI |
| Guaranteed | SONY |
| 15 | SONY |
| Made To Love Magic | Island Records |
| Five Leaves Left | Island Records |
| Bryter Layter | Island Records |
| NULL | Apple Records |
+--------------------+----------------+
9 rows in set (0.00 sec)

mysql>

Monday, July 13, 2009

A Simple Correlated Subquery Example

A correlated subquery is a subquery that needs to refer to the data returned in the main query. They can seem pretty scary at first, but they're fairly simply once you get the hang of it.

In this example, I want to list all my artists, along with the most recent CD they have released. To do this, I have to select all the artists and their CDs, and then use a sub query to select the latested (MAX) date published for each artist.

With a corrolated subquery you can (usually) run the query without the subquery to see all the results, like this:


mysql> SELECT artist.name, cd.title, cd.yearpublished
-> FROM artist, cdtable AS cd
-> WHERE artist.id = cd.artid
-> ;
+----------------+--------------------+---------------+
| name | title | yearpublished |
+----------------+--------------------+---------------+
| Phatfish | Guaranteed | 2007 |
| Phatfish | 15 | 2008 |
| Bethany Dillon | Waking Up | 2007 |
| Bethany Dillon | Bethany Dillon | 2004 |
| Bethany Dillon | Imagination | 2005 |
| Nick Drake | Made To Love Magic | 2004 |
| Nick Drake | Five Leaves Left | 2004 |
| Nick Drake | Bryter Layter | 2000 |
+----------------+--------------------+---------------+
8 rows in set (0.00 sec)



This lists out all the CDs for each artist, so we need to tell the query to only list the most recent:


mysql> SELECT artist.name, cd.title, cd.yearpublished
-> FROM artist, cdtable AS cd
-> WHERE artist.id = cd.artid
-> AND cd.yearpublished =
-> (SELECT MAX(yearpublished)
-> FROM cdtable
-> WHERE cdtable.artid = cd.artid)
-> ;
+----------------+--------------------+---------------+
| name | title | yearpublished |
+----------------+--------------------+---------------+
| Phatfish | 15 | 2008 |
| Bethany Dillon | Waking Up | 2007 |
| Nick Drake | Made To Love Magic | 2004 |
| Nick Drake | Five Leaves Left | 2004 |
+----------------+--------------------+---------------+
4 rows in set (0.00 sec)

mysql>


Notice that we have two rows returned for Nick Drake. This is because two CDs match the condition. Of course, Nick Drake wasn't publishing anything in 2004, unfortunately. Had I used the original vinyl publication date, it would have been different.

Tuesday, June 16, 2009

Improving the menus

It would be great if one single program were to be flexible enough to handle all the menus. After all, they are in a single database table.

The idea is to pass the program a parameter telling it which menu to show. It will test, and if no menu has been requested, it will show the main menu.

I'll also move tings around to put the database open and closing in to functions, and the head and tail printing as well.

One thing to bear in mind is that the menu header is currently hard code, and this will need to be pulled from the database.

So, create a table to store the menu details!

We'll need a primary index on the menus table:


CREATE TABLE menus (
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(20),
title VARCHAR(40),
description VARCHAR(80),
last_updated TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP (),
PRIMARY KEY (id)
) ENGINE=INNODB;

CREATE TABLE cddb.menuitems (
menuid INT NOT NULL,
itemno INT NOT NULL,
itemtext VARCHAR(50) NOT NULL,
itemlink VARCHAR(150) NOT NULL,
last_updated TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP (),
FOREIGN KEY (menuid) REFERENCES menus(id) ON DELETE CASCADE
) ENGINE=INNODB;



And repopulate:



INSERT INTO cddb.menus (name, title, description) values ('mainmenu','Main Menu', 'Welcome to my CD Collection');
INSERT INTO cddb.menus (name, title, description) values ('listmenu','List Menu', 'List Details of my CD Collection');

INSERT INTO cddb.menuitems (menuid, itemno, itemtext, itemlink)
values (1,1,'Data Entry','mainmenu.php?menuname=dataentrymenu');

INSERT INTO cddb.menuitems (menuid, itemno, itemtext, itemlink)
values (1,2,'Data Lists','mainmenu.php?menuname=listmenu');

INSERT INTO cddb.menuitems (menuid, itemno, itemtext, itemlink)
values (1,3,'Reports','mainmenu.php?menuname=reportsmenu');

INSERT INTO cddb.menuitems (menuid, itemno, itemtext, itemlink)
values (2,1,'List Publisher','publishers.php');
INSERT INTO cddb.menuitems (menuid, itemno, itemtext, itemlink)
values (2,2,'List Artists','artists.php');
INSERT INTO cddb.menuitems (menuid, itemno, itemtext, itemlink)
values (2,3,'CDs by Artists','lcdsbyartist.php');
INSERT INTO cddb.menuitems (menuid, itemno, itemtext, itemlink)
values (2,4,'Tracks by CD','track.php');



Finally - rewrite the menu PHP


<?php
function openMyDB()
{
include("DBInfo.inc");
$con = mysql_connect($mydbhost,$mydbuser,$mydbpassword);
if (!$con) {
die('Could not connect: ' . mysql_error());
}
mysql_select_db("cddb", $con);
return $con;
}

function closeMyDB($con)
{
mysql_close($con);
}

function writeHead($MenuName)
{
$MyQuery = "SELECT title, description FROM menus m ";
$MyQuery = $MyQuery . "WHERE m.name = '" . $MenuName . "'";

$result = mysql_query($MyQuery);
$row = mysql_fetch_array($result);

echo "<html>\n<head>\n<title>" . $row['title'] . "</title>\n</head>\n";
echo "<body>\n";
echo "<h1>" . $row['description'] . "</h1>\n";
mysql_free_result($result);
}

function writeTail()
{
echo "</body>\n</html>";
}

// Main Program starts here

$mycon=openMyDB();

if ( empty($_GET['menuname']))
$MyMenuName='mainmenu';
else
$MyMenuName=$_GET['menuname'];


writehead($MyMenuName);
echo "<ol>\n";

$MyQuery = "SELECT itemtext, itemlink, itemno FROM menus m, menuitems mi ";
$MyQuery = $MyQuery . "WHERE m.name = '" . $MyMenuName . "' ";
$MyQuery = $MyQuery . "and m.id=mi.menuid order by itemno";

$result = mysql_query($MyQuery);
while ($row = mysql_fetch_array($result)) {
echo "<li>\n";
echo "<a href=\"" . $row['itemlink'] . "\">";
echo $row['itemtext'] . "</a>";
echo "\n</li>\n";
}
mysql_free_result($result);
echo "</ol>";
writetail();
closeMyDB($mycon);

?>

Friday, June 5, 2009

My first form in PHP

This form checks to see if it has been past a parameter. If not it displays a pull-down list of the artists on file. If it has been passed a value, it uses that value to pull out the CDs on file for an artist. That way the same piece of code is used twice.

I've tried to put things in functions to make it a little more tidy.


<?php
function writeHead()
{
echo "<html>\n<head>\n<title>CDs By Artists</title>\n</head>\n";
echo "<body>\n";
echo "<h1>CDs By Artists</h1>\n";
}

function writeTail()
{
echo "</body>\n</html>";
}


function writeMenu()
{
echo "<form action=\"" . $_SERVER['PHP_SELF'] . "\" method=\"post\">\n";
echo "<select name=\"artid\">\n";

$result = mysql_query("SELECT id, name FROM artist order by name");
while ($row = mysql_fetch_array($result)) {
echo "<option value=\"";
echo $row['id'] . "\">".$row['name'];
echo "</option>\n";
}
mysql_free_result($result);
echo "</select>\n";
echo "<input type=\"submit\" value=\"Submit\">";
echo "</form>\n";
}

function writeDetails($artid)
{
$result = mysql_query("SELECT name FROM artist where id = " . $artid . ";");
$row = mysql_fetch_array($result);
echo "<h3>" . $row['name'] . "</h3>\n";
mysql_free_result($result);
$result = mysql_query("SELECT title, yearpublished from cdtable where artid = " . $artid . ";");
echo "<table>\n<tr><th>Year Published</th><th>Title</th></tr>\n";
while ($row = mysql_fetch_array($result)) {
echo "<tr><td>" . $row['yearpublished'] . "</td>";
echo "<td>" . $row[title] . "</td></tr>\n";
}
mysql_free_result($result);
echo "</table>\n";
}


#
## Main Body
#
$con = mysql_connect("localhost","timj","xxxxxx");
if (!$con) {
die('Could not connect: ' . mysql_error());
}
mysql_select_db("cddb", $con);

writehead();
if ($_POST['artid']=="") writeMenu();
else writeDetails($_POST['artid']);

writetail();

mysql_close($con);
?>