ヘッダーをスキップ

Oracle Database アプリケーション開発者ガイド-基礎編
10gリリース2(10.2)

B19248-02
目次
目次
索引
索引

戻る 次へ

8 動的SQLのコーディング

この章の内容は次のとおりです。

動的SQLの概要

何度実行されても変化しない静的SQLとは異なり、動的SQLを使用すると実行時に文字列としてSQL文を作成できます。この文字列にはSQL文またはPL/SQLブロックのテキストが含まれ、バインド引数のプレースホルダを含めることもできます。

SQL文のすべてのテキストはコンパイル時にわかっているわけではないため、動的SQLを使用すると、汎用的で柔軟なアプリケーションを作成できます。動的SQLは、PL/SQL、Pro*C/C++およびJavaなど、複数の異なる開発環境で使用できます。

動的SQLを使用するアプリケーションの例として、データ・ウェアハウス環境のレポート・アプリケーションでは、実行時まで表の名前がわからない場合があります。これらの表には、inv_01_2003inv_04_2003inv_07_2003inv_10_2003inv_01_2004など、四半期の開始月と年に従った名前が割り当てられます。この場合、レポート・アプリケーションで動的SQLを使用し、表の名前を実行時に指定するようにできます。

また、ユーザーが選択するソート順で複合問合せを実行する場合もあります。それぞれ異なるORDER BY句を使用して問合せのコーディングを2回行うかわりに、指定のORDER BY句を含む問合せを動的に作成できます。

動的SQLを使用したプログラミング

一貫性を持たせるために、この章では主にPL/SQLの観点から動的SQLについて説明します。ほとんどの動的SQL文を処理するには、EXECUTE IMMEDIATE文を使用します。PL/SQLプロシージャで複数行問合せを処理するには、OPEN-FOR文、FETCH文およびCLOSE文を使用します。

Oracle Databaseでは、PL/SQLアプリケーションに次の方法で動的SQL文を実装できます。

この章では、動的SQLに対するPL/SQLサポートについて説明しましたが、その他の言語でも動的SQLをコールできます。

動的SQLの実行にOCI、Pro*C/C++またはPro*COBOLを使用するプログラムの場合は、PL/SQLストアド・プロシージャおよびストアド・ファンクション内のネイティブ動的SQLに切り替えることを考慮してください。クライアント側アプリケーションからの動的SQL操作に必要なネットワーク・ラウンドトリップは、パフォーマンスを低下させる場合があります。ストアド・プロシージャはサーバーに常駐できるため、ネットワークのオーバヘッドをなくすことができます。PL/SQLストアド・プロシージャおよびストアド・ファンクションは、OCI、Pro*C/C++またはPro*COBOLのアプリケーションからコールできます。

関連項目

DBMS_SQLパッケージの詳細は、『Oracle Database PL/SQLパッケージ・プロシージャおよびタイプ・リファレンス』を参照してください。PL/SQL以外の言語からOracle Databaseのストアド・プロシージャおよびストアド・ファンクションをコールする方法は、次のマニュアルを参照してください。

  • 『Oracle Call Interfaceプログラマーズ・ガイド』

  • 『Pro*C/C++プログラマーズ・ガイド』

  • 『Pro*COBOL Precompilerプログラマーズ・ガイド』

 

動的SQLを使用する理由

動的SQLと静的SQLには、それぞれメリットとデメリットがあります。静的SQL文はコンパイル時に完全なテキストがわかっているもので、そのメリットは次のとおりです。

静的SQLには前述のメリットがありますが、次のように動的SQLで解決できる制約もあります。

原則として、静的SQLでは目的が達成できない場合、または静的SQLでは使用が煩雑になる場合のみ、動的SQLを使用する必要があります。次の各項では、動的SQLを使用する必要のある典型的な状況について説明します。

PL/SQLでのDDL文およびSCL文の実行

次の種類の文をPL/SQLプログラム・ユニット内で実行できるのは、動的SQLのみです。

次のネイティブ動的SQLの例では、SELECT文をTABLE句とともに使用しています。

例 8-1    動的SQLにおけるSELECT.. . TABLEの使用

-- Create an object t_emp and a datatype t_emplist as a table of type t_emp
CREATE TYPE t_emp AS OBJECT (id NUMBER, name VARCHAR2(20))
/
CREATE TYPE t_emplist AS TABLE OF t_emp
/
-- Create a table with a nested table of type t_emplist
CREATE TABLE dept_new (id NUMBER, emps t_emplist)
  NESTED TABLE emps STORE AS emp_table;
-- Populate the dept_new table with data
INSERT INTO dept_new VALUES 
(
  10, 
  t_emplist
  (
    t_emp(1, 'SCOTT'),
    t_emp(2, 'BRUCE')
  )
);
-- Write a PL/SQL block that queries table dept_new and nested table emps
-- SELECT ... FROM ... TABLE is not allowed in static SQL in PL/SQL
DECLARE
  v_deptid  NUMBER;
  v_ename   VARCHAR2(20);
BEGIN
  EXECUTE IMMEDIATE 'SELECT d.id, e.name
                     FROM dept_new d, TABLE(d.emps) e 
                     WHERE e.id = 1'
  INTO v_deptid, v_ename;
END;
/

関連項目

DDL文およびSCL文の詳細は、『Oracle Database SQLリファレンス』を参照してください。 

動的問合せの実行

動的SQLを使用すると、動的問合せ(実行時まで完全なテキストが使用できない問合せ)を実行するアプリケーションを作成できます。動的問合せは、次のような様々なアプリケーションで使用する必要があります。

コンパイル時には存在しないデータベース・オブジェクトの参照

多くのアプリケーションでは、定期的に生成されるデータと対話する必要があります。たとえば、表定義はコンパイル時にわかっても、表の名前はわからない場合があります。動的SQLを使用すると、実行時に表の名前を指定できます。

「動的SQLの概要」で説明したデータ・ウェアハウス・アプリケーションの例では、システムにより四半期ごとに新規の表が生成されます。この場合、次のサンプル・プロシージャのような動的SQL問合せを使用すると、実行時にユーザーが表の名前を指定できます。

