2

最後の質問で考えさせられました。

1)

SELECT COUNT(*) INTO count FROM foo WHERE bar = 123;
IF count > 0 THEN
    SELECT a INTO var FROM foo WHERE bar = 123;
    -- do stuff
ELSE
    -- do other stuff
END IF;

2)

BEGIN
    SELECT a INTO var FROM foo where bar = 123;
    -- do stuff
EXCEPTION
    WHEN no_data_found THEN
        --do other stuff
END ;

データベースへのアクセスが 1 回少ないため、2 番の方が高速であると思います。

私が考慮していない、1が優れている状況はありますか?

編集: 回答する前に、回答に対する投票をさらに集めるために、この質問をさらに数日間ハングアップさせます。

4

4 に答える 4

5

質問から正確なクエリを使用する場合、基準を満たすテーブル内のすべてのレコードをカウントする必要があるため、最初のバリアントはもちろん遅くなります。

次のように記述する必要があります。

SELECT COUNT(*) INTO row_count FROM foo WHERE bar = 123 and rownum = 1;

また

select 1 into row_count from dual where exists (select 1 from foo where bar = 123);

目的にはレコードの存在を確認するだけで十分だからです。

もちろん、どちらのバリアントも、他の誰かが 2 つのステートメントの間に何かを変更しないことを保証するものではありませんがfoo、このチェックがより複雑なシナリオの一部である場合は問題になりません。選択した値を参照するいくつかのアクションを実行しているときに、誰かがその値をfoo.a選択した後にその値を変更した状況を考えてみてください。したがって、複雑なシナリオでは、アプリケーション ロジック レベルでこのような同時実行の問題を処理する方が適切です。 アトミック操作を実行するには、単一の SQL ステートメントを使用する方が適切です。varvar

上記のバリアントはいずれも、SQL と PL/SQL の間で 2 つのコンテキスト スイッチと 2 つのクエリを必要とするため、テーブルで行が見つかった場合、以下で説明するバリアントよりもパフォーマンスが低下します。

例外なく行の存在をチェックする別のバリ​​アントがあります。

select max(a), count(1) into var, row_count 
from foo 
where bar = 123 and rownum < 3;

row_count = 1 の場合、基準を満たす行は 1 つだけです。

に重複する値fooがないことを保証するの一意の制約により、存在のみをチェックするだけで十分な場合があります。たとえば、主キーです。 そのような場合、クエリを単純化することができます:barfoobar

select max(a) into var from foo where bar = 123;
if(var is not null) then 
  ...
end if;

またはカーソルを使用して値を処理します。

for cValueA in ( 
  select a from foo where bar = 123
) loop
  ...  
end loop;

次のバリアントはリンクからのもので、@ user272735 の回答で提供されています。

select 
  (select a from foo where bar = 123)
  into var 
from dual;

私の経験から、例外ブロックのないバリアントは、ほとんどの場合、例外のあるバリアントよりも高速ですが、そのようなブロックの実行回数が少ない場合は、コードの読みやすさを向上させるため にno_data_found、例外を処理する例外ブロックを使用することをお勧めします。too_many_rows

例外を使用するか使用しないかを選択する正しいポイントは、「この状況はアプリケーションにとって正常ですか?」という質問をすることです。行が見つからず、それが処理可能な予想される状況 (たとえば、新しい行を追加する、別の場所からデータを取得するなど) である場合は、例外を回避することをお勧めします。それが予期せず、状況を修正する方法がない場合は、例外をキャッチしてエラー メッセージをカスタマイズするか、イベント ログに書き込んで再スローするか、まったくキャッチしないようにします。

パフォーマンスを比較するには、システムで単純なテスト ケースを作成し、両方のバリアントを何度も呼び出して比較します。
さらに言うと、アプリケーションの 90% では、最初に考慮しなければならないパフォーマンスの問題の原因が他にもたくさんあるため、この質問は実用的というよりも理論的なものです。

アップデート

SQLFiddle サイトのこのページの例を少し修正して再現しました (リンク)。
結果は、selected from を使用したバリアントdualが最高のパフォーマンスを発揮することを証明しています。ほとんどのクエリが成功した場合のオーバーヘッドはわずかであり、欠落している行の数が増えた場合のパフォーマンスの低下は最小限です。
驚くべきことに、count() と 2 つのクエリを使用したバリアントは、すべてのクエリが失敗した場合に最適な結果を示しました。

| FNAME | LOOP_COUNT | ALL_FAILED | ALL_SUCCEED | variant name |
----------------------------------------------------------------
|    f1 |       2000 |       2.09 |        0.28 |  exception   |
|    f2 |       2000 |       0.31 |        0.38 |  cursor      |
|    f3 |       2000 |       0.26 |        0.27 |  max()       |
|    f4 |       2000 |       0.23 |        0.28 |  dual        |
|    f5 |       2000 |       0.22 |        0.58 |  count()     |

