無稽筆談
2006-06-22 くもりのちあめ
■ Oracle 8でのNVL関数の不具合
Oracle 8から10gへの移行のテストを行っているとき、NVL関数が返す結果が違うという現象が発生した。例をあげる。
DECLARE
v VARCHAR2(10) := NULL;
BEGIN
IF NVL(v, 1) = '01' THEN
DBMS_OUTPUT.PUT_LINE('TRUE');
ELSE
DBMS_OUTPUT.PUT_LINE('FALSE');
END IF;
END;
このPL/SQLブロックを実行すると、Oracle 8では「TRUE」、10gでは「FALSE」となる。
NVLはふたつの引数を取り、最初の引数がNULLでなければその値を、NULLならば2番目の引数の値を返すという関数である。引数の型は任意であるが、戻り値の型は最初の引数の型となる。したがって上の例であれば、最初の引数の型がVARCHAR2でかつ値がNULLなので、戻り値は2番目の引数である数値リテラルの1が型変換されてVARCHAR2の'1'となる。IF文はこの'1'と'01'の文字列の比較となりFALSEと評価されるのが正しい。
ところがOracle 8ではどうもNVLが2番目の引数である数値リテラルの1をそのまま返しているようだ。そのためIF文は数値の1と文字列リテラルの'01'の比較となる。条件式で数値型と文字列型のデータを比較する場合、文字列型のデータを数値型に変換して比較が行われるので、数値の1と文字列の'01'の比較の結果はTRUEとなるのである(ちなみにOracle 8でも、最初の引数が数値型、2番目の引数が文字列型の場合は、仕様通り数値型のデータが返ってくる)。
いやぁ、今の今までOracle 8のNVLがこんな振る舞いをしていたとはまったく気がつかなかった。NVLの性格上、通常はふたつの引数にあえて違う型のデータを与えることはまずやらないし、さらに結果を変数に格納するとそこで暗黙の型変換が発生することなどから、問題が顕在化することがなかったのだろう。とはいえ、確認のためにざっとほかのプログラムのソースもながめてみたのだが、乱暴な使い方も所々に見受けられた。
品質の高いコードを書くには、データの型にもっとセンシティブにならんといかんなぁ。できれば言語側のチェックも厳しくあってほしいと思うのだった。
■ 回復
何日かぶりにビールをゴクッと飲んだ(^-^)。
■ 今日の歩数
8,814歩。徒歩で通勤すれば自ずと増える。