例 8-2    表名を動的に指定する例

CREATE OR REPLACE PROCEDURE query_invoice
  (p_month VARCHAR2, p_year VARCHAR2)
IS
  TYPE cur_typ IS REF CURSOR;
  v_inv_cursor cur_typ; -- Declare a cursor variable
  v_inv_query  VARCHAR2(200);
  v_inv_num    NUMBER;
  v_inv_cust   VARCHAR2(20);
  v_inv_amt    NUMBER;
BEGIN
  -- Write dynamic query receiving month and year as parameters
  -- and using these values to form the table name, for example, inv_APR_2004
  v_inv_query := 'SELECT num, cust, amt FROM inv_' 
                 || p_month 
                 || '_'
                 || p_year 
                 || ' WHERE v_inv_num = :g_id';
  -- Open a cursor variable
  OPEN v_inv_cursor FOR v_inv_query USING v_inv_num;
  -- Fetch row into variables
    LOOP
      FETCH v_inv_cursor 
        INTO v_inv_num, v_inv_cust, v_inv_amt;
      EXIT WHEN v_inv_cursor%NOTFOUND;
      -- process row here
    END LOOP;
  CLOSE v_inv_cursor;
END;
/

実行の動的最適化

動的SQLを使用すると、ヒントを動的にSQL文に連結して実行を最適化するSQL文を作成できます。これによって、再コンパイルしなくても、現在のデータベースの詳細情報に基づいてヒントを変更できます。たとえば、次のサンプル・プロシージャでは、p_hintという変数を使用して、ユーザーがSELECT文にヒント・オプションを渡せるようになっています。

例 8-3    ヒントの連結

CREATE OR REPLACE PROCEDURE query_emp
  (p_hint VARCHAR2) 
IS
  TYPE cur_typ IS REF CURSOR;
  v_emp_cursor cur_typ;
BEGIN
  OPEN v_emp_cursor FOR 'SELECT '
                        || p_hint 
                        ||' empno, ename, sal, job FROM emp WHERE empno = 7566';
  -- process ...
  CLOSE v_emp_cursor;
END;
/

例8-3では、ユーザーはp_hintとして次のような値を渡すことができます。

p_hint = '/*+ ALL_ROWS */' 
p_hint = '/*+ FIRST_ROWS */' 
p_hint = '/*+ CHOOSE */' 

関連項目

ヒントの使用方法の詳細は、『Oracle Databaseパフォーマンス・チューニング・ガイド』を参照してください。 

動的PL/SQLブロックの実行

無名PL/SQLブロックを実行するには、EXECUTE IMMEDIATE文を使用できます。このように、実行時にブロックの内容を構成することによって、柔軟性を向上できます。

たとえば、イベント番号を受け入れ、それをそのイベントのハンドラにディスパッチするアプリケーションを作成します。ハンドラの名前はEVENT_HANDLER_event_numという形式で、event_numがイベントの番号です。1つの方法として、ディスパッチャをスイッチ文として実装する方法があります。この場合、該当するハンドラにコードが静的コールを実行して、それぞれのイベントを処理します。このコードは、新しいイベント用のハンドラが追加されるたびにディスパッチャ・コードを更新する必要があるため、あまり拡張可能ではありません。

例 8-4    静的SQLを使用したイベントのディスパッチ

CREATE OR REPLACE PROCEDURE event_handler_1
  (p_handle NUMBER) 
IS
BEGIN 
  -- process event 1
  RETURN; 
END;
/

CREATE OR REPLACE PROCEDURE event_handler_2
  (p_handle NUMBER)
IS
BEGIN 
  -- process event 2
  RETURN; 
END;
/

CREATE OR REPLACE PROCEDURE event_handler_3
  (p_handle NUMBER)
IS
BEGIN 
  -- process event 3
  RETURN; 
END;
/

CREATE OR REPLACE PROCEDURE event_dispatcher
  (p_event_num NUMBER, p_handle NUMBER)
IS
BEGIN
  IF (p_event_num = 1) THEN
    EVENT_HANDLER_1(p_handle);
  ELSIF (p_event_num = 2) THEN
    EVENT_HANDLER_2(p_handle);
  ELSIF (p_event_num = 3) THEN
    EVENT_HANDLER_3(p_handle);
  END IF;
END;
/

ネイティブ動的SQLを使用すると、次の例に示すように、より小さく、柔軟なイベント・ディスパッチャを作成できます。

例 8-5    ネイティブ動的SQLを使用したイベントのディスパッチ

CREATE OR REPLACE PROCEDURE event_dispatcher
   (p_event_num NUMBER, p_handle NUMBER) 
IS
BEGIN
  EXECUTE IMMEDIATE
    'BEGIN
       EVENT_HANDLER_' || TO_CHAR(p_event_num) || '(:1);
     END;'
  USING p_handle;
END;
/

実行者権限を使用した動的操作の実行

動的SQLとともに実行者権限の機能を使用すると、実行者権限およびスキーマに基づいて動的SQL文を発行するアプリケーションを作成できます。実行者権限および動的SQLという2つの機能によって、実行者のデータおよびモジュールに対して操作およびアクセスできる再利用可能なアプリケーション・サブコンポーネントを作成できます。

関連項目

実行者権限とネイティブ動的SQLの詳細は、『PL/SQLユーザーズ・ガイドおよびリファレンス』を参照してください。 

ネイティブ動的SQLを使用した開発: 使用例

この使用例には、ネイティブ動的SQLを使用して次の操作を実行する方法が説明されています。

この使用例では、データベースが人事管理に使用されています。officesというマスター表には、この企業のすべての事務所のリストが含まれています。offices表は、次のように定義されています。

Column Name          Null?          Type         
LOCATION             NOT_NULL       VARCHAR2(200)

emp_location表は複数あり、これには社員情報が含まれています。locationは事務所のある都市の名前です。たとえば、emp_houstonという表には、ヒューストン事務所の社員の情報が含まれ、emp_bostonという表には、ボストン事務所の社員の情報が含まれています。

