Into to DBI with DBD::Oracle

download Into to DBI with DBD::Oracle

If you can't read please download the document

description

A quick tutorial to get you up and running with DBI and DBD::Oracle, Will work for any DBD for that matter

Transcript of Into to DBI with DBD::Oracle

Introduction to DBI

By John Scoles

The Pythian Group

I love deadlines. They make such funny noises as they swosh by.

Overview

Platform

Oracle XE

DDI

DBD::Oracle

Time flies like an arrow

Fruit flies like a banana

Overview

Topics

What are DBI and DBD?

Connecting

Error Handling

Basic SQL Examples

Advanced SQL Examples

What is DBI?

DBI is an interface (API)

It defines a set of standard

Methods

Variables, and

Conventions

Provides a consistent interface between Perl and almost any database

Conceived by Tim Bunce in 1994, It is now the de facto standard for interfacing databases with the Perl language.

Think of it as the glue layer that sticks Perl to your database

What is a DBD

A DBD is a specific implementation of a DBI against a specific database interface

DBD::Oracle

OCI (Oracle Call Interface)

DBD::ODBC

ODBC

Handles

DBI uses handles (objects) to do its work.

There are three of them

Driver handles

Database handles and

Statement handles.

Driver Handles

Created when DBI is initialized

Not normally used directly by programmers

Independent and encapsulated

Load many driver handles at one time

Guaranteed that they will not interfere with each other.

Transferring data from Informix into Oracle in a single Perl script that uses two Driver Handles at the same time

Database Handles

Encapsulation of a single database connection

Always the child of a Driver handle

Thread-safe

A Database handle will not leak into other handles

By convention noted as $dbh in Perl scripts.

Statement

Encapsulates a single SQL statement

Always the child of a Database Handle

Thread-safe

data will not leak out into other handles.

By convention noted as $sth in Perl scripts.

Connecting and Disconnecting

DBI has its own simple syntax for specifying the DB Driver.

''dbi''+'':''+''driver-name''+'':''

dbi:Oracle:

So a connection call could look like any of these:

$dbh = DBI->connect(dbi:Oracle:, 'user id', 'password');

$dbh = DBI->connect(dbi:Oracle:XE, 'user id', 'password');

$dbh = DBI->connect(dbi:Oracle:, 'user id@XE', 'password');

$dbh = DBI->connect(dbi:Oracle: 'user id@localhost/XE', 'password');

Connection Attributes

At connection time you can set both DBD and DBI attributes.

Some of these include

control error handling

data commitment

debugging info

Simply add them as a hash at the end of the connection command

$dbh = DBI->connect(dbi:Oracle:, 'system@XE','system', {RaiseError => 1, PrintError =>0});

Disconnecting

Not required

DBI has two ways to disconnect.

$dbh->disconnect();

or

$dbh->DESTROY();

Always good form to disconnect the statement handle as well with.

$sth->finish();

Errors

DBI reports all errors to a single variable on the database handle.

$dbh->errstr

Once connected the vast majority of errors will be some sort of SQL error

errstr will show you

the error message that DB generated

the ORA number

Two Options

PrintError

When on, all errors are sent as warnings to the errstr value.

RaiseError

When on, all errors are raised as exceptions

Both can be active at the same time

or

tack Perls or die syntax on the end of your command

$dbh = DBI->connect(dbi:Oracle:, system@XE, system) or die $dbh->errstr;

First Query

We start with

use DBI;

use strict;

No need for ''use DBD::Oracle'' as all the drivers will be there to start

Next, of course, we need to define our variables.

use vars qw($dbh $sth);

Connect to the DB

$dbh = DBI->connect('dbi:Oracle:XE', 'hr', 'hr');

Now a statement handle for our SQL with the prepare() method

$sth = $dbh->prepare('Select city from locations');

Then we call the execute() method

$sth->execute();

Now fetch the data using the fetchrow() method and print it.

