28

多くの条件付けが必要なストアド プロシージャを作成しています。例外がパフォーマンスを低下させる可能性があるという C#.NET コーディングの一般的な知識があるため、PL/SQL でも例外を使用することは常に避けてきました。このストアド プロシージャの条件付けは、主にレコードが存在するかどうかを中心に展開します。これは、次の 2 つの方法のいずれかで実行できます。

SELECT COUNT(*) INTO var WHERE condition;
IF var > 0 THEN
   SELECT NEEDED_FIELD INTO otherVar WHERE condition;
....

-また-

SELECT NEEDED_FIELD INTO var WHERE condition;
EXCEPTION
WHEN NO_DATA_FOUND
....

2 番目のケースは、最初のケースの条件の後の最初のステートメントで選択する必要があった NEEDED_FIELD を使用できるため、もう少しエレガントに思えます。コードが少ない。しかし、ストアド プロシージャが COUNT(*) を使用してより高速に実行される場合は、処理速度を補うためにもう少し入力してもかまいません。

ヒントはありますか?私は別の可能性を見逃していますか?

編集 これはすべてすでにFOR LOOPにネストされていることに言及する必要がありました。FOR LOOPで選択としてカーソルを宣言できるとは思わないため、これがカーソルを使用することで違いを生むかどうかはわかりません。

4

12 に答える 12

34

これを行うために明示カーソルは使用しません。Steve F. は、暗黙カーソルを使用できる場合に明示カーソルを使用するよう人々にアドバイスしなくなりました。

のメソッドcount(*)は安全ではありません。count(*)別のセッションが、 の行の後、 の行の前で条件に一致した行を削除するとselect ... into、コードは処理されない例外をスローします。

元の投稿の 2 番目のバージョンにはこの問題はなく、一般的に推奨されます。

とはいえ、例外を使用するとわずかなオーバーヘッドが発生します。データが変更されないことが 100% 確実な場合は、 を使用できますがcount(*)、使用しないことをお勧めします。

これらのベンチマークは、 32 ビット Windows上のOracle 10.2.0.1で実行しました。私は経過時間だけを見ています。詳細 (ラッチ数や使用メモリなど) を提供できるテスト ハーネスは他にもあります。

SQL>create table t (NEEDED_FIELD number, COND number);

テーブルが作成されました。

SQL>insert into t (NEEDED_FIELD, cond) values (1, 0);

1 行が作成されました。

declare
  otherVar  number;
  cnt number;
begin
  for i in 1 .. 50000 loop
     select count(*) into cnt from t where cond = 1;

     if (cnt = 1) then
       select NEEDED_FIELD INTO otherVar from t where cond = 1;
     else
       otherVar := 0;
     end if;
   end loop;
end;
/

PL/SQL プロシージャが正常に完了しました。

経過: 00:00:02.70

declare
  otherVar  number;
begin
  for i in 1 .. 50000 loop
     begin
       select NEEDED_FIELD INTO otherVar from t where cond = 1;
     exception
       when no_data_found then
         otherVar := 0;
     end;
   end loop;
end;
/

PL/SQL プロシージャが正常に完了しました。

経過: 00:00:03.06

于 2008-10-21T15:46:55.837 に答える
7

SELECT INTO は単一の行が返されることを想定しているため、次の形式のステートメントを使用できます。

SELECT MAX(column)
  INTO var
  FROM table
 WHERE conditions;

IF var IS NOT NULL
THEN ...

SELECT は、利用可能な場合は値を提供し、NO_DATA_FOUND 例外の代わりに NULL の値を提供します。結果セットには単一の行が含まれているため、MAX() によって導入されるオーバーヘッドは最小限からゼロになります。また、カーソルベースのソリューションに比べてコンパクトであり、元の投稿の 2 段階のソリューションのような同時実行の問題に対して脆弱ではないという利点もあります。

于 2008-10-21T19:19:17.420 に答える
6

@Steveのコードの代替。

DECLARE
  CURSOR foo_cur IS 
    SELECT NEEDED_FIELD WHERE condition ;
BEGIN
  FOR foo_rec IN foo_cur LOOP
     ...
  END LOOP;
EXCEPTION
  WHEN OTHERS THEN
    RAISE;
END ;

データがない場合、ループは実行されません。カーソルのFORループは、多くのハウスキーピングを回避するのに役立ちます。さらにコンパクトなソリューション:

DECLARE
BEGIN
  FOR foo_rec IN (SELECT NEEDED_FIELD WHERE condition) LOOP
     ...
  END LOOP;
EXCEPTION
  WHEN OTHERS THEN
    RAISE;
END ;

これは、コンパイル時に完全なselectステートメントを知っている場合に機能します。

于 2008-10-21T15:42:44.143 に答える
4

@DCookie

私はあなたが言う行を省くことができることを指摘したいだけです

EXCEPTION  
  WHEN OTHERS THEN    
    RAISE;

例外ブロックをすべて一緒に省略しても同じ効果が得られます。例外について報告される行番号は、例外が再発生した例外ブロックの行ではなく、実際に例外がスローされた行になります。

于 2008-10-21T15:53:27.010 に答える
3

