3

Oracle テーブル タイプを作成しました。そして、その配列を作成しました。これは、SQL クエリの IN 句には多すぎる可能性がある値の数がわからないためです。

- コードスニペット - - -

create or replace 
TYPE "INPUTCODE" 
as object
(pc varchar2(100) )

create or replace 
TYPE "INPUTCODEARR" 
    IS TABLE OF inputcode;

create or replace 
PROCEDURE "TEST_PROC" (testCodes IN inputcodeArr, timeHorizon IN NUMBER, p_recordset OUT SYS_REFCURSOR)
AS
var_sqlStmt VARCHAR2(4096);
BEGIN
var_sqlStmt := 'select t.a,t.b, t.c';
var_sqlStmt := var_sqlStmt || 'from test t';

if testCodes is not null then 
    var_sqlStmt := var_sqlStmt || ', table(testCodes) tc';
        var_sqlStmt := var_sqlStmt || 'where tc.pc = t.name';
end if; 
dbms_output.put_line('Final SQL Statement::' || var_sqlStmt);
open p_recordset for var_sqlStmt;
END TEST_PROC;

上記のものはすべてコンパイルされており、ほとんどの testCode 値を使用して TEST_PROC プロシージャを実行すると、エラー Invalid identifier for testCodes で失敗します。

手順では、コンソールに出力されている最終的なSQLステートメントは正しく、これをプロシージャ内の静的SQLステートメントとして実行すると、エラーなしで実行されます。しかし、動的SQL内では失敗します。DYNAMIC_SQL パッケージを使用して実行しようとしましたが、同じエラーが発生します。また、「table(testCodes)」のバインド変数として与えてみました。それも失敗しました。

提案してください。

4

1 に答える 1

2

動的 SQLを使用しているため、どの単語が識別子でどの単語が変数であるかを Oracle に伝える必要があります。

SQLPlus で直接実行される次のステートメントを考えてみましょう。

select t.a,t.b, t.c from test t, table(testCodes) tc

testCodesDBで名前が付けられているオブジェクトがないため、失敗します。testCodes実際には変数であることを SQL エンジンに伝える必要があります。これを行う必要があるのは、静的 SQL では変数バインディングが自動であるのに対し、動的 ​​SQL を使用することを選択したためです。

ほとんどの場合、「オブジェクト」変数は標準変数と同じ方法でバインドできます。PL/SQL では、これを行う方法がいくつかあります。たとえば、次のカーソルを使用しますUSING

SQL> DECLARE
  2     l_cur SYS_REFCURSOR;
  3     l_tab inputcodeArr := inputcodeArr(INPUTCODE('A'), INPUTCODE('B'));
  4     l_obj varchar2(100);
  5  BEGIN
  6     OPEN l_cur FOR 'SELECT pc FROM TABLE(:my_variable)' -- notice the ":"
  7        USING l_tab; -- binding by position
  8     LOOP
  9        FETCH l_cur
 10           INTO l_obj;
 11        EXIT WHEN l_cur%NOTFOUND;
 12        dbms_output.put_line(l_obj);
 13     END LOOP;
 14     CLOSE l_cur;
 15  END;
 16  /

A
B

PL/SQL procedure successfully completed

ただし、あなたの場合、条件付きでカーソルを開くことができるため、動的SQLを気にしません。

CREATE OR REPLACE PROCEDURE "TEST_PROC"(testCodes   IN inputcodeArr,
                                        timeHorizon IN NUMBER,
                                        p_recordset OUT SYS_REFCURSOR) AS
BEGIN
   IF testCodes IS NOT NULL THEN
      OPEN p_recordset FOR
         SELECT t.a, t.b, t.c FROM test t, TABLE(testCodes) tc 
          WHERE tc.pc = t.NAME;
   ELSE
      OPEN p_recordset FOR
         SELECT t.a, t.b, t.c FROM test t;
   END IF;
END TEST_PROC;

私のアドバイスは、動的 SQL を使用すると間違いを犯しやすいため、可能な限り静的 SQL を使用することです。


次のコメントを更新します。

入力の数が一定ではなく、フィルターの組み合わせが多数あるために動的 SQL を使用する必要がある場合は、次の戦略を使用できます。

CREATE OR REPLACE PROCEDURE "TEST_PROC"(testCodes   IN inputcodeArr,
                                        timeHorizon IN NUMBER,
                                        p_recordset OUT SYS_REFCURSOR) AS
   l_sql LONG := 'SELECT t.a, t.b, t.c FROM test t WHERE';
BEGIN
   -- filter #1
   IF testCodes IS NOT NULL THEN
      l_sql := l_sql || ' t.name IN (SELECT pc FROM TABLE(:filter1))';
   ELSE
      l_sql := l_sql || ' :filter1 IS NULL';
   END IF;
   -- filter #2
   IF timeHorizon IS NOT NULL THEN
      l_sql := l_sql || ' AND t.horizon = :filter2';
   ELSE
      l_sql := l_sql || ' AND :filter2 IS NULL';
   END IF;
   -- open cursor
   OPEN p_recordset FOR l_sql USING testCodes, timeHorizon;
END TEST_PROC;
/

最終的な SQL が常に同じ順序で同じ数の変数を持つようにしていますが、フィルターが NULL である各条件はトートロジー ( NULL IS NULL) になります。

于 2013-08-22T11:58:35.737 に答える