プログラム研究所
ソフトウェア開発やプログラミングの豆知識を載せています。
03 | 2017/04 | 05
S M T W T F S
- - - - - - 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 - - - - - -

スポンサーサイト
上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

SELECTのBULK化による高速化
PL/SQLにテーブルから大量データを読込み、ループ処理で計算や加工をおこなうようなルーチンを作るとき、カーソルからのデータの読み込みをBULK化すると高速に処理出来ます。高速化できるのはSQLエンジンとPL/SQLエンジンのコンテキストスイッチを極小化するためです。入力テーブルに関し下記のようなルーチンを適当なスコープに作成して活用することができます。

内部のデータ構造

{source}の部分は入力元のテーブル名に置換してください。

/* 入力テーブルを読み込むカーソルを定義 */ cursor curSource is SELECT SRC.col1, SRC.col2, ... FROM {SourceTable} AS SRC WHERE .......... ORDER BY .....; type tab{source} is table of curSource%rowtype index by pls_integer; bulk_buf tab{source}; bulk_idx pls_integer; bulk_siz constant pls_integer := 500; /* バッファ既定サイズ;最適値はテストで決まる */

内部バッファのサイズ bulk_siz の最適値は実行環境で速度を実測した上で決めてください。一般にサイズが小さいうちはリニアに速度が向上しますが、ある大きさ以上になると頭打ちになります。

プロシージャ定義

{source}の部分は出力先のテーブル名に置換してください。

err_buf{source} exception; --カーソルはアプリケーションに公開 cursor curSource; procedure open{source} is begin --カーソルをオープン open curSource; --バッファを空にする bulk_buf.delete; --次の読み出し位置 bulk_idx := 0; exception when others then if curSource%isopen then close curSource; end if; raise err_buf{source}; end open{source}; procedure close{source} is begin --バッファを空にする bulk_buf.delete; bulk_idx := 0; --カーソルをクローズ if curSource%isopen then close curSource; end if; exception when others then if curSource%isopen then close curSource; end if; raise err_buf{source}; end close{source}; function fetch{source}(out_buf out nocopy curSource%rowtype) return boolean is isValid boolean; begin -- 次の読み出し位置がバッファサイズを超えてない場合はデータ有効 if bulk_idx <= bulk_buf.count then isValid := true; -- 次の読み出し位置がバッファサイズを超えている場合はデータ無効。次のブロックの読み出しを試みる else bulk_buf.delete; bulk_idx := 0; fetch curSource bulk collect into bulk_buf limit bulk_siz; if bulk_buf.count = 0 then isValid := false; else isValid := true; bulk_idx := 1; end if; end if; -- データが有効な場合はデータを返却する if isValid then out_buf := bulk_buf(bulk_idx); bulk_idx := bulk_idx + 1; -- バッファが有効でない場合はもうデータがない else out_buf := null; end if; return isValid; exception when others then if curSource%isopen then close curSource; end if; raise err_buf{source}; end fetch{source};

サンプル

{source}の部分は出力先のテーブル名に置換してください。

アプリケーションでは1レコードずつ転送しているようにコーディングしますが、実際は、openでカーソルとバッファを準備し、fetch内部でバッファリングして1レコードずつ戻します。最後にアプリケーションは内部バッファをクリアしカーソルをクローズするためにcloseを呼びます。

declare rec curSource%rowtype; begin /* 前処理 */ open{source}; /* ソースデータの読み込みループ */ loop rec := null; exit when fetch{source} (rec); /* 行ごとの処理 */ ANY_PROC ( rec ); : end loop; /* 後処理 */ close{source} /* 例外処理 */ exception when err_buf{source} then dbms_output.put_line('処理失敗...'); end;

スポンサーサイト
INSERTのBULK化による高速化
PL/SQLでまとまったデータをテーブルにINSERTするようなルーチンを作る時にINSERTBULK化すると高速に処理できます。高速化できるのはSQLエンジンとPL/SQLエンジンのコンテキストスイッチを極小化するためです。出力テーブルに関し下記のようなルーチンを適当なスコープに作成して活用することができます。

内部のデータ構造

{target}の部分は出力先のテーブル名に置換してください。

type tab{target} is table of {target}%rowtype index by pls_integer; bulk_buf tab{target}; bulk_idx pls_integer; bulk_siz constant pls_integer := 500; /* バッファ既定サイズ;最適値はテストで決まる */

内部バッファのサイズ bulk_siz の最適値は実行環境で速度を実測した上で決めてください。一般にサイズが小さいうちはリニアに速度が向上しますが、ある大きさ以上になると頭打ちになります。