Stephen Darlington は非常に良い点を指摘しています。ベンチマークを変更してより現実的なサイズのテーブルを使用すると、次のようにテーブルに 10000 行を入力するとわかります。

begin 
  for i in 2 .. 10000 loop
    insert into t (NEEDED_FIELD, cond) values (i, 10);
  end loop;
end;

次に、ベンチマークを再実行します。(適切な時間を得るには、ループ回数を 5000 に減らす必要がありました)。

declare
  otherVar  number;
  cnt number;
begin
  for i in 1 .. 5000 loop
     select count(*) into cnt from t where cond = 0;

     if (cnt = 1) then
       select NEEDED_FIELD INTO otherVar from t where cond = 0;
     else
       otherVar := 0;
     end if;
   end loop;
end;
/

PL/SQL procedure successfully completed.

Elapsed: 00:00:04.34

declare
  otherVar  number;
begin
  for i in 1 .. 5000 loop
     begin
       select NEEDED_FIELD INTO otherVar from t where cond = 0;
     exception
       when no_data_found then
         otherVar := 0;
     end;
   end loop;
end;
/

PL/SQL procedure successfully completed.

Elapsed: 00:00:02.10

例外のあるメソッドは、2 倍以上高速になりました。したがって、ほとんどすべての場合、方法は次のとおりです。

SELECT NEEDED_FIELD INTO var WHERE condition;
EXCEPTION
WHEN NO_DATA_FOUND....

行く方法です。正しい結果が得られ、一般的に最速です。

于 2008-10-21T16:12:12.900 に答える
2

それが重要な場合は、両方のオプションをベンチマークする必要があります!

そうは言っても、私は常に例外メソッドを使用してきました。その理由は、データベースに一度だけヒットする方がよいからです。

于 2008-10-21T13:57:03.640 に答える
1

ネストされたカーソルループを持つよりも、より効率的なアプローチは、テーブル間の外部結合で1つのカーソルループを使用することです。

BEGIN
    FOR rec IN (SELECT a.needed_field,b.other_field
                  FROM table1 a
                  LEFT OUTER JOIN table2 b
                    ON a.needed_field = b.condition_field
                 WHERE a.column = ???)
    LOOP
       IF rec.other_field IS NOT NULL THEN
         -- whatever processing needs to be done to other_field
       END IF;
    END LOOP;
END;
于 2008-10-23T10:19:16.213 に答える
1

はい、カーソルを使用して行方不明です

DECLARE
  CURSOR foo_cur IS 
    SELECT NEEDED_FIELD WHERE condition ;
BEGIN
  OPEN foo_cur;
  FETCH foo_cur INTO foo_rec;
  IF foo_cur%FOUND THEN
     ...
  END IF;
  CLOSE foo_cur;
EXCEPTION
  WHEN OTHERS THEN
    CLOSE foo_cur;
    RAISE;
END ;

確かに、これはより多くのコードですが、Steve Feuerstein の PL/SQL プログラミングの本から私の PL/SQL のほとんどを学んだので、フロー制御として EXCEPTION を使用していません。これは良いことだと思います。

これが速いかどうかはわかりません (最近はほとんど PL/SQL を実行していません)。

于 2008-10-21T13:54:41.227 に答える
0

最初の(優れた)回答は次のように述べています-

count() を使用したメソッドは安全ではありません。別のセッションが count(*) のある行の後、select ... into のある行の前で条件に一致した行を削除すると、コードは処理されない例外をスローします。

そうではありません。特定の論理作業単位内では、Oracle は完全に一貫しています。誰かが count と select の間の行の削除をコミットしたとしても、Oracle はアクティブなセッションのためにログからデータを取得します。それができない場合、「スナップショットが古すぎます」というエラーが発生します。

于 2013-12-18T03:04:40.640 に答える
0

for ループを使用している場合は、open を使用する必要はありません。

declare
cursor cur_name is  select * from emp;
begin
for cur_rec in cur_name Loop
    dbms_output.put_line(cur_rec.ename);
end loop;
End ;

また

declare
cursor cur_name is  select * from emp;
cur_rec emp%rowtype;
begin
Open cur_name;
Loop
Fetch cur_name into  Cur_rec;
   Exit when cur_name%notfound;
    dbms_output.put_line(cur_rec.ename);
end loop;
Close cur_name;
End ;
于 2008-12-18T07:11:47.863 に答える
0

count(*) は、常に実際のカウントまたは 0 ~ 0 を返すため、例外が発生することはありません。カウントを使用します。

于 2013-01-03T13:26:42.173 に答える
0

ここで死んだ馬を打ち負かしているかもしれませんが、ループのカーソルをベンチマークし、no_data_found メソッドとほぼ同じように実行しました。

declare
  otherVar  number;
begin
  for i in 1 .. 5000 loop
     begin
       for foo_rec in (select NEEDED_FIELD from t where cond = 0) loop
         otherVar := foo_rec.NEEDED_FIELD;
       end loop;
       otherVar := 0;
     end;
   end loop;
end;

PL/SQL プロシージャが正常に完了しました。

経過: 00:00:02.18

于 2008-10-21T16:18:32.857 に答える