while (my ($city) = $sth->fetchrow(){

print " $city \n";

}

Finally the finish() and disconnect()

$sth->finish();

$dbh->disconnect();

The DBI Pattern

Prepare

Set up the SQL

Execute

Run the SQL on the DB

Fetch

Get the results into Perl

Do

ManyQuries return no rows

"UPDATE locations SET city = 'Ottawa' WHERE location_id = 1800"

DBI handels this with the do method

$rows_affected = $dbh->do("UPDATE locations SET city = 'Ottawa' WHERE location_id = 1800");

print "Rows Affected = $rows_affected\n";

No need for a statement handle

Added bonus DBD::Oracle returns the # of rows affected

Binding Data

In-line binding

$my $loc_id = 1800;

$sql = "UPDATE locations SET city = 'Ottawa' WHERE location_id = $loc_id";

$dbh->do($sql);

Simple and quick when using do method but ....

my $loc_id = 'IT'

$sql = "UPDATE locations SET city = 'Ottawa' WHERE location_id = $loc_id";

$dbh->do($sql);

Almost identical, but with nasty single quotes. So to make the SQL work we have to use.

$sql = "UPDATE locations SET city = 'Ottawa' WHERE location_id = '$loc_id'";

Caveats on In-Line code

You have to write you own code to account for '

No optimization. In Oracle the Optimizer may not work correctly which will slow down your SQL

Opens your system to SQL injection attacks.

Simple Placeholders

? is as simple as it gets

$sql = 'SELECT city FROM locations WHERE location_id = ?';

Use with the database handles prepare() method to get a statement handle.

$sth = $dbh->prepare('SELECT city FROM location WHERE location_id = ?');

Pass the parameter to the statement handle with the execute() method

$loc_id=1800;

$sth->execute($loc_id);

Benefits

No worries about single quotes

If $loc_id contained a ' , i.e. (IN'T), DBI would automatically fix this for us when you execute the SQL.

Execution plan reused

Other queries like this one will run faster

We can use as many ? As we want

SELECT city FROM locations WHERE location_id = ? or location = ? or location =?

$sth->execute($loc_id,22,'IT');

Just remember they always run in order

Advanced Placeholders

Given this SQL

SELECT city FROM locations

WHERE

(city = ? or city = ?)

AND country_id ?

AND state_province ?

This

$sth->execute('Rome', 'New York', 'IT', 'New York');

Will return a different set of records than this

$sth->execute('Rome', 'New York', 'New York', 'IT');

How about this

$sql = 'INSERT INTO table_1

values(field_1, field_2, field_3, field_4, month)

select field_1, field_2, field_3, field_4,

add_months(sysdate, ?) FROM locations

WHERE

(city = ? or city = ?)

AND country_id ?

AND state_province ?';

$sth = $dbh->prepare($sql);

$sth->execute($var_133,$var_133, $var_2, $var_133, $var_32);

6 months from now you might not remember which param is which

bind_param

With the bind_param we can link each of the ?'s to a numbered position like this

$sth->bind_param(1, "Rome");

$sth->bind_param(2, "New York");

$sth->bind_param(3, "US");

$sth->bind_param(4, "New York");

To make code more more readable change the SQL to use named placeholders

$sql = 'SELECT city FROM locations

WHERE

(city = :p_city_1 or city = :p_city_2)

AND country_id = :p_country_id

AND state_province p_state_province';

$sth = $dbh->prepare($sql);

$sth->bind_param(":p_city_1", "Rome");

$sth->bind_param(":p_city_2", "New York");

$sth->bind_param(":p_county_id", "US");

$sth->bind_param(":p_state_province", "New York");

Multiple Substitutions

Named place holders are great for this sort of SQL

$sql = "SELECT to_char(add_months(sysdate, :p_add_months),'Month yyyy'),

add_months(sysdate, :p_add_months),

Sysdate