プロシージャ定義

{target}の部分は出力先のテーブル名に置換してください。

err_buf{target} exception; procedure open{target} is begin --バッファを空にする bulk_buf.delete; bulk_idx := 0; exception when others then raise err_buf{target}; end open{target}; procedure close{target} is begin if bulk_idx > 0 then --バッファのデータをテーブルに転送 forall i in 1..bulk_idx insert into {target} values bulk_buf(i); commit; --バッファを空にする bulk_buf.delete; bulk_idx := 0; end if; exception when others then rollback; raise err_buf{target}; end close{target}; procedure write{target}(rec {target}%rowtype) is begin --バッファに追加 bulk_idx := bulk_idx + 1; bulk_buf(bulk_idx) := buf; --バッファが一杯になったらテーブルに転送 if bulk_idx >= bulk_siz then close{target}; end if; exception when others then raise err_buf{target}; end write{target};

サンプル

{target}の部分は出力先のテーブル名に置換してください。

アプリケーションでは1レコードずつ転送しているようにコーディングしますが、実際は、openで準備されたバッファにwriteで書き込みします。writeはバッファがいっぱいになると、テーブルに一括で転送を行います。最後にアプリケーションはバッファに残っておりテーブルに転送されていないデータをフラッシュするためにcloseを呼びます。

declare cursor c1 select col1,col2,... from {source} where ....; rec {target}%rowtype; begin /* 前処理 */ open{target}; /* ソースデータの読み込み */ for r1 in c1 loop /* ターゲットへの書き込みレコードを構成 */ rec := null; rec.col1 := r1.col1; rec.col2 := r1.col2; : /* 転送 */ write{target}(rec); end loop; /* 後処理 */ close{target} /* 例外処理 */ exception when err_buf{target} then dbms_output.put_line('処理失敗...'); end;

テーマ:プログラミング - ジャンル:コンピュータ

Tree構造への要素追加
最近はポインタをように取り扱うようなプログラムはあまり書かなくなったのだろうか。昔はよく書きました。今回紹介するのは昔『WRITING SOLID CODE(STEVE MAGUIRE 著)』という書籍で出会い、感動した二分木構造に要素を追加するときのコードです。

Tree構造への要素追加

階層ウィンドウを管理するデータ構造

typedef struct WINDOW { struct WINDOW* pwndChild; /* 子 がない場合は NULL */ struct WINDOW* pwndSibling; /* 兄弟がない場合は NULL */ char* strWndTitle; : : } window; /* 命名規則: wnd, *pwnd */ /* pwndDisplay はルートレベルのウィンドウを指している。 * このウィンドウはプログラムの初期化の際に割り当てられる */ window* pwndDisplay = NULL;

このようなバイナリツリー(二分木)の構造を操作する方法はちょっとしたアルゴリズムの本を探せばお手本が見つかりましたが、上手く書けているものもあればバグのあるサンプルが書かれている本もよくありました。

新しいウィンドウを追加する関数

void AddChild(window* pwndParent, window* pwndNewBorn) { window** ppwndNext; /* 新しいウィンドウは子を持っている可能性はあるが、兄弟はない */ ASSERT(pwndNewBorn->pwndSibling == NULL); /* * ポインタ中心のアルゴリズムを使って兄弟のチェインをたどる。 * 次のポインタがリストの最初の"次の兄弟のポインタ"になるように * ppwndNext を pwndParent->pwndChild を指すように設定する。 */ ppwndNext = &pwndParent->pwndChild; while (*ppwndNext != NULL) ppwndNext = &(*ppwndNext)->pwndSibling; *ppwndNext = pwndNewBorn; }

C言語で、ウィンドウシステムや、今でいうHTMLのような構造化ドキュメントを取り扱うようなプログラムを書いた時に、データ構造を設計し、データ構造へのアクセスルーチンをたくさん書きました。上記で紹介したようなコードの動きがイメージできたときは嬉しくなり、同じような設計をしたものです。


テーマ:プログラミング - ジャンル:コンピュータ