emp_location表は、次のように定義されています。

Column Name          Null?          Type         
EMPNO                NOT_NULL       NUMBER(4)
ENAME                NOT_NULL       VARCHAR2(10)
JOB                  NOT_NULL       VARCHAR2(9)
SAL                  NOT_NULL       NUMBER(7,2)
DEPTNO               NOT_NULL       NUMBER(2)

次の項では、hrデータベース内のデータに対して実行できる様々なネイティブ動的SQL操作を説明します。

ネイティブ動的SQLによるDML操作例

次のネイティブ動的SQLプロシージャによって、特定の役職を持つ社員全員の給料が増額されます。

例 8-6    ネイティブ動的SQLによるDMLの実行

CREATE OR REPLACE PROCEDURE salary_raise
  (p_raise_percent NUMBER, p_job VARCHAR2) 
IS
  TYPE loc_array_type IS TABLE OF VARCHAR2(40)
    INDEX BY binary_integer;
  v_dml_str VARCHAR2(200);
  v_loc_array loc_array_type;
BEGIN
  -- bulk fetch the list of office locations
  SELECT location BULK COLLECT 
    INTO v_loc_array
  FROM offices;
  -- for each location, give a raise to employees with the given 'job' 
  FOR i IN v_loc_array.first..v_loc_array.last LOOP
    v_dml_str := 'UPDATE emp_' 
                 || v_loc_array(i) 
                 || ' SET sal = sal * (1+(:p_raise_percent/100))'
                 || ' WHERE p_job = :g_job_title';
    EXECUTE IMMEDIATE v_dml_str USING p_raise_percent, p_job;
  END LOOP;
END;
/
SHOW ERRORS;

ネイティブ動的SQLによるDDL操作例

EXECUTE IMMEDIATE文は、DDL操作を実行できます。たとえば、次のプロシージャは、事務所の所在地を追加します。

例 8-7    ネイティブ動的SQLを使用した表の作成

CREATE OR REPLACE PROCEDURE add_location 
  (p_loc VARCHAR2) 
IS
BEGIN
  -- Insert new location in master table, for example, detroit
  INSERT INTO offices VALUES (p_loc);
  -- Create an employee information table
  EXECUTE IMMEDIATE  
  -- Use the parameter value to concatenate the table name, as in emp_detroit
    'CREATE TABLE emp_' 
    || p_loc 
    || '( 
           empno   NUMBER(4) NOT NULL,
           ename   VARCHAR2(10),
           job     VARCHAR2(9),
           sal     NUMBER(7,2),
           deptno  NUMBER(2) 
       )';
END;
/

SHOW ERRORS;

次のプロシージャでは、同じ連結方法を使用して表を削除します。

例 8-8    ネイティブ動的SQLを使用した表の削除

CREATE OR REPLACE PROCEDURE drop_location 
  (p_loc VARCHAR2)
IS
BEGIN
  -- Drop the employee table for location 'p_loc', for example, emp_detroit
  EXECUTE IMMEDIATE 'DROP TABLE ' || 'emp_' || p_loc;
  -- Remove location from master table
  DELETE FROM offices WHERE location = p_loc;
END;
/
SHOW ERRORS;

ネイティブ動的SQLによる単一行問合せ例

EXECUTE IMMEDIATE文は、動的な単一行問合せを実行できます。USING句にバインド変数を指定し、この文のINTO句に指定されているターゲットに結果の行をフェッチできます。次の関数は、特定の所在地で特定の職務を遂行している社員数を取得します。

例 8-9    ネイティブ動的SQLによる単一行問合せの実行

CREATE OR REPLACE FUNCTION get_num_of_employees 
  (p_loc VARCHAR2, p_job VARCHAR2) 
RETURN NUMBER
IS
  v_query_str VARCHAR2(1000);
  v_num_of_employees NUMBER;
BEGIN
  -- Use concatenation to form the table name in the SELECT statement
  v_query_str := 'SELECT COUNT(*) FROM emp_' 
                 || p_loc
                 || ' WHERE job = :1';
  -- Execute the query and put the result row in a variable
  EXECUTE IMMEDIATE v_query_str
    INTO v_num_of_employees
    USING p_job;
  RETURN v_num_of_employees;
END;
/
SHOW ERRORS;

ネイティブ動的SQLによる複数行問合せ例

動的な複数行問合せは、OPEN-FOR文、FETCH文およびCLOSE文を使用して実行できます。たとえば、次のプロシージャは、特定の所在地で特定の職種についているすべての社員をリストします。

例 8-10    動的SQLによる複数行問合せの実行

CREATE OR REPLACE PROCEDURE list_employees
  (p_loc VARCHAR2, p_job VARCHAR2) 
IS
  TYPE cur_typ IS REF CURSOR;
  -- Define a cursor variable
  v_emp_cursor  cur_typ;
  v_query_str   VARCHAR2(1000);
  v_emp_name    VARCHAR2(20);
  v_emp_num     NUMBER;
BEGIN
  -- Use concatenation to form the SELECT statement
  v_query_str := 'SELECT ename, empno FROM emp_' 
                 || p_loc 
                 || ' WHERE job = :g_job_title';
  -- Open a cursor variable for the query
  OPEN v_emp_cursor FOR v_query_str USING p_job;
    -- Loop through each row to find employees who perform the specified job
    LOOP
      -- Fetch the employee name and ID into variables
      FETCH v_emp_cursor INTO v_emp_name, v_emp_num;
      EXIT WHEN v_emp_cursor%NOTFOUND;
      -- Process row here
    END LOOP;
  CLOSE v_emp_cursor;
END;
/
SHOW ERRORS;

ネイティブ動的SQLまたはDBMS_SQLパッケージの選択

Oracle Databaseでは、PL/SQL内で動的SQLを使用するために、ネイティブ動的SQLおよびDBMS_SQLパッケージという2つの方法を提供しています。どちらの方法にもメリットとデメリットがあります。次の項では、この2つの方法のメリットを詳しく説明します。