FROM dual";

$sth->bind_param(":p_add_months", $months_to_add);

Only one bind needed.

Fetchrow

The fetchrow method works by getting the current row from the record set (cursor) and binding the values from the record set into specific Perl variables.

$sql = 'SELECT city, state_province, country_id FROM locations

WHERE city = :p_city';

$sth = $dbh->prepare($sql);

$sth->bind_param(':p_city', $city);

$sth->execute();

my ($r_city, $r_state) = $sth->fetchrow();

print "My city is $r_city. \n";

print "My state is $r_state.\n";

Fetchrow_array

Fetchrow() quickly gets tedious when returning a large number of fields.

The fetchrow_array() method can be used to pass the entire row into Perl variables

my ($r_city, $r_state, $r_country) = $sth->fetchrow_array();

Or directly into a Perl array like this

my (@a_row) = $sth->fetchrow_array();

Note: always returns a 0-based array.

Atomic Fetching

Many times you only need to get a single row of data,

DBI has atomic fetching, which compresses the data-fetching cycle into just a single method.

selectrow_array(), and

selectrow_arrayref()

my ($r_country_id, $r_country_name) =

$dbh->selectrow_array("SELECT country_id, country_name

FROM countries

WHERE country_id = '$country'");

Batch Fetching

One-way row-fetching is suitable for most work

Occasionally you will need to iterate a dataset more than once

DBI provides Batch fetching methods

selectall_arrayref()

fetchall_arrayref()

selectall_arrayref

Is the Batch version of selectrow_arrayref()

Most useful when you want to get the data from static SQL more that once in a program

my $countries = $dbh->selectall_arrayref("SELECT country_id,

country_name

FROM countries

ORDER BY country_name");

foreach my $row (@$countries){

print "My country_id is $row->[0], ";

print "my country name is $row->[1].\n";

}

foreach my $row (@$countries){

fetchall_arrayref()

The statement handles batch feed method

Works in one of three different ways

Without arguments,

my $employees = $sth->fetchall_arrayref();

Retruns all of the fields

With an array of field #s

my $employees = $sth->fetchall_arrayref([0,2,1,5]);

Returns the selected fields

With a hash of column names

fetchall_arrayref({

employee_id=>1,

hire_date=>1,

first_name=>1,

last_name=>1,})

Returns the named Fields

Transactions

DBI supports the age old ACID module for DB transactions

This is DBD dependant though

DBD::Oracle has it some others may not

By default transactions are off in DBI.

Set the AutoCommit attribute of the database handle to off to enable them

$dbh = DBI->connect('dbi:Oracle:', 'hr@localhost/XE',

'hr', {AutoCommit => 0});

Rollback and Commit

Works in the same manner as PLSQL program.

$last_name = "Cambrault";

$salary = 200000;

$sql = "UPDATE employees SET salary = :p_salary

WHERE last_name = :p_last_name";

$sth = $dbh->prepare($sql);

$sth->bind_param(':p_salary', $salary);

$sth->bind_param(':p_last_name', $last_name);

$rows_affected = $sth->execute();

if ($rows_affected > 1){

$dbh->rollback();

print "There are $rows_affected employees with ";

print "the last name $last_name. Transaction cancelled!\n";

}else{

$dbh->commit();

print "$last_name's salary is now $salary\n";

}

PL/SQL Procedures and Functions

DBI allows you to call PL/SQL programs and also get the data retrieved by them.

Wrap the call between the BEGIN and END PL/SQL keywords.

$sth = $dbh->prepare("BEGIN fire_employee(:p_employee_id); END;");

$sth->bind_param(':p_employee_id', 101);

$sth->execute();

Getting a returned value

You will have to use the statement handles bind_param_inout() method

$in_employee_id = 101;

my $hire_date;

$sth = $dbh->prepare("BEGIN get_startdate(:p_employee_id, :p_start_date);

END;");

$sth->bind_param(':p_employee_id', 101);

$sth->bind_param_inout("", \$hire_date, "SQL_NUMERIC");

$sth->execute();

When run, the $hire_date variable will automatically be updated to the value returned by the procedure

PL/SQL Functions

Similar to calling a PL/SQL Procuderes

Placeholder is used to store the returned value

my $big_seller;

$sth = $dbh->prepare("BEGIN :p_big_seller := get_top_seller() END;");

$sth->bind_param_inout("", \$big_seller, "SQL_NUMERIC");

$sth->execute();

SQL_NUMERIC??

This was added to each of the bind_param_inout() calls.

Why?

Perl is a loosely-typed language, so we have to explicitly tell Perl -- and the DBD::Oracle driver as well -- what type of variable is being passed to Oracle from Perl, and from Oracle and back into Perl.

PL/SQL CURSORS

DBD::Oracle and some other DBDs are able to use PL/SQL cursors

Remember

have to be bound to a specific DBD::Oracle datatype constant called an ORA_RSET.

This constant and the other Oracle-specific ones have to be explicitly imported into your program by the following command.

use DBD::Oracle qw(:ora_types);

PL/SQL CURSORS

Below is an example of binding to a cursor

$sth1 = $dbh->prepare(q{

BEGIN OPEN :p_cursor FOR

SELECT first_name, last_name

FROM employees ORDER BY last_name;

END;

});

$sth1->bind_param_inout(":cursor", \$sth2, 0, { ora_type => ORA_RSET });

$sth1->execute();

while (my @row = $sth2->fetchrow_array)

{

print "My name is $row[0] $row[1].\n";

}

Large Objects (CLOBs and BLOBs)

Use the statement handles bind_param() method to tell DBD::Oracle what sort of LOB to expect.

$sth->bind_param(:p_big_desc, $big_desc, {ora_type=>ORA_CLOB});

Or if you are dealing with a BLOB:

$sth->bind_param(:p_pic, $a_pic, {ora_type=>ORA_BLOB});

Caveats

When doing an insert or update with more than one CLOB or BLOB field, you will have to tell DBD::Oracle which field corresponds to which datatype.

Add the ora_field=> attribute to the bind call, with the corresponding table field name for the bind.

$sth->bind_param(:p_caption, $caption,

{ora_type=>ORA_CLOB, ora_field=>'caption}

);

$sth->bind_param(:p_picture, $a_pic,

{ora_type=>ORA_BLOB, ora_field=>'picture}

);

$sth->bind_param(:p_comment, $coms,

{ora_type=>ORA_CLOB, ora_field=>'comment}

);

Maximum size

To avert any problems you will have to set a few attributes on the database handle

LongTruncOk.

set this to false, so that any fetch of a LOB variable that is too big will cause the fetch to fail.

LongReadLen

Set this value to the maximum size in bytes you want your LOBs to be

XML

Best practice

Let Oracle process the data into XML format, retrieve it as a CLOB and then process it with one of Perls many XML modules.

PL/SQL DBMS_XMLGEN returns the XML as a CLOB column, which DBD::Oracle can easily handle

$dbh = DBI->connect('dbi:Oracle:', 'hr@localhost/XE', 'hr');

$dbh->{LongTruncOk} = 0;

$dbh->{LongReadLen} = 2*1024*1024; # 2MB limit for displaying LOG

$sql = "SELECT dbms_xmlgen.getxml('

SELECT last_name, first_name

FROM employees

') xml

FROM dual";

$sth = $dbh->prepare($sql);

$sth->execute();

my ($r_xml) = $sth->fetchrow();

# let Perl play with $r_xml

Click to edit the title text format

Click to edit the outline text format

Second Outline Level

Third Outline Level

Fourth Outline Level

Fifth Outline Level

Sixth Outline Level

Seventh Outline Level

Eighth Outline Level

Ninth Outline Level