■CALENDAR■
          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
31       
<<前月 2024年03月 次月>>
■LOGIN■
現在のモード: ゲストモード
USER ID:
USER PW:
■ADMIN■
ADMIN ID:
ADMIN PW:
■NEW ENTRIES■
■RECENT COMMENTS■
■RECENT TRACKBACK■
  • 吉本隆明の死去に寄せて - 反「反核」ということ
  • 2010 年の釣り - 2010~11 シーズン開幕! 肝和えの素(カワハギ)
  • 震災後の復興を考える
  • 夏本番ですが ・・・ 水の事故を防ぐために
  • 2011 年の釣り - 遅ればせながらシーズン開幕 東京湾のマゴチ
■CATEGORIES■
■ARCHIVES■
■PROFILE■
■POWERED BY■
BLOGN(ぶろぐん)
BLOGNPLUS(ぶろぐん+)
■OTHER■

PL/pgSQL で、第xN分位数を得る集約関数を定義する(その2)
 以前の記事で、「集約演算部分は、レコード毎に投入されるデータをソートして配列に格納し保持する処理のみで良い」と書きました。 そのような処理を行う集約関数を定義します。

 
 レコード毎に投入される numeric データを昇順にソートし、配列として格納・保持する集約関数 arraySorted() を、以下のように定義します。

-- arraySorted() : 集約対象フィールドを昇順ソートにて格納した配列を得る
CREATE AGGREGATE arraySorted (
  BASETYPE = float8,
  STYPE = float8[],
  SFUNC = insArraySorted
);
 BASETYPE とは、この集約関数に投入される(引数として渡される)フィールドデータの型を定義するもので、型変換にてあらゆる数値データを扱えるよう float8(倍精度浮動小数点実数)とします。
 STYPE は、この集約関数のなかで内部的に保持される(状態)データの型であり、投入されるデータ(float8)がソートされて保持される配列ですから、float8[] とします。 この配列は、新たなフィールデータが投入される都度、SFUNC により更新されます。
 SFUNC は、新たな投入データにより内部状態データがどのように更新されるのかを定義した状態遷移関数であり、ここでは insArraySorted() と命名しました。 この関数は、実際にはこの集約関数定義の前に、定義しておかなくてはなりません。
 なお、最終関数 FINALFUNC を宣言していませんので、この集約関数自体の型は STYPE で宣言した float8[] となります。

 次に、状態遷移関数 SFUNC として宣言した insArraySorted() を、以下のように定義します。
 SFUNC は原則として2つの引数を取ることとされています。 第1引数は状態遷移前の状態データであり、その型は STYPE、また第2引数は投入されたフィールドデータであり、その型は BASETYPE です。 第2引数は省略可能であり、その場合引数の数は1となります。 どちらの場合も、この関数が返す値の型は、STYPE となります。
 状態遷移関数の機能は、第1引数として与えられた状態遷移前の状態データを、何がしか処理して状態遷移後の状態データへと更新し、呼出し側に返すことです。 多くの場合には第2引数のフィールドデータが与えられ、それを使って更新処理をします。
 ここでは、第1引数で与えられたソート配列が状態データであり、第2引数で与えられた新たなエレメントをソート配列の適当な位置に挿入して更新します。
 なお、前述したとおり、この状態遷移関数は集約関数 arraySorted() の定義より先に定義しておかなくてはなりません。

-- insArraySorted() : 新規エレメントを昇順ソート配列中の適正な位置に挿入する
CREATE FUNCTION insArraySorted(float8[], float8) RETURNS float8[] AS $BODY$
DECLARE
  arrySrtd  alias for $1;  -- 昇順ソート配列
  elemIns  alias for $2;  -- 挿入する新規エレメント
  arrySrtdNew  float8[];
  head  integer;
  tail  integer;
  size  integer;
  posIns  integer;