ネイティブ動的SQLを使用すると、動的SQL文をPL/SQLコード内に直接入れることができます。このような動的な文は次のとおりです。

ほとんどのネイティブ動的SQL文を処理するには、EXECUTE IMMEDIATE文を使用します。複数行SELECT文を処理するには、OPEN-FOR文、FETCH文およびCLOSE文を使用します。


注意

ネイティブ動的SQLを使用するには、COMPATIBLE初期化パラメータを8.1.0以上に設定する必要があります。 


ネイティブ動的SQL文のかわりに、DBMS_SQLパッケージには動的SQL文を実行するためのPL/SQL APIが用意されています。たとえば、DBMS_SQLパッケージには、次の操作を実行するためのプロシージャが含まれています。

DBMS_SQLパッケージを使用するプログラムは、このパッケージをコールして動的SQL操作を実行します。

関連項目

  • ネイティブ動的SQLの詳細は、『PL/SQLユーザーズ・ガイドおよびリファレンス』を参照してください。

  • DBMS_SQLパッケージの詳細は、『Oracle Database PL/SQLパッケージ・プロシージャおよびタイプ・リファレンス』を参照してください。

  • COMPATIBLE初期化パラメータの詳細は、『Oracle Databaseアップグレード・ガイド』を参照してください。

 

ネイティブ動的SQLのメリット

ネイティブ動的SQLには、DBMS_SQLパッケージと比べて次のようなメリットがあります。

使用しやすさ

ネイティブ動的SQLはSQLと統合されているため、現在PL/SQLコードで静的SQLを使用している方法と同じ方法で使用できます。ネイティブ動的SQLコードは、通常、DBMS_SQLパッケージを使用した同等のコードより簡潔で読みやすくなります。

DBMS_SQLパッケージを使用すると、数多くのプロシージャおよびファンクションを厳密な順序に従ってコールする必要があり、単純な操作にも多量のコードが必要になります。ネイティブ動的SQLを使用すると、それほど複雑になることはありません。

表8-3に、同じ操作の実行で、DBMS_SQLパッケージを使用した場合と、ネイティブ動的SQLを使用した場合の、必要となるコード量の違いを示します。

表 8-1    DBMS_SQLパッケージとネイティブ動的SQLとのコード量の比較 
DBMS_SQLパッケージ  ネイティブ動的SQL 
CREATE OR REPLACE PROCEDURE insert_into_table 
(p_table_name VARCHAR2, p_deptnumber NUMBER,
p_deptname VARCHAR2, p_location VARCHAR2)
IS
v_cur_hdl INTEGER;
v_stmt_str VARCHAR2(200);
v_rows_processed BINARY_INTEGER;

BEGIN
v_stmt_str := 'INSERT INTO '
|| p_table_name
|| ' VALUES (:g_deptno, :g_dname, :g_loc)';
v_cur_hdl := DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.PARSE(v_cur_hdl, v_stmt_str,
DBMS_SQL.NATIVE);
DBMS_SQL.BIND_VARIABLE (v_cur_hdl,':g_deptno',
p_deptnumber);
DBMS_SQL.BIND_VARIABLE (v_cur_hdl, ':g_dname',
p_deptname);
DBMS_SQL.BIND_VARIABLE (v_cur_hdl, ':g_loc',
p_location);
v_rows_processed := DBMS_SQL.EXECUTE(v_cur_hdl);
DBMS_SQL.CLOSE_CURSOR(v_cur_hdl);
END;
/
 
CREATE OR REPLACE PROCEDURE insert_into_table 
(p_table_name VARCHAR2, p_deptnumber NUMBER,
p_deptname VARCHAR2, p_location VARCHAR2)
IS
v_stmt_str VARCHAR2(200);



BEGIN
v_stmt_str := 'INSERT INTO '
|| p_table_name
|| ' VALUES (:g_deptno, :g_dname, :g_loc)';
EXECUTE IMMEDIATE v_stmt_str
USING p_deptnumber, p_deptname,
p_location;








END;
/
 

DBMS_SQLパッケージを超える速度

PL/SQLインタプリタにはネイティブ動的SQLのサポートが組み込まれているため、PL/SQLでのネイティブ動的SQLのパフォーマンスは、静的SQLのパフォーマンスと同等になります。ネイティブ動的SQLを使用するプログラムは、DBMS_SQLパッケージを使用するプログラムよりはるかに高速です。通常、ネイティブ動的SQL文は、DBMS_SQLコールを使用する同等の文より1.5〜3倍パフォーマンスが向上しますパフォーマンスの向上は、アプリケーションによっても異なります。

ネイティブ動的SQLでは、文の準備手順、バインド手順および実行手順を1つの操作の中にまとめます。これによって、データ・コピーおよびプロシージャ・コールによるオーバーヘッドが最小化され、パフォーマンスが向上します。

DBMS_SQLパッケージはプロシージャAPIに基づいているため、プロシージャ・コールが多くなり、データをコピーするオーバーヘッドが発生します。DBMS_SQLパッケージは、変数がバインドされるたびにPL/SQLバインド変数を自身の領域にコピーし、実行時に使用できるようにします。同様に、フェッチを実行するたびに、まずDBMS_SQLパッケージが管理する領域にデータがコピーされ、次に、フェッチされたデータが1列ずつ適切なPL/SQL変数にコピーされます。この結果、データのコピーによるオーバーヘッドがかなり多くなります。

バインド変数を介したパフォーマンスの向上

ネイティブ動的SQLおよびDBMS_SQLパッケージのどちらの場合も、バインド変数を使用することでパフォーマンスを向上できます。これは、バインド変数の使用によって、Oracle Databaseが複数のSQL文に対して1つのカーソルを共有できるためです。

例8-11のネイティブ動的SQLコードでは、バインド変数のかわりにパラメータを使用してSQL文が作成されます。

例 8-11    バインド変数なしのネイティブ動的SQLの使用

CREATE OR REPLACE PROCEDURE del_dept
  (p_department_id  departments.department_id%TYPE) 
