oracle pl/sql面向对象支持及lightdb兼容
虽说现在新系统pl/sql已经很少用了,但是在信创迁移过程中,仍然有很多oracle pl/sql过程、函数、包需要能够无缝的移植到信创数据库,比如在很多风控系统移植lightdb的过程中,就遇到了很多,甚至还有pro*c。所以才有了本文。
-- 最常用,用于集合处理
DECLARE CURSOR allrows_cur IS SELECT * FROM emp; TYPE employee_aat IS TABLE OF emp%ROWTYPE INDEX BY PLS_INTEGER; -- 带index by,关联数组,Associative array l_employees employee_aat; TYPE employee_aat2 IS TABLE OF emp%ROWTYPE; -- 不带index by,嵌套表 l_employees2 employee_aat2; BEGIN OPEN allrows_cur; LOOP FETCH allrows_cur BULK COLLECT INTO l_employees LIMIT 100; /* Process the data by scanning through the collection. */ FOR l_row IN 1 .. l_employees.COUNT LOOP dbms_output.put_line (l_employees(l_row).ename || '-' || l_employees(l_row).EMPNO); END LOOP; EXIT WHEN allrows_cur%NOTFOUND; END LOOP; CLOSE allrows_cur; SELECT * BULK COLLECT INTO l_employees2 FROM emp; FOR l_row IN 1 .. l_employees2.COUNT LOOP dbms_output.put_line ('l_employees2==' || l_employees2(l_row).ename || '-' || l_employees2(l_row).EMPNO); END LOOP; END;
SMITH-7369
ALLEN-7499
WARD-7521
JONES-7566
MARTIN-7654
BLAKE-7698
CLARK-7782
SCOTT-7788
KING-7839
TURNER-7844
ADAMS-7876
JAMES-7900
FORD-7902
MILLER-7934
l_employees2==SMITH-7369
l_employees2==ALLEN-7499
l_employees2==WARD-7521
l_employees2==JONES-7566
l_employees2==MARTIN-7654
l_employees2==BLAKE-7698
l_employees2==CLARK-7782
l_employees2==SCOTT-7788
l_employees2==KING-7839
l_employees2==TURNER-7844
l_employees2==ADAMS-7876
l_employees2==JAMES-7900
l_employees2==FORD-7902
l_employees2==MILLER-7934
注:我不喜欢使用可变长度数组,所以本文不包含它。
Table 5-1 PL/SQL Collection Types
Collection Type | Number of Elements | Index Type | Dense or Sparse | Uninitialized Status | Where Defined | Can Be ADT Attribute Data Type |
---|---|---|---|---|---|---|
Associative array (or index-by table) |
Unspecified |
String or |
Either |
Empty |
In PL/SQL block or package |
No |
|
Specified |
Integer |
Always dense |
Null |
In PL/SQL block or package or at schema level |
Only if defined at schema level |
Nested table |
Unspecified |
Integer |
Starts dense, can become sparse |
Null |
In PL/SQL block or package or at schema level |
Only if defined at schema level |
Number of Elements
If the number of elements is specified, it is the maximum number of elements in the collection. If the number of elements is unspecified, the maximum number of elements in the collection is the upper limit of the index type.
Dense or Sparse
A dense collection has no gaps between elements—every element between the first and last element is defined and has a value (the value can be NULL
unless the element has a NOT
NULL
constraint). A sparse collection has gaps between elements.
Uninitialized Status
An empty collection exists but has no elements. To add elements to an empty collection, invoke the EXTEND
method (described in "EXTEND Collection Method").
A null collection (also called an atomically null collection) does not exist. To change a null collection to an existing collection, you must initialize it, either by making it empty or by assigning a non-NULL
value to it (for details, see "Collection Constructors" and "Assigning Values to Collection Variables"). You cannot use the EXTEND
method to initialize a null collection.
自定义记录类型
CREATE TABLE Book_table ( Booknum NUMBER, Section VARCHAR2(20), Title VARCHAR2(20), Author VARCHAR2(20), Available CHAR(1) ); INSERT INTO Book_table ( Booknum, Section, Title, Author, Available ) VALUES ( 121001, 'Classic', 'Iliad', 'Homer', 'Y' ); INSERT INTO Book_table ( Booknum, Section, Title, Author, Available ) VALUES ( 121002, 'Novel', 'Gone with the Wind', 'Mitchell M', 'N' ); SELECT * FROM Book_table ORDER BY Booknum;
DECLARE TYPE Book_list_record IS RECORD ( Booknum NUMBER, Section VARCHAR2(20), Title VARCHAR2(20), Author VARCHAR2(20), Available CHAR(1) ); TYPE Book_list_t1 IS TABLE OF Book_list_record; v_Book_list Book_list_t1; -- 嵌套表 BEGIN SELECT Booknum, Section, Title, Author, Available BULK COLLECT INTO v_Book_list FROM Book_table; FOR l_row IN 1 .. v_Book_list.COUNT LOOP dbms_output.put_line ('v_Book_list==' || v_Book_list(l_row).Booknum || '-' || v_Book_list(l_row).Title); END LOOP; END; v_Book_list==121001-Iliad v_Book_list==121002-Gone with the Wind
记录类型和对象类型的差异,需要特别注意,对象需要通过构造器设置值,记录类型和ROWTYPE类型可以赋值。直接将SELECT Booknum, Section, Title, Author, Available BULK COLLECT INTO v_Book_list赋值给对象嵌套表,将报值不足。
预定义基于对象的嵌套表类型
CREATE OR REPLACE TYPE Book_t1 AS OBJECT ( Booknum NUMBER, Section VARCHAR2(20), Title VARCHAR2(20), Author VARCHAR2(20), Available CHAR(1) ); CREATE OR REPLACE TYPE Book_list_t AS TABLE OF Book_t1; DECLARE v_Book_list Book_list_t; -- 嵌套表 BEGIN SELECT Book_t1(Booknum, Section, Title, Author, Available)/* 对象类型特殊的地方 */ BULK COLLECT INTO v_Book_list FROM Book_table; FOR l_row IN 1 .. v_Book_list.COUNT LOOP dbms_output.put_line ('v_Book_list==' || v_Book_list(l_row).Booknum || '-' || v_Book_list(l_row).Title); END LOOP; END; v_Book_list==121001-Iliad v_Book_list==121002-Gone with the Wind
is table of 和 as table of
在创建类型的时候是一样的,都可以生效。
CREATE OR REPLACE TYPE Book_list_t IS TABLE OF Book_t1;
和
CREATE OR REPLACE TYPE Book_list_t AS TABLE OF Book_t1;
都能生效。
但是在过程内定义嵌套表的时候,只能用IS TABLE OF,不能用AS TABLE OF,会报错。
DECLARE TYPE employee_aat2 AS TABLE OF emp%ROWTYPE; -- 不带index by,嵌套表 l_employees2 employee_aat2; BEGIN SELECT * BULK COLLECT INTO l_employees2 FROM emp; FOR l_row IN 1 .. l_employees2.COUNT LOOP dbms_output.put_line ('l_employees2==' || l_employees2(l_row).ename || '-' || l_employees2(l_row).EMPNO); END LOOP; END; SQL 错误 [6550] [65000]: ORA-06550: 第 2 行, 第 23 列: PLS-00103: 出现符号 "TABLE"在需要下列之一时: object opaque
object和record的区别
https://sqlpey.com/question/oracle-object-vs-record-type-whats-difference
https://forums.oracle.com/ords/apexds/post/object-type-vs-record-type-2620
对象嵌套表的构造与使用
CREATE TYPE address_typ AS OBJECT ( street VARCHAR2(30), city VARCHAR2(20), state CHAR(2), postal_code VARCHAR2(6) ); CREATE OR REPLACE TYPE employee_typ AS OBJECT ( employee_id NUMBER(6), first_name VARCHAR2(20), last_name VARCHAR2(25), email VARCHAR2(25), phone_number VARCHAR2(20), hire_date DATE, job_id VARCHAR2(10), salary NUMBER(8,2), commission_pct NUMBER(2,2), manager_id NUMBER(6), department_id NUMBER(4), address address_typ, MAP MEMBER FUNCTION get_idno RETURN NUMBER, MEMBER PROCEDURE display_address ( SELF IN OUT NOCOPY employee_typ ) ); CREATE OR REPLACE TYPE BODY employee_typ AS MAP MEMBER FUNCTION get_idno RETURN NUMBER IS BEGIN RETURN employee_id; END; MEMBER PROCEDURE display_address ( SELF IN OUT NOCOPY employee_typ ) IS BEGIN DBMS_OUTPUT.PUT_LINE(first_name || ' ' || last_name); DBMS_OUTPUT.PUT_LINE(address.street); DBMS_OUTPUT.PUT_LINE(address.city || ', ' || address.state || ' ' || address.postal_code); END; END; DECLARE TYPE emps IS TABLE OF employee_typ; emp_list emps := emps(); -- emp is atomically null BEGIN emp_list.extend(3); -- call the constructor for employee_typ emp_list(1) := employee_typ(315, 'Francis', 'Logan', 'FLOGAN', '415.555.0100', sysdate,'SA_MAN', 11000, 0.15, 101, 110, address_typ('376 Mission', 'San Francisco', 'CA', '94222')); emp_list(2) := employee_typ(215, '2Franis', '2Loan', '2FLGAN', '245.555.0100', sysdate,'SA_MAN', 11000, 0.15, 101, 110, address_typ('237 Mission', '2Sn Francisco', '2C', '94222')); FOR i IN 1..emp_list.count loop DBMS_OUTPUT.PUT_LINE(emp_list(i).first_name || ' from dbms_output ' || emp_list(i).last_name); -- display details emp_list(i).display_address(); -- call object method to display details END LOOP; END; -- 输出 Francis from dbms_output Logan Francis Logan 376 Mission San Francisco, CA 94222 2Franis from dbms_output 2Loan 2Franis 2Loan 237 Mission 2Sn Francisco, 2C 94222 from dbms_output
对象类型不仅可以包含成员,也可以包含成员函数、构造器(只能通过java或c编写,不支持PL/SQL定义)、静态成员函数等,典型的面向对象。
集合类型上的默认支持函数。
表函数支持
嵌套表和关联数组都支持作为表函数的参数。
CREATE OR REPLACE PACKAGE pkg AS TYPE rec IS RECORD(f1 NUMBER, f2 VARCHAR2(30)); -- TYPE mytab IS TABLE OF rec INDEX BY pls_integer; -- 默认空集合 TYPE mytab IS TABLE OF rec; -- 默认未初始化,需要显示初始化 END; DECLARE v1 pkg.mytab; -- collection of records v2 pkg.rec; c1 SYS_REFCURSOR; BEGIN v1 := pkg.mytab(); -- 嵌套表需要 v1.extend(2); -- 嵌套表需要 v1(1).f1 := 1; v1(1).f2 := 'one'; OPEN c1 FOR SELECT * FROM TABLE(v1); FETCH c1 INTO v2; CLOSE c1; DBMS_OUTPUT.PUT_LINE('Values in record are ' || v2.f1 || ' and ' || v2.f2); END;
自定义对象类型及嵌套表在Oracle及EDB中的实现
自定义类型无论是嵌套表还是类型如create type as of object, create type is/as table of; 在edb中,基于对象的自定义类型是通过模拟schema来实现的,和package一样,即type_name就是schema_name,成员函数这些存储在其对应的schema下,这样也不会污染全局pg_proc。而自定义类型又可以作为package的成员之一,这就会比较复杂。package.type.method或pakcage.type.member和schema.table.attribute看起来冲突,type.member和table.attribute也冲突,这就有个serach_path优先级的问题。在PG中,默认一般先搜索current_schema、其次public、然后才是其他schema,当然也可以配置,但一般不会修改。
SELECT * FROM user_types;
BOOK_LIST_T R¡P<1àcÇ ï COLLECTION
BOOK_T1 R¡@<1àcÇ ï OBJECT
EMPLOYEE_TYP R¡u<1àcÇ ï OBJECT
ADDRESS_TYP R¡Z<1àcÇ ï OBJECT
SELECT * FROM USER_TYPE_ATTRS; -- 存储属性
SELECT * FROM USER_TYPE_METHODS; -- 存储成员函数
SELECT * FROM DBA_PROCEDURES WHERE object_name='PKG'; -- 尚未找到类型存储的位置
OWNER |OBJECT_NAME|PROCEDURE_NAME|OBJECT_ID|SUBPROGRAM_ID
-----------+-----------+--------------+---------+-------------
SYSTEM |PKG | | 183968| 0
这意味着,如果有相同的表名和类型名,就不知道找谁。所以,一种实现就是不允许类型名和表名相同以保证唯一性,lightdb 23.2及之前是允许的,通过search_path搜索路径顺序保证,23.3开始不再允许重名,和edb规则一样。
在oracle中是4级存储,schema.package.type.member。
lightdb兼容情况
截止23.2,lightdb支持标量类型的关联数组和嵌套表,不支持可变数组,不支持create table as/is object,不支持关联数组作为过程/函数参数类型/table()有一些限制。package级别不支持类型定义,匿名块不能使用package级别的类型定义。
23.3开始,上述特性基本上已经支持。参见lightdb oracle package兼容中的示例,都可无需修改支持。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
2021-07-15 PostgreSQL查看索引、表对应的relfileno