10

すべての条件が必要なわけではないクエリがあります。すべての条件を使用した場合の例を次に示します。

select num
from (select distinct q.num
       from cqqv q
       where q.bcode = '1234567' --this is variable
             and q.lb = 'AXCT' --this is variable
             and q.type = 'privt' --this is variable
             and q.edate > sysdate - 30 --this is variable
       order by dbms_random.value()) subq
where rownum <= 10; --this is variable

とマークされ--this is variableた部分は、まあ、変化する部分です! 条件が指定されていない場合、デフォルト値はありません。たとえば、入力で q.type に「*」が指定されている場合 (ただし、他のすべては同じまま)、クエリは type のすべてに一致し、次のように実行されます。

select num
from (select distinct q.num
       from cqqv q
       where q.bcode = '1234567' --this is variable
             and q.lb = 'AXCT' --this is variable
             --and q.type = 'privt' --this condition ignored because of "type=*" in input
             and q.edate > sysdate - 30 --this is variable
       order by dbms_random.value()) subq
where rownum <= 10; --this is variable

動的 sql を使用してこのクエリをオンザフライで作成できることはわかっていますが、これによりどのようなパフォーマンスの問題が発生する可能性があるのか​​、またこれを行うためのより良い方法があるかどうか疑問に思っています。

4

5 に答える 5

12

あなたはこれを行うことができますが...

select num
from (select distinct q.num
       from cqqv q
       where 1=1
             and (:bcode is null or q.bcode = :bcode)
             and (:lb is null or q.lb = :lb)
             and (:type is null or q.type = :type)
             and (:edate is null or q.edate > :edate - 30)
       order by dbms_random.value()) subq
where rownum <= :numrows

...動的SQLを使用すると、よりターゲットを絞ったクエリプランが生成されるため、通常はパフォーマンスが向上します。上記のクエリでは、Oracle は bcode または lb、type または edate でインデックスを使用するかどうかを判断できず、おそらく毎回フル テーブル スキャンを実行します。

もちろん、動的クエリでバインド変数を使用する必要があります。リテラル値を文字列に連結しないでください。そうしないと、パフォーマンス (およびスケーラビリティとセキュリティ) が非常に悪くなります。

明確にするために、私が念頭に置いている動的バージョンは次のように機能します。

declare
    rc sys_refcursor;
    q long;