IS
BEGIN
  EXECUTE IMMEDIATE 'DELETE FROM departments WHERE department_id = ' 
  || TO_CHAR(p_department_id);
END;
/
SHOW ERRORS;

このプロシージャでは、p_department_idパラメータごとに新規カーソルが個別に作成されるため、リソースが競合してパフォーマンスが低下します。かわりに、例8-12に示すようにバインド変数を使用してSQL文を作成できます。

例 8-12    ネイティブ動的SQLとバインド変数の使用

CREATE OR REPLACE PROCEDURE del_dept
  (p_department_id  departments.department_id%TYPE)
IS
BEGIN
  EXECUTE IMMEDIATE 'DELETE FROM departments WHERE department_id = :1' 
    USING p_department_id;
END;
/
SHOW ERRORS;

例8-12では、1つのカーソルがバインド変数my_deptnoの異なる複数の値に再利用されているため、パフォーマンスおよびスケーラビリティが向上します。

ユーザー定義型のサポート

ネイティブ動的SQLは、PL/SQLで静的SQLがサポートしているすべての型(ユーザー定義のオブジェクト、コレクション、REFなどのユーザー定義型を含む)をサポートしています。DBMS_SQLパッケージでは、ユーザー定義型はサポートされません。


注意

DBMS_SQLパッケージは、配列を限定的にサポートします。詳細は、『Oracle Database PL/SQLパッケージ・プロシージャおよびタイプ・リファレンス』を参照してください。 


レコードへのフェッチのサポート

ネイティブ動的SQLおよび静的SQLは、どちらもレコードへのフェッチをサポートしますが、DBMS_SQLパッケージはこれをサポートしません。ネイティブ動的SQLの場合は、問合せの結果の行をPL/SQLレコードに直接フェッチできます。例8-13では、問合せ結果の行がv_emp_rec変数にフェッチされます。

例 8-13    ネイティブ動的SQLを使用したレコードへのフェッチ

DECLARE
  TYPE EmpCurTyp  IS REF CURSOR;
  v_emp_cursor    EmpCurTyp;
  emp_record      emp%ROWTYPE;
  v_stmt_str      VARCHAR2(200);
  v_e_job         emp.job%TYPE;
BEGIN
  v_stmt_str := 'SELECT * FROM emp WHERE job = :1';
  -- in a multi-row query
  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 v_emp_cursor;
  -- in a single-row query
  EXECUTE IMMEDIATE v_stmt_str INTO emp_record USING 'PRESIDENT';
END;
/

DBMS_SQLパッケージのメリット

DBMS_SQLパッケージには、ネイティブ動的SQLと比べて次のようなメリットがあります。

クライアント側プログラムでのサポート

DBMS_SQLパッケージはクライアント側プログラム内でサポートされていますが、ネイティブ動的SQLはサポートされていません。クライアント側プログラムからのDBMS_SQLパッケージへのすべてのコールは、PL/SQLリモート・プロシージャ・コール(RPC)に変換されます。これらのコールは、次のいずれかの操作が必要な場合に発生します。

入力数または出力数が不明な文のサポート

ネイティブ動的SQLは、入力数または出力数が不明な文をサポートしません。DBMS_SQLパッケージには、この制約がありません。そのため、DBMS_SQLパッケージのDESCRIBE_COLUMNSプロシージャを使用すると、DBMS_SQLによってオープンおよび解析されるカーソルの列を記述できます。このプロシージャの機能は、SQL*PlusのDESCRIBEコマンドと同様です。ネイティブ動的SQLにはDESCRIBE機能はありません。

関連項目

実行時までSELECTリストがわからない状況でDESCRIBE_COLUMNSを使用して問合せを作成する例は、『Oracle Database PL/SQLパッケージ・プロシージャおよびタイプ・リファレンス』を参照してください。 

32KBを超える大規模SQL文のサポート

DBMS_SQLパッケージは、32KBを超える大規模なSQL文をサポートします。ネイティブ動的SQLはサポートしません。

SQL文の再利用

DBMS_SQLパッケージのPARSEプロシージャは、SQL文を1回解析します。最初に解析した後、この文は異なるバインド引数を指定して複数回使用できます。

ネイティブ動的SQLでは、SQL文を使用するたびにその文を準備します。文の準備では、通常、解析、最適化および計画の生成が行われます。この準備によって、パフォーマンスはわずかに低下しますが、ネイティブ動的SQLによって、全体ではパフォーマンスは向上します。

DBMS_SQLパッケージ・コードおよびネイティブ動的SQLコードの例

次の例に、DBMS_SQLパッケージを使用した場合、およびネイティブ動的SQLを使用した場合の操作に必要なコードの違いを示します。次の操作について例を示します。

一般に、ネイティブ動的SQLコードの方が読みやすく簡潔なため、開発生産性が向上します。

動的SQLを使用した問合せの例

次に、バインド変数1つ(:g_jobname)および選択列2つ(enameおよびsal)で構成される動的問合せ文を示します。

v_stmt_str := 'SELECT ename, sal 
               FROM emp 
               WHERE job = :g_jobname';

この例では、emp表のjob列の職種がSALESMANの社員を問い合せます。表8-2に、DBMS_SQLパッケージおよびネイティブ動的SQLを使用してこの問合せを行うコード例を示します。

