2010-03-14 11:13:21
以下のような、メールアドレス等の情報を保持したテーブルがあるとします。
mysql> SELECT * FROM users;
+----+---------+------------------+
| id | name | mail |
+----+---------+------------------+
| 1 | foo | foo@yuyat.jp |
| 2 | bar | bar@yuyat.jp |
| 3 | baz | baz@example.com |
| 4 | hoge | hoge@example.org |
| 5 | moge | moge@example.com |
| 6 | foobar | foobar@yuyat.jp |
| 7 | test | test@example.net |
| 8 | example | example@yuyat.jp |
| 9 | mage | mage@example.org |
| 10 | huga | huga@example.com |
| 11 | piyo | piyo@yuyat.jp |
| 12 | hige | hige@yuyat.jp |
+----+---------+------------------+
12 rows in set (0.00 sec)
ここで、以下のような SQL を用意します。
SELECT
SUBSTRING_INDEX(mail, '@', -1) AS domain
FROM
users
すると、このような結果が得られます。
mysql> SELECT SUBSTRING_INDEX(mail, '@', -1) AS domain FROM users;
+-------------+
| domain |
+-------------+
| yuyat.jp |
| yuyat.jp |
| example.com |
| example.org |
| example.com |
| yuyat.jp |
| example.net |
| yuyat.jp |
| example.org |
| example.com |
| yuyat.jp |
| yuyat.jp |
+-------------+
12 rows in set (0.00 sec)
また、以下の用にグルーピングすることで、ドメイン数順にソートすることも簡単です。
SELECT
SUBSTRING_INDEX(mail, '@', -1) AS domain,
COUNT(id) AS count
FROM
users
GROUP BY
domain
ORDER BY
count DESC
mysql> SELECT SUBSTRING_INDEX(mail, '@', -1) AS domain, COUNT(id) AS count FROM users GROUP BY domain ORDER BY count DESC;
+-------------+-------+
| domain | count |
+-------------+-------+
| yuyat.jp | 6 |
| example.com | 3 |
| example.org | 2 |
| example.net | 1 |
+-------------+-------+
4 rows in set (0.00 sec)
これらの SQL で重要なのは SUBSTRING_INDEX という関数です。この関数は、ある文字列をデリミタで区切り、その n 番目を取得する というものなのですが、n に負の数を与えることで、末尾の要素を取得しています。
2010-03-06 23:40:44
2010-03-07 15:30 追記
Observer Pattern について調べ直してみたところ、一般的な Obsesrver Pattern と、以下のコードは似て非なるものであることに気づきました。
このエントリは GoF パターンの教材にはなり得ないので、その点に注意してお読みください。
入浴中にふと思いつき、書いてみたコードを紹介。
Observer Pattern については詳しく紹介しないので、興味のある方は Wikipedia の Observer パターンの頁をご覧ください。かくいう私自身も、いわゆる GoF パターンとしての Observer Pattern を暗記しているわけではないのですが、Observer Pattern 的にはなっていると思います。
Notifier.php
監視者にメッセージを送る通知者クラス。
class Notifier
{
private $_observers = array();
public function addObserver($observer)
{
$this->_observers[] = $observer;
return $this;
}
public function __call($methodName, $args)
{
foreach ($this->_observers as $observer)
{
call_user_func_array(array($observer, $methodName), $args);
}
return $this;
}
}
そして、次に監視者クラスを 2 つほど定義。
まずは文字列をテキストファイルに書き込んでいくロガー。
Logger.php
class Logger
{
private $_fp;
public function __construct($fileName)
{
$this->_fp = fopen($fileName, 'a');
}
public function __destruct()
{
fclose($this->_fp);
}
public function putString($str)
{
$this->_log($str);
}
public function putArray($arr)
{
foreach ($arr as $str)
{
$this->_log($str);
}
}
private function _log($str)
{
fputs($this->_fp, date('Y-m-d H:i:s') . "\t" . $str . "\n");
}
}
次は、顔文字の吹き出しとして、文字列を出力するもの。
Speaker.php
class Speaker
{
private $_face;
public function __construct($face)
{
$this->_face = $face;
}
public function putString($str)
{
$this->_speak($str);
}
public function putArray($arr)
{
foreach ($arr as $str)
{
$this->_speak($str);
}
}
public function _speak($str)
{
echo $this->_face . ' < ' . $str . "\n";
}
}
そして、実際に実行するのは以下のコード。
main.php
require_once './Notifier.php';
require_once './Logger.php';
require_once './Speaker.php';
// 通知者オブジェクトの生成
$notifier = new Notifier;
// 通知者に、監視者オブジェクトを登録する
$notifier->addObserver(new Logger('./log.txt'));
$notifier->addObserver(new Speaker('( `・ω・´)'));
$str = "Pentagram / Relentless (1985)";
$arr = array(
"01. Death Row",
"02. All Your Sins",
"03. Sign of the Wolf (Pentagram)",
"04. The Ghoul",
"05. Relentless",
"06. Run My Course",
"07. Sinister",
"08. The Deist",
"09. You're Lost I'm Free",
"10. Dying World",
"11. 20 Buck Spin"
);
// いろいろなメソッドを実行
$notifier->putString($str);
$notifier->putArray($arr);
これを実行すると、こうなります。
$ php main.php
( `・ω・´) < Pentagram / Relentless (1985)
( `・ω・´) < 01. Death Row
( `・ω・´) < 02. All Your Sins
( `・ω・´) < 03. Sign of the Wolf (Pentagram)
( `・ω・´) < 04. The Ghoul
( `・ω・´) < 05. Relentless
( `・ω・´) < 06. Run My Course
( `・ω・´) < 07. Sinister
( `・ω・´) < 08. The Deist
( `・ω・´) < 09. You're Lost I'm Free
( `・ω・´) < 10. Dying World
( `・ω・´) < 11. 20 Buck Spin
そして、同時に、以下のようなログファイルまで出力されます。
2010-03-06 22:42:09 Pentagram / Relentless (1985)
2010-03-06 22:42:09 01. Death Row
2010-03-06 22:42:09 02. All Your Sins
2010-03-06 22:42:09 03. Sign of the Wolf (Pentagram)
2010-03-06 22:42:09 04. The Ghoul
2010-03-06 22:42:09 05. Relentless
2010-03-06 22:42:09 06. Run My Course
2010-03-06 22:42:09 07. Sinister
2010-03-06 22:42:09 08. The Deist
2010-03-06 22:42:09 09. You're Lost I'm Free
2010-03-06 22:42:09 10. Dying World
2010-03-06 22:42:09 11. 20 Buck Spin
これがどう嬉しいのか。Observer Pattern の例としてよくあるのは、通知者オブジェクトの notify メソッドが呼び出されると、監視者オブジェクトたちの update メソッドを実行していく、というパターン。対する、この Notifier オブジェクトは、notify 的メソッドをいくつでも追加できるという特性を持ちます。いわば、動的 Observer Pattern。
putString メソッドも、putArray メソッドも、Notifier は知りませんが、自分が知らないメソッドを受けとると、監視者オブジェクトたちに「putString してください」「putArray してください」というメッセージを、引数を伴って通知して回るのです。
これを実現するのが、以前のメタプログラミングに関する記事で紹介した、__call というマジックメソッド。以前の繰り返しになりますが説明すると、実行しようとしたメソッドがなかったときの転送先のメソッドです。引数には、実行しようとしたメソッド名と、そのメソッドに渡された引数を受けます。
ところで Ruby には Observable という、Observer Pattern を実現するためのモジュールがあります。非常に動的な特性を持つ Ruby のことですから、上記の Notifier と同じような仕様になっているのではと思い、調べてみたところ、notify されたら update していくだけの普通のものでした。つまり、静的 Observer Pattern。
これは我ながらおもしろいアイディアではないかなと思っているのですが、やはり、PHP よりはむしろ Ruby のほうがしっくりパターンかもしれません。method_missing メソッドを使えば、簡単に同じようなことが実現できると思います。