BEGIN
  /* 新エレメントが NULL なら 現状のソート配列をそのまま返す */
  if elemIns is NULL then
    return arrySrtd;
  end if;

  /* 現状のソート配列について、先頭/末尾の添字およびサイズを得る */
  head :=coalesce(array_lower(arrySrtd, 1), 0);
  tail :=coalesce(array_upper(arrySrtd, 1), -1);
  size :=tail - head +1;

  if size = 0 then  /* 現状ソート配列が空なら、*/
    /* 新エレメントを要素として配列を新規生成 */
    arrySrtdNew :=array[elemIns];
  else  /* 空でなければ、 */
    /* 新エレメントの挿入位置を求めて */
    posIns :=srchPosIns(elemIns, arrySrtd, head, tail);
    /* 新エレメントを当該位置に挿入 */
    if posIns = head then
       arrySrtdNew :=elemIns || arrySrtd;
    elsif posIns > tail then
      arrySrtdNew := arrySrtd || elemIns;
    else
      arrySrtdNew :=arrySrtd[head:posIns-1] || elemIns || arrySrtd[posIns:tail];
    end if;
  end if;

  /* 新しいソート配列を返す */
  return arrySrtdNew;
END;
$BODY$ LANGUAGE plpgsql;
 単純な処理です。
 第2引数で与えられたフィールドデータ(新エレメント)が NULL ならば、第1引数で与えれた元のソート配列を何もしないで返します。
 フィールドデータが NULL でない(有意な)データであるならば、ソート配列中の挿入すべき位置を求めてそこに挿入しますが、ソート配列がまだ空である場合には、新エレメントのみを要素として持つ新しい配列を生成します。
 こうして更新あるいは新規生成されたソート配列を、返します。

 さらに、この insArraySorted() から呼ばれる、新エレメントの挿入位置を求める関数 srchPosIns() を定義します。
 この関数は、新たなエレメントがすでにソートされている配列のどこに挿入されるべきかを二分検索にて求め、その位置を返します。 その位置に実際に新エレメントを挿入する処理は、呼出した側で行います。
 二分検索というと再帰的呼出しによるのが定番であり、わきたのルーチンでもそうしていますが、再帰呼出しの際に教科書的に引渡す部分配列のため新たな記憶域を確保してしまうと、エレメント数によっては記憶域確保のオーバーヘッドが大きくなり過ぎることが危惧されます。 そこでわきたのルーチンでは、再帰呼出しで引渡す部分配列用の記憶域を新たに確保することはせず、元の配列の先頭と末尾を示す添え字を引渡すことで、部分配列の引渡しとしました。

-- srchPosIns() : 新規エレメントが挿入されるべき昇順ソート配列中の適正な位置を、二分検索により求める
CREATE FUNCTION srchPosIns(float8, float8[], integer, integer) RETURNS integer AS $BODY$
DECLARE
  elemIns  alias for $1;  -- 挿入しようとする新規エレメント
  arrySrtd  alias for $2;  -- 昇順ソート配列
  head  alias for $3;  -- 検索範囲の端点1(最小位)
  tail  alias for $4;  -- 検索範囲の端点2(最大位)
  center  integer;
BEGIN
  if (head = tail) or (head +1 = tail) then  -- 再帰脱出条件1 - head/tail による挟み込み完了 or 要素数が1
    if elemIns < arrySrtd[head] then
       return head;
    elsif elemIns < arrySrtd[tail] then
      return tail;
    else
       return tail +1;
    end if;
  end if;

  center :=(tail + head) /2;
  if elemIns < arrySrtd[center] then
    return srchPosIns(elemIns, arrySrtd, head, center);
  elsif arrySrtd[center] < elemIns then
    return srchPosIns(elemIns, arrySrtd, center, tail);
  else  -- 再帰脱出条件2 - 一致するエレメントを発見
    return center;
  end if;
END;
$BODY$ LANGUAGE plpgsql;
 再帰呼出しからの脱出条件は2つ。
  • 条件その1:ソート配列を二分して再帰呼出しを重ねていくと、やがて部分配列長が1エレメントのみになる。 挿入すべき位置は、そのエレメントとの比較で決まる

  • 条件その2:元のソート配列を二分する位置のエレメントが新エレメントと同値であった場合は、その位置が挿入すべき位置
 この2つの脱出条件に当てはまらなかった場合には、二分位置のエレメントと新エレメントとを比較して、新エレメントのほうが小さければ二分した先頭側を、大きければ末尾側を、部分配列として再帰呼出しをかけます。

 以上で、集約関数 arraySorted() が定義できました。
 あとはこれを使って、第xN分位数を求める関数を定義すれば良い。 ついでにジニ係数を求める関数も作りました。
 これらのソースリストの掲載は、また後日。、その後掲載しました
| http://blog.wakita.cc/index.php?e=87 |
| サーバ・Linux::PostgreSQL | 05:35 PM | comments (0) | trackback (0) |










http://blog.wakita.cc/tb.php/87
PAGE TOP ↑