表 8-2    DBMS_SQLパッケージを使用した問合せとネイティブ動的SQLを使用した問合せ 
DBMS_SQLの問合せ操作  ネイティブ動的SQLの問合せ操作 
DECLARE  
v_stmt_str VARCHAR2(200);
v_cur_hdl INT;
v_rows_processed INT;
v_name VARCHAR2(10);
v_salary INT;
BEGIN
v_cur_hdl := DBMS_SQL.OPEN_CURSOR; -- open cursor
v_stmt_str := 'SELECT ename, sal
FROM emp
WHERE job = :g_jobname';
DBMS_SQL.PARSE(v_cur_hdl,v_stmt_str,DBMS_SQL.NATIVE);
-- Supply binds (bind by name)
DBMS_SQL.BIND_VARIABLE(v_cur_hdl, 'g_jobname',
'SALESMAN');
-- Describe defines
DBMS_SQL.DEFINE_COLUMN(v_cur_hdl, 1, v_name, 200);
DBMS_SQL.DEFINE_COLUMN(v_cur_hdl, 2, v_salary);
-- Execute
v_rows_processed := DBMS_SQL.EXECUTE(v_cur_hdl);
LOOP
-- Fetch a row
IF DBMS_SQL.FETCH_ROWS(v_cur_hdl) > 0 THEN
-- Fetch columns from the row
DBMS_SQL.COLUMN_VALUE(v_cur_hdl, 1, v_name);
DBMS_SQL.COLUMN_VALUE(v_cur_hdl, 2, v_salary);
-- Process
ELSE
EXIT;
END IF;
END LOOP;
DBMS_SQL.CLOSE_CURSOR(v_cur_hdl); -- close cursor
END;
/
 
DECLARE
TYPE EmpCurTyp IS REF CURSOR;
v_emp_cursor EmpCurTyp;
v_stmt_str VARCHAR2(200);
v_name VARCHAR2(20);
v_salary NUMBER;
BEGIN
v_stmt_str := 'SELECT ename, sal
FROM emp
WHERE job = :1';
OPEN v_emp_cursor FOR v_stmt_str
USING 'SALESMAN';
LOOP
FETCH v_emp_cursor
INTO v_name, v_salary;
EXIT WHEN v_emp_cursor%NOTFOUND;
-- Process data
END LOOP;
CLOSE v_emp_cursor;













END;
/
 

動的SQLを使用したDMLの実行例

次に、列が3つある表に対する動的INSERT文を示します。

v_stmt_str := 'INSERT INTO dept_new VALUES (:g_deptno, :g_dname, :g_loc)';

この例は、PL/SQL変数deptnumberdeptnameおよびlocationを列値とする新しい行を挿入します。表8-3に、DBMS_SQLパッケージおよびネイティブ動的SQLを使用してこのタスクを行うコード例を示します。

表 8-3    DBMS_SQLパッケージを使用したDML操作とネイティブ動的SQLを使用したDML操作 
DBMS_SQLのDML操作  ネイティブ動的SQLのDML操作 
DECLARE
v_stmt_str VARCHAR2(200);
v_cur_hdl NUMBER;
v_deptnumber NUMBER := 99;
v_deptname VARCHAR2(20);
v_location VARCHAR2(10);
v_rows_processed NUMBER;
BEGIN
v_stmt_str := 'INSERT INTO dept
VALUES (:g_deptno, :g_dname, :g_loc)';
v_cur_hdl := DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.PARSE(v_cur_hdl, v_stmt_str,
DBMS_SQL.NATIVE);
-- Supply binds
DBMS_SQL.BIND_VARIABLE
(v_cur_hdl, ':g_deptno', v_deptnumber);
DBMS_SQL.BIND_VARIABLE
(v_cur_hdl, ':g_dname', v_deptname);
DBMS_SQL.BIND_VARIABLE
(v_cur_hdl, ':g_loc', v_location);
v_rows_processed := DBMS_SQL.EXECUTE(v_cur_hdl);
DBMS_SQL.CLOSE_CURSOR(v_cur_hdl);
END;
/
 
DECLARE
v_stmt_str VARCHAR2(200);
v_deptnumber NUMBER := 99;
v_deptname VARCHAR2(20);
v_location VARCHAR2(10);


BEGIN
v_stmt_str := 'INSERT INTO dept
VALUES (:g_deptno, :g_dname, :g_loc)';
EXECUTE IMMEDIATE v_stmt_str
USING v_deptnumber, v_deptname,
v_location;









END;
/
 

動的SQLを使用したRETURNING句を持つDMLの実行例

次に、部門の所在地を更新し、部門の名前を戻す動的UPDATE文を示します。

v_stmt_str := 'UPDATE dept_new 
               SET loc = :g_newloc
               WHERE deptno = :g_deptno
               RETURNING dname INTO :g_dname';

表8-4に、DBMS_SQLパッケージおよびネイティブ動的SQLを使用してこの操作を行うコード例を示します。

表 8-4    DBMS_SQLパッケージを使用したDML RETURNING操作とネイティブ動的SQLを使用したDML RETURNING操作 
DBMS_SQLのDML RETURNING操作  ネイティブ動的SQLのDML RETURNING操作 
DECLARE 
deptname_array DBMS_SQL.VARCHAR2_TABLE;
v_cur_hdl INT;
v_stmt_str VARCHAR2(200);
v_location VARCHAR2(20);
v_deptnumber NUMBER := 10;
v_rows_procsd NUMBER;
BEGIN
v_stmt_str := 'UPDATE dept
SET loc = :g_newloc
WHERE deptno = :g_deptno
RETURNING dname INTO :g_dname';
v_cur_hdl := DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.PARSE
(v_cur_hdl, v_stmt_str, DBMS_SQL.NATIVE);
-- Supply binds
DBMS_SQL.BIND_VARIABLE
(v_cur_hdl, ':g_newloc', v_location);
DBMS_SQL.BIND_VARIABLE
(v_cur_hdl, ':g_deptno', v_deptnumber);
DBMS_SQL.BIND_ARRAY
(v_cur_hdl, ':g_dname', deptname_array);
-- Execute cursor
v_rows_procsd := DBMS_SQL.EXECUTE(v_cur_hdl);
-- Get RETURNING column into OUT bind array
DBMS_SQL.VARIABLE_VALUE
(v_cur_hdl, ':g_dname', deptname_array);
DBMS_SQL.CLOSE_CURSOR(v_cur_hdl);
END;
/
 
DECLARE 
deptname_array DBMS_SQL.VARCHAR2_TABLE;
v_stmt_str VARCHAR2(200);
v_location VARCHAR2(20);
v_deptnumber NUMBER := 10;
v_deptname VARCHAR2(20);

BEGIN
v_stmt_str := 'UPDATE dept
SET loc = :g_newloc
WHERE deptno = :g_deptno
RETURNING dname INTO :g_dname';
EXECUTE IMMEDIATE v_stmt_str
USING v_location, v_deptnumber, OUT
v_deptname;













