質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
MySQL

MySQL(マイエスキューエル)は、TCX DataKonsultAB社などが開発するRDBMS(リレーショナルデータベースの管理システム)です。世界で最も人気の高いシステムで、オープンソースで開発されています。MySQLデータベースサーバは、高速性と信頼性があり、Linux、UNIX、Windowsなどの複数のプラットフォームで動作することができます。

Node.js

Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

React.js

Reactは、アプリケーションのインターフェースを構築するためのオープンソースJavaScriptライブラリです。

Q&A

解決済

1回答

1353閲覧

Node.jsでMySQLからSELECTした際のフォーマットを変えたい

mecab

総合スコア41

MySQL

MySQL(マイエスキューエル)は、TCX DataKonsultAB社などが開発するRDBMS(リレーショナルデータベースの管理システム)です。世界で最も人気の高いシステムで、オープンソースで開発されています。MySQLデータベースサーバは、高速性と信頼性があり、Linux、UNIX、Windowsなどの複数のプラットフォームで動作することができます。

Node.js

Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

React.js

Reactは、アプリケーションのインターフェースを構築するためのオープンソースJavaScriptライブラリです。

0グッド

0クリップ

投稿2022/04/19 15:58

Node.js、React初心者です。Node.jsでAPIを作成し、画面側のReactからAPIを叩くような環境を作りました。
Node.jsでMySQLからSELECTした際に、親テーブルと子テーブルを結合しているのですが、子の情報のところにも親テーブルの情報が格納されていて、フロント側でループなどで処理する際に扱いづらく困っています。
なにかオプションを設定すれば良い感じに整形してくれるのでしょうか?
forで回して整えることはできると思いますが、SELECTの度にそんなこともしてられませんし・・
一般的にはどのようにしているのかなども、教えていただけますと幸いです。
すみませんがよろしくお願いいたします。

以下、例です。

main

idname
1親A
2親B

sub

idmain_idname
11子1
21子2
32子3

javascript

1const sql = "SELECT * FROM main LEFT JOIN sub ON main.id = sub.main_id"; 2 db.query(sql, (err, result) => { 3 console.log(result); 4 });

以下のように、親Aが結合した子の数分、繰り返し格納されてしまう

json

1[ 2 {"id":1,"name":"親A","main_id":1,"name":"子1"}, 3 {"id":1,"name":"親A","main_id":1,"name":"子2"}, 4 {"id":2,"name":"親B","main_id":2,"name":"子3"} 5]

仮にPHPでしたら、イメージですが以下のようになると思います。
厳密にこのフォーマットでなくても良いですが、このような形で取得できれば処理しやすいです。

php

1 [0]=> 2 array(3) { 3 ["id"]=> 4 int(1) 5 ["name"]=> 6 string(4) "親A" 7 ["sub"]=> 8 array(2) { 9 [0]=> 10 array(2) { 11 ["main_id"]=> 12 int(1) 13 ["name"]=> 14 string(4) "子1" 15 } 16 [1]=> 17 array(2) { 18 ["main_id"]=> 19 int(1) 20 ["name"]=> 21 string(4) "子2" 22 } 23 } 24 } 25 [1]=> 26 array(3) { 27 ["id"]=> 28 int(1) 29 ["name"]=> 30 string(4) "親B" 31 ["sub"]=> 32 array(2) { 33 ["main_id"]=> 34 int(2) 35 ["name"]=> 36 string(4) "子3" 37 } 38 } 39}

上記の連想配列をjson化

json

1[ 2 {"id":1,"name":"親A", 3 "sub":[ 4 {"main_id":1,"name":"子1"}, 5 {"main_id":1,"name":"子2"}] 6 },{"id":1,"name":"親B","sub":{"main_id":2,"name":"子3"}} 7]

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

仮に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.jsJustがライバルになります。

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:20

編集2022/04/20 02:01
miyabi-sun

総合スコア21158

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

miyabi-sun

2022/04/20 00:41

長々と書きましたが、 PHPが得意でORマッパーでそういうのがあるなら、 Nginxのリバースプロキシを使ってフロントはNode.js+Reactでやるけど APIに関してはPHPにお願いしてJSONを返すサーバにする。 これが一番一般的です。 Reactの根っこにあるJavaScriptがAjax叩いてJSON持って帰れるならなんでも良いって言ってるので 無理にNode.jsのバックエンドで頑張る必要はありません。 人や言語、ライブラリに限らず得意な奴にやらせるのが最善です、餅は餅屋ってね。
mecab

2022/04/20 15:12

非常に参考になるご回答をありがとうございます・・! ORマッパーがないからだったのですね。なぜこんな結果が返ってくるのか、 他の人はこれを処理しているのか?と思っていたので、スッキリしました。 サーバー側が一般的になにを使うのかもわかっていなかったので、Node.jsではなく PHPを準備しようと思います。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問