-- FNAME        - tested function name 
-- LOOP_COUNT   - number of loops in one test run
-- ALL_FAILED   - time in seconds if all tested rows missed from table
-- ALL_SUCCEED  - time in seconds if all tested rows found in table
-- variant name - short name of tested variant

以下は、テスト環境とテスト スクリプトのセットアップ コードです。

create table t_test(a, b)
as
select level,level from dual connect by level<=1e5
/
insert into t_test(a, b) select null, level from dual connect by level < 100
/

create unique index x_text on t_test(a)
/

create table timings(
  fname varchar2(10), 
  loop_count number, 
  exec_time number
)
/

create table params(pstart number, pend number)
/
-- loop bounds
insert into params(pstart, pend) values(1, 2000)
/

-- f1 - 例外処理

create or replace function f1(p in number) return number
as
  res number;
begin
  select b into res
  from t_test t
  where t.a=p and rownum = 1;
  return res;
exception when no_data_found then
  return null;
end;
/

-- f2 - カーソル ループ

create or replace function f2(p in number) return number
as
  res number;
begin
  for rec in (select b from t_test t where t.a=p and rownum = 1) loop
    res:=rec.b;
  end loop;
  return res;
end;
/

-- f3 - max()

create or replace function f3(p in number) return number
as
  res number;
begin
  select max(b) into res
  from t_test t
  where t.a=p and rownum = 1;
  return res;
end;
/

-- f4 - select from dual でフィールドとして選択

create or replace function f4(p in number) return number
as
  res number;
begin
  select
    (select b from t_test t where t.a=p and rownum = 1)
    into res
  from dual;
  return res;
end;
/

-- f5 - count() をチェックして値を取得

create or replace function f5(p in number) return number
as
  res number;
  cnt number;
begin
  select count(*) into cnt
  from t_test t where t.a=p and rownum = 1;

  if(cnt = 1) then
    select b into res from t_test t where t.a=p;
  end if;

  return res;
end;
/

テスト スクリプト:

declare
  v       integer;
  v_start integer;
  v_end   integer;

  vStartTime number;

begin
  select pstart, pend into v_start, v_end from params;

  vStartTime := dbms_utility.get_cpu_time;

  for i in v_start .. v_end loop
    v:=f1(i);
  end loop;

  insert into timings(fname, loop_count, exec_time) 
    values ('f1', v_end-v_start+1, (dbms_utility.get_cpu_time - vStartTime)/100) ;
end;
/

declare
  v       integer;
  v_start integer;
  v_end   integer;

  vStartTime number;

begin
  select pstart, pend into v_start, v_end from params;

  vStartTime := dbms_utility.get_cpu_time;

  for i in v_start .. v_end loop
    v:=f2(i);
  end loop;

  insert into timings(fname, loop_count, exec_time) 
    values ('f2', v_end-v_start+1, (dbms_utility.get_cpu_time - vStartTime)/100) ;
end;
/

declare
  v       integer;
  v_start integer;
  v_end   integer;

  vStartTime number;

begin
  select pstart, pend into v_start, v_end from params;

  vStartTime := dbms_utility.get_cpu_time;

  for i in v_start .. v_end loop
    v:=f3(i);
  end loop;

  insert into timings(fname, loop_count, exec_time) 
    values ('f3', v_end-v_start+1, (dbms_utility.get_cpu_time - vStartTime)/100) ;
end;
/

declare
  v       integer;
  v_start integer;
  v_end   integer;

  vStartTime number;

begin
  select pstart, pend into v_start, v_end from params;

  vStartTime := dbms_utility.get_cpu_time;

  for i in v_start .. v_end loop
    v:=f4(i);
  end loop;

  insert into timings(fname, loop_count, exec_time) 
    values ('f4', v_end-v_start+1, (dbms_utility.get_cpu_time - vStartTime)/100) ;
end;
/

declare
  v       integer;
  v_start integer;
  v_end   integer;

  vStartTime number;

begin
  select pstart, pend into v_start, v_end from params;
  --v_end := v_start + trunc((v_end-v_start)*2/3);

  vStartTime := dbms_utility.get_cpu_time;

  for i in v_start .. v_end loop
    v:=f5(i);
  end loop;

  insert into timings(fname, loop_count, exec_time) 
    values ('f5', v_end-v_start+1, (dbms_utility.get_cpu_time - vStartTime)/100) ;
end;
/

select * from timings order by fname
/
于 2013-08-08T08:27:59.690 に答える
0

このような状況で、私は通常次のようにします。

DECALRE
   CURSOR cur IS
   SELECT a FROM foo where bar = 123;
BEGIN
   OPEN cur;
   FETCH cur INTO var;
   IF cur%FOUND THEN
      -- do stuff, maybe a LOOP if required
   ELSE
      --do other stuff
   END;
END;

これにはいくつかの利点があります。

データベースから1 つのレコードのみを読み取り、残りはスキップします。行数> 1かどうかを知る必要がある場合に備えて、これが最速の方法です。

「例外」ハンドラーで「通常」の状況を処理することはありません。これを「より美しい」コーディングと考える人もいます。

于 2014-01-09T19:21:58.080 に答える