web-dev-qa-db-ja.com

Oracleでグローバル一時テーブルを回避する方法

SQLServerのストアドプロシージャをOracleプロシージャに変換しました。 SQL Server SPは、セッションテーブルに大きく依存していました(INSERT INTO #table1...)これらのテーブルは、Oracleでグローバル一時テーブルとして変換されました。最終的に、400SPに対して500GTTが発生しました。

現在、OracleでGTTを使用することは、パフォーマンスやその他の問題のために最後のオプションと見なされていることがわかりました。

他にどのような選択肢がありますか?コレクション?カーソル?

GTTの一般的な使用法は次のとおりです。

GTTに挿入

INSERT INTO some_gtt_1
  (column_a,
   column_b,
   column_c)
  (SELECT someA,
      someB,
      someC
     FROM TABLE_A
    WHERE condition_1 = 'YN756'
      AND type_cd = 'P'
      AND TO_NUMBER(TO_CHAR(m_date, 'MM')) = '12'
      AND (lname LIKE (v_LnameUpper || '%') OR
      lname LIKE (v_searchLnameLower || '%'))
      AND (e_flag = 'Y' OR
      it_flag = 'Y' OR
      fit_flag = 'Y'));

GTTを更新する

UPDATE some_gtt_1 a
SET column_a = (SELECT b.data_a FROM some_table_b b 
               WHERE a.column_b = b.data_b AND a.column_c = 'C')
WHERE column_a IS NULL OR column_a = ' ';

その後、GTTからデータを取得します。これらは単なるサンプルクエリですが、実際には、クエリは非常に複雑で、多くの結合とサブクエリがあります。

私は3つの部分からなる質問があります:

  1. 上記のサンプルクエリをコレクションやカーソルに変換する方法を誰かが示すことができますか?
  2. GTTを使用すると、SQLをネイティブに操作できるので...なぜGTTから離れるのですか?彼らは本当にそんなに悪いのですか?.
  3. GTTをいつ使用するか、いつ回避するかに関するガイドラインはどうあるべきですか
22
Omnipresent

最初に2番目の質問に答えましょう:

「なぜGTTから離れるのですか?本当にそんなに悪いのですか?」

数日前、私は大きなXMLファイル(〜18MB)をXMLTypeにロードする概念実証をノックアップしていました。 XMLTypeを永続的に格納したくなかったので、PL/SQL変数(セッションメモリ)と一時テーブルにロードしてみました。一時テーブルへのロードには、XMLType変数へのロードの5倍の時間がかかりました(1秒に対して5秒)。違いは、一時テーブルがメモリ構造ではないためです。一時テーブルはディスク(具体的には指定された一時テーブルスペース)に書き込まれます。

大量のデータをキャッシュしたい場合、それをメモリに保存するとPGAにストレスがかかります。これは、セッションが多い場合には適切ではありません。つまり、RAMと時間の間のトレードオフです。

最初の質問へ:

「誰かが上記のサンプルクエリをコレクションやカーソルに変換する方法を示すことができますか?」

投稿したクエリは、1つのステートメントにマージできます。

_SELECT case when a.column_a IS NULL OR a.column_a = ' ' 
           then b.data_a
           else  column_a end AS someA,
       a.someB,
       a.someC
FROM TABLE_A a
      left outer join TABLE_B b
          on ( a.column_b = b.data_b AND a.column_c = 'C' )
WHERE condition_1 = 'YN756'
  AND type_cd = 'P'
  AND TO_NUMBER(TO_CHAR(m_date, 'MM')) = '12'
  AND (lname LIKE (v_LnameUpper || '%') OR
  lname LIKE (v_searchLnameLower || '%'))
  AND (e_flag = 'Y' OR
  it_flag = 'Y' OR
  fit_flag = 'Y'));
_

(私は単にあなたのロジックを転置しましたが、そのcase()ステートメントはよりきちんとしたnvl2(trim(a.column_a), a.column_a, b.data_a)に置き換えることができます)。

クエリはもっと​​複雑だとおっしゃっていますが、最初の呼び出しポートは、クエリの書き換えを検討することです。厄介なクエリをPL/SQLでつなぎ合わせた多数のベビーSQLに分割することがどれほど魅力的かは知っていますが、純粋なSQLの方がはるかに効率的です。

コレクションを使用するには、SQLでタイプを定義するのが最適です。これにより、PL/SQLだけでなくSQLステートメントでもタイプを柔軟に使用できるようになります。

