仮にPHPでしたら、イメージですが以下のようになると思います。
それは優秀なORマッパー使ってるだけです
PHPでもPDO等を使ってSQL直打ちで実行するとそうなります。
でもPHPのWebフレームワークだと何かしらのORマッパーが用意されているイメージありますね。
Node.jsに移行した時、Express.jsやmysqlのしょぼさシンプルさにびっくりした記憶があります。
forで回して整えることはできると思いますが、SELECTの度にそんなこともしてられませんし・・
アイデアとしては2つです。
- ORマッパーを導入して使ってみる
- group-byを覚える
前者のORマッパーは調べたら出てきました。
参考記事: Node.jsにてMySQLのORM:sequelize - Qiita
また、SailsのようにORマッパーを内蔵したWebフレームワークも存在します。
これはRuby on Railsを意識して作ったフレームワークですので、そこそこ気に入るような機能があるかもしれません。
参考記事: Waterline: SQL/noSQL Data Mapper (ORM/ODM) - Sails公式サイト
私個人ではORマッパーを利用していませんので、
どの程度使えるか、外部結合等の機能は使えるのか?などは知りませんが、
その辺含めて調査してみてください。
さて、後者の「group-byを覚える」の解説して締めようと思います。
確かにわざわざfor文なんて使ってられません。
何故か?for文で展開するだけで3行消費して可読性に負荷が掛かるからです。
でも同じことが1行で書けるようになればどうでしょう?
JavaScriptでは関数型プログラミングのエッセンスを流用した
メソッドチェーンを利用した値の加工テクニックが存在してて、コードの行数削減に役立ちます。
しかしJavaScriptのネイティブには配列を加工するメソッドが少ないので、
慣れるまで難解なreduce等を使う事になります。
(この辺RubyやPythonは充実してるんですけどね)
そこでライブラリの力を利用してgroup-byを連れてきます。
関数型プログラミング言語とか、ガチOOP言語だと結構用意してくれてる印象ありますね。
PHPの配列関数を眺めた感じそういうのなかったです。
PHPのreduceは筋が悪いのでforeachになりそうですね、たしかにやってられん。
JavaScript/Node.jsではLodashのライブラリがメジャーですかね。
参考サイト: groupBy - Lodash
Ramda.jsやJustがライバルになります。
Online Lodash Testerというサイトで確認してみましょう。
JavaScriptやJSONに於いてオブジェクトのキー名重複は許されないので、
sub.name
等のようにしておきます、実際のSQL文でasによる別名指定等をしながら調整してみてください。
js
1const arr = [
2 {"id":1,"name":"親A","sub.main_id":1,"sub.name":"子1"},
3 {"id":1,"name":"親A","sub.main_id":1,"sub.name":"子2"},
4 {"id":2,"name":"親B","sub.main_id":2,"sub.name":"子3"}
5];
6result = _.groupBy(arr, it => it.id);
it => it.id
という関数を使って配列の各要素からIDの数値を引っ張り出して、
そのIDが一致するもの同士で配列に投げ込んでいくという挙動をさせます。
結果を見てみます。
json
1{
2 "1": [
3 {
4 "id": 1,
5 "name": "親A",
6 "sub.main_id": 1,
7 "sub.name": "子1"
8 },
9 {
10 "id": 1,
11 "name": "親A",
12 "sub.main_id": 1,
13 "sub.name": "子2"
14 }
15 ],
16 "2": [
17 {
18 "id": 2,
19 "name": "親B",
20 "sub.main_id": 2,
21 "sub.name": "子3"
22 }
23 ]
24}
良好。
このままではまだ実用的な値になったとは言えないのでさらなる加工を施します。
オブジェクトの値だけ抽出(values)して二次元配列にしてから、
二次元配列にmapを使って整形します。
この時、Lodashのチェイン記法を使うときれいに記述出来ます。
参考記事: Lodashのチェーン記法について - Qiita
js
1const arr = [
2 {"id":1,"name":"親A","sub.main_id":1,"sub.name":"子1"},
3 {"id":1,"name":"親A","sub.main_id":1,"sub.name":"子2"},
4 {"id":2,"name":"親B","sub.main_id":2,"sub.name":"子3"}
5];
6result = _(arr)
7 .groupBy(it => it.id)
8 .values()
9 .map(arr => ({
10 id: arr[0].id,
11 name: arr[0].name,
12 sub: arr.map(it => ({
13 main_id: it["sub.main_id"],
14 name: it["sub.name"]
15 }))
16 }));
json
1[
2 {
3 "id": 1,
4 "name": "親A",
5 "sub": [
6 {
7 "main_id": 1,
8 "name": "子1"
9 },
10 {
11 "main_id": 1,
12 "name": "子2"
13 }
14 ]
15 },
16 {
17 "id": 2,
18 "name": "親B",
19 "sub": [
20 {
21 "main_id": 2,
22 "name": "子3"
23 }
24 ]
25 }
26]
だいたい要望どおりに仕上がったかと思います。
CRUDのシステムなら良かれと思った結合しまくると
全く関係のないデータにアクセスする余計なお世話になるケースもありますから
あまりに沢山の行を様々な観点で組み合わせるという事はそんなにないんじゃないかと思います。
実務ではこの程度がさっと書ければ十分ですかね。
【おまけ】LodashではなくネイティブのJSで頑張るとこうなります。
前述の章ではLodashで実装されているgourp-by機能を使いましたが、
JavaScriptにgroup-byはないのでreduceで頑張るしかありません。
また、汎用オブジェクトは好き勝手なキーを宣言できますので、
値を取り出すにはObject.values()
を使う必要があります。
メソッドチェーンが途切れて少し不格好になりますね。
js
1const arr = [
2 {"id":1,"name":"親A","sub.main_id":1,"sub.name":"子1"},
3 {"id":1,"name":"親A","sub.main_id":1,"sub.name":"子2"},
4 {"id":2,"name":"親B","sub.main_id":2,"sub.name":"子3"}
5];
6const result = Object.values(
7 arr.reduce((obj, it) => ({...obj, [it.id]: obj[it.id] ? [...obj[it.id], it] : [it]}), {})
8).map(arr => ({
9 id: arr[0].id,
10 name: arr[0].name,
11 sub: arr.map(it => ({
12 main_id: it["sub.main_id"],
13 name: it["sub.name"]
14 }))
15}));
16console.log(JSON.stringify(result, null, 2));
json
1[
2 {
3 "id": 1,
4 "name": "親A",
5 "sub": [
6 {
7 "main_id": 1,
8 "name": "子1"
9 },
10 {
11 "main_id": 1,
12 "name": "子2"
13 }
14 ]
15 },
16 {
17 "id": 2,
18 "name": "親B",
19 "sub": [
20 {
21 "main_id": 2,
22 "name": "子3"
23 }
24 ]
25 }
26]
group-byを再現したreduce関数の中身は
ECMAScript2015以降のイディオムをガシガシ使ってる難解なコードで読めたものではありませんね。
またワンライナーにするため
毎回配列やオブジェクトを生成しまくる富豪的なプログラミングになっているので
同じ配列・オブジェクトを使いまわして処理速度向上させます。
js
1const groupBy = cb => (obj, it) => {
2 const key = cb(it);
3 if (obj[key]) {
4 obj[key].push(it);
5 } else {
6 obj[key] = [it];
7 }
8 return obj;
9};
10
11const arr = [
12 {"id":1,"name":"親A","sub.main_id":1,"sub.name":"子1"},
13 {"id":1,"name":"親A","sub.main_id":1,"sub.name":"子2"},
14 {"id":2,"name":"親B","sub.main_id":2,"sub.name":"子3"}
15];
16const result = Object.values(
17 arr.reduce(groupBy(it => it.id), {})
18).map(arr => ({
19 id: arr[0].id,
20 name: arr[0].name,
21 sub: arr.map(it => ({
22 main_id: it["sub.main_id"],
23 name: it["sub.name"]
24 }))
25}));
26console.log(JSON.stringify(result, null, 2));
実行結果を確認しましたが全く同様でした。
可読性を考えるとこの辺がバランス良いんじゃないかと思います。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2022/04/20 00:41
2022/04/20 15:12