END;
/
 

PL/SQLでのSQLインジェクションの回避

SQLインジェクションとは、SQL文にクライアントから提供されるデータを使用するアプリケーションを悪用する手法です。この手法の目的は、制限付きデータを問い合せたり操作するためにデータベースに不正にアクセスすることです。この項では、PL/SQLにおけるSQLインジェクションの脆弱性と、SQLインジェクションの防止方法について説明します。

この項の内容は次のとおりです。

SQLインジェクション手法の概要

様々なSQLインジェクション手法がありますが、すべて1つの脆弱性を持っています。つまり、文字列入力が正常に検証されず、動的SQL文に連結されることです。ここでは、説明のためにSQLインジェクション攻撃を次のように分類します。

次の各項では、これらの手法について説明します。サンプル・データベースで例を試してみるために、例8-14のスクリプトを実行します。

例 8-14    インジェクション例のための設定

CONNECT hr/hr
SET SERVEROUTPUT ON
SET LINESIZE 150
SET ECHO OFF
 
DROP TABLE user_pwd;
CREATE TABLE user_pwd( username VARCHAR2(100), password VARCHAR2(100) );
INSERT INTO user_pwd VALUES('whitehat', 'secret');
DROP TABLE delemp;
CREATE TABLE delemp AS SELECT * FROM employees;
COMMIT;

文の変更

SQL変更では、アプリケーション開発者が意図していない方法で実行されるように、動的SQL文が故意に変更されます。通常、ユーザーは問合せのWHERE句を変更するか、UNION ALL句を挿入することで不正なデータを取得します。この手法の従来からの例は、WHERE句が常にTRUEになるようにしてパスワード認証をバイパスする方法です。

Webフォームにユーザー名とパスワードの入力を求めるプロンプトが表示されるとします。ユーザーが「発行」をクリックすると、フォームに入力されたユーザー名とパスワードを連結して動的SQL文を作成するPL/SQLストアド・プロシージャが起動されます。このプロシージャにより、ユーザー認証のための問合せが実行されます。

例8-15に、SQL*Plusでテストできる認証プロシージャを示します。ckpwdプロシージャは、ユーザー名とパスワードを連結した値を使用して、user_pwd表のSQL問合せを作成します。ユーザーが表に格納されているユーザー名とパスワードを入力すると、問合せの実行により1行が取得されてユーザーが認証されます。また、ckpwdプロシージャは連結された問合せも表示するため、どの問合せが実行されたかを確認できます。

例 8-15    ckpwdプロシージャ

CREATE OR REPLACE PROCEDURE ckpwd (p_user IN VARCHAR2, p_pass IN VARCHAR2) 
IS
 v_query  VARCHAR2(100);
 v_output NUMBER;
BEGIN
 v_query :=    q'{SELECT COUNT(*) FROM user_pwd }'
         ||    q'{WHERE username = '}'
         ||    p_user
         ||    q'{' AND password = '}'
         ||    p_pass
         ||    q'{'}';
 DBMS_OUTPUT.PUT_LINE(CHR(10)||'Built the following query:'||CHR(10)||v_query);
 EXECUTE IMMEDIATE v_query 
  INTO v_output;
 IF v_output = 1 THEN
   DBMS_OUTPUT.PUT_LINE(CHR(10)||p_user||' is authenticated');
 ELSE
   DBMS_OUTPUT.PUT_LINE(CHR(10)||'access denied');
 END IF;
END;
/

ユーザーwhitehatがWebフォームにパスワードsecretを入力するとします。この使用例は、例8-16に示すコードをSQL*Plusで起動し、whitehatを認証することでシミュレーションできます(サンプル出力が含まれています)。

例 8-16    ckpwdプロシージャによるユーザーの認証

BEGIN
  ckpwd
  (
    p_user => q'{whitehat}',
    p_pass => q'{secret}'
  );
END;
/

Built the following query:
SELECT COUNT(*) FROM user_pwd WHERE username = 'whitehat' AND password = 'secret'
 
whitehat is authenticated

悪意のあるユーザーが、この連結を故意に使用し、Webベース・フォームにユーザー名xを入力し、パスワードとして例8-17に示すテキストを入力するとします。

例 8-17    フォームに入力されたパスワード文字列

x' OR 'x' = 'x

この使用例は、例8-18に示すコードをSQL*Plusで実行することでシミュレーションできます(サンプル出力が含まれています)。

例 8-18    ckpwdプロシージャによる文の変更の実行

BEGIN
  ckpwd
  (
    p_user => q'{x}',
    p_pass => q'{x' OR 'x' = 'x}'
  );
END;
/
 
Built the following query:
SELECT COUNT(*) FROM user_pwd WHERE username = 'x' AND password = 'x' OR 'x' = 'x'
 
x is authenticated

例8-17のように巧妙に作成されたパスワードを使用すると、OR条件で常にTRUEが戻されるように連結SQL文を変更できます。そのため、user_pwd表の問合せは、どのようなユーザー名が入力されたかに関係なく常に成功します。

文のインジェクション

文のインジェクションが発生するのは、動的に生成されるSQL文に対してユーザーが1つ以上の新規SQL文を追加する場合です。無名PL/SQLブロックは、この手法に対して脆弱です。

Webフォームにユーザー名とパスワードの入力を求めるプロンプトが表示されるとします。ユーザーが「発行」をクリックすると、フォームに入力されたユーザー名とパスワードを連結して無名PL/SQLブロックにするPL/SQLストアド・プロシージャが起動されます。このプロシージャにより、ユーザー認証のための無名ブロックが実行されます。

例8-19に、SQL*Plusでテストできる認証プロシージャを示します。call_ckpwdプロシージャは、連結を使用して、例8-15ckpwdプロシージャを起動する無名ブロックを作成します。ユーザーがuser_pwd表に格納されているユーザー名とパスワードを入力すると、ブロックの実行により1行が取得されてユーザーが認証されます。また、call_ckpwdプロシージャは連結されたテキストも出力するため、どのブロックが実行されたかを確認できます。

