|
How do I connect to MySQL using a DBI (ODBC) Connection?
Here's a typical program. When you run it, it waits for you to type a last name. Then it searches the database for people with that last name and prints out the full name and ID number for each person it finds. For example: Enter name> Noether
118: Emmy Noether
Enter name> Smith
3: Mark Smith
28: Jeff Smith
Enter name> Snonkopus
No names matched `Snonkopus'.
Enter name> ^D
Here is the code: use DBI;
my $dbh = DBI->connect('DBI:Oracle:payroll')
or die "Couldn't connect to database: " . DBI->errstr;
my $sth = $dbh->prepare('SELECT * FROM people WHERE lastname = ?')
or die "Couldn't prepare statement: " . $dbh->errstr;
print "Enter name> ";
while ($lastname = <>) { # Read input from the user
my @data;
chomp $lastname;
$sth->execute($lastname) # Execute the query
or die "Couldn't execute statement: " . $sth->errstr;
# Read the matching records and print them out
while (@data = $sth->fetchrow_array()) {
my $firstname = $data[1];
my $id = $data[2];
print "\t$id: $firstname $lastname\n";
}
if ($sth->rows == 0) {
print "No names matched `$lastname'.\n\n";
}
$sth->finish;
print "\n";
print "Enter name> ";
}
$dbh->disconnect;
use DBI; This loads in the my $dbh = DBI->connect('DBI:Oracle:payroll');
or die "Couldn't connect to database: " . DBI->errstr;
The
my $dbh = DBI->connect('DBI:Oracle:payroll', 'username', 'password')
or die "Couldn't connect to database: " . DBI->errstr;
If If my $sth = $dbh->prepare('SELECT * FROM people WHERE lastname = ?')
or die "Couldn't prepare statement: " . $dbh->errstr;
The The print "Enter name> "; Here we just print a prompt for the user. while ($lastname = <>) { # Read input from the user
...
}
This loop will repeat over and over again as long as the user enters a last
name. If they type a blank line, it will exit. The Perl my @data; This declares a variable to hold the data that we will get back from the database. chomp $lastname; This trims the newline character off the end of the user's input. $sth->execute($lastname) # Execute the query
or die "Couldn't execute statement: " . $sth->errstr;
while (@data = $sth->fetchrow_array()) {
...
}
Each time we call my $firstname = $data[1];
my $id = $data[2];
These lines extract the first name and the ID number from the record data. print "\t$id: $firstname $lastname\n"; This prints out the result. if ($sth->rows == 0) {
print "No names matched `$lastname'.\n\n";
}
The $sth->finish;
print "\n";
print "Enter name> ";
Once we're done reporting about the result of the query, we print another
prompt so that the user can enter another name. $dbh->disconnect; When the user has finished querying the database, they type a blank line and
the main Cached QueriesHere's a function which looks up someone in the example table, given their ID number, and returns their age: sub age_by_id {
# Arguments: database handle, person ID number
my ($dbh, $id) = @_;
my $sth = $dbh->prepare('SELECT age FROM people WHERE id = ?')
or die "Couldn't prepare statement: " . $dbh->errstr;
$sth->execute($id)
or die "Couldn't execute statement: " . $sth->errstr;
my ($age) = $sth->fetchrow_array();
return $age;
}
It prepares the query, executes it, and retrieves the result. There's a problem here though. Even though the function works correctly, it's inefficient. Every time it's called, it prepares a new query. Typically, preparing a query is a relatively expensive operation. For example, the database engine may parse and understand the SQL and translate it into an internal format. Since the query is the same every time, it's wasteful to throw away this work when the function returns. Here's one solution: { my $sth;
sub age_by_id {
# Arguments: database handle, person ID number
my ($dbh, $id) = @_;
if (! defined $sth) {
$sth = $dbh->prepare('SELECT age FROM people WHERE id = ?')
or die "Couldn't prepare statement: " . $dbh->errstr;
}
$sth->execute($id)
or die "Couldn't execute statement: " . $sth->errstr;
my ($age) = $sth->fetchrow_array();
return $age;
}
}
There are two big changes to this function from the previous version. First,
the Second, the Here's another solution: sub age_by_id {
# Arguments: database handle, person ID number
my ($dbh, $id) = @_;
my $sth = $dbh->prepare_cached('SELECT age FROM people WHERE id = ?')
or die "Couldn't prepare statement: " . $dbh->errstr;
$sth->execute($id)
or die "Couldn't execute statement: " . $sth->errstr;
my ($age) = $sth->fetchrow_array();
return $age;
}
Here the only change to to replace TransactionsMany databases support transactions. This means that you can make a
whole bunch of queries which would modify the databases, but none of the changes
are actually made. Then at the end you issue the special SQL query
As an example of this, consider a function to add a new employee to a
database. The database has a table called FIRSTNAME LASTNAME DEPARTMENT_ID
Gauss Karl 17
Smith Mark 19
Noether Emmy 17
Smith Jeff 666
Hamilton William 17
and a table called ID NAME NUM_MEMBERS
17 Mathematics 3
666 Legal 1
19 Grounds Crew 1
The mathematics department is department #17 and has three members: Karl Gauss, Emmy Noether, and William Hamilton. Here's our first cut at a function to insert a new employee. It will return true or false depending on whether or not it was successful: sub new_employee {
# Arguments: database handle; first and last names of new employee;
# department ID number for new employee's work assignment
my ($dbh, $first, $last, $department) = @_;
my ($insert_handle, $update_handle);
my $insert_handle =
$dbh->prepare_cached('INSERT INTO employees VALUES (?,?,?)');
my $update_handle =
$dbh->prepare_cached('UPDATE departments
SET num_members = num_members + 1
WHERE id = ?');
die "Couldn't prepare queries; aborting"
unless defined $insert_handle && defined $update_handle;
$insert_handle->execute($first, $last, $department) or return 0;
$update_handle->execute($department) or return 0;
return 1; # Success
}
We create two handles, one for an There's a big problem here: Suppose, for some reason, the second query fails.
Our function returns a failure code, but it's too late, it has already added the
employee to the The solution is to make both updates part of the same transaction. Most databases will do this automatically, but without an explicit instruction about whether or not to commit the changes, some databases will commit the changes when we disconnect from the database, and others will roll them back. We should specify the behavior explicitly. Typically, no changes will actually be made to the database until we issue a
sub new_employee {
# Arguments: database handle; first and last names of new employee;
# department ID number for new employee's work assignment
my ($dbh, $first, $last, $department) = @_;
my ($insert_handle, $update_handle);
my $insert_handle =
$dbh->prepare_cached('INSERT INTO employees VALUES (?,?,?)');
my $update_handle =
$dbh->prepare_cached('UPDATE departments
SET num_members = num_members + 1
WHERE id = ?');
die "Couldn't prepare queries; aborting"
unless defined $insert_handle && defined $update_handle;
my $success = 1;
$success &&= $insert_handle->execute($first, $last, $department);
$success &&= $update_handle->execute($department);
my $result = ($success ? $dbh->commit : $dbh->rollback);
unless ($result) {
die "Couldn't finish transaction: " . $dbh->errstr
}
return $success;
}
We perform both queries, and record in The problem of concurrent database access is also solved by transactions.
Suppose that queries were executed immediately, and that some other program came
along and examined the database after our insert but before our update. It would
see inconsistent data in the database, even if our update would eventually have
succeeded. But with transactions, all the changes happen simultaneously when we
do the
|
| References |
|
• A complete list of DBD modules are available here |
People go to all sorts of trouble to get around these problems with
interpolation. They write a function that puts the last name in quotes and then
backslashes any apostrophes that appear in it. Then it breaks because they
forgot to backslash backslashes. Then they make their escape function better.
Then their code is a big message because they are calling the backslashing
function every other line. They put a lot of work into it the backslashing
function, and it was all for nothing, because the whole problem is solved by
just putting a ? into the query, like this
SELECT * FROM people WHERE lastname = ?