トラックバックとは?
『一般的にトラックバックとは、他人のブログの記事の内容を引用・参照した時、あるいは他人のブログの記事が自身のブログの記事と関連性のある話題を書いている場合などに、自身のブログの記事が引用・参照した事や関連性がある事を通知する目的で行なわれるものである。』(ウィキペディア・トラックバック
と、ありますが正直よくわからなかったので、今回チャレンジしてみることにしました。なんらかお作法的なものがありそうなので少し調べてみると、便利なのものでググると必要な知識がすぐ手に入った。
トラックバックの意味合いや設定方法,スパム呼ばわりされないためのお行儀については、『トラックバックとは?』を参照してください。さて、トラックバックというのを実際に試してみるがさてどういう事が起きるのだろうか。

テーマ:ホームページ・ブログ制作 - ジャンル:コンピュータ

CSVデータの分割
PL/SQLでユーザが入力したデータや、他のプログラムモジュールとのインタフェースのためにCSVデータを取り扱うことがあります。ここではソースのCSVデータを文字列の配列に分解する一般的なルーチンを紹介します。まずは引用符によるエスケープなどを考慮しなくて良い単純なカンマ区切りの分割です。データに区切り文字や引用符などが含まれておりそれらのエスケープも考慮しなければならない場合は、本稿のプログラムを改良してそれらの状況に対処できるようにする必要があります。

CSVデータの分割

プロシージャ仕様

プロシージャ名
CSV_TO_TABLE
入力データ
IN_CSV VARCHAR2;
出力データ
OUT_ITEMS CSV_TABLE;
出力データの型
TYPE CSV_TABLE IS TABLE OF VARCHAR2(32767) INDEX BY PLS_INTEGER;
例外
Oracleエラーなどが発生した場合はアプリケーション例外をスローします。

プロシージャ定義

PROCEDURE CSV_TO_TABLE(IN_CSV IN VARCHAR2, OUT_ITEMS OUT CSV_TABLE) IS ITEMS CSV_TABLE; -- 作業用のリスト COUNT PLS_INTEGER; -- 項目数 CSVLEN PLS_INTEGER; -- CSV長 POSCUR PLS_INTEGER; -- CSV中の検索位置 POSSEP PLS_INTEGER; -- 区切文字の位置 P_SEPARATER VARCHAR2(1) := ','; -- 区切文字 BEGIN ------------------------- -- 作業用リストの初期化 ------------------------- ITEMS.DELETE; COUNT := 0; ----------------------------------- -- CSVが空の場合 1番目の項目が空値 ----------------------------------- IF IN_CSV IS NULL OR LENGTH(IN_CSV) = 0 THEN COUNT := 1; ITEMS(COUNT) := NULL; ------------------------------ -- CSVが空でない場合 項目分割 ------------------------------ ELSE ------------------ -- ポインタ初期化 ------------------ CSVLEN := LENGTH(IN_CSV); POSCUR := 1; ---------- -- ループ ---------- WHILE (POSCUR <= CSVLEN) LOOP -- セパレータを探す POSSEP := INSTR( IN_CSV, P_SEPARATER, POSCUR, 1 ); -- テーブルに値をセットする COUNT := COUNT + 1; IF POSSEP = 0 THEN ITEMS(COUNT) := SUBSTR(IN_CSV, POSCUR, CSVLEN - POSCUR + 1); POSCUR := CSVLEN + 1; ELSE ITEMS(COUNT) := SUBSTR(IN_CSV, POSCUR, POSSEP - POSCUR); POSCUR := POSSEP + 1; END IF; END LOOP; ---------------------------------------------------------- -- 最後の文字がセパレータの場合、テーブルに空値を追加する ---------------------------------------------------------- IF SUBSTR(IN_CSV, CSVLEN, 1) = P_SEPARATER THEN COUNT := COUNT + 1; ITEMS( COUNT ) := NULL; END IF; END IF; -------------- -- 出力リスト -------------- OUT_ITEMS := ITEMS; ITEMS.DELETE; RETURN; EXCEPTION WHEN OTHERS THEN ITEMS.DELETE; RAISE_APPLICATION_ERROR(-20999, '例外:' || SQLERRM, TRUE); END;

使い方の例

DECLARE CSV_DATA VARCHAR2(2000); CSV_ITEMS CSV_TABLE I PLS_INTEGER; BEGIN -- テーブルやファイルから CSVデータ を読み込む。 SELECT CSV_DATA_COLUMN INTO CSV_DATA FROM TABLE_SOURCE; -- 項目リストに分割する。 CSV_TO_TABLE ( CSV_DATA, CSV_ITEMS ); -- 各項目の処理をする FOR I IN 1..CSV_ITEMS.COUNT LOOP DBMS_OUTPUT.PUT_LINE( CSV_ITEMS(I) ); END LOOP; -- リストの破棄 CSV_ITEMS.DELETE; EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE( SQLERRM ); END;

テーマ:プログラミング - ジャンル:コンピュータ



上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。