• 締切済み

カーソル処理とファンクションとインデックスの件

はじめまして。 環境 WinXP(Pro)/Oracle9i まず、やりたいことは和暦の年号と日付で別々になっているフィールドをファンクションで西暦に直し、その対象とする西暦をカーソル処理で1行つかって抽出したいのです。 具体的には以下の例ですが ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^(ファンクション FUNCTION FNCWSDATECNV(STRGEN IN VARCHAR2) RETURN DATE IS (一部省略しますが、A.WGENを和暦のアルファベット記記号に変換編集する[M/T/S/H]。    そして、文字列形式に連結してS10/12/10として西暦に変換して戻り値にセット ([S10/12/31 -> 1935/12/31]となる。) --------------------------------(カーソル処理定義 DECLARE SELYMD DATE; CURSOR CRSL1 IS SELECT A.SELID,A.SELNAME FROM KOJIN A WHERE FNCWSDATECNV(A.WGEN, A.WDATE) < SELYMD; ---------------------------------(実行 BEGIN SELYMD := GET_SELYMD; -- パラメータから得られた変数 OPEN CRSL1; FETCH CRSL1 INTO VAL_SID, VAL_SNAME; LOOP EXIT CRSL1%NOT FOUND; (省略) END LOOP; END; --------------------- 初回のときは、データは50件ならばうまく抽出できましたが、20万件あるとFETCHしたときには時間がかかると思うので、 CREATE INDEX KOJIN_IDX ON KOJIN(SELID); としておくことで通常SELECTすると早くなりましたが、 上記の定義したカーソルにはファンクションが効かず、0件として終わってしまいます。 また、カーソル1行ずつ読み込むのは別テーブルへ追加する際、必要なデータを対象とする別表に参照するためです。 カーソル上でTO_CHAR(DECODE(A.WGEN,0,'M',・・・),'JAPANESE・・・ というような形式にしてもうまくいきませんでした。(カレンダによるエラー) どうしたらよいのでしょうか?よろしくお願いします。

noname#82953
noname#82953
  • Oracle
  • 回答数2
  • ありがとう数1

みんなの回答

回答No.2

話の繋がりが見えないのですが・・ 質問に書いてあるSQLとは、大きく異なるSQLを使っていて、他の要素に問題があったということでしょうか? それとも、KOJINというのがビューであり、ビューの構成要素に問題があったということでしょうか? 質問は過不足無く書かないと。 (過不足無く書いても解決できるとは限りませんけどね) 20分で充分な速度あれば良いのですが、SQLの動作速度を気にする場合、 実行計画を見て、適正なアクセスをしているかを確認しなければ、 どこに問題があるのか解りません。 例えば、 >SELYMD側を、元号(SELYMD_WGEN)と和暦年月日(SELYMD_WDATE)に分解した上で、 >where WGEN < SELYMD_WGEN or (WGEN = SELYMD_WGEN and WDATE < SELYMD_WDATE) で、KOJIN_SEL_IDXという索引が使われているか、全表検索しているかで、 大きく速度が異なります。 KOJIN_SEL_IDXという索引が使われていないとすれば、如何にしたら使われるかを 模索することになります。

noname#82953
質問者

補足

返事が遅れて申し訳ありません。 質問した内容で問題があったと書きましたが、前提処理として行わなかったことで遅く感じとってしまっていました。 前提処理には、先ほど書きましたように、インデックス処理を設定することでアクセス効能が圧倒的に速くなることがわかりました。 確かに実行計画といい、統計情報(ANALYZE文)を使って検討しなければ問題がわかりませんね。 >>SELYMD側を、元号(SELYMD_WGEN)と和暦年月日(SELYMD_WDATE)に分解した上で、 >>where WGEN < SELYMD_WGEN or (WGEN = SELYMD_WGEN and WDATE < SELYMD_WDATE) >で、KOJIN_SEL_IDXという索引が使われているか、全表検索しているかで、大きく速度が異なります。 >KOJIN_SEL_IDXという索引が使われていないとすれば、如何にしたら使われるかを模索することになります。 確かに、その索引を定義してあるのとしてないのではアクセス速度が違いますね。 SELECT /*+ INDEX(A KOJIN_SEL_IDX) */ SELID FROM KOJIN_SEL A という具合ですね。 (ただ、書いていただいたSQL文の内容が異なりますが。本来はSELYMDの年月日を対象として抽出するべきですが、WGEN<SELYMD_WGENがあるので月日に関係ないその年のみ抽出してしまいます) 結局は、質問した内容のSQL文で良いとして、インデックスのあるなしによってアクセス効率が変わってしまうということがわかりました。

回答No.1

WHERE FNCWSDATECNV(A.WGEN, A.WDATE) < SELYMD; というように、テーブル上の項目に対して、演算式や関数を使うと、通常の索引は使いません。 こういった問題に対処するために、ファンクション索引というものが用意されています。 ファンクション索引については、マニュアルで詳細を確認してください。 さて、このSQLですが、ファンクション索引を使わないと実現できないかというと、 SELYMD側を、元号(SELYMD_WGEN)と和暦年月日(SELYMD_WDATE)に分解した上で、 where WGEN < SELYMD_WGEN or (WGEN = SELYMD_WGEN and WDATE < SELYMD_WDATE) のようにすれば、WGENとWDATEに対する通常の索引でも対処可能かと思います。

noname#82953
質問者

お礼

自己レス的で解決・対策が見つかったようなので書きます。 今まで記述していたSQLを応用としてやっていましたが、 SELECT ~ FROM A.SELNO IN (SELECT DISTINCT ~として記述したり、 SELECTと和集合であるUNIONとして連結していたりしたため、かなり遅くしていたのが原因でした。 そのためには、IN述語でなくEXISTS述語を使うことで圧倒的に速くなる(実は別サイトでインデックスに関して調べているうちにヒントらしきものが1つだけと)、 それからUNIONでなくUNIONALLで使う。 そうしたことで、前回では1時間経っても終わらなかった(あきらめて強制終了したりもした)のが、20分程度で(10万件)読み取ることができました。 あと、ビットマップ索引というのが別サイトから出てきましたが、まだ未検証ですが、おそらくWHERE句で論理とするORなどある場合には有効だと聞きました。 今度時間あるときに試してみたいと思います。 ありがとうございました。

noname#82953
質問者

補足

さっそくの回答ありがとうございます。 >こういった問題に対処するために、ファンクション索引というものが用意されています。 そういうのあったんですね。 >さて、このSQLですが、ファンクション索引を使わないと実現できないかというと、 >SELYMD側を、元号(SELYMD_WGEN)と和暦年月日(SELYMD_WDATE)に分解した上で、 >where WGEN < SELYMD_WGEN or (WGEN = SELYMD_WGEN and WDATE < SELYMD_WDATE) >のようにすれば、WGENとWDATEに対する通常の索引でも対処可能かと思います。 これは、ファンクション関数を使わない方法でのインデックス有効法のことを説明しているのでしょうか? 上記の条件をもとに、抽出する日付側を変換して処理する形式にして実行しましたが、まだ早くなりません。 CREATE INDEX KOJIN_SEL_IDX ON KOJIN(SELID, WGEN, WDATE); これを処理してカーソル処理に SELECT /*+ A KOJIN_SEL_IDX(WGEN,WDATE) */ A.SELID,A.SELNAME FROM KOJIN A WHERE WGEN < SELYMD_WGEN or (WGEN = SELYMD_WGEN and WDATE < SELYMD_WDATE) として処理させるようにしましたが、あまり遅すぎているせいか 処理が長い感じです。 初歩的ですみません。何かINDEXの定義など設定が足りないのかもしれません。よろしくお願いします。 ちなみに (訂正) FNCWSDATECNV(STRGEN IN VARCHAR2) ではなく FNCWSDATECNV(STRGEN IN VARCHAR2, STRDATE IN VARCHAR2)でした。

関連するQ&A

  • MySQLではカーソルを使うことができない?

    MySQLでデータベースの勉強をしています。 先頭からレコードを1件ずつシーケンシャル処理を行う時、カーソル定義をすればFETCHが使えそうです。 でもMySQLにはカーソルがないようです(SQLポケットリファレンス 技術評論社 平成17年版) ではどのような方法があるのでしょうか。 或いは最新版のMySQLではカーソル変数定義できますか?

  • 動的に生成したカラムを使ったFUNCTION

    レコード型の使用方法がわからないので、悪戦苦闘しております。。。 【やりたいこと】 ・あるテーブルのある項目でGROUP BYし、別の項目事に集計した結果を  カラムとして出力 クロス集計と呼ぶのでしょうか? 上記の結果表を取得出来るFUNCTIONを作成したいのです。 【サンプルデータ】 CREATE TABLE t1 ( col1 integer ,col2 integer ,val integer ); INSERT INTO t1 VALUES( 1, 1, 1, ); INSERT INTO t1 VALUES( 1, 1, 1, ); INSERT INTO t1 VALUES( 2, 1, 1, ); INSERT INTO t1 VALUES( 2, 1, 1, ); INSERT INTO t1 VALUES( 3, 2, 1, ); INSERT INTO t1 VALUES( 3, 2, 1, ); INSERT INTO t1 VALUES( 4, 2, 1, ); INSERT INTO t1 VALUES( 4, 2, 1, ); ※ここでcol1、col2ともどんなデータが入っているか、わからないとします。 なので、まずcol2をDISTINCTしたものを求めました。 SELECT DISTINCT col2 FROM t1; この結果をカーソルを使ってループし、動的にカラムを作成するSQLを 作りました。 DECLARE cu refcursor; rec record; str text := 'SELECT col1 '; str2 text := ' FROM t1 GROUP BY col1 ORDER BY col1'; BEGIN OPEN cu FOR EXECUTE SELECT DISTINCT col2 FROM t1; LOOP FETCH cu INTO rec; IF NOT FOUND THEN EXIT; END IF; str := str || ', SUM( CASE WHEN col2 = rec.col2 THEN val END ) AS col_name' || rec.col2; END LOOP; CLOSE cu; RAISE INFO 'key = %', str || str2;  動的に生成されたSQLを実行したい ------------------------------------------------------- ※ちなみに動的に生成されたSQLはこんな感じになります。 SELECT col1 ,SUM( CASE WHEN col2 = 1 THEN val END ) AS col_name1 ,SUM( CASE WHEN col2 = 2 THEN val END ) AS col_name2 FROM t1 GROUP BY col1 ORDER BY col1; ------------------------------------------------------- あとがわかりません。。動的なカラムなので、どうやって取得すれば 良いのでしょうか? レコード型を使うと上手くいくのでしょうか? 試しにFUNCTION( func1 )の戻り値をRETURNS SETOF record として 内部で rec1 record; : :  FOR rec1 IN EXECUTE str || str2 LOOP RETURN NEXT rec1; END LOOP; RETURN; SELECT * FROM func1(); とやってみたら、 ERROR: a column definition list is required for functions returning "record" となります。。。 カラムの定義が必要だと言われているんだと思いますが、カラムは 動的になっているので、どうすれば良いのでしょうか??

  • foreachの処理方法について

    ●やりたい事 【変更前】は抽出した顧客を一度に全て処理していた。 【変更後】は抽出した顧客を100件ごとに処理したい。 抽出後の処理をforeachでやっているので 100件処理して次の101件目からなど、どう対処してよいか わかりません。よい対処方法があれな教えてください。 ※「対象条件のデータを全件抽出する」部分は変更したくないです。 その後の処理で対応できればと考えています。 DB接続処理は省略してます。 【変更前】 //対象条件のデータを全件抽出する $sql = "select * from テーブル名"; $result=mysql_query($sql); if(!$result){ mysql_close($conn); exit; } while ($row = mysql_fetch_array($result)) { $data[] = $row; } mysql_free_result($result); foreach($data as $value){ //該当顧客を対象に処理する } 【変更後】 //対象条件のデータを全件抽出する $sql = "select * from テーブル名"; $result=mysql_query($sql); if(!$result){ mysql_close($conn); exit; } while ($row = mysql_fetch_array($result)) { $data[] = $row; } mysql_free_result($result); //ループ数の計算 $loop = ceil( count($data) / 100 ); //************************************* //ここで$dataを分割できないでしょうか? //************************************* for($i=0;i<$loop;$i++){ foreach($data as $value){ //該当100件ごと顧客を対象に処理する } }

    • ベストアンサー
    • PHP
  • SQL xx件目からxx件目までを抽出したい

    SQLを習いはじめました。 ユーザーIDが100件あるテーブルの30件目~50件目の ユーザIDを抽出し、別テーブルに登録をしたいと思っています。 『このxx件目からxx件目まで』の抽出のしかたがわかりません。 どなたかわかる方がいましたら教えてください。 DECLARE CURSOR cursor_name IS SELECT USER_ID FROM TBL_USERID ; getid VARCHAR2(16); ryear NUMBER := '2008'; ryear2 NUMBER := '08'; rmonth NUMBER; rday NUMBER; usercnt NUMBER; rcnt NUMBER := '0'; --処理開始 BEGIN SELECT count(USER_ID) INTO usercnt FROM TBL_USERID ; OPEN cursor_name ; LOOP FETCH cursor_name INTO getid ; EXIT WHEN cursor_name%NOTFOUND; --月でループ(1~12月) For rmonth IN 1..12 LOOP --日でループ(1~22日) For rday IN 1..22 LOOP --スケジュール1件目 rcnt := (rcnt + 1); INSERT INTO TBL_SCH ( SCHEDULE_ID ,USER_ID ,UPDATE_DATETIME ) VALUES ( ryear || Trim(TO_CHAR(rmonth,'00')) || Trim(TO_CHAR(rday,'00')) || Trim(TO_CHAR(rcnt,'00000000')) ,'getid' ,SYSTIMESTAMP ); END LOOP; END LOOP; END LOOP ; CLOSE cursor_name ; END ;

  • 動的SQLのfetch

    動的SQLのfetch やりたいこととしては、下記の(1)のような動的SQLをfetchなりforなりで まわしたいのですがどうしたらいいのでしょうか? よろしくお願いします。 for c_rec in (select table_name from user_tables) loop   v_sql := 'select * from '||rowtbl.table_name; ・・・(1)   ~ 省略 ~ end loop;

  • PL/SQLプログラムの書き方がわかりません。

    「課題」2つのデータベースがあり、それぞれ別インスタンス。A―DBからB―DBに、データを抽出し、もっていきます。このとき、キーとなるデータが、Bに既にある場合は、UPDATEを、ない場合はINSERTします。 プログラムの流れは、LOOP FETCH でAから取り出すSELECT文につけられたカーソルを実行し、次に SELECT文で、Bからキーとなるデータがあるか検索して、それで、IF文で、なかったときは、INSERT、あるときはUPDATE だと思うのですが・・・書き方がいまいちわかりません。どなたか、サンプルになるようなプログラムがございましたら、教えていただけませんでしょうか?まったくの素人なのですが、この課題をしなければいけません。よろしくお願いします。

  • カーソルで取得した項目のサイズについて質問です。。

    カーソルで取得した項目のサイズについて質問です。。 SELECTした項目のサイズが下記の場合、 レコード格納変数.Cの最大サイズは(14)で、それ以上のバイト数値を代入すると エラーになると思ったのですが、エラーになりませんでした。(最大28バイトになった) レコード格納変数の取得した項目サイズは何で決まってくるのでしょうか? この項目を別テーブルのVARCHAR(20)の項目へ設定しようと思っているのですが、 理由がわからないので、このままでいいのか、サイズを考慮した設計にするべきか困っています。 <oracle 9i> TABLE1{A VARCHAR2(4), B VARCHAR2(10)} DECLARE  CURSOR カーソル変数 IS SELECT A||B AS C FROM TABLE1;  レコード格納変数 カーソル変数%ROWTYPE; BEGIN  OPEN カーソル変数  LOOP   FETCH カーソル変数 INTO レコード格納変数;   EXIT WHEN カーソル変数%NOTFOUND;    :    レコード格納変数.C := VARCAHR2(15) --代入可能?    :    INSERT 項目1 VARCHAR(20) TABLE2 レコード格納変数.C;    :  END LOOP  CLOSE カーソル変数 END;

  • テーブル名が可変の動的SQLをファンクションにしたい

    Oracle9iのWindowsXP環境です。 以下のようなPL/SQLを作成しましたが、同じようなselect文の繰り返しなので整理したいのと、レスポンスが悪いのでファンクションにしたいのですが、テーブル名が可変のため上手くいきません。「Table&1」は置換変数で、batファイルからパラメータが渡り、「Table200812」のように変更されます。 【***.bat】 set /p phara sqlplus -s ***/***@*** ***.sql %phara% 【***.sql】 省略 begin select aaa into a1 from Table&1 where code = 'AAAAA'; ・・ select aaa into a2 from Table&1 where code = 'BBBBB'; ・・ select aaa into a3 from Table&1 where code = 'CCCCC'; ・・ 省略 end; / 以下、試しに作成しようとしたファンクションですが、やはり予想どおりですが「表がありません」とコンパイルエラーになります。 create function Func_test(code IN varchar2, Table1 IN varchar2) return number is ret number; begin select aaa into ret from Table1 where code = 'code'; return ret; end; / ※コール側は、「a1 = Func_Test('AAAAA', Table&1);」 そもそもこれが実現できたとしてレスポンスが上がるものなのでしょうか?どちらにせよコードを整理する意味でもファンクションにはしたいのですが。。 宜しくお願い致します。

  • function内でメンバー変数を作れる?

    ActionScriptのオブジェクトスクリプトは初めてで、ActionScript2.0で制作しようとしています。 クラス内での変数の扱い方が解らずはまっています。 class Sample{ var temp:Array = Array(); function Func_01(key, val){ this.temp[key] = val; } 上の二行目メンバー変数tempを定義する部分をFunc_01内で行うことはできないのでしょうか? やりたいことは、外部テキストファイルを読み込み、中身によって持つメンバー変数を変えたいだけです。例えば 外部テキストより sample=key_A>>>1,key_B>>>2,key_C>>>3,key_D>>>4 を読み込み sample['key_A']=1; sample['key_B']=2; sample['key_C']=3; sample['key_D']=4; のように','と'>>>'でsplit等を利用し連想配列にして利用したいのですが、function内で読み込んだ値をメンバー変数にできずはまっています。お解りの方ご教授願います。

    • ベストアンサー
    • Flash
  • LOOP処理について

    条件に当てはまる値を10件SELECTし、 INSERTしたいのですが試せる環境がなく確認できない状態です。 もしテーブルを全件みて該当データがない場合も処理終了と したいのですが下記のような書き方でよいのでしょうか? どなたかご助言いただけないでしょうか?お願いします。 BEGIN WHILE (10=i) LOOP SELECT A INTO AA FROM TBL_A WHERE A = "xxx" INSERT INTO TBL_B (A_ID)VALUES(AA) EXIT WHEN (10=i) ; END LOOP; DBMS_OUTPUT.PUT_LINE('呼び出されます'); END;