例 8-19    call_ckpwdプロシージャ

CREATE OR REPLACE PROCEDURE call_ckpwd (p_user IN VARCHAR2, p_pass IN VARCHAR2) 
IS
  v_block VARCHAR2(100);
BEGIN
  v_block := q'{BEGIN ckpwd( '}' 
          ||    p_user
          || q'{' , '}'
          ||    p_pass
          || q'{' ); END; }';
 
  DBMS_OUTPUT.PUT_LINE(CHR(10)||
                       'Built the following anonymous block:'||CHR(10)||v_block);
  EXECUTE IMMEDIATE v_block;
END;
/

ユーザーwhitehatがWebベース・フォームにパスワードsecretを入力するとします。この使用例は、例8-20に示すcall_ckpwdプロシージャをSQL*Plusで起動することでシミュレーションできます(サンプル出力が含まれています)。

例 8-20    call_ckpwdプロシージャによるユーザーの認証

BEGIN
  call_ckpwd 
  (
    p_user => q'{whitehat}',
    p_pass => q'{secret}'
  );
END;
/

Built the following anonymous block:
BEGIN ckpwd( 'whitehat' , 'secret' ); END;
 
Built the following query:
SELECT COUNT(*) FROM user_pwd WHERE username = 'whitehat' AND password = 'secret'
 
whitehat is authenticated

whitehatに悪意があれば、例8-21に示す文字列をWebフォームにパスワードとして入力する可能性があります。

例 8-21    フォームに入力された偽のパスワード

secret'); DELETE FROM hr.delemp WHERE UPPER('x') = UPPER('x

この手法は、例8-22に示すcall_ckpwdプロシージャをSQL*Plusで起動することでシミュレーションできます(サンプル出力が含まれています)。

例 8-22    call_ckpwdプロシージャを使用した文のインジェクションの実行

BEGIN
call_ckpwd
(
p_user => q'{whitehat}',
p_pass => q'{secret'); DELETE FROM hr.delemp WHERE UPPER('x') = UPPER('x}'
);
END;
/

Built the following anonymous block:
BEGIN ckpwd( 'whitehat' , 'secret' ); DELETE FROM hr.delemp WHERE UPPER('x') = UPPER('x'); END;

Built the following query:
SELECT COUNT(*) FROM user_pwd WHERE username = 'whitehat' AND password = 'secret'

whitehat is authenticated

例8-21に示した偽のパスワードが入力されると、システムではwhitehatが認証され、インジェクトされたDELETE文が暗黙的に実行されます。delemp表を問い合せると、インジェクトされた文により表からすべての行が暗黙的に削除されたことがわかります。

SELECT * FROM delemp;
 
no rows selected

SQLインジェクションの防止

PL/SQLアプリケーションに動的SQLを使用する場合は、入力テキストをチェックして、正確で予期した内容のみであることを確認する必要があります。必要に応じて次の方法を使用すると役立ちます。

バインド変数を使用したSQLインジェクションの防止

「バインド変数を介したパフォーマンスの向上」では、バインド変数を使用して動的SQLのパフォーマンスを向上させる方法を説明しました。プレースホルダをバインドすると、パフォーマンスは向上しますが、PL/SQLコードがSQLインジェクション攻撃にさらされます。

例8-15に示したckpwdプロシージャは、バインド変数のかわりに連結を使用していました。例8-23に、このプロシージャを書き換えて連結のかわりにバインド変数を使用する方法を示します。

例 8-23    ckpwd_bindプロシージャ

CREATE OR REPLACE PROCEDURE ckpwd_bind (p_user IN VARCHAR2, p_pass IN VARCHAR2) 
IS
 v_query  VARCHAR2(100);
 v_output NUMBER;
BEGIN
 v_query := 
   q'{SELECT COUNT(*) FROM user_pwd WHERE username = :1 AND password = :2}';
 DBMS_OUTPUT.PUT_LINE(CHR(10)||'Built the following query:'||CHR(10)||v_query);
 EXECUTE IMMEDIATE v_query 
  INTO v_output
  USING p_user, p_pass;
 IF v_output = 1 THEN
   DBMS_OUTPUT.PUT_LINE(CHR(10)||p_user||' is authenticated');
 ELSE
   DBMS_OUTPUT.PUT_LINE(CHR(10)||'access denied');
 END IF;
END;
/

ユーザーが例8-17に示した偽のパスワードをckpwd_bindプロシージャに渡そうとしても、この方法ではユーザーの認証に失敗します。例8-24に示すブロックをSQL*Plusで実行すると、コードの改訂バージョンをテストできます(サンプル出力が含まれています)。

例 8-24    ckpwd_bindプロシージャを使用した文の変更の防止

BEGIN
  ckpwd_bind 
  (
    p_user => q'{x}',
    p_pass => q'{x' OR 'x' = 'x}'
  );
END;
/

Built the following query:
SELECT COUNT(*) FROM user_pwd WHERE username = :1 AND password = :2
 
access denied

これと同じバインド方法で、例8-19に示した脆弱なcall_ckpwdプロシージャを修正できます。バインド変数をコードに排他的に使用すると、SQL文の連結を回避することで、悪意のあるユーザーによる文の変更や追加インジェクションを防止できます。Oracleデータベースでは、バインド変数の値が排他的に使用され、その内容は解析されません。これは、PL/SQLプログラムにおけるSQLインジェクションを防止する上で最も有効な方法です。

妥当性チェックを使用したSQLインジェクションの防止

プログラムでは、意図した内容であることを確認するために、常にユーザー入力を検証する必要があります。たとえば、ユーザーがDELETE文に対して部門番号を渡すと、departments表を問い合せることで、この部門番号の妥当性がチェックされます。同様に、ユーザーが削除対象の表の名前を入力すると、ALL_TABLESビューを問い合せることで、この表の存在が確認されます。


戻る 次へ
Oracle
Copyright © 2006 Oracle Corporation.

All Rights Reserved.
目次
目次
索引
索引