本文介绍了集合类型的一些特殊用法。
示例
以下是一个取出一张表里的数据到集合变量,并插入另一张表的示例。
-- 前置准备
CREATE TABLE test (id INT, name VARCHAR(10));
INSERT INTO test VALUES(1, 'a'), (2, 'b');
CREATE TABLE test_backup (id INT, name VARCHAR(10));
DECLARE
TYPE r_type IS RECORD(id INT, name VARCHAR(10));
TYPE nt_type IS TABLE OF r_type;
nt nt_type;
BEGIN
SELECT * BULK COLLECT INTO nt FROM test;
FORALL i IN INDICES OF nt
INSERT INTO test_backup VALUES nt(i);
END;
SELECT * FROM test_backup;
结果显示如下:
id | name
----+------
1 | a
2 | b
(2 rows)
以下是一个利用集合变量更新表的示例。
DECLARE
TYPE r_type IS RECORD(id INT, name VARCHAR(10));
TYPE nt_type IS TABLE OF r_type;
nt nt_type := nt_type(r_type(1, 'c'), r_type(2, 'd'));
BEGIN
FORALL i IN nt.first..nt.last
UPDATE test_backup SET ROW = nt(i) WHERE id = nt(i).id;
END;
SELECT * FROM test_backup;
结果显示如下:
id | name
----+------
1 | c
2 | d
(2 rows)
BULK COLLECT INTO
BULK COLLECT INTO
的使用场景与INTO
类似,区别在于INTO
的目标是标量或行变量,而BULK COLLECT INTO
的目标是集合变量。它能够将表中的多行数据批量插入到集合变量中。使用该语法时,不需要初始化集合变量。即使目标的集合变量中已经含有元素,也会重新初始化后再使用。除了上述例子中的SELECT ... BULK COLLECT INTO
以外,您还可以在以下场景中使用。
以下是一个FETCH cursor BULK COLLECT INTO
的示例。
DECLARE
TYPE nt_int IS TABLE OF test.id%TYPE;
TYPE nt_char IS TABLE OF test.name%TYPE;
nt1 nt_int;
nt2 nt_char;
CURSOR cur IS SELECT id, name FROM test;
BEGIN
OPEN cur;
-- 为了防止 FETCH 的数量过多,您可以通过 limit 关键字来限制它
FETCH cur BULK COLLECT INTO nt1, nt2 limit 1;
FOR i IN 1..nt1.COUNT LOOP
RAISE NOTICE '% %', nt1(i), nt2(i);
END LOOP;
CLOSE cur;
END;
结果显示如下:
NOTICE: 1 a
DO
以下是一个RETURNING ... BULK COLLECT INTO
的示例。
DECLARE
TYPE t_rowtype IS TABLE OF test%rowtype INDEX BY PLS_INTEGER;
r t_rowtype;
BEGIN
-- DELETE/INSERT/UPDATE/EXECUTE IMMEDIATE 时支持 RETURNING 语法
UPDATE test SET id = id + 1 RETURNING * BULK COLLECT INTO r;
FOR i IN r.FIRST..r.LAST LOOP
RAISE NOTICE '%', r(i);
END LOOP;
END;
结果显示如下:
NOTICE: (2,a)
NOTICE: (3,b)
DO
FORALL
FORALL
语句的功能与FOR .. LOOP
类似,但它有一些额外的用法和限制。其三种声明语法如下:
-- 遍历下界..上界的写法
FORALL i IN lower_bound..upper_bound
single SQL -- 仅允许使用单条 SQL 或动态 SQL 语句
-- 遍历索引的写法 (对于关联数组来说,仅支持使用索引为数值的关联数组)
FORALL i IN INDICES OF collection
single SQL
-- 遍历值的写法 (仅支持使用元素类型为数值类型的集合变量)
FORALL i IN VALUES OF collection
single SQL
您可以通过一个特殊的关联数组SQL%BULK_ROWCOUNT
来访问每条成功执行语句影响的行数,示例如下。
-- 前置准备
DELETE FROM test;
INSERT INTO test VALUES (1, 'a'), (1, 'b'), (2, 'c');
DECLARE
TYPE nt_type IS TABLE OF INT;
nt nt_type := nt_type(1, 2);
BEGIN
FORALL i IN 1..2
UPDATE test set id = id + 10 WHERE id = nt(i);
-- 预期第一次更新2行, 第二次更新1行
FOR i in SQL%BULK_ROWCOUNT.first..SQL%BULK_ROWCOUNT.last LOOP
RAISE NOTICE '%', SQL%BULK_ROWCOUNT(i);
END LOOP;
END;
结果显示如下:
NOTICE: 2
NOTICE: 1
DO
正常情况下,FORALL
语句会在循环执行的SQL语句第一次出现异常时抛出异常。如果您声明了EXCEPTION
块,则会进入异常处理的流程。如果您想跳过抛出异常的SQL语句继续循环执行,您需要确认数据库已经打开语句级事务,并使用SAVE EXCEPTIONS
的语法。此时,FORALL
语句会遍历完所有的变量,执行完所有SQL,然后抛出FORALL_DML_ERROR
异常。以下是一个保存异常并继续执行的示例。
FORALL i IN lower_bound..upper_bound SAVE EXCEPTIONS
single SQL
如果在执行的过程中出现异常,您可以通过另一个特殊的关联数组SQL%BULK_EXCEPTIONS
来访问每条出错语句的具体错误信息。以下是一个查看异常SQL错误信息的示例。
-- 保证语句级别事务功能已经打开
-- 前置准备, 创建一个具有唯一约束的表
CREATE TABLE test_unique (id INT primary key);
DECLARE
TYPE nt_int IS TABLE OF INT;
nt nt_int := nt_int(1, 1, 2, 2, 3);
BEGIN
-- 在主键约束下, 第二条和第四条会插入失败
FORALL i IN 1..nt.COUNT SAVE EXCEPTIONS
INSERT INTO test_unique VALUES(nt(i));
EXCEPTION
WHEN FORALL_DML_ERROR THEN
RAISE NOTICE 'SQLCODE: %, SQLERRM: %', SQLCODE, SQLERRM;
FOR i IN 1..SQL%BULK_EXCEPTIONS.COUNT LOOP
RAISE NOTICE 'no: %', i;
RAISE NOTICE 'err_index: %', SQL%BULK_EXCEPTIONS(i).ERROR_INDEX;
RAISE NOTICE 'err_code: %', SQL%BULK_EXCEPTIONS(i).ERROR_CODE;
RAISE NOTICE 'err_msg: %', SQL%BULK_EXCEPTIONS(i).ERROR_MSG;
RAISE NOTICE '---------------';
END LOOP;
END;
-- 查看插入情况
SELECT * FROM test_unique;
结果显示如下:
-- PL/SQL 的输出
NOTICE: SQLCODE: P0005, SQLERRM: save exceptions raised in FORALL statememt
NOTICE: no: 1
NOTICE: err_index: 2
NOTICE: err_code: 23505
NOTICE: err_msg: duplicate key value violates unique constraint "test_unique_pkey"
NOTICE: ---------------
NOTICE: no: 2
NOTICE: err_index: 4
NOTICE: err_code: 23505
NOTICE: err_msg: duplicate key value violates unique constraint "test_unique_pkey"
NOTICE: ---------------
DO
-- SELECT 的输出
id
----
1
2
3
(3 rows)
当使用FORALL
或FOR
语句进行循环遍历集合变量时,需要考虑集合变量中是否存在delete
过的占位符,以防出现访问出错的问题。
INSERT/UPDATE
由于记录类型在PL/SQL中支持如下语法。
-- 使用记录变量插入 test 表
INSERT INTO test VALUES record;
-- 使用记录变量更新 test 表
UPDATE table SET ROW = record;
因此,对于元素类型为记录类型的集合变量,也可以使用类似的语法与表进行交互。
-- 清空 test 表
DELETE FROM test;
DECLARE
TYPE r_type IS RECORD(id INT, name VARCHAR(10));
TYPE nt_type IS TABLE OF r_type;
nt nt_type := nt_type(r_type(25, 'y'));
BEGIN
-- 使用集合变量的元素插入 test 表
INSERT INTO test VALUES nt(1);
END;
-- 查看插入结果
SELECT * from test;
结果显示如下:
id | name
----+------
25 | y
(1 row)
使用特殊语法更新表。
DECLARE
TYPE r_type IS RECORD(id INT, name VARCHAR(10));
TYPE nt_type IS TABLE OF r_type;
nt nt_type := nt_type(r_type(26, 'z'));
BEGIN
-- 使用集合变量的元素更新 test 表
UPDATE test SET ROW = nt(1);
END;
-- 查看结果
SELECT * from test;
结果显示如下:
id | name
----+------
26 | z
(1 row)