begin
    q := 'select num
    from (select distinct q.num
           from cqqv q
           where 1=1';

    if p_bcode is not null then
        q := q || 'and q.bcode = :bcode';
    else
        q := q || 'and (1=1 or :bcode is null)';
    end if;

    if p_lb is not null then
        q := q || 'and q.lb = :lb';
    else
        q := q || 'and (1=1 or :lb is null)';
    end if;

    if p_type is not null then
        q := q || 'and q.type = :type';
    else
        q := q || 'and (1=1 or :type is null)';
    end if;

    if p_edate is not null then
        q := q || 'and q.edate = :edate';
    else
        q := q || 'and (1=1 or :edate is null)';
    end if;

    q := q || ' order by dbms_random.value()) subq
    where rownum <= :numrows';

    open rc for q using p_bcode, p_lb, p_type, p_edate, p_numrows;
    return rc;
end;

これは、結果のクエリ実行が次のようになるため、結果のクエリが "sargable"になることを意味します (私には認めざるを得ない新しい言葉です!)。

select num
from (select distinct q.num
       from cqqv q
       where 1=1
             and q.bcode = :bcode
             and q.lb = :lb
             and (1=1 or :type is null)
             and (1=1 or :edate is null)
       order by dbms_random.value()) subq
where rownum <= :numrows

ただし、この例では最大 16 回のハード パースが必要になる可能性があることは承知しています。ネイティブの動的 SQL を使用する場合は「and :bv is null」句が必要ですが、DBMS_SQL を使用することで回避できます。

注:(1=1 or :bindvar is null)バインド変数が null の場合の使用は、Michal Pravda のコメントで提案されました。これにより、オプティマイザーが句を削除できるようになるからです。

于 2009-11-11T17:12:35.020 に答える
5

動的 SQL を使用した方がパフォーマンスが優れているという Tony の意見には同意しますが、コンテキスト変数はバインド変数を使用するよりも優れたアプローチです。

UsingIN_VARIABLE IS NULL OR table.fieldx = IN_VARIABLEは、オプションの値を処理するのに理想的ではありません。クエリが送信されるたびに、Oracle は最初に共有プールをチェックして、ステートメントが以前に送信されたかどうかを確認します。含まれている場合は、クエリの実行プランが取得され、SQL が実行されます。ステートメントが共有プールで見つからない場合、オラクルはステートメントを解析し、さまざまな実行パスを調べて、実行前に最適なアクセス プラン (別名「ベスト パス」) を考え出すプロセスを実行する必要があります。このプロセスは「ハード解析」と呼ばれ、クエリ自体よりも時間がかかる場合があります。Oracle のハード/ソフト解析の詳細については、こちらを、AskTom についてはこちらをお読みください。

要するに - これ:

and (:bcode is null or q.bcode = :bcode)

...同じ、動的またはその他の方法で実行されます。オプションのパラメーターに動的 SQL でバインド変数を使用するメリットはありません。セットアップはまだ SARGability を破壊します...

コンテキスト パラメータは、Oracle 9iで導入された機能です。これらはパッケージに関連付けられており、属性値の設定に使用できます (パッケージに対する EXECUTE 権限を持つユーザーのみ。スキーマに CREATE CONTEXT を付与する必要があります)。コンテキスト変数を使用して動的 SQL を調整し、フィルター/検索基準に基づいてクエリに必要なものだけを含めることができます。IN_VARIABLE IS NULL OR table.fieldx = IN_VARIABLE対照的に、Bind 変数 (動的 SQL でもサポートされています)では、検索クエリでテストを実行できる値を指定する必要があります。実際には、値のコンタミネーションのリスクを排除するために、プロシージャまたは関数ごとに個別のコンテキスト変数を使用する必要があります。

コンテキスト変数を使用したクエリは次のとおりです。

L_CURSOR SYS_REFCURSOR;
L_QUERY  VARCHAR2(5000) DEFAULT 'SELECT num
                                   FROM (SELECT DISTINCT q.num
                                           FROM CQQV q
                                          WHERE 1 = 1 ';
BEGIN

    IF IN_BCODE IS NOT NULL THEN
      DBMS_SESSION.SET_CONTEXT('THE_CTX',
                               'BCODE',
                               IN_BCODE);
      L_QUERY := L_QUERY || ' AND q.bcode = SYS_CONTEXT(''THE_CTX'', ''BCODE'') ';
    END IF;

    IF IN_LB IS NOT NULL THEN
      DBMS_SESSION.SET_CONTEXT('THE_CTX',
                               'LB',
                               IN_LB);
      L_QUERY := L_QUERY || ' AND q.lb = SYS_CONTEXT(''THE_CTX'', ''LB'') ';
    END IF;

    IF IN_TYPE IS NOT NULL THEN
      DBMS_SESSION.SET_CONTEXT('THE_CTX',
                               'TYPE',
                               IN_TYPE);
      L_QUERY := L_QUERY || ' AND q.type = SYS_CONTEXT(''THE_CTX'', ''TYPE'') ';
    END IF;

    IF IN_EDATE IS NOT NULL THEN
      DBMS_SESSION.SET_CONTEXT('THE_CTX',
                               'EDATE',
                               IN_EDATE);
      L_QUERY := L_QUERY || ' AND q.edate = SYS_CONTEXT(''THE_CTX'', ''EDATE'') - 30 ';
    END IF;

    L_QUERY := L_QUERY || ' ORDER BY dbms_random.value()) subq
           WHERE rownum <= :numrows ';

    FOR I IN 0 .. (TRUNC(LENGTH(L_QUERY) / 255)) LOOP
      DBMS_OUTPUT.PUT_LINE(SUBSTR(L_QUERY, I * 255 + 1, 255));
    END LOOP;

    OPEN L_CURSOR FOR L_QUERY USING IN_ROWNUM;
    RETURN L_CURSOR;

END;

この例では、値がオプションではないため、依然として rownum にバインド変数を使用しています。

DBMS_SESSION.SET_CONTEXT('THE_CTX', 'LB', IN_LB);

SET_CONTEXT パラメータは次のとおりです。

  1. コンテキスト変数名。インスタンスの作成は必要ありません
  2. コンテキスト変数内の変数。コンテキスト変数はセッション変数のようなもので、Web アプリケーションとセッション オブジェクトに精通していることを前提としています。
  3. パラメータ #2 で定義された変数の値。

バインドとコンテキスト

変数をバインドするということは、Oracle が変数参照が入力されることを期待していることを意味します。それ以外の場合は ORA エラーです。例えば:

... L_QUERY USING IN_EXAMPLE_VALUE

...移入される単一のバインド変数参照があることを期待しています。IN_EXAMPLE_VALUEnull の場合、クエリに存在する必要があります。いいえ::variableAND :variable IS NULL

コンテキスト変数を使用すると、無関係/冗長なロジックを含める必要がなくなり、値が null かどうかをチェックできます。

重要: バインド変数は、名前順ではなく、発生順に処理されます (序数として知られています)USING句にデータ型宣言がないことに気付くでしょう。序数は理想的ではありません。USING句を更新せずにクエリで序数を変更すると、修正されるまでクエリが壊れます。

于 2009-11-11T18:16:33.380 に答える
3

私が落ち着いた解決策は、次のような動的 SQL クエリを生成するものです。

select num
from (select distinct q.NUM
       from cqqv q 
       where  (q.bcode = :bcode) 
                  and  (1=1 or :lb is null) 
                  and  (1=1 or :type is null) 
                  and  (q.edate> :edate) 
                order by dbms_random.value()) subq 
where rownum <= :numrows

(この例では、bcode と edate 条件はオプションではありませんでしたが、lb と type はオプションでした)

これは Michal Pravda が提案していたこと (または非常に似ている) だと思います。ここの DBA は、コンテキスト変数ソリューションよりもこのソリューションを好みます。助けてアドバイスを提供してくれたすべての人に感謝します!

DBA が見つけた、このソリューションの詳細を示すリンクは次のとおりです。

トムに聞く:人気と自然淘汰について

于 2009-11-13T15:06:30.813 に答える
0

私はこれをするだけです

select num
from (select distinct q.num
       from cqqv q
       where q.bcode = '1234567' --this is variable
             and q.lb = 'AXCT' --this is variable
             and q.type = nvl(<variable-type>, q.type)  --this condition ignored because of "type=*" in input
             and q.edate > sysdate - 30 --this is variable
       order by dbms_random.value()) subq
where rownum <= 10; --this is variable

q.TYPE フィルタリングを無視する場合、変数の型が null であることを保証する必要があるだけです。

于 2011-03-15T23:35:13.740 に答える