はじめに
例えば、以下のようなSQLでDBからレコードを取得してきた場合
SELECT
a,
SUM(CASE WHEN case = 'b' THEN 1 ELSE 0 END) AS b,
SUM(CASE WHEN case = 'c' THEN 1 ELSE 0 END) AS c
FROM table
GROUP BY a
;
戻り値はこんな感じで取得できたりする。
src = [{ a: 1, b: 2, c: 3 }, { a: 10, b: 20, c: 30 }]
group by a ごとの集計(SQL結果そのまま)と
group by しない合計を出したい場合を考えた時に、
単純にSQLを2発投げることもできるが、JS側で足し算すればいいじゃん?とも思った。
(ただし、足し算するレコードが多い場合は、SQLでやった方が早い気もする。
今回は100〜200レコードしか取得されない前提。)
JavaScriptのArrayには強力なメソッドがいくつもあり、
その中の一つ、reduceがかなりいい仕事をしてくれるのだが
[1,2,3].reduce((p,c)=>{
return p + c
})
// 6
Object同士の足し算だとポンコツな結果が返ってくる(いや、ポンコツなのは仕様を理解できていない自分なのだと思うが)
[{a:1},{a:2},{a:3}].reduce((p,c)=>{
return p + c
})
// {a:6} を期待するが、実際はこれ
// [object Object][object Object][object Object]
どうやら、Objectの場合は、ちょっと工夫が必要らしい
[{a:1},{a:2},{a:3}].reduce((p,c)=>{
return {a:p.a + c.a}
})
// {a:6}
結論
※追記 コメントでこのような書き方もあると教えていただきました!
だいぶスマートですね!src = [{ a: 1, b: 2, c: 3 }, { a: 10, b: 20, c: 30 }] keys = ["a","b","c"] keys.map(k=>( {[k]: src.map(e=>e[k]).reduce((p,c)=>(p+c))} )).reduce((p,c)=>({...p,...c}))
で、完成したのがこれ。
/**
* Array[Object]型の全要素を足し上げし、
* Object型で返す。
* @param {Array<Object>} src
* @return Object
*/
reduceObjectArray(src) { // src = [{ a: 1, b: 2, c: 3 }, { a: 10, b: 20, c: 30 }]
let result = {}
Object.keys(src[0]).forEach((name) => {
result[name] = src.reduce((p, c) => {
return p + c[name]
}, 0)
})
return result // result = { a: 11, b: 22, c: 33 }
};
関数名があれだが
解説
Object.keys(src[0]).forEach((name) => {
で、要素名(今回はaとbとc)分繰り返し、
result[name] = src.reduce((p, c) => {
return p + c[name]
}, 0)
で、resultという連想配列(let result = {}で定義)に
1要素ずつreduceした結果を入れていく。