php 调用oracle存储过程代码

作者:袖梨 2022-06-24

/*
速度和效率。使用存储过程的速度显然更快。在效率上,如果应用一次需要做一系列sql操作,则需要往返于php与oracle,不如把该应用直接放到数据库教程方以减少往返次数,增加效率。但是在internet应用上,速度是极度重要的,所以很有必要使用存储过程
*/

//建立一个test表

create table test (

id number(16) not null,

name varchar2(30) not null,

primary key (id)

);

//插入一条数据

insert into test values (5, 'php/index.html>php_book');

//建立一个存储过程

create or replace procedure proc_test (

p_id in out number,

p_name out varchar2

) as

begin

select name into p_name

from test

where id = 5;

end proc_test;

/

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

php/index.html>php代码:--------------------------------------------------------------------------------


//建立数据库连接

$user = "scott"; //数据库用户名

$password = "tiger"; //密码

$conn_str = "tnsname"; //连接串(cstr : connection_string)

$remote = true //是否远程连接

 

 

if ($remote) {

$conn = ocilogon($user, $password, $conn_str);

}

else {

$conn = ocilogon($user, $password);

}

//设定绑定

$id = 5; //准备用以绑定的php变量 id

$name = ""; //准备用以绑定的php变量 name

/** 调用存储过程的sql语句(sql_sp : sql_storeprocedure)

* 语法:

* begin 存储过程名([[:]参数]); end;

* 加上冒号表示该参数是一个位置

**/

$sql_sp = "begin proc_test(:id, :name); end;";

//parse

$stmt = ociparse($conn, $sql_sp);

//执行绑定

ocibindbyname($stmt, ":id", $id, 16); //参数说明:绑定php变量$id到位置:id,并设定绑定长度16位

ocibindbyname($stmt, ":name", $name, 30);

//execute

ociexecute($stmt);

//结果

echo "name is : $name
";


//php oralce官方实例

php 中调用存储过程

对于要从 php 中执行以调用过程的 sql 语句而言,您将通常在 oracle begin ...end; 块(称作匿名块)中嵌入调用。例如:
// etc.

$sql = 'begin sayhello(:name, :message); end;';

然后,通过调用 oci_bind_by_name() 将参数绑定到 php 变量。

如果使用以下 ddl 语句定义了 sayhello:
create or replace procedure
sayhello (name in varchar2, greeting out varchar2)
as
begin
greeting := 'hello ' || name;
end;
/

注意,您可以使用 sql*plus 命令行运行上面的语句。将该语句保存到文件 (sayhello.sql)。接下来,使用 sql*plus 登录:
$ sqlplus username@sid

然后,使用 start 命令创建该过程:
sql> start /home/username/sayhello.sql

以下 php 脚本调用该过程:
$conn = oci_connect('scott','tiger') or die;


$sql = 'begin sayhello(:name, :message); end;';

$stmt = oci_parse($conn,$sql);

//  bind the input parameter
oci_bind_by_name($stmt,':name',$name,32);

// bind the output parameter
oci_bind_by_name($stmt,':message',$message,32);

// assign a value to the input
$name = 'harry';

oci_execute($stmt);

// $message is now populated with the output value
print "$message ";
?>

blog 示例程序包。为演示调用存储过程方面的某些技巧,您将在此处使用以下名为 blog 的程序包,该程序包提供了一个 api,用于获取和修改假设的网志应用程序中的条目。程序包用于通过其自身的作用域将过程、函数和数据封装在其自身的命名空间内部,并使它们独立于全局数据库命名空间中的其他过程。调用程序包中的过程时,将使用句号来分隔程序包名称与过程名称。

可以使用以下语句指定 blog 程序包:
create or replace package blog as
   
type cursortype is ref cursor return blogs%rowtype;
   
    /*
fetch the latest num_entries_in from the blogs table, populating
entries_cursor_out with the result
    */
procedure latest(
num_entries_in in number,
entries_cursor_out out cursortype
    );
   
    /*
edit a blog entry.if id_inout is null, results in an insert, otherwise
attempts to update the existing blog entry. status_out will have the value
1 on success, otherwise a negative number on failure with status_msg_out
containing a description
categories_in is a collection where list_of_numbers is described by
type list_of_numbers as varray(50) of number;
    */
procedure edit_entry(
status_out out number,
status_msg_out out varchar2,
id_inout in out integer,
title_in in varchar2,
text_out out clob,
categories_in in list_of_numbers
    );
   
end blog;
/

该程序包提供了两个过程:blog.latest(返回包含最新 num_entries 网志条目的结果游标)和 blog.edit_entry(允许插入新的网志条目以及修改现有的网志条目)。如果为 id_inout 参数提供值,则该过程将尝试更新具有该 id 的相应网志条目。否则,它将插入一个新的网志条目并使用新行的主键填充 id_inout。该过程还接受与网志条目的主体相对应的 clob 对象以及与该条目归档到的类别列表相对应的集合对象。此处引用的集合类型 list_of_numbers 由以下语句定义:
create or replace type list_of_numbers as varray(50) of number;

