[连载]Oracle 12cR2 PL/SQL语言参考 - 07 动态SQL

737 阅读12分钟

概述

动态SQL可以在运行时生成和运行SQL语句,常用于编写通用的和灵活的程序 PL/SQL提供了两种方式来写动态SQL:

  • 原生动态SQL,这是PL/SQL的用于构建和运行动态SQL语句的特性
  • DBMS_SQL包,用于构建,运行和描述动态SQL语句 原生动态SQL代码比使用DBMS_SQL包更容易阅读和编写,但是你必须在编译时就要知道原生动态
    SQL语句中的输入/输出变量的数量和数据类型,否则你就必须使用DBMS_SQL包,而且如果你想要
    一个存储过程隐式的返回一个结果集,也必须使用DBMS_SQL包(如果不给存储过程使用 OUT REF CURSOR
    类型的参数)
    当你同时需要DBMS_SQL包和原生动态SQL时,你可以使用"DBMS_SQL.TO_REFCURSOR"函数和 "DBMS_SQL.TO_CURSOR_NUMBER"函数在它们之间转换

7.1 你什么时候需要动态SQL

在PL/SQL中,有如下情况时你需要动态SQL

  • 在编译时还不知道SQL内容
    例如,SELECT语句中有一个标识符(例如表名)在编译时还是未知的
  • 静态SQL不支持的SQL 如果你不需要动态SQL,而使用静态SQL,有如下好处:
  • 成功的编译会验证静态SQL引用的有效数据库对象以及对这些对象的必要的访问权限
  • 成功的编译会创建数据库对象依赖

7.2 原生动态语句

大多数情况下使用 EXECUTE IMMEDIATE 语句来处理原生动态SQL
如果动态SQL语句是SELECT语句,而且会返回多行数据,那么原生动态SQL给你两个选择:

  • 使用带有 BULK COLLECT INTO 子句的 EXECUTE IMMEDIATE 语句
  • 搭配使用 OPEN FOR, FETCH 和 CLOSE 语句 在动态SQL执行了INSERT, UPDATE, DELETE和MERGE后,SQL游标属性依然和静态SQL是一样的

7.2.1 EXECUTE IMMEDIATE语句

原生动态SQL使用 EXECUTE IMMEDIATE 语句来处理动态SQL语句
如果动态语句是自包含的(即它没有绑定变量占位符,且仅有的返回可能会是一个错误),那么
EXECUTE IMMEDIATE语句就不需要子句
如果动态SQL语句中包含了绑定变量占位符,而每个占位符必须在合适的子句中对应一个绑定变量:

  • 如果动态SQL语句是一个SELECT语句,而且最多返回一行数据,那么将输出变量放在INTO子句中,而将输入变量放在USING子句中
  • 如果动态SQL语句是一个SELECT语句,而且会返回多行数据,那么输出变量就应该放在 BULK COLLECT INTO子句中,而将输入变量放在USING子句中
  • 如果动态SQL语句是一个DML语句,且没有使用RETURNING INTO子句,那么就将所有输入变量放在USING子句中
  • 如果动态SQL语句是一个DML语句,且使用了RETURNING INTO子句,那么就将输入变量放在USING子句中,而将输出变量放在RETURNING INTO子句中
  • 如果动态SQL语句是一个匿名PL/SQL代码块或CALL语句,将所有绑定变量放在USING子句中如果动态SQL语句调用了一个子程序,需要确保:
    • 子程序定义在模式(schema)层级,或定义在包头中
    • 绑定变量需要和子程序的参数对应(类型要兼容)
    • 绑定变量不能是NULL,为了突破这个限制,可以在你想使用NULL的地方使用未初始
    • 化的变量
    • 绑定变量不能拥有SQL不支持的数据类型(例如字符串做索引的关联数组),如果数
    • 据类型是一个集合或记录,必须声明在包头中 示例程序(匿名PL/SQL代码块调用声明于模式中的子程序):
