文章目录
- 概述
- 开发工具
- ltsql
- PL/SQL基础
- 程序结构
- 匿名块
- 存储过程
- 函数
- 数据类型
- 控制语句
- 条件语句
- IF 条件语句
- CASE条件语句
- 循环语句
- 简单LOOP
- FOR LOOP
- WHILE循环
- 循环退出
- 循环继续
- 循序控制
- 静态语句
- 动态语句
- 游标
- 隐式游标
- 隐式游标属性
- 隐式游标的异常处理
- 显式游标
- 声明显式游标
- 打开显式游标
- 使用显式游标获取数据
- 关闭显式游标
- 显式游标属性
- 游标变量
- 创建游标变量
概述
LightDB 数据库系统的PL/oraSQL(兼容Oracle PL/SQL) 是一个可加载的过程化语言,它用于与 Oracle PL/SQL 应用程序兼容。 后面章节PL/oraSQL都简称为PL/SQL
开发工具
ltsql
ltsql是一个LightDB的基于终端的前端。它能让你交互式地键入查询,把它们发送给LightDB,并且查看执行结果;也可以让输入来自于一个文件或者命令行参数。ltsql随着lightdb服务器的安装而自动安装。PL/SQL应用程序可以在ltsql终端下进行命令的执行。
ltsql --help
可以查看ltsql的参数说明,后续讲解的PL/SQL功能都将基于ltsql进行内容介绍和效果展示。
PL/SQL基础
程序结构
PL/SQL程序主要三种类型的程序,分别是匿名块,存储过程和存储函数。匿名块无需提前定义,可以直接运行,存储过程和存储函数需要提前创建,然后再进行调用。
匿名块
一个完整的匿名块由三个部分组成。
DECLARE 声明部分
BEGIN 执行部分
EXCEPTION 异常处理
END;
- 声明部分:以关键字DECLARE开始,以BEGIN结束。该 部分主要用于声明变量、常量、数据类型、游标、异常处理名 称和本地(局部)子程序定义等。
- 执行部分:是PLoraSQLs的功能实现部分,以关键字 BEGIN开始,以EXCEPTION或END结束。该部分通过变量赋值、流 程控制、数据查询、数据操纵、数据定义、事务控制、游标处理等操作实现块的功能。
- 异常部分:以关键字EXCEPTION开始,以END结束。当执行部分发生异常时,会进入异常部分进行执行,否则不会进入异常部分进行。
存储过程
结构同匿名块,DECLARE是可选项,如下示例:
CREATE OR REPLACE PROCEDURE remove_emp (employee_id NUMBER) AS声明部分
BEGIN 执行部分
EXCEPTION 异常处理
END;
/
函数
结构同匿名块和存储过程,相比于存储过程,需要声明一个返回值类型,如下示例:
CREATE OR REPLACE FUNCTION func_name (arg int) RETURN NUMBER AS声明部分
BEGIN 执行部分
EXCEPTION 异常处理
END;
/
数据类型
在PL/SQL中声明变量、常量、参数、函数返回值时都需要指定数据类型,以确定数据的存储格式、有效取值以及可以进行操作的类型。
- 内建数据类型
- PL/SQL拓展数据类型
- PLS_INTEGER
PLS_INTEGER 和 INT4 是完全相同的。PLS_INTEGER 存储有符号整数,范围介于 -2,147,483,648 到 2,147,483,647,占 32 个比特位。 - STRING
- 用户自定义数据类型
控制语句
控制结构可能是PL/SQL中最有用(也是最重要)的部分。使用PL/SQL的控制结构,可以以非常灵活和强大的方式操纵LightDB数据。
条件语句
IF 条件语句
- IF THEN 语句
语法结构
IF condition THENstatements
END IF;
示例
CREATE OR REPLACE PROCEDURE p_test(val int) AS
BEGINIF val < 0 THENDBMS_OUTPUT.PUT_LINE('val < 0');END IF;
END;
/
- IF THEN ELSE 语句
语法结构
IF condition THENstatements
ELSEelse_statements
END IF;
示例
CREATE OR REPLACE PROCEDURE p_test(val int) AS
BEGINIF val < 0 THENDBMS_OUTPUT.PUT_LINE('val < 0');ELSEDBMS_OUTPUT.PUT_LINE('val >= 0');END IF;
END;
/
- IF THEN ELSIF 语句
语法结构
IF condition_1 THENstatements_1
ELSIF condition_2 THENstatements_2
[ ELSIF condition_3 THENstatements_3
]...
[ ELSEelse_statements
]
END IF;
示例
CREATE OR REPLACE PROCEDURE p_test(val int) AS
BEGINIF val < 0 THENDBMS_OUTPUT.PUT_LINE('val < 0');ELSEIF val > 0 THENDBMS_OUTPUT.PUT_LINE('val > 0');ELSEDBMS_OUTPUT.PUT_LINE('val = 0');END IF;
END;
/
输出结果
lightdb@ora=# call p_test(-1);
val < 0
CALL
lightdb@ora=# call p_test(0);
val = 0
CALL
lightdb@ora=# call p_test(1);
val > 0
CALL
CASE条件语句
- 简单CASE语句
语法结构
CASE search-expressionWHEN expression [, expression [ ... ]] THENstatements[ WHEN expression [, expression [ ... ]] THENstatements... ][ ELSEstatements ]
END CASE;
简单的CASE形式提供了基于操作数的相等性条件执行。 search-expression
被评估(一次)并逐个与WHEN子句中的每个expression
进行比较。 如果找到匹配项,则执行相应的statements
,然后控制传递到 END CASE之后的下一条语句。(后续的WHEN表达式不会被评估。)如果找不到匹配项,则执行ELSE statements
;但是如果没有ELSE
,则会引发CASE_NOT_FOUND
异常。
示例
CREATE OR REPLACE PROCEDURE print_grade(grade char) AS
BEGINCASE gradeWHEN 'A' THEN DBMS_OUTPUT.PUT_LINE('优秀');WHEN 'B' THEN DBMS_OUTPUT.PUT_LINE('良好');WHEN 'C' THEN DBMS_OUTPUT.PUT_LINE('及格');WHEN 'D' THEN DBMS_OUTPUT.PUT_LINE('不及格');ELSE DBMS_OUTPUT.PUT_LINE('没有名次');END CASE;
END;
/或CREATE OR REPLACE PROCEDURE print_grade(grade char) AS
BEGINCASE gradeWHEN 'A' THEN DBMS_OUTPUT.PUT_LINE('优秀');WHEN 'B' THEN DBMS_OUTPUT.PUT_LINE('良好');WHEN 'C' THEN DBMS_OUTPUT.PUT_LINE('及格');WHEN 'D' THEN DBMS_OUTPUT.PUT_LINE('不及格');END CASE;
EXCEPTIONWHEN CASE_NOT_FOUND THENDBMS_OUTPUT.PUT_LINE('没有名次');
END;
/
输出结果:
lightdb@ora=# CALL print_grade('A');
优秀
CALL
lightdb@ora=# CALL print_grade('b');
没有名次
CALL
lightdb@ora=# CALL print_grade('B');
良好
CALL
lightdb@ora=# CALL print_grade('C');
及格
CALL
lightdb@ora=# CALL print_grade('D');
不及格
CALL
lightdb@ora=# CALL print_grade('E');
没有名次
CALL
- 搜索式CASE语句
语法结构
CASEWHEN boolean-expression THENstatements[ WHEN boolean-expression THENstatements... ][ ELSEstatements ]
END CASE;
CASE 的搜索形式根据布尔表达式的真值提供条件执行。 每个 WHEN 子句的 boolean-expression
会依次被评估, 直到找到一个产生 true 的表达式。然后执行相应的 statements
, 然后控制权转移到 END CASE 后的下一条语句。 (后续的 WHEN 表达式不会被评估。) 如果找不到真实结果,则会执行 ELSE statements
; 但是如果没有 ELSE,则会引发 CASE_NOT_FOUND 异常。
循环语句
简单LOOP
语法结构
[ <<label>> ]
LOOPstatements
END LOOP [ label ];
LOOP 定义了一个无条件循环,直到被 EXIT 或 RETURN 语句终止
示例
CREATE TABLE stduents(s_id integer, s_name varchar(64));
INSERT INTO stduents(s_id, s_name) VALUES(1, '张三');
INSERT INTO stduents(s_id, s_name) VALUES(2, '李四');DECLAREx NUMBER := 0;
BEGINLOOPDBMS_OUTPUT.PUT_LINE ('Inside loop: x = ' || TO_CHAR(x));x := x + 1;IF x > 3 THENEXIT;END IF;END LOOP;-- After EXIT, control resumes hereDBMS_OUTPUT.PUT_LINE(' After loop: x = ' || TO_CHAR(x));
END;
/
输出结果
Inside loop: x = 0
Inside loop: x = 1
Inside loop: x = 2
Inside loop: x = 3After loop: x = 4
DO
FOR LOOP
语法结构
[ <<label>> ]
FOR name IN [ REVERSE ] expression .. expression [ BY expression ] LOOPstatements
END LOOP [ label ];
这种形式的FOR创建一个循环,遍历整数值的范围。 变量name会自动定义为integer类型, 仅在循环内存在(循环内会忽略任何现有的该变量的定义)。 指定范围的两个表达式在进入循环时只会计算一次。 如果没有指定BY子句,则迭代步长为1, 否则为BY子句中指定的值,该值也会在循环进入时计算一次。 如果指定了REVERSE,则在每次迭代后,步长值会被减去而不是加上。
示例
BEGINDBMS_OUTPUT.PUT_LINE ('lower_bound < upper_bound');FOR i IN 1..3 LOOPDBMS_OUTPUT.PUT_LINE (i);END LOOP;DBMS_OUTPUT.PUT_LINE ('lower_bound = upper_bound');FOR i IN 2..2 LOOPDBMS_OUTPUT.PUT_LINE (i);END LOOP;DBMS_OUTPUT.PUT_LINE ('lower_bound > upper_bound');FOR i IN REVERSE 3..1 LOOPDBMS_OUTPUT.PUT_LINE (i);END LOOP;
END;
/
输出结果
lower_bound < upper_bound
1
2
3
lower_bound = upper_bound
2
lower_bound > upper_bound
3
2
1
DO
WHILE循环
[ <<label>> ]
WHILE boolean-expression LOOPstatements
END LOOP [ label ];
WHILE 语句会重复执行一系列语句,只要 boolean-expression 评估为 true。该表达式在每次进入循环体之前进行检查。
示例
DECLAREdone BOOLEAN := TRUE;
BEGINWHILE done LOOPDBMS_OUTPUT.PUT_LINE ('进入循环1');done := FALSE; -- This assignment is not made.END LOOP;WHILE NOT done LOOPDBMS_OUTPUT.PUT_LINE ('进入循环2');done := TRUE;END LOOP;DBMS_OUTPUT.PUT_LINE('退出了循环');
END;
/
输出结果
进入循环1
进入循环2
退出了循环
DO
循环退出
- EXIT
EXIT语句无条件退出循环的当前迭代,并将控制权转移到当前循环或封闭标记循环的末尾。 - EXIT WHEN
当WHEN子句中的条件为true时,EXIT WHEN语句退出循环的当前迭代,并将控制权转移到当前循环或封闭标记循环的末尾。
循环继续
- CONTINUE
CONTINUE语句无条件退出循环的当前迭代,并将控制权转移到当前循环或封闭标记循环的下一次迭代。 - CONTINUE WHEN
当WHEN子句中的条件为true时,CONTINUE WHEN语句退出循环的当前迭代,并将控制权转移到当前循环或封闭标记循环的下一次迭代。
循序控制
-
GOTO语句
GOTO语句无条件地将控制权转移到标签。标签在其作用域中必须是唯一的,并且必须位于可执行语句或PL/SQL块之前。运行时,GOTO语句将控制权转移到标记的语句或块。 -
NULL语句
NULL语句只将控制权传递给下一个语句。有些语言将这样的指令称为no-op(无操作)。
示例 GOTO到NULL
DECLAREdone BOOLEAN;
BEGINFOR i IN 1..50 LOOPDBMS_OUTPUT.PUT_LINE('i=' || i);IF i = 3 THENGOTO end_loop;END IF;END LOOP;<<end_loop>>NULL;
END;
/
利用GOTO 到NULL语句的标签来提前结束循环。
静态语句
静态SQL是一种PL/SQL功能,它允许在PL/SQL语句中直接使用SQL语法。
- SELECT
查询语句 - INSERT
插入语句 - UPDATE
更新语句 - DELETE
删除语句 - MERGE
MERGE语句 - WITH
WITH语句 - COMMIT
提交语句 - ROLLBACK
回滚语句 - SAVEPOINT
保存点语句 - SET TRANSACTION
设置事务隔离级别语句
示例1 - 增删改查
CREATE TABLE stduents(sid integer, sname varchar2(64), age integer);DECLAREv_id integer;v_name varchar2(64);v_age integer;v_count integer;
BEGIN-- 插入数据INSERT INTO stduents(sid, sname, age) VALUES(1, '张三', 18);INSERT INTO stduents(sid, sname, age) VALUES(2, '李四', 19);-- 查询数据SELECT COUNT(*) INTO v_count FROM stduents;DBMS_OUTPUT.PUT_LINE('总记录数:' || v_count);FOR i IN 1 .. 2 LOOPSELECT sname INTO v_name FROM stduents WHERE sid = i;DBMS_OUTPUT.PUT_LINE('sid=' || i || ' sname=' || v_name);UPDATE stduents SET age = age + i WHERE sid = i;END LOOP;FOR i IN 1 .. 2 LOOPSELECT age INTO v_age FROM stduents WHERE sid = i;DBMS_OUTPUT.PUT_LINE('sid=' || i || ' age=' || v_age);UPDATE stduents SET age = age + i WHERE sid = i;END LOOP;-- 删除数据DELETE FROM stduents;SELECT COUNT(*) INTO v_count FROM stduents;DBMS_OUTPUT.PUT_LINE('总记录数:' || v_count);END;
/
输出结果
总记录数:2
sid=1 sname=张三
sid=2 sname=李四
sid=1 age=19
sid=2 age=21
总记录数:0
DO
示例2 - MERGE使用
当使用MERGE语句时,你通常想将源表(people_source)中的数据与目标表(people_target)进行同步,如下用例:
CREATE TABLE people_source ( person_id INTEGER NOT NULL PRIMARY KEY, name VARCHAR2(20) NOT NULL
);CREATE TABLE people_target ( person_id INTEGER NOT NULL PRIMARY KEY, name VARCHAR2(20) NOT NULL
);INSERT INTO people_target VALUES (1, 'John Smith');
INSERT INTO people_target VALUES (2, 'alice jones');
INSERT INTO people_source VALUES (2, '张三');
INSERT INTO people_source VALUES (3, '李四');
INSERT INTO people_source VALUES (4, '王五');
匿名块更新
BEGINMERGE INTO people_target pt USING (SELECT person_id, name FROM people_source) ps ON (pt.person_id = ps.person_id) WHEN MATCHED THEN UPDATE SET pt.name = ps.name WHEN NOT MATCHED THEN INSERT (pt.person_id, pt.name) VALUES (ps.person_id, ps.name);
END;
/
查询更新结果
select * from people_source ;person_id | name
-----------+------2 | 张三3 | 李四4 | 王五
(3 rows)select * from people_target;person_id | name
-----------+------------1 | John Smith2 | 张三4 | 王五3 | 李四
(4 rows)
动态语句
动态SQL是一种用于在运行时生成和运行SQL语句的编程方法。
当编写通用且灵活的程序(如特设查询系统)、编写必须运行数据库定义语言(DDL)语句的程序,或者在编译时不知道SQL语句的全文或其输入和输出变量的数量或数据类型时,它非常有用。
- EXECUTE IMMEDIATE
如果动态语句没有绑定变量的占位符那么EXECUTE IMMEDIATE不需要从句。
示例1 动态语句不包含占位符
DECLARE table_name VARCHAR2(50) := 'my_table'; sql_stmt VARCHAR2(4000); v_retrieved_name VARCHAR2(100);
BEGIN -- 构建创建表的SQL语句 sql_stmt := 'CREATE TABLE ' || table_name || ' ( ' || 'ID NUMBER PRIMARY KEY, ' || 'NAME VARCHAR2(100), ' || 'CREATED_DATE DATE ' || ')'; -- 执行创建表的SQL语句 EXECUTE IMMEDIATE sql_stmt; -- 构建插入数据的SQL语句(硬编码值) sql_stmt := 'INSERT INTO ' || table_name || ' (ID, NAME, CREATED_DATE) VALUES (1, ''张三'', SYSDATE)'; -- 执行插入数据的SQL语句 EXECUTE IMMEDIATE sql_stmt; -- 输出插入成功信息 DBMS_OUTPUT.PUT_LINE('数据插入成功。'); -- 构建查询数据的SQL语句(硬编码ID) sql_stmt := 'SELECT NAME FROM ' || table_name || ' WHERE ID = 1'; -- 执行查询数据的SQL语句,并将结果存储在变量中 EXECUTE IMMEDIATE sql_stmt INTO v_retrieved_name; -- 输出查询到的数据 DBMS_OUTPUT.PUT_LINE('从表中检索到的名字是: ' || v_retrieved_name); EXCEPTION WHEN OTHERS THEN -- 处理异常 DBMS_OUTPUT.PUT_LINE('发生错误: ' || SQLERRM);
END;
/
示例2 动态语句包含占位符
CREATE TABLE employees ( emp_id NUMBER PRIMARY KEY, first_name VARCHAR2(50), last_name VARCHAR2(50), hire_date DATE
);DECLARE v_emp_id NUMBER := 1001; v_first_name VARCHAR2(50) := 'John'; v_last_name VARCHAR2(50) := 'Doe'; v_hire_date DATE := SYSDATE;
BEGIN EXECUTE IMMEDIATE 'INSERT INTO employees (emp_id, first_name, last_name, hire_date) VALUES (:1, :2, :3, :4)' USING v_emp_id, v_first_name, v_last_name, v_hire_date;
END;
/
输出结果
select * from employees;emp_id | first_name | last_name | hire_date
--------+------------+-----------+---------------------1001 | John | Doe | 2024-05-31 00:00:00
在这个示例中,:1、:2、:3和:4是占位符,它们分别被USING子句中的v_emp_id、v_first_name、v_last_name和v_hire_date变量替换。这种方法允许你在动态SQL中安全地插入数据,因为它可以防止SQL注入攻击。
注意:在实际应用中,确保你的变量和占位符的数量和类型匹配,否则你会遇到运行时错误。
示例3 动态语句检索值
DECLAREv_emp_id NUMBER; v_first_name VARCHAR2(50); v_last_name VARCHAR2(50); v_hire_date DATE; v_sql VARCHAR2(1024);v_emp employees%rowtype;
BEGINv_sql := 'select emp_id, first_name, last_name, hire_date from employees where emp_id = :1';execute immediate v_sql into v_emp_id, v_first_name, v_last_name, v_hire_date using 1001;dbms_output.put_line('emp_id=' || v_emp_id || ' first_name=' || v_first_name || ' last_name=' || v_last_name || ' hire_date=' || v_hire_date);execute immediate v_sql into v_emp using 1001;dbms_output.put_line('emp_id=' || v_emp.emp_id || ' first_name=' || v_emp.first_name || ' last_name=' || v_emp.last_name || ' hire_date=' || v_emp.hire_date);execute immediate v_sql into v_emp_id, v_first_name, v_last_name, v_hire_date using 1002;dbms_output.put_line('emp_id=' || v_emp_id || ' first_name=' || v_first_name || ' last_name=' || v_last_name || ' hire_date=' || v_hire_date);EXCEPTIONWHEN NO_DATA_FOUND THENDBMS_OUTPUT.PUT_LINE('未找到数据, errcode=' || sqlcode || ' errmsg=' || sqlerrm );
END;
/
游标
隐式游标
隐式游标是由PL/SQL构建和管理的会话游标。每次运行SELECT或DML语句时,PL/SQL都会打开一个隐式游标。您无法控制隐式游标,但可以从其属性获取信息。
隐式游标属性
变量名 | 说明 |
---|---|
SQL%ISOPEN | 总是返回FALSE,因为一个隐式游标在关联的语句执行完后总是会关闭 |
SQL%FOUND | 如果没有执行SELECT或者DML语句则为NULL,如果最近执行的SELECT或者DML语句返回了行记录则为TRUE,如果最近执行的SELECT或者DML语句没有返回行记录则为FALSE |
SQL%NOTFOUND | 如果没有执行SELECT或者DML语句则为NULL,如果最近执行的SELECT或者DML语句返回了行记录则为FALSE,如果最近执行的SELECT或者DML语句没有返回行记录则为TRUE |
SQL%ROWCOUNT | 如果没有执行SELECT或者DML语句)则为NULL,如果最近执行的SELECT或者DML语句返回了行记录则为获取的行记录数 |
示例
BEGINIF SQL%ISOPEN THENDBMS_OUTPUT.PUT_LINE('isopen is true');ELSEDBMS_OUTPUT.PUT_LINE('isopen is false');END IF;
END;
/BEGINIF SQL%FOUND THENDBMS_OUTPUT.PUT_LINE('found is true');ELSIF SQL%FOUND IS NULL THENDBMS_OUTPUT.PUT_LINE('found is null');ELSEDBMS_OUTPUT.PUT_LINE('found is false');END IF;
END;
/BEGINIF SQL%NOTFOUND THENDBMS_OUTPUT.PUT_LINE('notfound is true');ELSIF SQL%NOTFOUND IS NULL THENDBMS_OUTPUT.PUT_LINE('notfound is null');ELSEDBMS_OUTPUT.PUT_LINE('notfound is false');END IF;
END;
/DECLAREv_num integer;
BEGINv_num := SQL%ROWCOUNT;DBMS_OUTPUT.PUT_LINE('rowcount is ' || v_num);
END;
/DECLAREv_num integer;v_emp employees%rowtype;
BEGINv_num := SQL%ROWCOUNT;DBMS_OUTPUT.PUT_LINE('select 之前 rowcount 值是 ' || v_num);SELECT * INTO v_emp FROM employees WHERE emp_id = 1001;v_num := SQL%ROWCOUNT;DBMS_OUTPUT.PUT_LINE('select 之后 rowcount 值是 ' || v_num);
END;
/
注意: 使用隐式游标属性和SELECT或DML语句之间不要插入DBMS_OUTPUT.PUT_LINE指令,这个指令在LightDB中会影响隐式游标属性值。
隐式游标的异常处理
查询语句中找不到与我们条件相匹配的行,在这里情况下,数据库将引发名为NO_DATA_FOUND的异常
查询语句返回多行情况下,数据库将引发为TO_MANY_ROWS的异常
示例1 NO_DATA_FOUND的异常
select * from employees;emp_id | first_name | last_name | hire_date
--------+------------+-----------+---------------------1002 | John2 | Doe2 | 2024-06-01 04:31:301001 | John | Doe | 2024-06-01 00:00:00DECLAREv_emp employees%rowtype;
BEGINSELECT * INTO v_emp FROM employees WHERE emp_id = 1003;
EXCEPTIONWHEN NO_DATA_FOUND THENDBMS_OUTPUT.PUT_LINE('不能找到数据');WHEN TOO_MANY_ROWS THENDBMS_OUTPUT.PUT_LINE('找到的记录数大多');WHEN OTHERS THENDBMS_OUTPUT.PUT_LINE('未知错误');
END;
/
输出结果
不能找到数据
示例2 TO_MANY_ROWS的异常
DECLAREv_emp employees%rowtype;
BEGINSELECT * INTO v_emp FROM employees;
EXCEPTIONWHEN NO_DATA_FOUND THENDBMS_OUTPUT.PUT_LINE('不能找到数据');WHEN TOO_MANY_ROWS THENDBMS_OUTPUT.PUT_LINE('找到的记录数大多');WHEN OTHERS THENDBMS_OUTPUT.PUT_LINE('未知错误');
END;
/
输出结果
找到的记录数大多
显式游标
显式游标是一种会话游标,由您构造和管理。 您必须声明和定义一个显式游标,给它起一个名称并将其与一个查询关联(通常,查询会返回多行)。 然后,您可以通过以下两种方式之一处理查询结果集:
- open 显式游标,从结果集中 fetch 行,并 close 显式游标。
- 在游标 FOR LOOP 语句中使用显式游标。
声明显式游标
您可以先声明显式游标,然后在同一块、子程序或包中稍后定义它,也可以同时声明和定义它。
显式游标声明,仅声明游标,其语法如下:
CURSOR cursor_name parameter_list RETURN return_type;
要实际使用显式游标,可以直接定义它而不声明它。
在下面的示例中,声明并定义了三个显式游标:
DECLARECURSOR c1 RETURN employees%ROWTYPE; -- 声明 c1CURSOR c2 IS -- 声明并定义 c2SELECT emp_id,first_name, last_name, hire_date s FROM employeesWHERE emp_id > 2000; CURSOR c1 RETURN employees%ROWTYPE IS -- 定义 c1,SELECT emp_id,first_name, last_name, hire_date s FROM employeesWHERE emp_id = 110;CURSOR c3 RETURN employees%ROWTYPE; -- 声明 c3CURSOR c3 IS -- 定义 c3,SELECT emp_id,first_name, last_name, hire_date s FROM employeesWHERE emp_id = 110;
BEGINNULL;
END;
/
打开显式游标
声明并定义了显式游标后,可以使用OPEN语句打开它。游标名忽略大小写。 定义的游标不带参数,打开时也可以使用游标名加()的形式。
下面是打开游标的几种方式:
open C_tmp;
open c_tmp;
open c_tmp();
使用显式游标获取数据
打开显式游标后,可以使用FETCH语句获取查询结果集的行。返回一行的FETCH语句的基本语法如下:
FETCH cursor_name INTO into_clause;
into_clause 是变量列表或单个记录变量。 对于查询返回的每一列,变量列表或记录必须有一个相应的类型兼容变量或字段。 在声明用于FETCH语句的变量和记录时,%TYPE和%ROWTYPE属性非常有用。
FETCH语句检索结果集的当前行,将该行的列值存储到变量或记录中,并将游标移动到下一行。
通常,您在LOOP语句中使用FETCH语句,当 FETCH语句运行完所有行时退出循环。 要检测此退出条件,请使用游标属性%NOTFOUND。当FETCH语句返回没有行时,PL/SQL不会引发异常。
关闭显式游标
使用CLOSE语句关闭打开的显式游标,从而允许重用其资源。关闭游标后,无法从其结果集中获取记录或引用其属性。
close c_tmp;
显式游标属性
同隐式游标属性,游标名%isopen
,游标名%found
,游标名%notfound
,游标名%rowcount
示例1 无参数显式游标
DECLAREv_emp employees%rowtype;CURSOR cur IS select emp_id,first_name,last_name,hire_date from employees order by emp_id asc;
BEGINIF NOT cur%isOPEN THENOPEN cur;END IF;LOOPFETCH cur INTO v_emp;EXIT WHEN cur%NOTFOUND;DBMS_OUTPUT.PUT_LINE('emp_id=' || v_emp.emp_id || ' first_name=' || v_emp.first_name || ' last_name=' || v_emp.last_name || ' hire_date' || v_emp.hire_date);END LOOP;CLOSE cur;DBMS_OUTPUT.PUT_LINE('查询结束');
END;
/
输出结果
emp_id=1001 first_name=John last_name=Doe hire_date2024-06-01 00:00:00
emp_id=1002 first_name=John2 last_name=Doe2 hire_date2024-06-01 04:31:30
查询结束
示例2 带参数的显示游标
DECLAREv_emp employees%rowtype;CURSOR cur(v_id number) IS select emp_id,first_name,last_name,hire_date from employees where emp_id = v_id order by emp_id asc;
BEGINIF NOT cur%isOPEN THENOPEN cur(1001);END IF;LOOPFETCH cur INTO v_emp;EXIT WHEN cur%NOTFOUND;DBMS_OUTPUT.PUT_LINE('emp_id=' || v_emp.emp_id || ' first_name=' || v_emp.first_name || ' last_name=' || v_emp.last_name || ' hire_date' || v_emp.hire_date);END LOOP;CLOSE cur;DBMS_OUTPUT.PUT_LINE('查询结束');
END;
/
输出结果
emp_id=1001 first_name=John last_name=Doe hire_date2024-06-01 00:00:00
查询结束
DO
游标变量
游标变量类似于显式游标,但具有以下特点:
它不限于一个查询。 您可以为一个查询打开一个游标变量,处理结果集,然后将游标变量用于另一个查询。
- 您可以为它赋值。
- 您可以在表达式中使用它。
- 它可以是子程序参数。 您可以使用游标变量在子程序之间传递查询结果集。
- 它可以是宿主变量。
- 您可以使用游标变量在PL/SQL存储的子程序和它们的客户端之间传递查询结果集。
- 它无法接受参数。 您不能向游标变量传递参数,但可以将整个查询传递给它。 查询可以包括变量。
创建游标变量
要创建游标变量,请声明一个预定义类型 SYS_REFCURSOR 的变量, 或者定义一个 REF CURSOR 类型,然后声明一个该类型的变量。
REF CURSOR 类型定义的基本语法如下:
TYPE type_name IS REF CURSOR RETURN return_type%TYPE
游标变量声明。例如:
DECLARETYPE empcurtyp IS REF CURSOR RETURN employees%ROWTYPE;cursor1 empcurtyp;
BEGINNULL;
END;
/
具有用户定义返回类型的游标变量。例如:
DECLARETYPE EmpRecTyp IS RECORD (employee_id NUMBER,last_name VARCHAR2(25),salary NUMBER(8,2));TYPE EmpCurTyp IS REF CURSOR RETURN EmpRecTyp;emp_cv EmpCurTyp;
BEGINNULL;
END;
/
使用sys_refcursor作为参数变量。例如:
CREATE OR REPLACE PROCEDURE p_getemployees(cur OUT sys_refcursor) IS
BEGINOPEN cur FOR select emp_id,first_name,last_name,hire_date from employees order by emp_id asc;
END;
/DECLAREcur sys_refcursor;v_emp employees%rowtype;
BEGINp_getemployees(cur);LOOPFETCH cur INTO v_emp;EXIT WHEN cur%NOTFOUND;DBMS_OUTPUT.PUT_LINE('emp_id=' || v_emp.emp_id || ' first_name=' || v_emp.first_name || ' last_name=' || v_emp.last_name || ' hire_date' || v_emp.hire_date);END LOOP;CLOSE cur;DBMS_OUTPUT.PUT_LINE('查询结束');
END;
/