动态SQL
在之前用户所编写的PL/SQL程序时有一个最大的特点:就是所操作的数据库对象(例如:表)必须存在,否则创建的子程序就会出问题,而这样的操作在开发之中被称为静态SQL操作,而动态SQL操作可以让用户在定义程序时不指定具体的操作对象,而在执行时动态的传入所需要的数据库对象,从而使程序变得更加的灵活。
创建一个功能,输入表名,输出表中有多少行,若没有这个表,则创建这个表。
首先禁用所有触发器
GRANT CREATE ANY TABLE TO SCOTT
create or replace function get_table_count_fun(p_table_name varchar2) return number as
v_sql_statement varchar2(200);--定义操作的SQL语句
v_count number;--保存表中记录
begin
select count(*) into v_count from user_tables where table_name=UPPER(p_table_name);
if v_count=0 then--数据表不存在
v_sql_statement:='CREATE TABLE '|| p_table_name ||'(id number ,name varchar2(30) not null)';--创建数据表,注意此处不要忽略空格,按照正常写create table语句的格式加空格,否则无法识别
execute immediate v_sql_statement;--执行动态SQL
end if;
v_sql_statement:=' select count(*) from '|| p_table_name;--查询数据表记录,注意加空格
execute immediate v_sql_statement into v_count;--执行动态SQL并保存数据记录
return v_count;
end;
/
declare
v1 varchar2(100);
begin
v1:=get_table_count_fun('t100');--查看t100表
dbms_output.put_line(v1);
end;
/
EXECUTE IMMEDIATE语句
在动态SQL之中EXECUTE IMMEDIATE是最为重要的执行命令,使用此语句可以方便的在PL/SQL程序之中执行DML(INSERT/UPDATE/DELETE/单列SELECT)、DDL(CREATE/ALTER/DROP)、DCL(GRANT/REVOKE)语句,EXECUTE IMMEDIATE语法定义如下:
EXECUTE IMMEDIATE 动态SQL字符串 [[BULK COLLECT]] INTO 自定义变量,...|记录类型|
[USING [IN | OUT |IN OUT|绑定参数,...]
[[RETURNING | RETURN][BULK COLLECT] INTO 绑定参数,...];
EXECUTE IMMEDIATE由以下三个主要字句组成:
INTO:保存动态SQL执行结果,如果返回多行记录可以通过BULK COLLECT设置批量保存;
USING:用来为动态SQL设置占位符设置内容;
RETURNING | RETURN:两者使用效果一样,是取得更新表记录被影响的数据,通过BULK COLLECT来设置批量绑定。
使用动态SQL创建表和PLSQL块
declare
v_sql_statement varchar2(200);
v_count number;--保存查找结果
begin
select count(*) into v_count from user_tables where table_name='VDATA_TAB';
if v_count=0 then--数据表不存在
v_sql_statement:='CREATE TABLE vdata_tab(
id number primary key,
url varchar2(50) not null)';--定义动态SQL
execute immediate v_sql_statement;
Else--数据表存在
v_sql_statement:='TRUNCATE TABLE vdata_tab';
execute immediate v_sql_statement;
end if;
v_sql_statement:='begin
for x in 1 .. 10 loop
insert into vdata_tab(id,url) values(x,''www.vdata.com.cn''||x);
end loop;
end;';
execute immediate v_sql_statement;
commit;--提交事物
end;
/
使用绑定变量
Declare
V_sql_statement varchar2(200);
V_deptno dept.deptno%type:=60;
V_dname dept.dname%type:=’VDATA’;
V_loc dept.loc%type:=’beijing’;
Begin
V_sql_statement:=’insert into dept(deptno,dname,loc) values(:dno,:dna,:dl)’;
Execute immediate v_sql_statement using v_deptno,v_dname,v_loc;
Commit;
End;
/
如果有字段为NULL,则不能直接绑定NULL,需要通过变量设置
如果在本程序中索要增加的部门位置为NULL,则以下的设置方式是错误的:EXECUTE IMMEDIATE v_sql_statement using v_deptno,v_dname,NULL;如果现在希望LOC的内容为NULL,可以将v_loc变量设置为NULL,其他执行部分不做改变。
查询数据
declare
v_sql_statement1 varchar2(2000);
v_empno emp.empno%type:=7369;
v_emprow emp%rowtype;
begin
v_sql_statement1:='select * from emp where empno=:eno';
execute immediate v_sql_statement1 into v_emprow using v_empno;
dbms_output.put_line('ename: '||v_emprow.empno||', ename: '||v_emprow.ename||', position: '||v_emprow.job);
end;
/
通过以上的操作可以发现,所有使用绑定变量的代码都只是针对基本的数据类型,例如字符串、数字等,但是这种方式不可能针对DDL操作,例如,将要创建或阶段的表名称使用绑定变量的话就是出现错误。
创建表时使用绑定变量
DECLARE
v_sql_statement VARCHAR2(200) ;
v_table_name VARCHAR2(200) := 'mldn' ;
v_id_column VARCHAR2(200) := 'id' ;
BEGIN
v_sql_statement := 'CREATE TABLE ' || v_table_name ||' (' || v_id_column ||' NUMBER PRIMARY KEY)' ;
EXECUTE IMMEDIATE v_sql_statement ;
END ;
更新数据,取得更新后的结果
Declare
V_sql_statement varchar2(200);--定义SQL操作语句
V_empno emp.empno%type:=7369;--要更新的雇员编号
V_salary emp.sal%type;--保存更新后的sal内容
V_job emp.job%type;--保存更新后的Job内容
Begin
V_sql_statement:=’update emp set sal=sal*1.2,job=”developer”’||’where empno=:eno returning sal.job into :salary,:job’;
Execute immediate v_sql_statement using v_empno returning into v_salary,v_job;
Dbms_output.put_line(‘salary: ‘||v_salary||’, new position: ‘||v_job);
End;
/
也可以使用RETURN接收影响数据行的数据。
在接收影响数据行数据时,也可以利用return进行操作,如下所示:
v_sql_statement:=’update emp set sal=sal*12,job=”developer” ’||’ where empno=:eno return sal,job into :salary,:job’;
Execute immediate v_sql_statement using v_empno return into v_salary,v_job;
使用return与returning相比没有任何区别
删除数据,取得删除前的结果
Declare
V_sql_statement varchar2(200);--定义SQL操作语句
V_emprow emp%rowtype;--保存emp类型
V_empno emp.empno%type:=7369;--删除的雇员编号
V_ename emp.ename%type;--删除的雇员姓名
V_sal emp.sal%type;--删除的雇员工资
Begin
V_sql_statement:=’delete from emp where empno=:eno returning ename,sal into :name,:sal;
Execute immediate v_sql_statement using v_empno returning into v_ename,v_sal;
Dbms_output.put_line(‘empno: ‘||v_empno||’, ename: ‘||v_ename||’, salary: ‘||v_sal);
End;
/
在使用USING或RETURNING语句时都可以设置参数模式(IN/OUT/IN OUT),其中对USING子句主要是使用变量定义的内容,所以默认的模式是IN模式。使用RETURNING子句时不需要设置内容,只需接收返回内容,所以其模式为OUT。
编写部门增加过程
Create or replace procedure dept_insert_proc(
P_deptno in out dept.deptno%type,--此处可以将p_deptno的内容回传
P_dname dept.dname%type,--默认为in模式
P_loc dept.loc%type) as --默认为in模式
Begin
Select max(deptno) into p_deptno from dept;--取得最大的deptno内容
P_deptno:=p_deptno+1;--让最大值部门编号+1,此处不考虑超过两位数字情况
Insert into dept(deptno,dname,loc) values (p_deptno,p_dname,p_loc);
End;
/
批量绑定
通过动态SQL进行查询或更新操作时,每次都是向数据库提交了一条操作语句,如果现在希望数据库可以一次性接收多条SQL,以及数据库可以一次性将操作结果返回到某一个集合中时,就可以采用批量处理操作完成,在进行批量处理操作中,注意依靠BULK COLLECT进行操作。
使用BULK COLLECT更新
DECLARE
TYPE ename_index IS TABLE OF emp.ename%TYPE INDEX BY PLS_INTEGER ;
TYPE job_index IS TABLE OF emp.job%TYPE INDEX BY PLS_INTEGER ;
TYPE sal_index IS TABLE OF emp.sal%TYPE INDEX BY PLS_INTEGER ;
v_ename ename_index ;
v_job job_index ;
v_sal sal_index ;
v_sql_statement VARCHAR2(200) ; --定义动态SQL
v_deptno emp.deptno%TYPE := 10 ; --查询10部门
BEGIN
v_sql_statement := 'UPDATE emp SET sal=sal*1.2 WHERE deptno=:dno ' ||
' RETURNING ename,job,sal INTO :ena, :ej, :es' ;-- 此时返回多行更新结果(注意空格,否则无法识别语句)
EXECUTE IMMEDIATE v_sql_statement USING v_deptno
RETURNING BULK COLLECT INTO v_ename,v_job,v_sal ;
FOR x IN 1 .. v_ename.COUNT LOOP
DBMS_OUTPUT.put_line('雇员姓名' || v_ename(x) || ',职位:' || v_job(x) || ',工资:' || v_sal(x)) ;
END LOOP ;
END ;
雇员姓名:CLARK,职位:MANAGER,工资:3528
雇员姓名:KING,职位:PRESIDENT,工资:7200
雇员姓名:MILLER,职位:CLERK,工资:1872
查询时使用BULK COLLECT
Declare
type ename_index is table of emp.ename%type index by pls_integer;
Type job_index is table of emp.job%type index by pls_integer;
Type sal_index is table of emp.sal%type index by pls_integer;
V_ename ename_index;
V_job job_index;
V_sal sal_index;
V_sql_statement varchar2(200);--定义动态SQL
V_deptno emp.deptno%type:=10;--查询10部门
Begin
V_sql_statement:=’select ename,job,sal from emp where deptno=:dno’;--此时返回多行更新结果
Execute immediate v_sql_statement
Bulk collect into v_ename,v_job,v_sal
Using v_deptno;
For x in 1..v_ename.count loop
Dbms_output.put_line(‘ename: ’||v_ename(x)||’ , position: ‘||v_job(x)||’, salary: ‘||v_sal(x));
End loop;
End;
/
FORALL
如果要向动态SQL之中设置多个绑定参数,则就必须利用FORALL语句完成,此语句的语法如下所示。
FORALL索引变量IN参数集合最小值..参数集合最大值
EXECUTE IMMEDIATE 动态SQL字符串
[USING 绑定参数|绑定参数(索引), ...]
[[RETURNING | RETURN] BULK COLLECT INTO 绑定参数集合 , ...];
通过FORALL设置多个参数
Declare
Type empno_nested is table of emp.empno%type;--定义嵌套表
Type ename_index is table of emp.ename%type index by pls_integer;--定义索引表
Type job_index is table of emp.job%type index by pls_integer;--定义索引表
Type sal_index is table of emp.sal%type index by pls_integer;--定义索引表
V_ename ename_index;--保存删除后的姓名
V_job job_index;--保存删除后的职位
V_sal sal_index;--保存删除后的工资
V_empno empno_nested:=empno_nested(7369,7566,7788);--定义要删除雇员编号
V_sql_statement varchar2(200);--动态SQL
Begin
V_sql_statement:=’delete from emp where empno=:eno’||’
Returning ename,job,sal into :ena, :ej, :es’;--删除数据SQL
FORALL x in 1 .. v_empno.count --FORALL绑定多个变量
EXECUTE IMMEDIATE v_sql_statement using v_empno(x)
Returning bulk collect into v_ename,v_job,v_sal;
For x in 1 .. v_ename.count loop
Dbms_output.put_line(‘ename: ‘||v_ename(x)||’, position: ‘||’, salary: ‘||v_sal(x));
End loop;
End;
/
处理游标操作
动态SQL操作之中,除了可以处理单行查询操作之外,也可以利用游标完成多行数据的操作,而在游标定义时也同样可以使用动态绑定变量的方式,此时就需要在打开游标变量时增加USING子句操作。
在游标中使用动态SQL
DECLARE
emp_cur SYS_REFCURSOR ; -- 定义游标变量
v_emprow emp%ROWTYPE ; -- 定义emp行类型
v_deptno emp.deptno%TYPE := 10 ; -- 定义要查询雇员的部门编号
BEGIN
OPEN emp_cur FOR 'SELECT * FROM emp WHERE deptno=:dno '
USING v_deptno ;
LOOP
FETCH emp_cur INTO v_emprow ; -- 取得游标数据
EXIT WHEN emp_cur%NOTFOUND ; -- 如果没有数据则退出
DBMS_OUTPUT.put_line('雇员姓名:' || v_emprow.ename || ',雇员职位:' || v_emprow.job) ;
END LOOP ;
CLOSE emp_cur ;
END ;
FETCH
在FETCH语句之中利用BULK COLLECT一次性将多个数据保存到集合类型之中,语法如下所示。
FETCH 动态游标 BULK COLLECT INTO 集合变量 ...;
利用FETCH保存查询结果
Declare
Emp_cur sys_refcursor;--定义游标变量
Type emp_index is table of emp%rowtype index by pls_integer;--定义索引表
V_emprow emp_index;--定义emp行类型
V_deptno emp.deptno%type:=10;--定义要查询雇员的部门编号
Begin
Open emp_cur for ‘select * from emp where deptno=:dno’
Using v_deptno;
Fetch emp_cur bulk collect into v_emprow;
Close emp_cur
For x in 1 .. v_emprow.count loop
Dbms_output.put_line(‘empno: ‘||v_emprow(x).empno||’, ename: ‘||v_emprow(x).ename||’, position: ‘||v_emprow(x).job);
End loop;
End;
/
使用动态SQL可以在依赖对象不存在时创建子程序;
动态SQL主要利用EXECUTE IMMEDIATE语句执行DML/DDL/DCL等语句操作;
如果使用了绑定变量,则必须在EXECUTE IMMEDIATE中使用USING子句设置所需要的绑定变量;
使用RETURNING或RETURN语句可以接收查询或更新后的返回结果;
使用批处理可以一次性将数据库之中取回的多个数据保存在集合里,或者使用FORALL将多个绑定参数设置到动态SQL之中。
动态显示游标
输出HR或SCOTT下每张表对应的所有记录数
declare
emp_cur sys_refcursor;
v_emprow emp%rowtype;
v_deptno emp.deptno%type:=10;
begin
open emp_cur for 'select * from emp where deptno=:dno'
using v_deptno;
loop
fetch emp_cur into v_emprow;
exit when emp_cur%notfound;
dbms_output.put_line('ename: '||v_emprow.ename||' position: '||v_emprow.job);
end loop;
close emp_cur;
end;
/