PL/SQL 训练09--面向对象
---对象基本声明、实现、使用
--对象类型,类似与JAVA中的类,通俗的讲,就是捆绑了相关函数和过程的记录类型。
---对象声明 --create type 创建一个对象类型的规范部分 create or replace type hello_object as object ( obj_name varchar2(100), constructor function hello_object return self as result, constructor function hello_object(obj_name varchar2 /*:='world'*/) return self as result, member function to_string return varchar2 ) instantiable not final; -- obj_name :实例变量 -- constructor function:声明 构造函数,构造函数可以重载,但名字必须更类名称一致 -- self as result : 构造函数总是返回一个对象实例,java中的this? -- member procedure/function:定义成员函数,成员函数只能用于对象实例 -- not final 表示可以被继承 -- instantiable:表示可实例化,如果指定NOT INSTANTIABLE ,则这种类型只能做一个基类 --每个成员之间,使用逗号分隔 --构造函数要尽可能多的给当前对象任何一个属性赋值
--定义对象类型后,就可以创建对象体,跟创建包体规则一样
--create type body 创建对象体 create or replace type body hello_object as --默认构造方法 constructor function hello_object return self as result is v_obj hello_object := hello_object('generic object'); begin self := v_obj; return; end hello_object; --重写构造方法 constructor function hello_object(obj_name varchar2) return self as result is begin self.obj_name := obj_name; return; -- 返回一个副本,即对象实例的当前内存引用 end hello_object; --成员函数 member function to_string return varchar2 is begin return 'hello,' || self.obj_name ; end to_string; end; /
--不懂Java?self只不过是在编写成员方法时,用来引用调用当前对象的一个手段,可以用SELF指代对象自己,也可以
--用句点法来引用对象的属性或者方法
--缺省情况下,SELF是函数的IN 变量,是过程和构造函数的IN OUT变量 。怎么理解?
--可以把SELF作为第一个形参来改变缺省模式
--如果另外一个带参数的构造函数,使用了默认的入参,则这种构造函数,跟默认构造函数会有冲突
--当调用无入参的构造函数构建对象时,会出现多个可调用的构造函数,引发混乱报错
--使用对象类型
select hello_object().to_string() from dual; select hello_object('world').to_string() from dual; declare v_obj hello_object := hello_object(); begin dbms_output.put_line(v_obj.obj_name); dbms_output.put_line(v_obj.to_string()); end; / --对象在运行时创建并丢弃,这种对象就叫做瞬态对象;有瞬态的就有持久型对象 create table sample_object(persistent hello_object); --创建表,字段是对象类型 insert into sample_object values( hello_object()); insert into sample_object values( hello_object('world')); select * from sample_object ; select t.persistent.obj_name from sample_object t ; --两种调用方式 select t.persistent.to_string() from sample_object t ; select treat(t.persistent as hello_object).to_string() from sample_object t ; drop table sample_object;
--实现getter和setter --getter是一个方法,可以达到对象内部获取一些信息,setter也是一个方法,可以发送信息到对象内部 --设置实例变量 create or replace type hello_object as object ( obj_name varchar2(100), constructor function hello_object return self as result, constructor function hello_object(obj_name varchar2/* :='world'*/) return self as result, member function to_string return varchar2, member function get_name return varchar2, member procedure set_name(i_name in varchar2) ) instantiable not final; create or replace type body hello_object as --默认构造方法 constructor function hello_object return self as result is v_obj hello_object := hello_object('generic object'); begin self := v_obj; return; end hello_object; --重写构造方法 constructor function hello_object(obj_name varchar2) return self as result is begin self.obj_name := obj_name; return; -- 返回一个副本,即对象实例的当前内存引用 end hello_object; --成员函数 member function to_string return varchar2 is begin return 'hello,' || self.obj_name ; end to_string; member function get_name return varchar2 is begin return self.obj_name; end get_name; member procedure set_name(i_name in varchar2)is begin self.obj_name := i_name; end set_name; end; / declare v_obj hello_object := hello_object(); begin dbms_output.put_line(v_obj.get_name()); v_obj.set_name('test'); dbms_output.put_line(v_obj.get_name()); v_obj.obj_name :='ssdddee'; dbms_output.put_line(v_obj.obj_name); end; /
--实现静态方法
create or replace type hello_object as object ( obj_name varchar2(100), constructor function hello_object return self as result, constructor function hello_object(obj_name varchar2/* :='world'*/) return self as result, member function to_string return varchar2, member function get_name return varchar2, member procedure set_name(i_name in varchar2), static procedure print ) instantiable not final; create or replace type body hello_object as --默认构造方法 constructor function hello_object return self as result is v_obj hello_object := hello_object('generic object'); begin self := v_obj; return; end hello_object; --重写构造方法 constructor function hello_object(obj_name varchar2) return self as result is begin self.obj_name := obj_name; return; -- 返回一个副本,即对象实例的当前内存引用 end hello_object; --成员函数 member function to_string return varchar2 is begin return 'hello,' || self.obj_name ; end to_string; member function get_name return varchar2 is begin return self.obj_name; end get_name; member procedure set_name(i_name in varchar2)is begin self.obj_name := i_name; end set_name; static procedure print is begin dbms_output.put_line('welcome to learn object'); end print ; end; /
--使用static来声明一个静态方法或函数
--静态方法允许像使用标准包一样使用对象类型。可以使用静态函数和过程来打印常量和消息
--但不能使用其访问实例变量。静态函数可以返回一个实例化类
--静态方法中不能使用SELF,因为对于静态方法而言没有当前对象
begin
hello_object.print();
end;
/
--比较对象
--先来看一个例子
declare v_obj1 hello_object := hello_object(); v_obj2 hello_object := hello_object(); begin if v_obj1 = v_obj2 then dbms_output.put_line('equal'); else dbms_output.put_line('not equal'); end if; end; / --怎样比较两个对象呢?比如数值变量,我们可以直接比较数值来判断大小,对象却没有那么方便 --但我们可以给对象指定比较的规则,规定在什么情况下相等,什么情况下哪个比较大,哪个比较小 --使用MAP或者ORDER方法,可以来定义比较规则
--Map函数比较
--Map 函数不接受形参,只返回CHAR、DATE ,NUMBER或VARCHAR2的标量类型 create or replace type map_comp is object ( cmp_data varchar2(20), constructor function map_comp(cmp_data varchar2) return self as result, map member function equal return varchar2 ); create or replace type body map_comp is constructor function map_comp(cmp_data varchar2) return self as result is begin self.cmp_data := cmp_data; return ; end map_comp; map member function equal return varchar2 is begin return self.cmp_data; end equal; end ; / declare v_data1 map_comp := map_comp('a'); v_data2 map_comp := map_comp('a'); v_data3 map_comp := map_comp('b'); begin if v_data1 = v_data2 then dbms_output.put_line('equal'); else dbms_output.put_line('not equal'); end if; if v_data1 = v_data3 then dbms_output.put_line('equal'); else dbms_output.put_line('not equal'); end if; end; /
--作业:给定上述对象的对象列表,使用冒泡排序法进行排序
--ORDER 方法比较 --order 函数运行将任何数据类型定义为形参。通过将形参定义为相同的对象类型,可以模拟JAVA中的 --对象比较方法,这样可以把某个对象的副本传递给另一个对象,然后比较这两个对象 create or replace type order_comp is object ( first_name varchar2(100), second_name varchar2(100), constructor function order_comp(first_name varchar2,second_name varchar2) return self as result, order member function equal(i_obj order_comp) return number, member function to_string return varchar2 ) instantiable not final; --没有指定NOT FINAL 默认是FINAL的 create or replace type body order_comp is constructor function order_comp(first_name varchar2, second_name varchar2) return self as result is begin self.first_name := first_name; self.second_name := second_name; return; end order_comp; order member function equal(i_obj order_comp) return number is begin if self.first_name > i_obj.first_name then return -1; elsif self.first_name = i_obj.first_name and self.second_name > i_obj.second_name then return -1; elsif self.first_name = i_obj.first_name and self.second_name = i_obj.second_name then return 0 ; else return 1 ; end if; end equal; member function to_string return varchar2 is begin return self.first_name||'-'||self.second_name; end to_string; end; / --如果self < i_obj order函数返回任意负数 -1 --如果SELF = i_obj 返回 0 --如果SELF > i_obj 返回 任意正数 1 declare v_data1 order_comp := order_comp('a','c'); v_data2 order_comp := order_comp('a','d'); v_data3 order_comp := order_comp('b','s'); begin if v_data1< v_data2 then dbms_output.put_line('yes'); else dbms_output.put_line('no'); end if; end; / ---map 和order在同一个对象类型中不能并存,只能使用一个 --当有大量的对象需要排序和比较时,比如在SQL语句中,ORACLE建议使用MAP --ORACLE并不关系方法的名字,我们可以任意起名 --子类型也可以有MAP方法,不过只有当基类也有这个方法时才行 --子类型不能有ORDER方法;我们必须聪明地把所有比较都放在基类中
---继承和多态
--跟JAVA中类可以被继承外,对象类型也可以被继承
--在其它语言中,比如JAVA都有一个基类型,比如Object,在ORACLE里没有定义一个主的基类
drop type order_subcomp; ---under 标识子类型的关键字 create or replace type order_subcomp under order_comp ( salucation varchar2(20), constructor function order_subcomp(first_name varchar2, second_name varchar2, salucation varchar2) return self as result, overriding member function to_string return varchar2 )instantiable final; create or replace type body order_subcomp is constructor function order_subcomp(first_name varchar2, second_name varchar2, salucation varchar2) return self as result is begin self.first_name := first_name; self.second_name := second_name; self.salucation := salucation; return; end order_subcomp; overriding member function to_string return varchar2 is begin return(self as order_comp) .to_string() || ',' || self.salucation; end to_string; end; /
---使用(self as supertype) 来调用父类的方法,11g及之后可以这样使用
--11g之前,这是不可能的
declare v_data1 order_subcomp := order_subcomp('a','c','s'); v_data2 order_subcomp := order_subcomp('a','d','t'); v_data3 order_subcomp := order_subcomp('b','s','y'); v_parent order_comp ; begin if v_data1< v_data2 then dbms_output.put_line('small'); else dbms_output.put_line('not small'); end if; v_parent := v_data1; dbms_output.put_line(v_data1.to_string()); dbms_output.put_line(v_parent.to_string()); end; / --声明子类型时,父类型中的属性不用列出,因其自动继承 --子类型构建,将变量分配给父类型属性 --子类型可以访问父类型的构造函数 --子类型不能覆写父类型的构造函数 --对象的子类型不能覆写对象的MAP和ORDER函数
--我们来看下多态
--创建基类型
create or replace type ma_product is object ( product_id number, product_name varchar2(100), product_price number, not instantiable member procedure show_discount ) not instantiable not final ; --创建子类型 create or replace type ma_book under ma_product ( book_author varchar2(100), book_pages number, constructor function ma_book(product_id number, product_name varchar2, product_price number, book_author varchar2, book_pages number) return self as result, overriding member procedure show_discount ) instantiable not final; create or replace type body ma_book is constructor function ma_book(product_id number, product_name varchar2, product_price number, book_author varchar2, book_pages number) return self as result is begin self.product_id := product_id; self.product_name := product_name; self.product_price := product_price; self.book_author := book_author; self.book_pages := book_pages; return; end ma_book; overriding member procedure show_discount is begin dbms_output.put_line(self.product_name || ' 作者是' || self.book_author || ',共' || self.book_pages || '页'); end show_discount; end; / --创建子类型 create or replace type ma_computer under ma_product ( cpu_size number, brand varchar2(100), constructor function ma_computer(product_id number, product_name varchar2, product_price number, brand varchar2, cpu_size number) return self as result, overriding member procedure show_discount ) instantiable not final; create or replace type body ma_computer is constructor function ma_computer(product_id number, product_name varchar2, product_price number, brand varchar2, cpu_size number) return self as result is begin self.product_id := product_id; self.product_name := product_name; self.product_price := product_price; self.brand := brand; self.cpu_size := cpu_size; return; end ma_computer; overriding member procedure show_discount is begin dbms_output.put_line(self.product_name || ' 品牌是' || self.brand || ',CPU大小' || self.cpu_size || 'M'); end show_discount; end; / declare type list_t is table of ma_product; product_list list_t; v_product1 ma_book := ma_book(1, 'plsql实战训练', 25, 'testma', 55); v_product2 ma_book := ma_book(1, 'plsql实战训练2', 30, 'testma12', 56); v_product3 ma_computer := ma_computer(1, '联想笔记本', 3000, '联想', 1024); v_product4 ma_computer := ma_computer(1, '清华同方笔记本', 1999, '清华同方', 2048); begin product_list := list_t(v_product1, v_product3, v_product2, v_product4); for i in 1 .. product_list.count loop product_list(i) .show_discount(); end loop; end; /
--对象集合
--对象类型和集合对象类型之间的唯一区别是,对象只保存单个对象类型,而集合
--保存一个对象类型数组或嵌套表
ma_order_items create or replace type order_item is object ( order_id varchar2(32), product_no number, product_name varchar2(100), product_size varchar2(20), product_num number , product_ori_price number , product_new_price number ); declare v order_item := order_item('','','','','','','');--不能使用ORDER_ITEM()进行初始化 begin v.order_id := sys_guid(); dbms_output.put_line(v.order_id); end; / create or replace type order_item_table is table of order_item; create or replace type order_objects is object ( order_table order_item_table, constructor function order_objects(order_table order_item_table) return self as result, constructor function order_objects return self as result, member function get_size return number, member function get_table return order_item_table, static function get_order_items(i_low number, i_high number) return order_item_table ) instantiable not final; create or replace type body order_objects is constructor function order_objects(order_table order_item_table) return self as result is begin self.order_table := order_table; return; end order_objects; constructor function order_objects return self as result is cursor cur_item is select * from ma_order_items; c number := 1; v_item order_item; begin self.order_table := order_item_table(); for v in cur_item loop v_item := order_item(v.ID_MA_ORDERS, v.product_no, v.product_name, v.product_size, v.product_num, v.product_ori_price, v.product_new_price); self.order_table.extend; self.order_table(c) := v_item; c := c + 1; end loop; return; end order_objects; member function get_size return number is begin return self.order_table.count; end get_size; member function get_table return order_item_table is begin return self.order_table; end get_table; static function get_order_items(i_low number, i_high number) return order_item_table is cursor cur_item is select * from ma_order_items t where t.product_no between i_low and i_high; c number := 1; v_item order_item; v_order_table order_item_table; begin v_order_table := order_item_table(); for v in cur_item loop v_item := order_item(v.ID_MA_ORDERS, v.product_no, v.product_name, v.product_size, v.product_num, v.product_ori_price, v.product_new_price); v_order_table.extend; v_order_table(c) := v_item; c := c + 1; end loop; return v_order_table; end get_order_items; end; / select * from table(order_objects().get_table());
---对象表
--在ORACLE里,可以把对象保存到数据库中
create table product_objects of ma_product (constraint pk_ma_product primary key(product_id)); --这个语句创建了一个叫做ma_product的对象表,每一行都是一个ma_product对象 --一般来说,对象的每个属性都对应着表中的一列 select * from product_objects ;
--需注意的是ma_product是不可实例化的,这个表中的每一行实际是类似于ma_book或ma_computer的子类型 insert into product_objects values (ma_book(1, 'plsql实战训练', 25, 'testma', 55)); insert into product_objects values (ma_book(2, 'plsql实战训练2', 30, 'testma12', 56)); insert into product_objects values (ma_computer(3,'联想笔记本',3000,'联想',1024)); insert into product_objects values (ma_computer(4,'清华同方笔记本',1999,'清华同方',2048)); select * from product_objects;
---所属子类型的属性哪去了呢?
--oracle 把子类型专有的属性放在了product_objects的隐藏列中
--从对象编程角度来看,这种方法既保留了商品的抽象性,有能够在需要的时候暴露子类型的而外信息
--对象标识符
--ORALE可以用主键值或这系统生成的值(SYS_NC_OID$)来产生对象标识符
--系统生成的OID:不透明,可以数据库全局唯一,不可变;
---VALUE函数 --从数据库中提取一个对象,可以使用VALUE函数 select value(p) from product_objects p ; --value只接受一个参数,这个参数必须是当前FROM子句中的表别名,返回的是一个用于定义表的类型对象; declare v_product ma_product; cursor cur_product is select value(p) from product_objects p; begin open cur_product; loop fetch cur_product into v_product; exit when cur_product%notfound; v_product.show_discount(); end loop; end; / --还可以直接访问属于基类的属性 select value(p).product_id from product_objects p ; --能否直接访问子类型的属性呢? select value(p).book_author from product_objects p ; ---如果一个对象表基于的对象类型没有子类,就可以利用传统的SQL语句对于所有的列执行选择,插入,更新,删除操作 select product_id from product_objects p ; update product_objects p set p.product_id = 6 where p.product_id = 1; --对于那些因为是子类型而被当作隐藏的列,就不能使用传统的关系DML操作,,必须使用对象DML方法 update product_objects p set p = ma_book(1, 'plsql实战训练', 25, 'testma', 55) where p.product_id = 6; --要想更新某个子类型特有列值,唯一的好办法是更新整个对象
--TREAT 函数
--怎样去访问对象表中子类型的特有属性呢?
DECLARE V_BOOK ma_book; V_PRODUCT ma_product := ma_book(1, 'plsql实战训练', 25, 'testma', 55); BEGIN v_book := treat(v_product as ma_book);---从父类型向子类型转换,向下转换或者缩小 v_book.show_discount(); END; / --TREAT函数的语法 treat(object_instance as subtype)[.{attribute|method(args...)}]; --怎么去感知某个父类型是某个子类型呢? OBJECT IS OF ([ONLY] TYPENAME) --ONLY:如果一个对象属于指定的类型或者任意一个子类型,不带ONLY的话,返回TRUE --如果使用了ONLY,这个表达式不会检查子类型,只有对象完全匹配时才返回TRUE declare v_product ma_product; cursor cur_product is select value(p) product from product_objects p; begin for v in cur_product loop case when v.product is of(ma_book) then dbms_output.put_line(treat(v.product as ma_book).book_author); when v.product is of(ma_computer) then dbms_output.put_line(treat(v.product as ma_computer).brand); else dbms_output.put_line('unknown object'); end case; end loop; end; / ---如果想修改MA_PRODUCT,怎么办 --比如删除对象类型 drop type ma_product validate; drop type typename [force | validate] --在MA_PRODUCT新增一个属性? alter type ma_product add attribute publication_date date cascade including table data ; --删除类型ma_book的方法 alter type ma_book drop constructor function ma_book(product_id number, product_name varchar2, product_price number, book_author varchar2, book_pages number) return self as result cascade;
----------------------------------------------------
declare p_numbers varchar2(100) :='tydbser'; type t_table is table of char index by pls_integer; v_tab t_table; v_tmp char(1); Result varchar2(100); begin for i in 1 .. nvl(length(p_numbers), 0) loop v_tab(i) := substr(p_numbers, i, 1); end loop; for p in 1 .. nvl(length(p_numbers), 0) - 1 loop for q in reverse p .. nvl(length(p_numbers), 0)-1 loop ---一定要从冒泡轨迹的起点开始比较。 if v_tab(q) <= v_tab(q+1) then v_tmp := v_tab(q); v_tab(q) := v_tab(q+1); v_tab(q+1) := v_tmp; end if; end loop; end loop; for i in 1 .. nvl(length(p_numbers), 0) loop Result := Result || v_tab(i); end loop; dbms_output.put_line(Result); end ; declare v_data1 map_comp := map_comp('a'); v_data2 map_comp := map_comp('a'); v_data3 map_comp := map_comp('b'); v_data4 map_comp; begin if v_data1 = v_data2 then dbms_output.put_line('equal'); else dbms_output.put_line('not equal'); end if; if v_data1 = v_data3 then dbms_output.put_line('equal'); else dbms_output.put_line('not equal'); end if; if v_data1 <= v_data2 then v_data4 := v_data1; v_data1 := v_data2; v_data2:= v_data4; -- dbms_output.put_line(v_data1); -- dbms_output.put_line(v_data2); -- dbms_output.put_line(v_data4); end if; end; / --定义对象 create or replace type bhgx_obj is object ( letter varchar2(1), constructor function bhgx_obj return self as result, constructor function bhgx_obj(letter varchar2) return self as result, member function toString return varchar2, order member function equal(v_obj bhgx_obj) return number ) instantiable not final; CREATE OR REPLACE TYPE let_obj IS TABLE OF bhgx_obj; --创建对象体 create or replace type body bhgx_obj as --默认构造方法 constructor function bhgx_obj return self as result is v_obj bhgx_obj := bhgx_obj(''); begin self := v_obj; return; end bhgx_obj; --重写构造方法 constructor function bhgx_obj(letter varchar2) return self as result is begin self.letter := letter; return; -- 返回一个副本,即对象实例的当前内存引用 end bhgx_obj; --成员函数 member function toString return varchar2 is begin return self.letter ; end toString; order member function equal(v_obj bhgx_obj) return number is begin if self.letter > v_obj.letter then return 1 ; elsif self.letter < v_obj.letter then return -1 ; elsif self.letter = v_obj.letter then return 0 ; else return 0 ; end if; end equal; end; --创建包 create or replace package pkg_data_order is function order_letter(v_l let_obj) return let_obj ; end pkg_data_order; --创建包体 CREATE OR REPLACE PACKAGE BODY pkg_data_order IS function order_letter(v_l let_obj) return let_obj as v_t bhgx_obj; v_letts let_obj:=v_l; begin FOR i IN 1..v_letts.COUNT LOOP FOR j IN 1..(v_letts.COUNT-i) LOOP if v_letts(j).equal(v_letts(j+1))>0 then v_t := v_letts(j) ; v_letts(j) := v_letts(j+1); v_letts(j+1) := v_t; end if; END LOOP; END LOOP; return v_letts ; end; end pkg_data_order; --调用 declare v_p let_obj:=let_obj() ; begin v_p.extend ; v_p(1):=bhgx_obj('z'); v_p.extend ; v_p(2):=bhgx_obj('b'); v_p.extend ; v_p(3):=bhgx_obj('d'); v_p.extend ; v_p(4):=bhgx_obj('m'); v_p.extend ; v_p(5):=bhgx_obj('a'); v_p.extend ; v_p(6):=bhgx_obj('k'); v_p.extend ; v_p(7):=bhgx_obj('c'); v_p.extend ; v_p(8):=bhgx_obj('q'); v_p := pkg_data_order.order_letter(v_p) ; FOR i IN 1..v_p.COUNT LOOP dbms_output.put_line(v_p(i).toString()); END LOOP; end; -------- CREATE OR REPLACE procedure test_bubble(str IN VARCHAR2) IS type v_type is varray(1000) of varchar2(100); var v_type; temp varchar2(100); flag boolean; results VARCHAR2(4000); BEGIN select substr(str,level,1) bulk collect into var from dual connect by level <=length(str); <<outer_scope>> for i in 1 .. var.count-1 loop flag := false; for j in reverse i .. var.count-1 loop if var(j+1) < var(j) then temp := var(j+1); var(j+1) := var(j); var(j) := temp; flag := true; end if; end loop; if (not flag) then exit outer_scope; end if; end loop; for i in var.first .. var.last loop results :=concat(results,var(i)); end loop; dbms_output.put_line('out_results='||results); END test_bubble; --测试过程 declare begin test_bubble('zlmbwacgiofrskne'); end; ----------- create or replace type map_comp is object( cmp_data varchar2(1), constructor function map_comp(cmp_data varchar2) return self as result, map member function equal return varchar2, member function to_string return varchar2 )instantiable not final; create or replace type body map_comp is constructor function map_comp(cmp_data varchar2) return self as result is begin self.cmp_data := cmp_data; return; end map_comp; map member function equal return varchar2 is begin return self.cmp_data; end equal; member function to_string return varchar2 is begin return self.cmp_data; end to_string; end; --赋值和排序 declare type ind_obj_type is table of map_comp index by pls_integer; v_ind_obj ind_obj_type; v_aaa map_comp; begin --初始化,随机赋值 for i in 1 .. 15 loop v_ind_obj(i) := map_comp(chr(97 + dbms_random.value(1, 25))); end loop; --打印排序前对象中的字母 dbms_output.put('排序前: '); for i in 1 .. v_ind_obj.count loop dbms_output.put(v_ind_obj(i).to_string() || ' '); end loop; dbms_output.put_line(''); --冒泡排序 for i in 1 .. v_ind_obj.count loop for j in i + 1 .. v_ind_obj.count loop if v_ind_obj(i) > v_ind_obj(j) then v_aaa := v_ind_obj(j); v_ind_obj(j) := v_ind_obj(i); v_ind_obj(i) := v_aaa; end if; end loop; end loop; --打印排序后 dbms_output.put('排序后: '); for i in 1 .. v_ind_obj.count loop dbms_output.put(v_ind_obj(i).to_string() || ' '); end loop; dbms_output.put_line(''); exception when others then dbms_output.put_line(sqlerrm); end;