CREATE OR REPLACE PROCEDURE create_dept (
    deptid IN OUT NUMBER,
    dname         VARCHAR2,
    mgrid         NUMBER,
    locid         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
    plsql_block := 'BEGIN create_dept(:a, :b, :c, :d); END;';
    EXECUTE IMMEDIATE plsql_block
        USING IN OUT new_deptid, new_dname, new_mgrid, new_locid;
END;
/

示例程序(动态调用带BOOLEAN类型形参的子程序):

CREATE OR REPLACE p (
    x BOOLEAN
) AUTHID DEFINER
AS
BEGIN
    IF x THEN
        DBMS_OUTPUT.PUT_LINE('x is true');
    END IF;
END;
/
DECLARE
    dyn_stmt VARCHAR2(200);
    b        BOOLEAN := TRUE;
BEGIN
    dyn_stmt := 'BEGIN p(:x); END;';
    EXECUTE IMMEDIATE dyn_stmt USING b;
END;
/

示例程序(动态调用带RECORD类型形参的子程序):

CREATE OR REPLACE PACKAGE pkg
AUTHID DEFINER
AS
    TYPE rec IS RECORD (n1 NUMBER, n2 NUMBER);
    PROCEDURE p (x OUT rec, y NUMBER, z NUMBER);
END pkg;
/
CREATE OR REPLACE PACKAGE BODY pkg
AS
    PROCEDURE p (
        x OUT rec,
        y NUMBER,
        z NUMBER
    )
    AS
    BEGIN
        x.n1 := y;
        x.n2 := z;
    END p;
END pkg;
/
DECLARE
    r       pkg.rec;
    dyn_str VARCHAR2(3000);
BEGIN
    dyn_str := 'BEGIN pkg.p(:x, 6, 8); END;';
    EXECUTE IMMEDIATE dyn_str USING OUT r;
    DBMS_OUTPUT.PUT_LINE('r.n1 = ' || r.n1);
    DBMS_OUTPUT.PUT_LINE('r.n2 = ' || r.n2);
END;
/

示例程序(动态调用带关联数组类型形参的子程序):

CREATE OR REPLACE PACKAGE pkg AUTHID DEFINER AS
    TYPE number_names IS TABLE OF VARCHAR(5)
        INDEX BY PLS_INTEGER;  -- 在这种情况下使用的关联数组必须以PLS_INTEGER来索引
    PROCEDURE print_number_names (x number_names);
END pkg;
/
CREATE OR REPLACE PACKAGE BODY pkg AS
    PROCEDURE print_number_names (
        x number_names
    )
    IS
    BEGIN
        FOR i IN x.FIRST .. x.LAST
        LOOP
            DBMS_OUTPUT.PUT_LINE(x(i));
        END LOOP;
    END;
END pkg;
/
DECLARE
    digit_names pkg.number_names;
    dyn_stmt    VARCHAR2(3000);
BEGIN
    digit_names(0) := 'zero';
    digit_names(1) := 'one';
    digit_names(2) := 'two';
    digit_names(3) := 'three';
    digit_names(4) := 'four';
    digit_names(5) := 'five';
    digit_names(6) := 'six';
    digit_names(7) := 'seven';
    digit_names(8) := 'eight';
    digit_names(9) := 'nine';
    dyn_stmt := 'BEGIN pkg.print_number_names(:x); END;';
    EXECUTE IMMEDIATE dyn_stmt USING digit_names;
END;
/

示例程序(动态调用带嵌套表类型形参的子程序):

CREATE OR REPLACE PACKAGE pkg AUTHID DEFINER AS
    TYPE names IS TABLE OF VARCHAR2(10);
    PROCEDURE print_names (x names);
END pkg;
/
CREATE OR REPLACE PACKAGE BODY pkg AS
    PROCEDURE print_names (x names) IS
    BEGIN
        FOR i IN x.FIRST .. x.LAST
        LOOP
            DBMS_OUTPUT.PUT_LINE(x(i));
        END LOOP;
    END;
END pkg;
/
DECLARE
    fruits   pkg.names;
    dyn_stmt VARCHAR2(3000);
BEGIN
    fruits := pkg.names('apple', 'banana', 'cherry');
    dyn_stmt := 'BEGIN pkg.print_names(:x); END;';
    EXECUTE IMMEDIATE dyn_stmt USING fruites;
END;
/

示例程序(动态调用VARRAY类型形参的子程序):

CREATE OR REPLACE PACKAGE pkg AUTHID DEFINER AS
    TYPE foursome IS VARRAY(4) OF VARCHAR2(5);
    PROCEDURE print_foursome (x foursome);
END pkg;
/
CREATE OR REPLACE PACKAGE BODY pkg AS
    PROCEDURE print_foursome (x foursome) IS
    BEGIN
        IF x.COUNT = 0 
        THEN
            DBMS_OUTPUT.PUT_LINE('Empty');
        ELSE
            FOR i IN x.FIRST .. x.LAST
            LOOP
                DBMS_OUTPUT.PUT_LINE(x(i));
            END LOOP;
        END IF;
    END;
END pkg;
/
DECLARE
    directions pkg.foursome;
    dyn_stmt   VARCHAR2(3000);
BEGIN
    directions := pkg.foursome('north', 'south', 'east', 'west');
    dyn_stmt := 'BEGIN pkg.print_foursome(:x); END;';
    EXECUTE IMMEDIATE dyn_stmt USING directions;
END;
/

示例程序(在USING子句中使用未初始化的变量来代替NULL):

CREATE TABLE employees_temp AS SELECT * FROM employees;
DECLARE
    a_null CHAR(1);  -- 未初始化的变量在运行时会被设置为NULL值
BEGIN
    EXECUTE IMMEDIATE 'UPDATE employees_temp SET commission_pct = :x'
        USING a_null;
END;
/

7.2.2 OPEN FOR, FETCH 和 CLOSE语句

如果动态SQL语句代表的是一个SELECT语句,而且返回了多行数据,那么你可以使用如下的原生
动态SQL来处理结果集:

  • 使用OPEN FOR语句来将一个游标变量和动态SQL语句关联起来
    在OPEN FOR语句中的USING子句中,为动态SQL语句中的绑定变量占位符指定对应的绑定变量
    USING子句中不能使用NULL字面量,你可以使用一个未初始化的变量来代替它
  • 使用FETCH语句来获取结果集数据(一次一条/一次多条/一次全部)
  • 使用CLOSE语句来关闭游标变量 示例程序:
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';
    -- 使用FETCH语句从结果集中每次获取一条数据
    LOOP
        FETCH v_emp_cursor INTO emp_record;
        EXIT WHEN v_emp_cursor%NOTFOUND;
    END LOOP;
    -- 关闭游标
    CLOSE v_emp_cursor;
END;
/

示例程序:

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;  -- 记录(record)的集合(collection)
    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;
/

7.2.3 在动态SQL语句中的重复的占位符名称

7.2.3.1 动态SQL语句不是匿名PL/SQL块或CALL语句

这种情况下,占位符名字的重复是无意义的,在USING子句中的绑定变量,靠位置与占位符
关联,而不是名字
例如,在下面这个动态SQL语句中, :x这个占位符名字的重复是无关紧要的

sql_stmt := 'INSERT INTO payroll VALUES (:x, :x, :y, :x)';

在与之对应的USING子句中,你必须提供4个绑定变量,它们是不同的

EXECUTE IMMEDIATE sql_stmt USING a, b, c, d;

7.2.3.2 动态SQL语句是匿名PL/SQL块或CALL语句

这种情况下,占位符名字的重复就是有意义的,每一个不同的占位符都必须在USING子句中
有一个对应的绑定变量
如果你重复了一个占位符名字,你不必重复对应的绑定变量,对这个重复占位符的引用,会
对应USING子句中的一个绑定变量
在下面的例子中,所有的对 :x 占位符的引用,都对应USING子句中的第一个绑定变量a,而
所有的对 :y 占位符的引用,都对应USING子句中的第二个绑定变量b

CREATE PROCEDURE calc_stats (
    w NUMBER,
    x NUMBER,
    y NUMBER,
    z NUMBER
) IS
BEGIN
    DBMS_OUTPUT.PUT_LINE(w + x + y + z);
END;
/
DECLARE
    a NUMBER := 4;
    b NUMBER := 7;
    plsql_block VARCHAR2(100);
BEGIN
    plsql_block := 'BEGIN calc_stats(:x, :x, :y, :x); END;';
    EXECUTE IMMEDIATE plsql_block USING a, b;
END;
/

7.3 DBMS_SQL包

DBMS_SQL包定义了一个称为SQL游标数字的实体,因为它是一个PL/SQL integer,所以你可以将它
在调用"边界"(boundaries)之间传递或存储它
如果符合如下情况之一,你就必须使用DBMS_SQL包:

  • 直到运行时之前,你还无法确定SELECT列表
  • 直到运行时之前,你还无法确定一个SELECT或DML语句中的哪些占位符需要绑定
  • 你想要一个存储过程隐式的返回查询结果集(不通过OUT REF CURSOR参数),这需要用到DBMS_SQL.RETURN_RESULT存储过程 在如下情况下,你必须使用原生动态SQL,而不是DBMS_SQL包:
  • 动态SQL语句获取数据并放入记录(record)中
  • 在执行了一个动态SQL语句(INSERT, UPDATE, DELETE, MERGE, 单行SELECT语句)之后,你想使用SQL游标属性(%FOUND, %ISOPEN, %NOTFOUND 或 %ROWTYPE)

7.3.1 DBMS_SQL.RETURN_RESULT存储过程

DBMS_SQL.RETURN_RESULT存储过程可以让一个存储子程序隐式返回查询结果集给客户端
程序(间接调用子程序)或子程序的直接调用者,在它返回结果集后,只有接收者才能访问
这个结果集
DBMS_SQL.RETURN_RESULT存储过程有两个重载:

PROCEDURE RETURN_RESULT (rc IN OUT SYS_REFCURSOR,
                         to_client IN BOOLEAN DEFAULT TRUE);
PROCEDURE RETURN_RESULT (rc IN OUT INTEGER,
                         to_client IN BOOLEAN DEFAULT TRUE);

rc参数要么是一个打开的游标变量(SYS_REFCURSOR类型),要么是一个打开的游标的游标数
字(INTEGER),要想打开一个游标并获得它的游标数字,需要调用DBMS_SQL.OPEN_CURSOR函数
当to_client参数的值是TRUE(默认)时,DBMS_SQL.RETURN_RESULT存储过程会将查询结果返回
给客户端程序(子程序的间接调用者),如果to_client参数的值为false,那么将会把查询结果
返回给子程序的直接调用者
示例程序:

CREATE OR REPLACE PROCEDURE p AUTHID DEFINER 
AS
    c1 SYS_REFCURSOR,
    c2 SYS_REFCURSOR
BEGIN
    OPEN c1 FOR
        SELECT first_name, last_name
          FROM employees
         WHERE employee_id = 176;
    DBMS_SQL.RETURN_RESULT(c1);
    -- 现在p无法访问结果集
    OPEN c2 FOR
        SELECT city, state_province
          FROM locations
         WHERE country_id = 'AU';
    DBMS_SQL.RETURN_RESULT(c2);
    -- 现在p无法访问结果集
END;
/
-- 匿名块调用存储过程p(在sqlplus中执行,结果集也会在sqlplus中打印输出)
BEGIN
    p;
END;
/

7.3.2 DBMS_SQL.GET_NEXT_RESULT存储过程

DBMS_SQL.GET_NEXT_RESULT存储过程可以获得DBMS_SQL.RETURN_RESULT存储过程返回给
接收者的下一个结果集,这两个存储过程以相同的顺序返回结果集
它也有两个重载:

PROCEDURE GET_NEXT_RESULT(c IN INTEGER, rc OUT SYS_REFCURSOR);
PROCEDURE GET_NEXT_RESULT(c IN INTEGER, rc OUT INTEGER);

c参数是一个已打开的游标的游标数字,该游标由调用DBMS_SQL.RETURN_RESULT的子程序直接 或间接打开
为了打开一个游标并获得它的游标数字,可以调用DBMS_SQL.OPEN_CURSOR函数,它有一个可选
参数treat_as_client_for_results,当这个参数值为false(默认)时,调用者(打开游标的)不
会被当做接收查询结果的客户端,当值为true时,调用者(打开游标的)被当作客户端程序
rc参数的值要么是一个游标变量(SYS_REFCURSOR类型)要么是一个游标数字(INTEGER类型)
示例程序:

CREATE OR REPLACE PROCEDURE get_employee_info (
    id IN VARCHAR2
) AUTHID DEFINER AS
    rc SYS_REFCURSOR;
BEGIN
    OPEN rc FOR
        SELECT first_name, last_name, email, phone_number
          FROM employees
         WHERE employee_id = id;
    DBMS_SQL.RETURN_RESULT(rc);
    OPEN rc FOR
        SELECT job_title, start_date, end_date
          FROM job_history jh, jobs j
         WHERE jh.employee_id = id AND
               jh.job_id = j.job_id
         ORDER BY start_date DESC;
    DBMS_SQL.RETURN_RESULT(rc);
END;
/
-- 匿名块中调用
<<main>>
DECLARE
    c             INTEGER;
    rc            SYS_REFCURSOR;
    n             NUMBER;
    first_name    VARCHAR2(20);
    last_name     VARCHAR2(25);
    email         VARCHAR2(25);
    phone_number  VARCHAR2(20);
    job_title     VARCHAR2(35);
    start_date    DATE;
    end_data      DATE;
BEGIN
    c := DBMS_SQL.OPEN_CURSOR(true);
    DBMS.SQL.PARSE(c, 'BEGIN get_employee_info(:id); END;', DBMS_SQL.NATIVE);
    DBMS_SQL.BIND_VARIABLE(c, ':id', 176);
    n := DBMS_SQL.EXECUTE(c);
    -- Get employee info
    DBMS_SQL.GET_NEXT_RESULT(c, rc);
    FETCH rc INTO first_name, last_name, email, phone_number;
    DBMS_OUTPUT.PUT_LINE('Employee: '||first_name||' '||last_name);
    DBMS_OUTPUT.PUT_LINE('Email: '||email);
    DBMS_OUTPUT.PUT_LINE('Phone: '||phone_number);
    -- Get employee job history
    DBMS_OUTPUT.PUT_LINE('Titles:');
    DBMS_SQL.GET_NEXT_RESULT(c, rc);
    LOOP
        FETCH rc INTO job_title, start_date, end_date;
        EXIT WHEN rc%NOTFOUND;
        DBMS_OUTPUT.PUT_LINE('- '||job_title||' ('||start_date||' - '||end_date||')');
    END LOOP;
    DBMS_SQL.CLOSE_CURSOR(c);
END main;
/

7.3.3 DBMS_SQL.TO_REFCURSOR函数

DBMS_SQL.TO_REFCURSOR函数将一个SQL游标数字转换为一个弱类型游标变量,你可以在原生
动态SQL语句中使用这个游标变量
在将游标数字传递给DBMS_SQL.TO_REFCURSOR之前,你必须先OPEN, PARSE并EXECUTE它
在你将游标数字转换为REF CURSOR类型的游标变量之后,DBMS_SQL操作只能将它当作REF CURSOR
类型的游标变量,例如,使用DBMS_SQL.IS_OPEN函数来查看已经转换过的SQL游标数字是否是打开
的,将导致一个错误
示例程序:

CREATE OR REPLACE TYPE vc_array IS TABLE OF VARCHAR2(200);
/
CREATE OR REPLACE TYPE numlist IS TABLE OF NUMBER;
/
CREATE OR REPLACE PROCEDURE do_query_1 (
    placeholder  vc_array,
    bindvars     vc_array,
    sql_stmt     VARCHAR2
) AUTHID DEFINER IS
    TYPE curtype IS REF CURSOR;
    src_cur      curtype;
    curid        NUMBER;
    bindnames    vc_array;
    empnos       numlist;
    depts        numlist;
    ret          NUMBER;
    isopen       BOOLEAN;
BEGIN
    -- Open SQL cursor number;
    curid := DBMS_SQL.OPEN_CURSOR;
    -- Parse SQL cursor number
    DBMS_SQL.PARSE(curid, sql_stmt, DBMS_SQL.NATIVE);
    bindnames := placeholders;
    -- Bind variables
    FOR i IN 1 .. bindnames.COUNT
    LOOP
        DBMS_SQL.BIND_VARIABLE(curid, bindnames(i), bindvars(i));
    END LOOP;
    -- Run SQL cursor number
    ret := DBMS_SQL.EXECUTE(curid);
    -- Switch from DBMS_SQL to native dynamic SQL
    src_cur := DBMS_SQL.TO_REFCURSOR(curid);
    FETCH src_cur INTO BULK COLLECT INTO empnos, depts;
    -- This would cause an error because curid was converted to a REF CURSOR
    -- isopen := DBMS_SQL.IS_OPEN(curid);
    CLOSE src_cur;
END;
/

7.3.4 DBMS_SQL.TO_CURSOR_NUMBER函数

DBMS_SQL.TO_CURSOR_NUMBER函数可以将一个REF CURSOR类型的游标变量(无论是强类型还是
弱类型的)转换为一个SQL游标数字,你可以将它传递给DBMS_SQL子程序
在将REF CURSOR类型的游标变量传递给DBMS_SQL.TO_CURSOR_NUMBER函数之前,你必须先打开它
在将REF CURSOR类型的游标变量转换为SQL游标数字之后,你就不用使用原生动态SQL操作它了
示例程序:

CREATE OR REPLACE PROCEDURE do_query_2 (
    sql_stmt VARCHAR2
) AUTHID DEFINER 
IS
    TYPE curtyp IS REF CURSOR;
    src_cur     curtyp;
    curid       NUMBER;
    desctab     DBMS_SQL.DESC_TAB;
    colcnt      NUMBER;
    namevar     VARCHAR2(50);
    numvar      NUMBER;
    datevar     DATE;
    empno       NUMBER := 100;
BEGIN
    -- sql_stmt := 'SELECT ... FROM employees WHERE employee_id = :b1';
    -- Open REF CURSOR variable
    OPEN src_cur FOR sql_stmt USING empno;
    -- Switch from native dynamic SQL to DBMS_SQL package
    curid := DBMS_SQL.TO_CURSOR_NUMBER(src_cur);
    DBMS_SQL.DESCRIBE_COLUMNS(curid, colcnt, desctab);
    -- Define columns
    FOR i IN 1 .. colcnt
    LOOP
        IF desctab(i).col_type = 2
        THEN
            DBMS_SQL.DEFINE_COLUMN(curid, i, numvar);
        ELSIF desctab(i).col_type = 12
        THEN
            DBMS_SQL.DEFINE_COLUMN(curid, i, datevar);
            -- statements
        ELSE
            DBMS_SQL.DEFINE_COLUMN(curid, i, namevar, 50);
        END IF;
    END LOOP;
    -- Fetch rows with DBMS_SQL package
    WHILE DBMS_SQL.FETCH_ROWS(curid) > 0
    LOOP
        FOR i IN 1 .. colcnt
        LOOP
            IF desctab(i).col_type = 1
            THEN
                DBMS_SQL.COLUMN_VALUE(curid, i, namevar);
            ELSIF desctab(i).col_type = 2
            THEN
                DBMS_SQL.COLUMN_VALUE(curid, i, numvar);
            ELSIF desctab(i).col_type = 12
            THEN
                DBMS_SQL.COLUMN_VALUE(curid, i, datevar);
                -- statements
            END IF;
        END LOOP;
    END LOOP;
    DBMS_SQL.CLOSE_CURSOR(curid);
END;
/

7.4 SQL注入

7.4.1 SQL注入技术

7.4.1.1 语句修改

7.4.1.2 语句注入

7.4.1.3 数据类型转换

7.4.2 防止SQL注入

7.4.2.1 绑定变量

7.4.2.2 验证检查

7.4.2.3 显式格式模型