下面显示了该程序包的主体。您可以通过其中的注释了解它的功能而不必深入了解 pl/sql:
create or replace package body blog as
   
    /*------------------------------------------------*/
procedure latest(
num_entries_in in number,
entries_cursor_out out cursortype
) as
       
begin
           
open entries_cursor_out for
select * from blogs where rownum < num_entries_in
order by date_published desc;
           
end latest;
   
    /*------------------------------------------------*/
procedure edit_entry(
status_out out number,
status_msg_out out varchar2,
id_inout in out integer,
title_in in varchar2,
text_out out clob,
categories_in in list_of_numbers
as
       
entry_not_found exception;
entry_found integer := 0;
       
begin
           
/* default status to success */
status_out := 1;
           
/* if id_inout has a value then attempt to update */
if id_inout is not null then
               
/* check the id exists - raise entry_not_found if not */
select count(*) into entry_found
from blogs b where b.id = id_inout;
if entry_found != 1 then raise entry_not_found; end if;
               
/* update the blogs table returning the clob field */
update blogs b set b.title = title_in, b.text = empty_clob()
where b.id = id_inout returning b.text into text_out;
               
/* remove any existing relationships教程 to categories
- new categories inserted below */
delete from blogs_to_categories where blog_id = id_inout;
               
status_msg_out := 'blog entry ' || id_inout || ' updated';
           
/* id_inout was null so insert new record */
else
               
insert into blogs b ( b.id, b.title, b.date_published, b.text )
values ( blog_id_seq.nextval, title_in, sysdate, empty_clob() )
returning b.id, b.text into id_inout, text_out;
               
status_msg_out := 'blog entry ' || id_inout || ' inserted';
               
end if;
           
/* now handle assignment to categories.
loop over the categories_in collection,
inserting the new category assignments */
for i in 1 .. categories_in.count
loop
insert into blogs_to_categories (blog_id,category_id)
values (id_inout,categories_in(i));
end loop;
           
status_msg_out := status_msg_out || ' - added to '
|| categories_in.count || ' categories';
           
exception
/* catch the exception when id_inout not found */
when entry_not_found then
status_out := -1001;
status_msg_out := 'no entry found in table blogs with id = '
|| id_inout;
/* catch any other exceptions raised by oracle */
when others then
status_out := -1;
status_msg_out := 'error:' || to_char (sqlcode) || sqlerrm;
       
end edit_entry;

end blog;

 

/

 

the underlying table structure the procedures are using is:
create sequence blog_id_seq
increment by 1;
/
create table blogs (
id number primary key,
title varchar2(200),
date_published date,
text clob
);
/
create sequence category_id_seq
increment by 1;

create table categories (
id number primary key,
name varchar2(30) unique
);
/
create table blogs_to_categories (
blog_id integer not null
references blogs(id),
category_id integer not null
references categories(id),
primary key (blog_id, category_id)
);
/

存储过程和引用游标

看一下 blog.latest 过程,您将看到它返回一个用于迭代 blogs 表行的引用游标。

与直接从 select 语句中访问行相比,在 php 中使用游标需要两个额外的步骤。第一步是使用 oci_new_cursor() 函数(该函数随后用于绑定到相应的参数)在 php 中准备一个游标资源。执行 sql 语句后,第二步是对游标资源调用 oci_execute()。

以下 php 脚本演示了该过程:
$conn = oci_connect('scott','tiger') or die;


$sql = 'begin blog.latest(:num_entries, :blog_entries); end;';

$stmt = oci_parse($conn, $sql);

// bind the input num_entries argument to the $max_entries php variable
$max_entries = 5;
oci_bind_by_name($stmt,":num_entries",$max_entries,32);

// create a new cursor resource
$blog_entries = oci_new_cursor($conn);

// bind the cursor resource to the oracle argument
oci_bind_by_name($stmt,":blog_entries",$blog_entries,-1,oci_b_cursor);

// execute the statement
oci_execute($stmt);

// execute the cursor
oci_execute($blog_entries);

print "the $max_entries most recent blog entries ";

// use ocifetchinto in the same way as you would with select
while ($entry = oci_fetch_assoc($blog_entries, oci_return_lobs )) {
print_r($entry);
}
?>

存储过程和 lob

oracle long 对象与存储过程之间可以进行相互传递,方法与内部的 sql 之间进行的相互传递几乎相同。

以下示例演示了如何使用 clob 调用 blog.edit_entry 过程。该示例未向 id 参数分配值,因此它相当于插入一个新的网志条目:
$conn = oci_connect('scott','tiger') or die;

$sql = 'begin blog.edit_entry(:status, :status_msg, :id, :title,  :text, :categories); end;';

$stmt = oci_parse($conn,$sql);

$title = 'this is a test entry';

oci_bind_by_name($stmt,":status",$status,32);
oci_bind_by_name($stmt,":status_msg",$status_msg,500);
oci_bind_by_name($stmt,":id",$id,32);
oci_bind_by_name($stmt,":title",$title,200);

// explained in the next example...(use an empty value for now)
$categories = oci_new_collection($conn,'list_of_numbers');
oci_bind_by_name($stmt,':categories',$categories,32,oci_b_sqlt_nty);

// create a new lob descriptor object
$textlob = oci_new_descriptor($conn, oci_d_lob);

// bind it to the parameter
oci_bind_by_name($stmt, ":text", $textlob, -1, oci_b_clob);

// execute the statement but do not commit
oci_execute($stmt, oci_default);

// the status parameter will be negative if the procedure encountered a problem
if ( !$status ) {
// rollback the procedure
oci_rollback($conn);
die ("$status_msg ");
}

// save the body of the blog entry to the clob
if ( !$textlob->save('this is the body of the test entry') ) {
// rollback the procedure
oci_rollback($conn);
die ("error saving lob ");
}

// everything ok so commit
oci_commit($conn);
print $status_msg." ";
?>

正如该脚本所演示的,关键问题是如何在使用 lob 时处理事务。由于更新 lob 是一个分为两阶段的过程,因此您在此处选择将所有事务处理委托给 php 脚本。

注意,默认情况下,oracle 只允许在任何给定的会话中一次运行一个事务。这意味着从 php 调用的过程中发出的 commit 或 rollback 语句将覆盖对 oci_commit() 或 oci_rollback() 的调用。可以使用匿名事务(使用位于过程定义内部的 pragma pragma autonomous_transaction 启用)更改此行为。例如,您可以在从其他过程中调用的日志记录程序包中使用匿名事务;使用这一方法您可以记录有关存储过程调用的信息,而不会干扰正在会话中运行的事务。

存储过程和集合

集合是一种用于将复杂数据类型传递到存储过程中的机制。在网志应用程序中,可以将网志条目归档到多个分类中(与"blogs"表和"categories"表之间的多对多关系相对应)。

必须在数据库中全局定义 oracle 中的集合类型,在本示例中,您将使用以下定义:
create or replace type list_of_numbers as varray(50) of number;

该定义允许您一次最多向 50 个类别分配一个网志条目,方法是将该类型的实例传递给 blog.edit_entry 过程。

在 php 中,集合由预定义的 php 类 oci-collection 表示。可以通过调用 oci_new_collection() 函数创建此类实例。oci-collection 对象提供了以下方法:
append:将元素添加到集合末尾
assign:从现有集合中将元素添加到某个集合
assignelem:将值分配给集合,并标识应将该元素置于的集合中的索引位置
free:释放与集合句柄关联的资源
getelem:从集合中的特殊索引位置检索元素
max:返回集合中的最大元素数
size:返回集合的当前大小
trim:从集合末尾删除一些元素
此处,您只希望使用 append 方法,因此可以将类别 id 列表附加到过程调用。在以下示例中,您将更新在前一个示例中创建的现有网志条目,方法是将它的 id 传递给 blog.edit_entry 过程以及类别 id 列表:
$conn = oci_connect('scott','tiger') or die;


$sql = 'begin blog.edit_entry(:status, :status_msg, :id, :title, :text, :categories); end;';

$stmt = oci_parse($conn, $sql);

$id = 1; // id of the new entry
$title = 'this is a test entry (v2)';

oci_bind_by_name($stmt,":status",$status,32);
oci_bind_by_name($stmt,":status_msg",$status_msg,500);
oci_bind_by_name($stmt,":id",$id,32);
oci_bind_by_name($stmt,":title",$title,200);
$textlob = oci_new_descriptor($conn, oci_d_lob);
oci_bind_by_name($stmt, ":text", $textlob, -1, oci_b_clob);

// create an oci-collection object
$categories = oci_new_collection($conn,'list_of_numbers');

// append some category ids to the collection;
$categories->append(2);
$categories->append(4);
$categories->append(5);

// bind the collection to the parameter
oci_bind_by_name($stmt,':categories',$categories,-1,oci_b_sqlt_nty);

oci_execute($stmt, oci_default);

if ( !$status ) {
oci_rollback($conn);
die ("$status_msg ");
}

if ( !$textlob->save('this is the body of the test entry [v2]') ) {
oci_rollback($conn);
die ("error saving lob ");
}

oci_commit($conn);
print $status_msg." ";
?>

结论

您现在已经了解了有关如何从 php 中调用存储过程(既包括只涉及标量数据类型的简单过程,也包含更复杂的使用 lob、游标和集合的过程)的示例。还对存储过程的定义进行了足够的了解,能读懂它们的 pl/sql 规范,这样您就可以从 php 中正确地调用它们并绑定相应的类型。

相关文章

精彩推荐