通常はデータベース上でSQLを使ってgroup byをすれば十分ですが、下記のような場合プログラム側で実施するのもありです。
- DBから取得したデータから別の複数の集計(group by)結果を得ることができる
- DBのgroup byの実行に時間がかかり、何度も似たような集計をDBに計算させるのは実行コスト(時間、CPU負荷)が高い
今回は汎用的にPHP側でgroup byを実行できるコードを書いてみました。
任意の複数フィールドで集計できるようにするため、多少コードが複雑になっています。
またkeyのencode/decode部分の処理で多少無駄があります。
<?php
function groupBy(array $rows, array $groupByFields, callable $aggregate)
{
$groupByFieldsAsKey = array_flip($groupByFields);
$map = [];
foreach($rows as $row)
{
// キーになるならどんな関数でもOK。ここではjson_encodeを採用
$key = json_encode(array_intersect_key($row, $groupByFieldsAsKey));
$aggregatedRow = &$map[$key] ?? [];
$aggregate($aggregatedRow, $row);
}
$result = [];
foreach ($map as $key => &$aggregatedRow)
{
// キーをデシリアライズして、フィールドと値を集計結果の行にコピー
$keyValue = json_decode($key, true);
foreach($groupByFields as $groupByField)
{
$aggregatedRow[$groupByField] = $keyValue[$groupByField];
}
$result[] = $aggregatedRow;
}
return $result;
}
下記は使用例です。$rowsを'country'、'campany'の2つのフィールドでgroup byしています。
$rows = [
['id' => 1, 'campany' => 'Amazon', 'country' => 'USA', 'name' => 'Jeff Bezos' , 'net_worth' => 113],
['id' => 2, 'campany' => 'Microsoft', 'country' => 'USA', 'name' => 'Bill Gates' , 'net_worth' => 98],
['id' => 3, 'campany' => 'LVMH', 'country' => 'France', 'name' => 'Bernard Arnault' , 'net_worth' => 76],
['id' => 4, 'campany' => 'Berkshire Hathaway', 'country' => 'USA', 'name' => 'Warren Buffett' , 'net_worth' => 68],
['id' => 5, 'campany' => 'Oracle Corporation', 'country' => 'USA', 'name' => 'Larry Ellison' , 'net_worth' => 59],
['id' => 6, 'campany' => 'Inditex, Zara', 'country' => 'Spain', 'name' => 'Amancio Ortega' , 'net_worth' => 55],
['id' => 7, 'campany' => 'Facebook', 'country' => 'USA', 'name' => 'Mark Zuckerberg' , 'net_worth' => 54],
['id' => 8, 'campany' => 'Walmart', 'country' => 'USA', 'name' => 'Jim Walton' , 'net_worth' => 54],
['id' => 9, 'campany' => 'Walmart', 'country' => 'USA', 'name' => 'Jeff Bezos' , 'net_worth' => 54],
['id' => 10, 'campany' => 'Walmart', 'country' => 'USA', 'name' => 'S. Robson Walton' , 'net_worth' => 54],
];
$groupByFields = ['country', 'campany'];
$calculateTotalNetWorth = function(&$aggregatedRow, $row) {
$aggregatedRow['total_net_worth'] = ($aggregatedRow['total_net_worth'] ?? 0) + $row['net_worth'];
};
$result = groupBy($rows, $groupByFields, $calculateTotalNetWorth);
echo json_encode($result, JSON_PRETTY_PRINT);
/* 出力は下記のようになります。
[
{
"total_net_worth": 113,
"country": "USA",
"campany": "Amazon"
},
{
"total_net_worth": 98,
"country": "USA",
"campany": "Microsoft"
},
{
"total_net_worth": 76,
"country": "France",
"campany": "LVMH"
},
{
"total_net_worth": 68,
"country": "USA",
"campany": "Berkshire Hathaway"
},
{
"total_net_worth": 59,
"country": "USA",
"campany": "Oracle Corporation"
},
{
"total_net_worth": 55,
"country": "Spain",
"campany": "Inditex, Zara"
},
{
"total_net_worth": 54,
"country": "USA",
"campany": "Facebook"
},
{
"total_net_worth": 162,
"country": "USA",
"campany": "Walmart"
}
]
*/
コメント