存储过程官方文档
PLSQL的语法大全
语法|标识符|分隔符等等文档
存储过程也可以调用匿名存储过程,具体看官网
-- CREATE OR REPLACE 创建或者替换
CREATE OR REPLACE PROCEDURE P_TEST(
-- 这个可以是多个参数,用',' 分割
testParams IN varchar2,
returnMsg2233 IN OUT varchar2 -- 返回信息的方式111: 推荐来个返回信息,比较友好,可以在存储过程的逻辑设置该值,在调用结束后就可以得到值的内容
) IS
-- 定义的变量
LOOP_COUNT number DEFAULT 5;
error_message varchar2(512) DEFAULT '发生错误鸭!';
BEGIN
-- 代码逻辑
SELECT * FROM DUAL;
returnMsg2233 := '我执行成功了哦哦!!!'; -- 返回信息的方式111
DBMS_OUTPUT.PUT_LINE(returnMsg2233);-- 打印输出信息
-- 异常处理部分开始
EXCEPTION
WHEN VALUE_ERROR THEN
DBMS_OUTPUT.PUT_LINE(error_message); -- 输出错误信息
END;
CREATE OR REPLACE ... xxx ... -- 创建或者替换
DROP PROCEDURE TEST.P_TEST; -- 删除
ALTER PROCEDURE hr.remove_emp ... xxx ...; -- 更新
-- 使用call
CALL P_TEST('test','returnMsg');
-- 使用begin end;
BEGIN
P_TEST('test','returnMsg');
END;
IN
和out
用这个关键词可以控制,过程调用之前、期间和之后的参数值
IN
你可以使用,但是无法赋值OUT
为 OUT 参数赋值in/out
测试案例CREATE OR REPLACE PROCEDURE p (
a PLS_INTEGER, -- IN by default
b IN PLS_INTEGER,
c OUT PLS_INTEGER,
d IN OUT BINARY_FLOAT
) AUTHID DEFINER IS
BEGIN
-- 打印参数值
DBMS_OUTPUT.PUT_LINE('Inside procedure p:');
DBMS_OUTPUT.PUT('IN a = ');
DBMS_OUTPUT.PUT_LINE(NVL(TO_CHAR(a), 'NULL'));
DBMS_OUTPUT.PUT('IN b = ');
DBMS_OUTPUT.PUT_LINE(NVL(TO_CHAR(b), 'NULL'));
DBMS_OUTPUT.PUT('OUT c = ');
DBMS_OUTPUT.PUT_LINE(NVL(TO_CHAR(c), 'NULL'));
DBMS_OUTPUT.PUT_LINE('IN OUT d = ' || TO_CHAR(d));
-- 可以引用 IN 参数 a 和 b,
-- 但不能为它们赋值。
c := a+10; -- 为 OUT 参数赋值
d := 10/b; -- 将值赋给 IN OUT 参数
END;
in/out
测试案例-- 调用 out类型参数,必须先用DECLARE进行定义
DECLARE
aa CONSTANT PLS_INTEGER := 1;
bb PLS_INTEGER := 2;
cc PLS_INTEGER := 3;
dd BINARY_FLOAT := 4;
ee PLS_INTEGER;
ff BINARY_FLOAT := 5;
BEGIN
DBMS_OUTPUT.PUT_LINE('Before invoking procedure p:');
DBMS_OUTPUT.PUT('aa = ');
DBMS_OUTPUT.PUT_LINE(NVL(TO_CHAR(aa), 'NULL'));
DBMS_OUTPUT.PUT('bb = ');
DBMS_OUTPUT.PUT_LINE(NVL(TO_CHAR(bb), 'NULL'));
DBMS_OUTPUT.PUT('cc = ');
DBMS_OUTPUT.PUT_LINE(NVL(TO_CHAR(cc), 'NULL'));
DBMS_OUTPUT.PUT_LINE('dd = ' || TO_CHAR(dd));
p(aa, -- constant
bb, -- initialized variable
cc, -- initialized variable
dd -- initialized variable
);
DBMS_OUTPUT.PUT_LINE('After invoking procedure p:');
DBMS_OUTPUT.PUT('aa = ');
DBMS_OUTPUT.PUT_LINE(NVL(TO_CHAR(aa), 'NULL'));
DBMS_OUTPUT.PUT('bb = ');
DBMS_OUTPUT.PUT_LINE(NVL(TO_CHAR(bb), 'NULL'));
DBMS_OUTPUT.PUT('cc = ');
DBMS_OUTPUT.PUT_LINE(NVL(TO_CHAR(cc), 'NULL'));
DBMS_OUTPUT.PUT_LINE('dd = ' || TO_CHAR(dd));
DBMS_OUTPUT.PUT_LINE('Before invoking procedure p:');
DBMS_OUTPUT.PUT('ee = ');
DBMS_OUTPUT.PUT_LINE(NVL(TO_CHAR(ee), 'NULL'));
DBMS_OUTPUT.PUT_LINE('ff = ' || TO_CHAR(ff));
p(1, -- literal
(bb + 3) * 4, -- expression
ee, -- uninitialized variable
ff -- initialized variable
);
DBMS_OUTPUT.PUT_LINE('After invoking procedure p:');
DBMS_OUTPUT.PUT('ee = ');
DBMS_OUTPUT.PUT_LINE(NVL(TO_CHAR(ee), 'NULL'));
DBMS_OUTPUT.PUT_LINE('ff = ' || TO_CHAR(ff));
END;
DECLARE
声明关键词非空约束
DECLARE acct_id INTEGER(4) NOT NULL := 9999;
变量
DECLARE part_number NUMBER(6);
常量CONSTANT
DECLARE CONSTANT part_number NUMBER(6);
变量和常量的初始值
DECLARE
hours_worked INTEGER := 40;
pi CONSTANT REAL := 3.14159;
%TYPE
:声明时使用上一个变量的类型
该属性允许您声明与以前声明的变量或列具有相同数据类型的数据项(不知道该类型是什么)。如果引用项的声明发生更改,则引用项的声明也会相应更改
DECLARE
name VARCHAR(25) NOT NULL := 'Smith';
surname name%TYPE := 'Jones';
使用赋值语句
variable_name := 表达式;
DECLARE money number := 8+2*3
使用select into
为变量赋值
-- 为变量赋值
SELECT select_item [, select_item ]... -- 查询的字段
INTO variable_name [, variable_name ]... --变量
FROM table_name;
-- 为集合赋值
SELECT employee_id, last_name BULK COLLECT INTO enums, names; -- enums 和names是声明的集合
in/out
将值作为子程序的参数分配给变量,看上面的案例DECLARE
done BOOLEAN; -- 初始值默认为 NULL
counter NUMBER := 0;
BEGIN
done := (counter > 500); -- 用表达式赋值
END;
||
DECLARE
x VARCHAR2(4) := 'suit';
y VARCHAR2(4) := 'case';
BEGIN
DBMS_OUTPUT.PUT_LINE (x || y);
DBMS_OUTPUT.PUT_LINE ('apple' || NULL || NULL || 'sauce');
END;
-- 输出结果:
suitcase
applesauce
算子 | 操作 |
---|---|
** |
幂 |
+ ,- |
同一性,否定 |
* ,/ |
乘法、除法 |
+ , ,`-`` |
|
= , , , , , , , , , , , ,<``>``<=``>=``<>``!=``~=`` ^=``IS``NULL``LIKE``BETWEEN``IN |
比较 |
NOT |
否定 |
AND |
连接 |
OR |
包含 |
x | y | x AND y | x OR y | NOT x |
---|---|---|---|---|
TRUE |
TRUE |
TRUE |
TRUE |
FALSE |
TRUE |
FALSE |
FALSE |
TRUE |
FALSE |
TRUE |
NULL |
NULL |
TRUE |
FALSE |
FALSE |
TRUE |
FALSE |
TRUE |
TRUE |
FALSE |
FALSE |
FALSE |
FALSE |
TRUE |
FALSE |
NULL |
FALSE |
NULL |
TRUE |
NULL |
TRUE |
NULL |
TRUE |
NULL |
NULL |
FALSE |
FALSE |
NULL |
NULL |
NULL |
NULL |
NULL |
NULL |
NULL |
or
和and
or
,前面的不成立,就不执行,and
都必须成立
DECLARE
on_hand INTEGER := 0;
on_order INTEGER := 100;
BEGIN
-- 不会导致被零除错误;
-- 求值在第一次表达式后停止
IF (on_hand = 0) OR ((on_order / on_hand) < 5) THEN
DBMS_OUTPUT.PUT_LINE('On hand quantity is zero.');
END IF;
END;
is null /is not null
算子 | 意义 |
---|---|
= |
等于 |
<> , , ,!=``~=`` ^= |
不等于 |
< |
小于 |
> |
大于 |
<= |
小于或等于 |
>= |
大于或等于 |
LIKE
DECLARE
PROCEDURE compare (
value VARCHAR2,
pattern VARCHAR2
) IS
BEGIN
IF value LIKE pattern THEN
DBMS_OUTPUT.PUT_LINE ('TRUE');
ELSE
DBMS_OUTPUT.PUT_LINE ('FALSE');
END IF;
END;
BEGIN
compare('Johnson', 'J%s_n');
compare('Johnson', 'J%S_N');
END;
-- 输出结果
TRUE
FALSE
BETWEEN
和IN
:和平常sql一样
case when
:和平常sql一样
CASE selector
WHEN selector_value_1 THEN result_1
WHEN selector_value_2 THEN result_2
...
WHEN selector_value_n THEN result_n
[ ELSE
else_result ]
END
if
$IF boolean_static_expression $THEN
text
[ $ELSIF boolean_static_expression $THEN
text
]...
[ $ELSE
text
$END
]
BEGIN
$IF DBMS_DB_VERSION.VER_LE_10_1 $THEN -- 选择命令判断
$ERROR 'unsupported database release' $END -- 错误命令,输出错误信息
$ELSE
DBMS_OUTPUT.PUT_LINE (
'Release ' || DBMS_DB_VERSION.VERSION || '.' ||
DBMS_DB_VERSION.RELEASE || ' is supported.'
);
-- 10.2版本支持下面的commit语法 :
COMMIT WRITE IMMEDIATE NOWAIT;
$END -- 终结命令
END;
LOOP
简单循环
LOOP
-- 代码逻辑
END LOOP;
WHILE
循环
-- 使用condition,这个会一直循环,只能手动退出循环
WHILE condition LOOP
-- 代码逻辑
END LOOP;
-- 条件不成立会退出循环
WHILE a>2 LOOP
-- 代码逻辑
END LOOP;
FOR
循环
-- 语法
FOR loop_variable IN [REVERSE] lower_bound .. upper_bound LOOP
statements
END LOOP;
-- 案例一,不使用 REVERSE ,loop_variable初始值=下限值1
FOR loop_variable IN 1..5 LOOP
statements
END LOOP;
-- 案例二,使用 REVERSE ,loop_variable初始值=上限值5
FOR loop_variable IN REVERSE 1..5 LOOP
statements
END LOOP;
CONTINUE
:跳出本次循环EXIT
:结束循环CONTINUE WHEN
:条件跳出本次循环EXIT WHEN
:条件结束循环RETURN
:结束代码GOTO
跳转可以跳到某个定义的声明标签<
,这个标签可以在GOTO
的前面也可以在后面
DECLARE
done BOOLEAN;
BEGIN
FOR i IN 1..5 LOOP
IF done THEN
DBMS_OUTPUT.PUT_LINE('这里是1111');
GOTO gotp_here; --可以跳到某个定义的声明标签
END IF;
<<gotp_here>>
DBMS_OUTPUT.PUT_LINE('跳到22222');
END LOOP;
END;
数据类型,就用数据库的,要多的去看官网
集合的官网
声明一个table,可以用 declare声明局部使用,也可以直接创建这么一个类型,全局使用
CREATE OR REPLACE TYPE nt_type IS TABLE OF NUMBER;
/
CREATE OR REPLACE PROCEDURE print_nt (nt nt_type) AUTHID DEFINER IS
i NUMBER;
BEGIN
i := nt.FIRST;
IF i IS NULL THEN
DBMS_OUTPUT.PUT_LINE('nt is empty');
ELSE
WHILE i IS NOT NULL LOOP
DBMS_OUTPUT.PUT('nt.(' || i || ') = ');
DBMS_OUTPUT.PUT_LINE(NVL(TO_CHAR(nt(i)), 'NULL'));
i := nt.NEXT(i);
END LOOP;
END IF;
DBMS_OUTPUT.PUT_LINE('---');
END print_nt;
/
DECLARE
nt nt_type := nt_type(); -- nested table variable initialized to empty
BEGIN
print_nt(nt);
nt := nt_type(90, 9, 29, 58);
print_nt(nt);
END;
DECLARE
TYPE Roster IS TABLE OF VARCHAR2(15); -- nested table type
-- 使用构造函数初始化的嵌套表变量:
names Roster := Roster('D Caruso', 'J Hamil', 'D Piro', 'R Singh');
...
DECLARE
TYPE NumTab IS TABLE OF employees.employee_id%TYPE;
TYPE NameTab IS TABLE OF employees.last_name%TYPE;
enums NumTab;
names NameTab;
PROCEDURE print_first_n (n POSITIVE) IS
BEGIN
IF enums.COUNT = 0 THEN
DBMS_OUTPUT.PUT_LINE ('Collections are empty.');
ELSE
DBMS_OUTPUT.PUT_LINE ('First ' || n || ' employees:');
FOR i IN 1 .. n LOOP
DBMS_OUTPUT.PUT_LINE (
' Employee #' || enums(i) || ': ' || names(i));
END LOOP;
END IF;
END;
BEGIN
SELECT employee_id, last_name
BULK COLLECT INTO enums, names
FROM employees
ORDER BY employee_id;
print_first_n(3);
print_first_n(6);
END;
DECLARE
TYPE dnames_tab IS TABLE OF VARCHAR2(30); -- element type is not record type
dept_names1 dnames_tab :=
dnames_tab('Shipping','Sales','Finance','Payroll');
dept_names2 dnames_tab :=
dnames_tab('Sales','Finance','Shipping','Payroll');
dept_names3 dnames_tab :=
dnames_tab('Sales','Finance','Payroll');
BEGIN
-- 判断 is null
IF dept_names1 IS NOT NULL THEN
DBMS_OUTPUT.PUT_LINE('dept_names1 IS NOT NULL');
ELSE
DBMS_OUTPUT.PUT_LINE('dept_names1 IS NULL');
END IF;
-- 判断集合相等
IF dept_names1 = dept_names2 THEN
DBMS_OUTPUT.PUT_LINE('dept_names1 = dept_names2');
END IF;
IF dept_names2 != dept_names3 THEN
DBMS_OUTPUT.PUT_LINE('dept_names2 != dept_names3');
END IF;
END;
DECLARE
TYPE nested_typ IS TABLE OF NUMBER;
nt1 nested_typ := nested_typ(1,2,3);
nt2 nested_typ := nested_typ(3,2,1);
nt3 nested_typ := nested_typ(2,3,1,3);
nt4 nested_typ := nested_typ(1,2,4);
PROCEDURE testify (
truth BOOLEAN := NULL,
quantity NUMBER := NULL
) IS
BEGIN
IF truth IS NOT NULL THEN
DBMS_OUTPUT.PUT_LINE (
CASE truth
WHEN TRUE THEN 'True'
WHEN FALSE THEN 'False'
END
);
END IF;
IF quantity IS NOT NULL THEN
DBMS_OUTPUT.PUT_LINE(quantity);
END IF;
END;
BEGIN
testify(truth => (nt1 IN (nt2,nt3,nt4))); -- True
testify(truth => (nt1 SUBMULTISET OF nt3)); -- True
testify(truth => (nt1 NOT SUBMULTISET OF nt4)); -- True
testify(truth => (4 MEMBER OF nt1)); -- False
testify(truth => (nt3 IS A SET)); -- False
testify(truth => (nt3 IS NOT A SET)); -- True
testify(truth => (nt1 IS EMPTY)); -- False
testify(quantity => (CARDINALITY(nt3))); -- 4
testify(quantity => (CARDINALITY(SET(nt3)))); -- 3
END;
collection_name.method
DELETE |
程序 | 从集合中删除元素。 |
---|---|---|
TRIM |
程序 | 从阵列或嵌套表的末尾删除元素。 |
EXTEND |
程序 | 将元素添加到阵列或嵌套表的末尾。 |
EXISTS |
功能 | 当且仅当 varray 或嵌套表的指定元素存在时返回。TRUE |
FIRST |
功能 | 返回集合中的第一个索引。 |
LAST |
功能 | 返回集合中的最后一个索引。 |
COUNT |
功能 | 返回集合中的元素数。 |
LIMIT |
功能 | 返回集合可以具有的最大元素数。 |
PRIOR |
功能 | 返回指定索引前面的索引。 |
NEXT |
功能 | 返回成功指定索引的索引。 |
collection.DELETE; -- 删除所有
collection.DELETE(2,4); -- range删除index 2到5的, 包含左右2和5,都会删除
collection.DELETE('A','C'); -- range删除字符串索引,'A'到'C' 包含左右A\C,都会删除
CURSOR
由 PL/SQL 构造和管理的游标是隐式游标。您构造和管理的游标是显式游标。
隐式游标是由 PL/SQL 构造和管理的会话游标。PL/SQL 每次运行 或 DML 语句时都会打开一个隐式游标。您无法控制隐式游标,但可以从其属性中获取信息。
隐式游标属性包括,**注:**这个了解有即可,没啥意思。
SQL%ISOPEN
:游标是否打开?SQL%FOUND
:执行上一个sql后是否有任何行受到影响?SQL%NOTFOUND
:执行上一个sql后没有受影响的行吗?SQL%ROWCOUNT
:执行上一个sql后受影响的行数是多少?SQL%BULK_ROWCOUNT
(请参阅“获取受 FORALL 语句影响的行数”SQL%BULK_EXCEPTIONS
(请参阅“在 FORALL 语句完成后处理 FORALL 异常”直接使用sql语句结果作为遍历的对象。
BEGIN
FOR item IN (
SELECT last_name, job_id
FROM employees
WHERE job_id LIKE '%CLERK%'
AND manager_id > 120
ORDER BY last_name
)
LOOP
DBMS_OUTPUT.PUT_LINE
('Name = ' || item.last_name || ', Job = ' || item.job_id);
END LOOP;
END;
可以先声明显式游标,然后再在同一块、子程序或包中定义它,也可以同时声明和定义它
-- 仅声明游标
CURSOR cursor_name [ parameter_list ] RETURN return_type;
-- 声明并定义
CURSOR cursor_name [ parameter_list ] [ RETURN return_type ]
IS select_statement;
DECLARE
CURSOR c1 RETURN departments%ROWTYPE; -- 声明 c1
CURSOR c2 IS -- 声明 and 定义 c2
SELECT employee_id, job_id, salary FROM employees
WHERE salary > 2000;
CURSOR c1 RETURN departments%ROWTYPE IS -- 定义 c1,
SELECT * FROM departments -- 重复返回类型
WHERE department_id = 110;
CURSOR c3 RETURN locations%ROWTYPE; -- 声明 c3
CURSOR c3 IS -- 定义 c3,
SELECT * FROM locations -- 省略返回类型
WHERE country_id = 'JP';
BEGIN
NULL;
END;
CURSOR
open cursor_name;
close cursor_name;
Cursor
的属性调用%ISOPEN
: 是否打开%FOUND
: 是否提取到行内容%NOTFOUND
: 是否未提取到任何内容%ROWCOUNT
: 提取了多少行数据FETCH
提取游标查询的数据/*语法*/
FETCH cursor_name INTO into_clause;
DECLARE
CURSOR c1 IS
SELECT last_name, job_id FROM employees
WHERE
ORDER BY last_name;
v_lastname employees.last_name%TYPE; -- last_name变量
v_jobid employees.job_id%TYPE; -- job_id变量
CURSOR c2 IS
SELECT * FROM employees
WHERE
ORDER BY job_id;
v_employees employees%ROWTYPE; -- 表行的记录变量
BEGIN
OPEN c1;
LOOP -- 将 2 列提取到变量中
FETCH c1 INTO v_lastname, v_jobid;
EXIT WHEN c1%NOTFOUND;
DBMS_OUTPUT.PUT_LINE( RPAD(v_lastname, 25, ' ') || v_jobid );
END LOOP;
CLOSE c1;
DBMS_OUTPUT.PUT_LINE( '-------------------------------------' );
OPEN c2;
LOOP -- 将整行提取到v_employees记录中
FETCH c2 INTO v_employees;
EXIT WHEN c2%NOTFOUND;
DBMS_OUTPUT.PUT_LINE( RPAD(v_employees.last_name, 25, ' ') ||
v_employees.job_id );
END LOOP;
CLOSE c2;
END;
DECLARE
CURSOR c IS
SELECT e.job_id, j.job_title
FROM employees e, jobs j
WHERE e.job_id = j.job_id AND e.manager_id = 100
ORDER BY last_name;
-- 记录游标结果集行的变量:
job1 c%ROWTYPE;
job2 c%ROWTYPE;
job3 c%ROWTYPE;
job4 c%ROWTYPE;
job5 c%ROWTYPE;
BEGIN
OPEN c;
FETCH c INTO job1; -- fetches first row
FETCH c INTO job2; -- fetches second row
FETCH c INTO job3; -- fetches third row
FETCH c INTO job4; -- fetches fourth row
FETCH c INTO job5; -- fetches fifth row
CLOSE c;
DBMS_OUTPUT.PUT_LINE(job1.job_title || ' (' || job1.job_id || ')');
DBMS_OUTPUT.PUT_LINE(job2.job_title || ' (' || job2.job_id || ')');
DBMS_OUTPUT.PUT_LINE(job3.job_title || ' (' || job3.job_id || ')');
DBMS_OUTPUT.PUT_LINE(job4.job_title || ' (' || job4.job_id || ')');
DBMS_OUTPUT.PUT_LINE(job5.job_title || ' (' || job5.job_id || ')');
END;
CURSOR
使用DEFAULT
修饰,可以为CURSOR
设置默认值参数,就可以选择传参或者不传参数进行调用。
DECLARE
CURSOR c (job VARCHAR2, max_sal NUMBER 10000,
hired DATE DEFAULT TO_DATE('2022-01-01', 'yyyy-mm-dd')) IS
SELECT last_name, first_name, (salary - max_sal) overpayment
FROM employees
WHERE job_id = job
AND salary > max_sal
AND hire_date > hired
ORDER BY salary;
PROCEDURE print_overpaid IS
last_name_ employees.last_name%TYPE;
first_name_ employees.first_name%TYPE;
overpayment_ employees.salary%TYPE;
BEGIN
LOOP
FETCH c INTO last_name_, first_name_, overpayment_;
EXIT WHEN c%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(last_name_ || ', ' || first_name_ ||
' (by ' || overpayment_ || ')');
END LOOP;
END print_overpaid;
BEGIN
DBMS_OUTPUT.PUT_LINE('-------------------------------');
DBMS_OUTPUT.PUT_LINE('Overpaid Sales Representatives:');
DBMS_OUTPUT.PUT_LINE('-------------------------------');
OPEN c('22', 10000); -- 不传参数
print_overpaid;
CLOSE c;
DBMS_OUTPUT.PUT_LINE('------------------------------------------------');
DBMS_OUTPUT.PUT_LINE('Overpaid Sales Representatives Hired After 2022-12-12:');
DBMS_OUTPUT.PUT_LINE('------------------------------------------------');
OPEN c('22', 10000, TO_DATE('2022-12-12', 'yyyy-mm-dd')); -- 传入参数
-- new reference
print_overpaid;
CLOSE c;
END;
EXECUTE IMMEDIATE
调用动态sql或子程序EXECUTE IMMEDIATE 存储过程子程序/sql USING IN OUT[参数:params111,params222...] INTO [结果:result] ;
-- 动态调用sql
sql_stmt := 'INSERT INTO payroll VALUES (:a, :b, :c, :d)';
EXECUTE IMMEDIATE sql_stmt USING a,b,c,d INTO result_22333;
CREATE OR REPLACE PROCEDURE create_dept (
deptid IN OUT NUMBER,
dname IN VARCHAR2,
mgrid IN NUMBER,
locid IN NUMBER
) AUTHID DEFINER AS
BEGIN
deptid := departments_seq.NEXTVAL;
INSERT INTO departments (
department_id,
department_name,
manager_id,
location_id
)
VALUES (deptid, dname, mgrid, locid);
END;
/
DECLARE
plsql_block VARCHAR2(500);
new_deptid NUMBER(4);
new_dname VARCHAR2(30) := 'Advertising';
new_mgrid NUMBER(6) := 200;
new_locid NUMBER(4) := 1700;
BEGIN
-- 动态调用sql
sql_stmt := 'INSERT INTO payroll VALUES (:a, :b, :c, :d)';
EXECUTE IMMEDIATE sql_stmt USING new_deptid, new_dname, new_mgrid, new_locid;
-- 动态 PL/SQL 块调用子程序
plsql_block := 'BEGIN create_dept(:a, :b, :c, :d); END;';
/* 在 USING 子句中指定绑定变量。
指定第一个参数的模式。
默认情况下,其他参数的模式是正确的 */
EXECUTE IMMEDIATE plsql_block
USING IN OUT new_deptid, new_dname, new_mgrid, new_locid;
END;
open
调用动态sql方式一:open 调用游标查询数据库数据
DECLARE
TYPE EmpCurTyp IS REF CURSOR;
v_emp_cursor EmpCurTyp;
emp_record employees%ROWTYPE;
v_stmt_str VARCHAR2(200);
v_e_job employees.job%TYPE;
BEGIN
-- 带占位符的动态 SQL 语句:
v_stmt_str := 'SELECT * FROM employees WHERE job_id = :j';
-- 打开光标并在 USING 子句中指定绑定变量:
OPEN v_emp_cursor FOR v_stmt_str USING 'MANAGER';
-- 一次从结果集中获取一行:
LOOP
FETCH v_emp_cursor INTO emp_record;
EXIT WHEN v_emp_cursor%NOTFOUND;
END LOOP;
-- Close cursor:
CLOSE v_emp_cursor;
END;
方式二:open查询集合中数据
CREATE OR REPLACE PACKAGE pkg AUTHID DEFINER AS
TYPE rec IS RECORD(f1 NUMBER, f2 VARCHAR2(30));
TYPE mytab IS TABLE OF rec INDEX BY pls_integer;
END;
/
DECLARE
v1 pkg.mytab; -- collection of records
v2 pkg.rec;
c1 SYS_REFCURSOR;
BEGIN
OPEN c1 FOR 'SELECT * FROM TABLE(:1)' USING v1;
FETCH c1 INTO v2;
CLOSE c1;
DBMS_OUTPUT.PUT_LINE('Values in record are ' || v2.f1 || ' and ' || v2.f2);
END;
这种方式如果参数是外部的,那么会有安全隐患,尽量不要使用
CREATE OR REPLACE PROCEDURE get_recent_record (
user_name IN VARCHAR2,
service_type IN VARCHAR2,
rec OUT VARCHAR2
) AUTHID DEFINER
IS
query VARCHAR2(4000);
BEGIN
/* 以下 SELECT 语句很容易被修改 因为它使用串联来构建 WHERE 子句。 */
query := 'SELECT value FROM secret_records WHERE user_name='''
|| user_name
|| ''' AND service_type='''
|| service_type
|| ''' AND date_created> DATE '''
|| TO_CHAR(SYSDATE - 30,'YYYY-MM-DD')
|| '''';
DBMS_OUTPUT.PUT_LINE('Query: ' || query);
EXECUTE IMMEDIATE query INTO rec;
DBMS_OUTPUT.PUT_LINE('Rec: ' || rec);
END;
COMMIT;
:提交事务 ROLLBACK;
:回滚事务SAVEPOINT xxxx;
:设置保存点ROLLBACK TO xxxxx;
:回滚至保存点COMMIT WRITE IMMEDIATE NOWAIT;
:提交并立即写入保存点允许您回滚部分事务而不是整个事务。每个会话的活动保存点数不受限制。
回滚到保存点时,将擦除在该保存点之后标记的任何保存点。不会擦除回滚到的保存点。简单的回滚或提交会擦除所有保存点。
DROP TABLE emp_name;
CREATE TABLE emp_name AS
SELECT employee_id, last_name
FROM employees;
CREATE UNIQUE INDEX empname_ix
ON emp_name (employee_id);
DROP TABLE emp_sal;
CREATE TABLE emp_sal AS
SELECT employee_id, salary
FROM employees;
CREATE UNIQUE INDEX empsal_ix
ON emp_sal (employee_id);
DROP TABLE emp_job;
CREATE TABLE emp_job AS
SELECT employee_id, job_id
FROM employees;
CREATE UNIQUE INDEX empjobid_ix
ON emp_job (employee_id);
DECLARE
emp_id NUMBER(6);
emp_lastname VARCHAR2(25);
emp_salary NUMBER(8, 2);
emp_jobid VARCHAR2(10);
BEGIN
SELECT employee_id, last_name, salary, job_id
INTO emp_id, emp_lastname, emp_salary, emp_jobid
FROM employees
WHERE employee_id = 120;
INSERT INTO emp_name (employee_id, last_name)
VALUES (emp_id, emp_lastname);
-- 保存点
SAVEPOINT do_insert;
INSERT INTO emp_sal (employee_id, salary)
VALUES (emp_id, emp_salary);
IF SQL%ROWCOUNT <= 0 THEN
-- 回滚到保存点
ROLLBACK TO do_insert;
END IF;
INSERT INTO emp_job (employee_id, job_id)
VALUES (emp_id, emp_jobid);
-- 提交事务,提交事务,并且立即写入
COMMIT WRITE IMMEDIATE NOWAIT;
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN
-- 回滚事务
ROLLBACK;
DBMS_OUTPUT.PUT_LINE('Inserts were rolled back');
END;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
可以给函数、存储过程等等设置私有事务,进入带有私有事务的函数、存储过程时,主事务将被挂起,私有事务不影响主事务。
启动后,自治事务是完全独立的。它不与主事务共享锁、资源或提交依赖项。您可以记录事件、递增重试计数器等,即使主事务回滚也是如此。
自主事务可帮助您构建模块化、可重用的软件组件。您可以将自治事务封装在存储的子程序中。调用应用程序不需要知道该存储子程序执行的操作是成功还是失败。
PRAGMA AUTONOMOUS_TRANSACTION;
-- 案例1:函数私有事务
CREATE OR REPLACE PACKAGE emp_actions AUTHID DEFINER AS -- package specification
FUNCTION raise_salary (emp_id NUMBER, sal_raise NUMBER)
RETURN NUMBER;
END emp_actions;
/
CREATE OR REPLACE PACKAGE BODY emp_actions AS -- package body
-- code for function raise_salary
FUNCTION raise_salary (emp_id NUMBER, sal_raise NUMBER)
RETURN NUMBER IS
PRAGMA AUTONOMOUS_TRANSACTION;
new_sal NUMBER(8,2);
BEGIN
UPDATE employees SET salary =
salary + sal_raise WHERE employee_id = emp_id;
COMMIT;
SELECT salary INTO new_sal FROM employees
WHERE employee_id = emp_id;
RETURN new_sal;
END raise_salary;
END emp_actions;
/
-- 案例二:存储过程私有事务
CREATE OR REPLACE PROCEDURE lower_salary
(emp_id NUMBER, amount NUMBER)
AUTHID DEFINER AS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
UPDATE employees
SET salary = salary - amount
WHERE employee_id = emp_id;
COMMIT;
END lower_salary;
/
-- 案例3:声明私有事务
DROP TABLE emp;
CREATE TABLE emp AS SELECT * FROM employees;
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
emp_id NUMBER(6) := 200;
amount NUMBER(6,2) := 200;
BEGIN
UPDATE employees
SET salary = salary - amount
WHERE employee_id = emp_id;
COMMIT;
END;
DROP TABLE debug_output;
CREATE TABLE debug_output (message VARCHAR2(200));
CREATE OR REPLACE PACKAGE debugging AUTHID DEFINER AS
FUNCTION log_msg (msg VARCHAR2) RETURN VARCHAR2;
END debugging;
/
CREATE OR REPLACE PACKAGE BODY debugging AS
FUNCTION log_msg (msg VARCHAR2) RETURN VARCHAR2 IS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
INSERT INTO debug_output (message) VALUES (msg);
COMMIT;
RETURN msg;
END;
END debugging;
/
-- 查询时调用包函数
DECLARE
my_emp_id NUMBER(6);
my_last_name VARCHAR2(25);
my_count NUMBER;
BEGIN
my_emp_id := 120;
SELECT debugging.log_msg(last_name)
INTO my_last_name
FROM employees
WHERE employee_id = my_emp_id;
/* 即使您在此这里回滚,
插入“debug_output”的操作依然还是提交了,
因为它是自主事务,不受外部事务影响。 */
ROLLBACK;
END;
/
您可以使用该语句开始只读或读写事务、建立隔离级别或将当前事务分配给指定的回滚段。SET``TRANSACTION
只读事务对于在其他用户更新相同表时运行多个查询非常有用。
在只读事务期间,所有查询都引用数据库的同一快照,从而提供多表、多查询、只读一致性视图。其他用户可以像往常一样继续查询或更新数据。提交或回滚将结束事务。
该语句必须是只读事务中的第一个 SQL 语句,并且在事务中只能出现一次。如果将事务设置为 ,则后续查询仅看到事务开始之前提交的更改。的使用不会影响其他用户或交易
DECLARE
daily_order_total NUMBER(12,2);
weekly_order_total NUMBER(12,2);
monthly_order_total NUMBER(12,2);
BEGIN
COMMIT; -- 提交当前事务
-- 设置 READ ONLY 事务
SET TRANSACTION READ ONLY NAME 'Calculate Order Totals';
SELECT SUM (order_total)
INTO daily_order_total
FROM orders
WHERE order_date = SYSDATE;
SELECT SUM (order_total)
INTO weekly_order_total
FROM orders
WHERE order_date = SYSDATE - 7;
SELECT SUM (order_total)
INTO monthly_order_total
FROM orders
WHERE order_date = SYSDATE - 30;
-- 结束 read-only transaction
COMMIT;
END;
触发器根据触发语句及其作用的项目来指定触发事件,触发器有: DML 触发器、系统触发器、条件触发器。
触发器的作用:
一个简单的 DML 触发器正好在以下时间点之一触发:
在触发语句运行之前
(触发器称为 BEFORE 语句触发器或语句级 ``**BEFORE
触发器*。*)
触发语句运行后
(该触发器称为 AFTER 语句触发器或语句级 ``**AFTER
触发器*。*)
在触发语句影响的每一行之前
(该触发器称为每行*``*触发器之前或行级别 BEFORE
触发器。)
在触发语句影响的每一行之后
(触发器称为*每
行触发器或行级别* AFTER
触发器。)
大量案例在这里
/* 设置一个抛异常终止的触发器 */
CREATE OR REPLACE TRIGGER dept_restrict
BEFORE DELETE OR UPDATE OF Deptno ON dept
FOR EACH ROW
--在从部门中删除行或更新部门的主键 (DEPTNO) 之前,
-- 检查 EMP 中的从属外键值;
-- 如果找到任何内容,请回滚。
DECLARE
Dummy INTEGER; -- Use for cursor fetch
employees_present EXCEPTION;
employees_not_present EXCEPTION;
PRAGMA EXCEPTION_INIT (employees_present, -4094);
PRAGMA EXCEPTION_INIT (employees_not_present, -4095);
-- Cursor used to check for dependent foreign key values.
CURSOR Dummy_cursor (Dn NUMBER) IS
SELECT Deptno FROM emp WHERE Deptno = Dn;
BEGIN
OPEN Dummy_cursor (:OLD.Deptno);
FETCH Dummy_cursor INTO Dummy;
-- 如果找到依赖外键,则引发用户指定的外键 错误代码和消息,通过抛异常终止或回滚事务。如果未找到,关闭光标
-- 在允许触发语句完成之前。
IF Dummy_cursor%FOUND THEN
RAISE employees_present; -- Dependent rows exist
ELSE
RAISE employees_not_present; -- No dependent rows exist
END IF;
CLOSE Dummy_cursor;
EXCEPTION
WHEN employees_present THEN
CLOSE Dummy_cursor;
-- 通过抛异常终止或回滚事务
Raise_application_error(-20001, 'Employees Present in'
|| ' Department ' || TO_CHAR(:OLD.DEPTNO));
WHEN employees_not_present THEN
CLOSE Dummy_cursor;
END;
/* 设置一个监听sql后插入日志的触发器 */
CREATE OR REPLACE TRIGGER log_salary_increase
AFTER UPDATE OF salary ON employees
FOR EACH ROW
BEGIN
INSERT INTO Emp_log (Emp_id, Log_date, New_salary, Action)
VALUES (:NEW.employee_id, SYSDATE, :NEW.salary, 'New Salary');
END;
-- 代码逻辑
...
EXCEPTION
WHEN ex_name_1 THEN
statements_1 -- Exception handler
WHEN ex_name_2 OR ex_name_3 THEN
statements_2 -- Exception handler
WHEN OTHERS THEN
ROLLBACK;
RAISE; -- 再向外面抛出异常
END;
... -- 如果只是处理异常,继续执行,还是可以在EXCEPTION后加代码逻辑
CREATE OR REPLACE PROCEDURE P_TEST(
testParams IN varchar2, -- 这个可以是多个参数,用',' 分割
returnMsg IN OUT varchar2 -- 这个作为返回信息
) IS
LOOP_COUNT number DEFAULT 5; /* 获取全局锁的最大重试次数 */
ID VARCHAR2(16) DEFAULT ''; /* 存储锁的主键ID */
LOCK_VERSION INTEGER DEFAULT 0; /* 存储锁的版本变量 */
UP_LOCK_SQL varchar2(512) DEFAULT ''; /* 更新锁的sql */
UP_NUM number DEFAULT 0; /* 更新锁的行数,作为是否更新成功的标志 */
CURSOR VERSIONS IS SELECT ID,VERSIONS
FROM TEST_LOCK
WHERE ID = 'APP_SERVERS_LOCK'; /* 获取锁版本的游标sql,游标可以查询多个数据,并进行存储 */
APP_COUNT INTEGER DEFAULT 0; /* 测试查询sql */
BEGIN
-- 使用 WHILE 循环
WHILE LOOP_COUNT > 0
LOOP
-- ============ 使用游标 ========
OPEN VERSIONS; -- 打开游标
/*游标无法打开*/
IF VERSIONS%ISOPEN = FALSE THEN
returnMsg := '异常原因:游标打开失败';
CLOSE VERSIONS;
RETURN;
END IF;
FETCH VERSIONS INTO ID,LOCK_VERSION; -- 使用FETCH获取游标结果
-- EXIT WHEN VERSIONS%NOTFOUND; -- 当VERSIONS是数组需要遍历时,,可以用这个判断是否遍历完成,从而退出循环
-- 使用完毕后,关闭游标
IF VERSIONS%ISOPEN THEN
CLOSE VERSIONS;
END IF;
-- ========== 执行自定义sql ===========
-- 为了使用变量参数,这里使用 || 进行拼接,
UP_LOCK_SQL :=
'UPDATE TEST_LOCK SET VERSIONS = ' || LOCK_VERSION ||
' + 1 WHERE ID = ''APP_SERVERS_LOCK'' AND VERSIONS = ' || LOCK_VERSION || ' ';
-- 执行sql
EXECUTE IMMEDIATE UP_LOCK_SQL;
-- 获取更新结果
UP_NUM := SQL%ROWCOUNT;
-- 如果更新成功,跳出循环
IF UP_NUM = 1 THEN
-- -- 跳出本次循环
-- CONTINUE;
-- 结束循环
EXIT;
END IF;
-- 代码逻辑,查询版本,更新,重试次数5次
LOOP_COUNT := LOOP_COUNT - 1;
END LOOP;
-- ============ 使用for 循环
FOR v_counter2 IN 1..5
LOOP
<<GO_BACK>> -- 定义一个返回点
-- 将 COUNT(*) 结果放入变量 APP_COUNT
SELECT COUNT(*) INTO APP_COUNT FROM TEST_LOCK;
IF APP_COUNT > 0 THEN
UPDATE TEST_LOCK SET VERSIONS=VERSIONS+1 WHERE ID='APP_SERVERS_LOCK';
-- -- 跳出本次循环
-- CONTINUE;
-- 结束循环
EXIT;
ELSE
GOTO GO_BACK;
END IF;
END LOOP;
END ;
CREATE TABLE TEST_LOCK
(
ID VARCHAR2(16) NOT NULL
CONSTRAINT "TEST_LOCK_pk"
PRIMARY KEY,
VERSIONS NUMBER(16) NOT NULL
)
/
COMMENT ON TABLE TEST_LOCK IS '全局锁的表'
/
COMMENT ON COLUMN TEST_LOCK.ID IS '唯一id'
/
COMMENT ON COLUMN TEST_LOCK.VERSIONS IS '版本号';
/
INSERT INTO SUNCPS.TEST_LOCK(ID, VERSIONS) VALUES ('APP_SERVERS_LOCK', 0);
DECLARE c PLS_INTEGER; d BINARY_FLOAT;BEGIN /
p(1, 2, c, d);/
dbms_output.PUT_LINE(c);/
dbms_output.PUT_LINE(d);/
END;