在本节和接下来的几节中,我们描述了所有由 pl/sql 显式理解的语句类型。任何不被识别为这些语句类型之一的内容都被认为是 SQL 命令,并被发送到主数据库引擎执行。
一个给pl/sql变量赋值的语句写作如下:
variable:=expression;
如之前所述,这条语句中的表达式通过发送SQL SELECT命令到主数据库引擎进行评估。表达式必须产生一个单一的值(如果变量是行或记录变量,则可能是一行值)。目标变量可以是一个简单的变量(可选地带有块名称),行或记录变量的字段,或者是一个简单变量或字段的数组元素。
示例:
tax := subtotal * 0.06; my_record.user_id := 20;
pl/sql在匿名块、函数、存储过程,包中支持DML,
DML包括 INSERT, UPDATE,
and DELETE 语句。如果需要使用 SELECT 语句,
必须加上into子句,否则必然报错。
例子如下:
create table dml_table(id int, name varchar2(100), job varchar2(100), age int); insert into dml_table values (1, 'asda', 'gfdgd', 12); insert into dml_table values (2, 'sdfsd', 'cvxvx', 14); insert into dml_table values (3, 'uyiy', 'mmbv', 16); create or replace function support_dml return int as begin update dml_table set id = 4 where name = 'uyiy'; return 12; end; / select support_dml() from dual; declare retcode int := 1; begin retcode := support_dml(); dbms_output.put_line(retcode); end; /
产生单行结果(可能是多列)的SQL命令的结果可以赋值给一个记录变量、行类型变量或标量变量列表。这可以通过编写基本SQL命令并添加一个INTO子句来完成。例如:
SELECTselect_expressionsINTOtargetFROM ...; INSERT ... RETURNINGexpressionsINTOtarget; UPDATE ... RETURNINGexpressionsINTOtarget; DELETE ... RETURNINGexpressionsINTOtarget;
其中target可以是一个记录变量,一个行变量,或者是一个逗号分隔的简单变量和记录/行字段的列表。
pl/sql变量将被替换到查询的剩余部分中,并且计划被缓存,就像上面描述的那样,对于不返回行的命令。
这适用于带有RETURNING的SELECT、INSERT/UPDATE/DELETE以及返回行集结果的实用命令(例如EXPLAIN)。
除了INTO子句之外,SQL命令与在pl/sql外部编写的命令相同。
注意,这种带有INTO的SELECT的解释与LightDB的常规SELECT INTO命令截然不同,其中INTO目标是一个新创建的表。如果你想从pl/sql函数内部的SELECT结果创建一个表,请使用语法CREATE TABLE ... AS SELECT。
如果行或变量列表被用作目标,则查询的结果列必须与目标的结构在数量和数据类型上完全匹配,否则将发生运行时错误。当记录变量是目标时,它会自动将自己配置为查询结果列的行类型。
INTO子句可以出现在SQL命令的几乎任何位置。通常,它要么在SELECT命令中的select_expressions列表之前或之后,要么在其他命令类型的命令的末尾。建议你遵循这个惯例,以防pl/sql解析器在未来版本中变得更加严格。
如果INTO子句中未指定STRICT,则target将被设置为查询返回的第一行,如果查询未返回任何行,则设置为nulls。(请注意,“第一行”未定义,除非你使用了ORDER BY。)第一行后的任何结果行都将被丢弃。
您可以检查特殊的SQL%FOUND变量来确定是否返回了一行:
DROP TABLE t1;
DROP SEQUENCE t1_seq;
CREATE TABLE t1 (
id NUMBER(10),
description VARCHAR2(50),
CONSTRAINT t1_pk PRIMARY KEY (id)
);
CREATE SEQUENCE t1_seq;
INSERT INTO t1 VALUES (t1_seq.nextval, 'ONE');
INSERT INTO t1 VALUES (t1_seq.nextval, 'TWO');
INSERT INTO t1 VALUES (t1_seq.nextval, 'THREE');
RETURNING INTO子句允许我们返回受DML语句影响的行的列值。返回的数据可以是单列。例如,当我们使用序列插入数据来生成我们的主键值时,我们可以如下返回主键值:
DECLARE
l_id t1.id%TYPE;
BEGIN
INSERT INTO t1 VALUES (t1_seq.nextval, 'FOUR')
RETURNING id INTO l_id;
COMMIT;
DBMS_OUTPUT.put_line('ID=' || l_id);
END;
/
例如,语法也适用于更新和删除语句。
DECLARE
l_id t1.id%TYPE;
BEGIN
UPDATE t1
SET description = description
WHERE description = 'FOUR'
RETURNING id INTO l_id;
DBMS_OUTPUT.put_line('UPDATE ID=' || l_id);
DELETE FROM t1
WHERE description = 'FOUR'
RETURNING id INTO l_id;
DBMS_OUTPUT.put_line('DELETE ID=' || l_id);
COMMIT;
END;
/
通常情况下,你会想在pl/sql函数内部生成动态命令,即每次执行时都涉及不同的表或不同的数据类型。 pl/sql的正常尝试缓存命令计划在这种情况下无法工作。为了处理这种问题,提供了EXECUTE IMMEDIATE语句:
EXECUTE IMMEDIATEcommand-string[ [ BULK COLLECT ] INTOtarget] [ USINGexpression[, ... ] ];
其中command-string是一个表达式,产生一个包含要执行的命令的字符串(类型为varchar)。可选的target是一个记录变量、行变量或由简单变量和记录/行字段组成的逗号分隔列表,用于存储命令的结果。可选的USING表达式提供要插入命令的值。
在计算的命令字符串上不执行pl/sql变量的替换。任何必需的变量值必须在构建命令字符串时插入;或者你可以使用下面描述的参数。
此外,通过EXECUTE IMMEDIATE执行的命令没有计划缓存。相反,每次运行语句时,命令都会被计划。因此,命令字符串可以在函数内部动态创建,以执行对不同表和列的操作。
INTO子句指定SQL命令返回的行应分配到哪里。如果提供了行或变量列表,则它必须与查询结果的结构完全匹配(当使用记录变量时,它将自动配置自身以匹配结果结构)。如果返回多行,则仅将第一行分配给INTO变量。如果没有返回行,则将NULL分配给INTO变量。如果没有指定INTO子句,则丢弃查询结果。
BULK COLLECT INTO子句指定SQL命令返回的行应分配到哪里。如果返回多行,则将全部分配给BULK COLLECT INTO变量。如果没有返回行,则将NULL分配给BULK COLLECT INTO变量。如果没有指定BULK COLLECT INTO子句,则丢弃查询结果。
例如:
declare
type array_number is table of number;
type array_varchar is table of varchar2(255);
a_employee_id array_number := array_number();
a_first_name array_varchar := array_varchar();
BEGIN
dbms_output.put_line('----------multiple nested table---------');
execute IMMEDIATE 'WITH employeetemp AS (
SELECT employee_id AS eid, first_name AS fname FROM employees
)
SELECT eid, fname FROM employeetemp' bulk collect into a_employee_id, a_first_name ;
for i in a_employee_id.first .. a_employee_id.last loop
dbms_output.put_line(a_employee_id(i));
end loop;
for i in a_first_name.first .. a_first_name.last loop
dbms_output.put_line(a_first_name(i));
end loop;
end;
/
如果提供了STRICT选项,除非查询产生恰好一行,否则将报告错误。
命令字符串可以使用参数值,在命令中引用为:inserted_by、:inserted等。这些符号指的是在USING子句中提供的值。这种方法通常比将数据值作为varchar插入命令字符串更可取:它避免了将值转换为varchar并返回的运行时开销,并且由于不需要引用或转义,它更不容易受到SQL注入攻击的影响。例如:
EXECUTE IMMEDIATE 'SELECT count(*) FROM mytable WHERE inserted_by = :inserted_by AND inserted <= :inserted' INTO c USING checked_user, checked_date;
简单常量命令字符串和一些USING参数的EXECUTE IMMEDIATE(如上面的第一个示例)在功能上等同于直接在pl/sql中编写命令,并允许自动替换pl/sql变量。重要的区别在于,EXECUTE IMMEDIATE将在每次执行时重新计划命令,生成一个特定于当前参数值的计划;而pl/sql可能会创建一个通用计划并缓存它以供重用。在最佳计划强烈依赖于参数值的情况下,使用EXECUTE IMMEDIATE可以帮助确保不选择通用计划。
EXECUTE IMMEDIATE中目前不支持SELECT INTO;相反,执行一个简单的SELECT命令,并将INTO作为EXECUTE IMMEDIATE本身的一部分指定。
using 节点所使用的变量前面可以添加变量类型
in 和 out。
in 输入的变量类型表示不可以被更改,
out 输入的变量类型表示可以被更改。
例如:
CREATE OR REPLACE PROCEDURE TargetProcedure(
p_input1 IN VARCHAR2,
p_input2 IN NUMBER,
p_output1 OUT VARCHAR2,
p_output2 OUT NUMBER
) AS
BEGIN
DBMS_Output.PUT_LINE('TargetProcedure begin-> p_input1 '||' is: '||p_input1);
DBMS_Output.PUT_LINE('TargetProcedure begin-> p_input2 '||' is: '||p_input2);
DBMS_Output.PUT_LINE('TargetProcedure begin-> p_output1 '||' is: '||p_output1);
DBMS_Output.PUT_LINE('TargetProcedure begin-> p_output2 '||' is: '||p_output2);
p_output1 := 'Response from TargetProcedure: ' || p_input1;
p_output2 := p_input2 * 10;
DBMS_Output.PUT_LINE('TargetProcedure end-> p_input1 '||' is: '||p_input1);
DBMS_Output.PUT_LINE('TargetProcedure end-> p_input2 '||' is: '||p_input2);
DBMS_Output.PUT_LINE('TargetProcedure end-> p_output1 '||' is: '||p_output1);
DBMS_Output.PUT_LINE('TargetProcedure end-> p_output2 '||' is: '||p_output2);
END TargetProcedure;
/
CREATE OR REPLACE PROCEDURE MainProcedure(
main_input1 IN VARCHAR2,
main_input2 IN NUMBER,
main_output1 OUT VARCHAR2,
main_output2 OUT NUMBER
) AS
BEGIN
main_output2 := 0;
main_output1 := 'Operation successful';
-- Use EXECUTE IMMEDIATE to execute dynamic SQL and pass parameters
DBMS_Output.PUT_LINE('MainProcedure begin-> main_input1 '||' is: '||main_input1);
DBMS_Output.PUT_LINE('MainProcedure begin-> main_input2 '||' is: '||main_input2);
DBMS_Output.PUT_LINE('MainProcedure begin-> main_output1 '||' is: '||main_output1);
DBMS_Output.PUT_LINE('MainProcedure begin-> main_output2 '||' is: '||main_output2);
EXECUTE IMMEDIATE 'BEGIN TargetProcedure(:p_input1, :p_input2, :p_output1, :p_output2); END;'
USING IN main_input1, IN main_input2, OUT main_output1, OUT main_output2;
DBMS_Output.PUT_LINE('MainProcedure end-> main_input1 '||' is: '||main_input1);
DBMS_Output.PUT_LINE('MainProcedure end-> main_input2 '||' is: '||main_input2);
DBMS_Output.PUT_LINE('MainProcedure end-> main_output1 '||' is: '||main_output1);
DBMS_Output.PUT_LINE('MainProcedure end-> main_output2 '||' is: '||main_output2);
END MainProcedure;
/
call MainProcedure('xiaoming',123,'xiaohong',234);
有时候,一个不做任何事情的占位符语句是有用的。例如,它可以表示if/then/else语句中的一个分支是故意空的。为此,使用NULL语句:
NULL;
例如,以下两个代码片段是等效的:
declare y int := 10; x int := 10; BEGIN NULL; END; /
pipe row 语句处理字符串类型(或者可以隐式转换成字符串的类型),返回的结果类型必须是集合类型。
PIPE ROW (row);
row:返回给集合的元素,该元素可以为表达式(集合的元素与表达式的结果要支持隐式转换)。集合中元素目前仅支持简单字符串类型,不支持数值类型和复合类型。
使用方法,参考下面的代码:
CREATE OR REPLACE TYPE SPLIT_TBL as table of varchar2(32767);
CREATE OR REPLACE FUNCTION SPLIT
(
p_list in varchar2,
p_del in varchar2 := ','
) return split_tbl pipelined
is
l_idx pls_integer;
l_list varchar2(32767):= p_list;
l_value varchar2(32767);
begin
loop
l_idx :=instr(l_list,p_del);
if l_idx > 0then
pipe row(substr(l_list,1,l_idx-1));
l_list:= substr(l_list,l_idx+length(p_del));
else
pipe row(l_list);
exit;
end if;
end loop;
return;
end split;
/
select * from SPLIT('123,345,567',',');