SimpleDBで手軽に集計処理を行う

あまりがっつりとSimpleDBを使い込んでいる訳ではないですが、ちょっとしたデータをつっこんでおくにはなかなか便利です。特にログを残しておきたいときは特別なミドルウェアを用意する必要がないので、サーバやミドルウェアの検証をやるときにはよく使っています。

クライアントはいくつかあるのですが、コマンドラインで使えるクライアントがamazon-simpledb-cliぐらいしか見つからなかったので、一年くらい前に自作しました

このクライアントの売りはmysqlクライアント感覚でデータを引っ張ってこれるところで、普通にSELECTはたたけるし

ap-northeast-1> select * from employees limit 3;
---
- ["100000", {first_name: Hiroyasu, hire_date: "1991-07-02", birth_date: "1956-01-11", last_name: Emden}]
- ["100001", {first_name: Jasminko, hire_date: "1994-12-25", birth_date: "1953-02-07", last_name: Antonakopoulos}]
- ["100002", {first_name: Claudi, hire_date: "1988-02-20", birth_date: "1957-03-04", last_name: Kolinko}]
# 3 rows in set

複数行のUPDATEも(一応)出来るし

ap-northeast-1> update employees set age = '35';
# 100 rows changed

と、結構便利に作っていたのですが、集計処理が出来ないのが難点でした。


一応、SQLのパーサを書いたのですが、SELECT文のパースをSimpleDBに丸投げだったりとなかなかヤクザなパーサです。集計系の構文(MAX、SUM、GROUP BY)をマジでパースしようと思うとかなり骨なので、集計処理の実装については棚上げ状態でした。

もちろん標準出力に出してRubyでこねくり回せばどうとでも出来るのですがめんどくさい!
どうしたものかと悶々としていたところ『だいたいRubyのメソッド使えばいくらでも集計できるじゃん?構文のきれいさにこだわらなければどうとでもなるんじゃね?』と思い至ったのが以下の構文です。

ap-northeast-1> select * from employees limit 3 | map {|i| i.hire_date };
---
- "1991-07-02"
- "1994-12-25"
- "1988-02-20"
# 3 rows in set

『|』以降の文字列をRubyに丸投げというひどい代物になりました。


とはいえRubyをほぼそのまま使えるのはなかなか強力です。

first_nameだけを抽出することも出来ますし

ap-northeast-1> select * from employees limit 3 | first_name;
---
- Hiroyasu
- Jasminko
- Claudi
# 3 rows in set

first_nameが『C』で始まる人は何月生まれが多いかとかも集計できますし

ap-northeast-1> select * from employees | select {|i| i.first_name =~ /^C/ }.map {|i| Time.parse(i.birth_date).mon }.inject({}) {|r, i| r[i] ||= 0 \; r[i] += 1\; r }.sort_by {|k,v| k } ;
---
- [1, 1]
- [3, 1]
- [5, 1]
- [8, 2]
- [10, 1]
- [12, 3]
# 6 rows in set

itemNameを数値に変換して平均を出すとか訳のわからないことも出来ます。

ap-northeast-1> select * from employees | itemname.to_f.avg;
--- 91941.4


というわけで見た目はクソですが意外と便利ですので、興味のある方はどうぞご利用ください。