Tips9.SQLの発行回数を減らす
SQLチューニングの方法として良くある方法に「SQLの発行回数を減らす」というのがあります。
簡単に言われますが、では具体的にどのようにしてSQLは纏められていくのでしょうか。
簡単なケースとしては、下記のような例が考えられます。
部門別に従業員の一覧を取得するSQLですが、深く考えなければ次のようなコーディングになるでしょう。
部門別のSQLと従業員のSQLを二つのカーソルとして定義し、部門別のSQLのループの中で従業員のSQLを呼び出します。
DECLARE
CURSOR cur_dept
IS -- 部門別のSQL
SELECT deptno, dname FROM dept ORDER BY deptno;
--
CURSOR cur_emp(p_deptno IN NUMBER)
IS -- 従業員別SQL
SELECT empno, ename FROM emp
WHERE deptno = p_deptno
ORDER BY empno;
--
rec_dept cur_dept%ROWTYPE;
rec_emp cur_emp%ROWTYPE;
BEGIN
-- 部門別のカーソル
FOR rec_dept IN cur_dept LOOP
-- 従業員別のカーソル
FOR rec_emp IN cur_emp(rec_dept.deptno) LOOP
dbms_output.put_line(rec_dept.dname || ' -- ' || rec_emp.empno || ':' ||rec_emp.ename);
END LOOP;
END LOOP;
END;
この場合、部門の種類数分だけ従業員別SQLを発行することになります。
しかし、次のように記述することで、SQLの発行回数を1回で済ますことが可能です。
DECLARE
CURSOR cur_deptemp
IS -- 部門と従業員の表をJOINして一つのSQLにまとめた
SELECT d.dname, e.empno, e.ename
FROM dept d INNER JOIN emp e
ON d.deptno = e.deptno
ORDER BY d.dname, e.empno;
--
rec_deptemp cur_deptemp%ROWTYPE;
BEGIN
-- 一度のカーソル呼出で済む
FOR rec_deptemp IN cur_deptemp LOOP
dbms_output.put_line(rec_deptemp.dname || ' -- ' || rec_deptemp.empno || ':' ||rec_deptemp.ename);
END LOOP;
END;
実際に、apex.oracle.comでそれぞれを実行してみると。。。
前者のカーソル2回ループするやり方では、

カーソルを1つにまとめたやり方では

と、処理時間は1.3秒から0.9秒弱に短縮されました。
これくらいなら、まだ当たり前に思いつく範囲かもしれません。
しかし、次のように条件が少し複雑になると、どうでしょう。
emp表のsal列を以下のように更新します。
・JOBがMANAGERならsalの1.1倍(小数点以下切捨て)、
・JOBがPRESIDENTは更新対象外。
・それ以外で、DEPTがACCOUNTING(deptno=10)ならsalの1.2倍(小数点以下切捨て)、
・DEPTがRESEARCH(deptno=20)ならsalの1.25倍(小数点以下切捨て)、SALES(deptno=30)ならsal+(comm/2)
これを一つのSQLで実装してみましょう。
UPDATE emp
SET sal = CASE WHEN job = 'MANAGER' THEN TRUNC(sal*1.1)
ELSE CASE deptno
WHEN 10 THEN TRUNC(sal*1.2)
WHEN 20 THEN TRUNC(sal*1.25)
WHEN 30 THEN sal+(NVL(comm,0)/2)+100
END
END
WHERE job <> 'PRESIDENT';
と記述することができます。
普通に考えると下記のようになるのかもしれませんが、性能面でも、コードの長さという意味でも、全くメリットはありません。
DECLARE
CURSOR c1 IS
SELECT e.empno, e.deptno, e.sal, e.comm, e.job
FROM emp e
WHERE e.job <> 'PRESIDENT';
r1 c1%ROWTYPE;
ln_new_sal NUMBER; -- 算出したsalの値を一時的に保持する変数
BEGIN
-- 全従業員を1件ずつループ
FOR r1 IN c1 LOOP
IF r1.job = 'MANAGER' THEN
-- JOBがMANAGERならsalの1.1倍(小数点以下切捨て)
ln_new_sal := TRUNC(r1.sal*1.1);
ELSIF r1.deptno = 10 THEN
:
以下、下記の内容をそれぞれ実装
-- JOBがPRESIDENTは更新対象外。
-- それ以外で、DEPTがACCOUNTING(deptno=10)ならsalの1.2倍(小数点以下切捨て)、
-- DEPTがRESEARCH(deptno=20)ならsalの1.25倍(小数点以下切捨て)
-- SALES(deptno=30)ならsal+(comm/2)
:
END IF;
--
-- 1件ずつUPDATE
UPDATE emp SET sal = ln_new_sal
WHERE empno = r1.emp;
--
END LOOP;
END;
・・・途中で断念してしまいましたが、どちらが良いかは言うまでも無いでしょう。
ディスカッション
コメント一覧
まだ、コメントがありません