在本节和接下来的几节中,我们描述了所有由 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_expressions
INTOtarget
FROM ...; INSERT ... RETURNINGexpressions
INTOtarget
; UPDATE ... RETURNINGexpressions
INTOtarget
; DELETE ... RETURNINGexpressions
INTOtarget
;
其中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; /