_create or replace type tab_a_row as object
    (col_a number
     , col_b varchar2(23)
     , col_c date);
/
create or replace type tab_a_nt as table of tab_a_row;
/
_

結果セットを返すサンプル関数を次に示します。

_create or replace function get_table_a 
      (p_arg in number) 
      return sys_refcursor 
is 
    tab_a_recs tab_a_nt; 
    rv sys_refcursor; 
begin 
    select tab_a_row(col_a, col_b, col_c)  
    bulk collect into tab_a_recs 
    from table_a 
    where col_a = p_arg; 

    for i in tab_a_recs.first()..tab_a_recs.last() 
    loop 
        if tab_a_recs(i).col_b is null 
        then 
            tab_a_recs(i).col_b :=  'something'; 
        end if; 
    end loop;  

    open rv for select * from table(tab_a_recs); 
    return rv; 
end; 
/ 
_

そして、ここでそれが実行されています:

_SQL> select * from table_a
  2  /

     COL_A COL_B                   COL_C
---------- ----------------------- ---------
         1 whatever                13-JUN-10
         1                         12-JUN-10

SQL> var rc refcursor
SQL> exec :rc := get_table_a(1)

PL/SQL procedure successfully completed.

SQL> print rc

     COL_A COL_B                   COL_C
---------- ----------------------- ---------
         1 whatever                13-JUN-10
         1 something               12-JUN-10

SQL>
_

関数では、ORA-00947例外を回避するために、列を使用して型をインスタンス化する必要があります。 PL/SQL表タイプを設定する場合、これは必要ありません。

_SQL> create or replace procedure pop_table_a
  2        (p_arg in number)
  3  is
  4      type table_a_nt is table of table_a%rowtype;
  5      tab_a_recs table_a_nt;
  6  begin
  7      select *
  8      bulk collect into tab_a_recs
  9      from table_a
 10      where col_a = p_arg;
 11  end;
 12  /

Procedure created.

SQL> 
_

最後に、ガイドライン

「GTTをいつ使用するか、いつ回避するかのガイドラインはどうあるべきか」

グローバル一時テーブルは、同じセッション内の異なるプログラムユニット間でキャッシュされたデータを共有する必要がある場合に非常に適しています。たとえば、いくつかの手順の1つによって入力されたGTTをフィードする単一の関数によって生成された一般的なレポート構造があるとします。 (それでも動的参照カーソルで実装できますが...)

グローバル一時テーブルは、単一のSQLクエリでは解決できないほど複雑な中間処理が多数ある場合にも適しています。特に、その処理を取得した行のサブセットに適用する必要がある場合。

ただし、一般的には、一時テーブルを使用する必要はないと想定する必要があります。そう

  1. それが難しすぎない限り、SQLでそれを実行してください...
  2. ...メモリを大量に消費しない限り、PL/SQL変数(通常はコレクション)で実行します。
  3. ...グローバル一時テーブルでそれを行う
30
APC

通常、少量のデータ(おそらく1000行)を格納するためにPL/SQLコレクションを使用します。データ量がはるかに大きい場合は、プロセスメモリが過負荷にならないようにGTTを使用します。

したがって、データベースからPL/SQLコレクションに数百行を選択し、それらをループして計算を実行したり、いくつかを削除したりして、そのコレクションを別のテーブルに挿入する場合があります。

数十万の行を処理している場合、「重労働」処理の多くを大きなSQLステートメントにプッシュしようとします。それにはGTTが必要な場合と不要な場合があります。

SQLレベルのコレクションオブジェクトは、SQLとPL/SQLの間で非常に簡単に変換できるものとして使用できます。

create type typ_car is object (make varchar2(10), model varchar2(20), year number(4));
/

create type typ_coll_car is table of typ_car;
/

select * from table (typ_coll_car(typ_car('a','b',1999), typ_car('A','Z',2000)));
MAKE       MODEL                           YEAR
---------- -------------------- ---------------
a          b                           1,999.00
A          Z                           2,000.00

declare
  v_car1 typ_car := typ_car('a','b',1999);
  v_car2 typ_car := typ_car('A','Z',2000);
  t_car  typ_coll_car := typ_coll_car();
begin
  t_car := typ_coll_car(v_car1, v_car2);
  FOR i in (SELECT * from table(t_car)) LOOP
    dbms_output.put_line(i.year);
    END LOOP;
end;
/
3
Gary Myers