Oracle の隠し関数 wmsys.wm_concat が非常に強力な件について
Oracle には非公開の隠し関数やパラメータが非常に多く存在します。そんな中でもつい最近知った wmsys.wm_concat という関数が非常に強力です。機能としては集約関数に相当するもので、正規化したテーブルを非正規化して出力するような要件にピッタリですが、Oracle 11g のマニュアルにも記載されていないので Undocumented な関数であることを理解して使用する必要があります。(※つまりは Oracle 保守でもサポート外ってわけです。)
さて、実際どのようなことができるかをみてみます。例えばポイントサイトのようなサービスにおけるポイント管理テーブルを考えてみます。たとえば、以下のような簡易なテーブル構造であったとします。※このエントリ用の仮想的なテーブルですので実用的ではないです。
名前 NULL? 型 備考 ----------------- -------- -------------- ----------- PID NOT NULL NUMBER(38) PRIMARY KEY USERID NOT NULL VARCHAR2(40) ユーザID POINT NOT NULL NUMBER(38) 取得ポイント数 GETDATE NOT NULL DATE 取得日 CODE NOT NULL VARCHAR2(50) 取得理由コード
テーブルの使われ方として、ユーザが何らかの理由(会員登録や広告クリック等)によりポイントを獲得したら、このテーブルに獲得履歴を登録して管理するってイメージです。このような場合、当然ですが各ユーザ毎にレコード数もまちまちになります。このような正規化されたテーブルを以下のような非正規化した形に加工する必要があるとします。(※技術担当→営業担当にデータを渡すときとかでこんな要件は結構ありますよね。w)
ユーザID 合計ポイント数 獲得コード一覧( , 区切りで羅列。CODE+日付で) ----------- ---------------- ---------------------------------------------- hogehoge 14650 9909click13RC(99-09-20),9910reg06(99-10-15),9911click15RE(99-11-19),・・・ hoge2222 24820 0002click10EL(00-02-16),0003click07VG(00-03-19),0005click12CS(00-05-19)・・・ ・・・以下他のユーザ続く・・・・
今までは、普通に select 文でデータをとってきてアプリ側(PL/SQL や Perl スクリプト)で加工して出力なんてことをやっていましたが、この wmsys.wm_concat を使うと SQL 一発で目的のデータを出すことができます。
一発で出す SQL はこうなります。ポイントは wmsys.wm_concat 関数です。wmsys.wm_concat は group 化した各グループ内のデータに対して、あるフィールドをカンマ区切りで結合する役割をします。グループ化されたグループ内の任意のカラム名が指定可能です。あとはいつもの group by 句を書くときの流儀と何ら変わりありません。
select USERID, sum(GET_POINT) as POINT, wmsys.wm_concat(CODE || '(' ||GETDATE|| ')') as CODE from USER_POINT group by USERID order by CODE;
上記の SQL を説明すると、USERID(ユーザID)で正規化されたデータをグループ化して、CODE(取得理由コード)でソートした後に、「ユーザID, ポイント合計値, CODE+日時の羅列」の3カラムを1レコードで出力しています。
この wmsys.wm_concat を使うことで、データマイニング分野では定石となるデータの非正規化の多くの部分も簡略化できます。例えば、アクセス履歴をユーザ毎のページ遷移として加工するのも、IPアドレスでグループ化して uri を日付でソートして wm_concat すればOKです。何しろ履歴系のデータ加工ならその適用範囲は非常に広いと思います。
ちなみに wm_concat 関数の所有者 wmsys ってのは、Oracle Workspace Manager用のメタデータ情報の格納に使用されるアカウントです。wm_concat 関数のソースは残念ながら暗号化されていて見ることができないのでどのような実装かは不明ですが、実行速度はネイティブ関数とおもわれ、大量データでテストしてみた場合でも非常に高速に動作しています。
SQL> select TEXT from DBA_SOURCE where OWNER='WMSYS' and NAME='WM_CONCAT'; TEXT ------------------------------------------------------------------------------ function wm_concat wrapped a000000 1 abcd ・・・中略・・・ abcd 8 58 89 G2IOCTwNs2RlB9JzCCQPJq/UuH0wg8eZgcfLCNL+Xuf+8L8z/sefdLPnfMPnx3TAM7h0ZSXD j57Asr2ym9ZtFldFmFu+RdpAU8VGORKSvh2BRIA5Hq8yxhKSOc4WVIKmpu8+Hw0=
最後に wmsys.wm_concat を体感するための簡単なサンプルを記載しておきます。
※http://blog.csdn.net/zhpsam109/archive/2007/12/04/1917011.aspx より拝借しました。
create table IDTABLE ( id number, val varchar2(20) ); insert into IDTABLE (ID, VAL) values (10, 'abc'); insert into IDTABLE (ID, VAL) values (10, 'abc'); insert into IDTABLE (ID, VAL) values (10, 'def'); insert into IDTABLE (ID, VAL) values (10, 'def'); insert into IDTABLE (ID, VAL) values (20, 'ghi'); insert into IDTABLE (ID, VAL) values (20, 'jkl'); insert into IDTABLE (ID, VAL) values (20, 'mno'); insert into IDTABLE (ID, VAL) values (20, 'mno'); commit; column ENAMES format a50; select id,val from idtable; select ID, wmsys.wm_concat(VAL) as ENAMES from IDTABLE group by ID; select ID, wmsys.wm_concat(distinct VAL) as ENAMES from IDTABLE group by ID order by ID; select ID, VAL, wmsys.wm_concat(VAL) over(partition by ID) as ENAMES from IDTABLE order by ID; select ID, VAL, wmsys.wm_concat(VAL) over(order by ID, VAL) as ENAMES from IDTABLE order by ID;
sql の実行結果は以下のようになると思います。
SQL> select id,val from idtable; ID VAL ---------- -------------------- 10 abc 10 abc 10 def 10 def 20 ghi 20 jkl 20 mno 20 mno 8行が選択されました。 SQL> select ID, wmsys.wm_concat(VAL) as ENAMES from IDTABLE group by ID; ID ENAMES ---------- -------------------------------------------------- 10 abc,abc,def,def 20 ghi,jkl,mno,mno SQL> select ID, wmsys.wm_concat(distinct VAL) as ENAMES from IDTABLE group by ID order by ID; ID ENAMES ---------- -------------------------------------------------- 10 abc,def 20 ghi,jkl,mno SQL> select ID, VAL, wmsys.wm_concat(VAL) over(partition by ID) as ENAMES from IDTABLE order by ID; ID VAL ENAMES ---------- -------------------- -------------------------------------------------- 10 abc abc,abc,def,def 10 abc abc,abc,def,def 10 def abc,abc,def,def 10 def abc,abc,def,def 20 ghi ghi,jkl,mno,mno 20 jkl ghi,jkl,mno,mno 20 mno ghi,jkl,mno,mno 20 mno ghi,jkl,mno,mno 8行が選択されました。 SQL> select ID, VAL, wmsys.wm_concat(VAL) over(order by ID, VAL) as ENAMES from IDTABLE order by ID; ID VAL ENAMES ---------- -------------------- -------------------------------------------------- 10 abc abc,abc 10 abc abc,abc 10 def abc,abc,def,def 10 def abc,abc,def,def 20 ghi abc,abc,def,def,ghi 20 jkl abc,abc,def,def,ghi,jkl 20 mno abc,abc,def,def,ghi,jkl,mno,mno 20 mno abc,abc,def,def,ghi,jkl,mno,mno 8行が選択されました。
wmsys.wm_concat に関する他のサイトの情報はこんなところを見ると良いと思います。Oracle の隠し系で久々に感動を覚えました。以下のリンクの最後にご紹介した、Undocumented Oracle にはその他の隠し関数が紹介されています。
merge$actions や sys_op_distinct が場合によっては使いどころがあると思いました。
- OracleSQLパズル - 10-218 wmsys.wm_concat
- OracleSQLパズル - 10-262 累計を求めるwm_concatをXML関数で模倣
- Oracle Technology Network (OTN) Japan - 掲示板 : 集約されたID群の取得について ...
- 使用WMSYS.WM_CONCAT函数实现行列转换 - JACKY.昊昊 的专栏 - CSDNBlog
- Undocumented Oracle
コメントやシェアをお願いします!