DBクラスを使っての集計の仕方は検索すればでてくるけど、ORMだけでする方法がなかなか無かったので検証してみました。
そもそもなぜORMでかというと
集計クエリを発行する方法として、検索すると以下のような方法がよく出てきます。
$result = \DB::select(\DB::expr('SUM(`score`)'))->from('answers')->exectute();
もちろん、うまくいきますが、
このModel_Answer
クラスが論理削除モデルだったらどうなるかというと、
$result = \DB::select(\DB::expr('SUM(`score`)'))
->where(array(
array('deleted_at','IS',null)
))
->from('answers')
->exectute();
なんて感じになって、論理削除モデルでせっかくdeleted_atを意識しなくてよくなってたのにもうめんどくさい!
さらに、
FuelPHPのORMで子モデルを作成する
とかでクエリーを追加しているモデルでやろうとすると条件文が増えてしまうし、条件が変更になったらこちらも修正しないといけないので大変!
できればモデルでの定義をそのまま使って集計したい!
ということです。
とりあえずやってみる
class Model_Question extends Model
{
...
public function calc_total()
{
$options = array(
'select' => array(
array(DB::expr('COUNT(*)'), 'num_answers') ,
array(DB::expr('FLOOR( AVG(`score`) )'), 'average_answers')
),
'where' => array(
'question_id' => $this->id ?: -1
),
'group_by' => array(
'question_id',
)
);
$total = Model_Answer::find('first',$options);
$result = ( $total )? array_diff_assoc($total->to_array(true), $total->to_array()): array();
return $result;
}
※クイズ(Model_Question)に解答(Model_Answer)が複数紐付いている状態です。
実行結果と流れたSQLはこんな感じです。(適宜改行しています)
[projects@d2ee29bda854 project]$ oil console
>>> Model_Question::find('first')->calc_total()
array (
'num_answers' => '9',
'average_answers' => '65',
)
>>> DB::last_query()
SELECT COUNT(*) AS `t0_c0`, FLOOR( AVG(`score`) ) AS `t0_c1`, `t0`.`id` AS `t0_c2`
FROM `answers` AS `t0`
WHERE `t0`.`question_id` = '2'
GROUP BY `t0`.`question_id`
ORDER BY `t0`.`id` ASC
LIMIT 1
うまくいきました。
論理削除モデルだと
そして、論理削除モデルにしてみるとこうなります。(calc_total()
は変えてません)
[projects@d2ee29bda854 project]$ oil console
>>> Model_Question::find('first')->calc_total()
array (
'num_answers' => '8',
'average_answers' => '70',
)
>>> DB::last_query()
SELECT COUNT(*) AS `t0_c0`, FLOOR( AVG(`score`) ) AS `t0_c1`, `t0`.`id` AS `t0_c2`
FROM `answers` AS `t0`
WHERE `t0`.`question_id` = '2' AND `t0`.`deleted_at` IS null
GROUP BY `t0`.`question_id`
ORDER BY `t0`.`id` ASC
LIMIT 1
やっほー!
なんてシンプル!
最後の1行は何なんだと
思われるでしょうと思いますので、補足です。
[projects@d2ee29bda854 project]$ oil console
>>> $options = array( ... ) //上と同じものを入れたとします
>>> $total = Model_Answer::find('first',$options);
>>> $total
Model_Answer::__set_state(array(
'_is_new' => false,
'_frozen' => false,
'_sanitization_enabled' => false,
'_data' =>
array (
'id' => '10',
),
'_custom_data' =>
array (
'num_answers' => '1',
'average_answers' => '99',
),
'_original' =>
...
))
こんな感じで、モデルのproperties
に定義されていなカラムは_custom_data
という所に格納されていて、普通にto_array()
しても出てきません。
が、第一引数にtrueを入れるとあっさりでてきます。
※もちろん、個別にgetすれば普通にでてきますが、今は一覧で欲しかったのです。
>>> $total->to_array()
array (
'id' => '10',
)
>>> $total->to_array(true)
array (
'num_answers' => '1',
'average_answers' => '99',
'id' => '10',
)
>>> $total->average_answers
99
>>> $total->get('average_answers')
99
このidはおそらくPrimaryKeyが全て出てきてしまうような気がしたので、これらを除いて_custom_data
の部分だけ配列にしてもらいたかったので、array_diff_assoc
で差分をとっています。
おわり
もちろん、sumでももっと複雑な集計でもできると思いますし、リレーションしているモデルがあればそれも今までどおり使えるはずです。(未検証)
要はみんなもっとORM使おうよ!っていうまとめ