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

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

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

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

PDO

PDO(PHP Data Objects)はPHPのデータベース抽象化レイヤーです。

PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

Q&A

解決済

3回答

1695閲覧

MySQLで、動的にレコードを参照できますか?

TsunakiM

総合スコア13

MySQL

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

PDO

PDO(PHP Data Objects)はPHPのデータベース抽象化レイヤーです。

PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

0グッド

1クリップ

投稿2018/12/11 17:28

編集2018/12/11 17:49

実現したいこと

MySQLで、動的にDB内を参照するようなDB構成、あるいはSQL文は作成できますか?

例えば、以下のようなDB構成にしたいです。

ID製品名原料1原料2
1製品A原料x原料y
2製品B製品A原料z
3製品C製品B原料n

このとき、製品Bを取り出したときに、原料に含まれる製品Aのレコードを参照し、出力結果として「原料x,原料y,原料z」という結果が得たいです。
理由として、製品Aの原料が変わる可能性があるため、原料をベタ書きすることを避けたいです。
また、多重で参照する場合もあり得、製品Cを取り出したときには、結果として「原料x,原料y,原料z,原料n」が得たいです。
出力結果は、多次元配列でも問題ありません。製品Cを取り出したときには、結果として[[[原料x,原料y],原料z],原料n]でもOKです。

最終的にPHP(PDO)で扱いたいと考えています。

そもそもこういったことが実現可能なのかどうかが、調べた限りだと皆目見当がつかなかったため、質問させていただいた次第です。
どうぞよろしくお願いいたします。

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

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

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

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

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

guest

回答3

0

ベストアンサー

テーブルを分割して「製品・原料マスタ」と、「製品構成マスタ」にします。

ID製品・原料名
1製品A
2製品B
3製品C
4原料x
5原料y
6原料z
7原料n
製品ID連番原料ID
114
125
211
226
312
327
41null
51null
61null
71null

※原料IDがnullはすなわち、この製品自体が原料であることを示します

こうすれば、ある製品が3以上の素材から作られる場合でも対応可能ですし。

ただ、「ある製品を構成するすべての原料」を列挙する際には、階層問い合わせを行わねばなりませんが、MySQL は 8.0 以前では対応していません。
その場合はプログラムで親から順にたどっていくなどする必要があります。

投稿2018/12/11 23:59

tacsheaven

総合スコア13703

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

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

TsunakiM

2018/12/12 06:21

ご回答ありがとうございます。 ご提示の方法にヒントを得、原料に別の製品を使っているかどうかのカラムを作成します。 それを元に、PHP側で再検索をかけるか否かを処理すればいけそうです。 もしご存知でしたら、負荷量の相談なのですが、上記の方法を取るとして  ・レコードの総数は約500  ・一度の操作において、再問い合わせでDBを参照する回数は最大で60回程度  ・同時に操作する人数は10人程度。どんなに多くても100人は超えない 以上の条件では、常識的な処理量でしょうか? (一般的にみて、明らかに過剰な処理量でなければ問題ないと考えています)
tacsheaven

2018/12/12 07:08

全然常識的な処理量ですが、レコード数が500程度なら、全件取ってきて配列に格納しておいて、PHP で再帰的に処理してもよいような気がします。
guest

0

目的としては階層構造なので、ベタ打ちしたくないのであれば、経路列挙モデルではなく入れ子集合モデルということになります。

MySQLが8.0なら再帰クエリーが使えますが、そうでないならSQLには手間や制約(階層の深さ)が掛かりますが実現できないわけではありません。

phpで再帰を行うのはレスポンスやメモリなどの問題が生じるでしょうから、DB側での処理とした方が良いでしょう。

SQLで木と階層構造のデータを扱う(1)―― 入れ子集合モデル
階層構造(入れ子集合モデル)について

<例>

製品

ID名称
1製品A
2製品B
3製品C

原料

ID名称
1原料n
2原料z

構成
|ID |親ID|構成ID|製品ID|原料ID
|:--|:--:|:--:|--:|
|1 |0| ||1
|2 |0| ||2
|3 |0| |1|
|4 |3|1|||
|5 |3|2|||
|6 |0| |2|
|7 |6|3|||
|8 |6|2|||
|9 |0| |3|
|10 |9|6|||
|11 |9|1|||

下位ノードへの問い合わせは自己結合となり、COALESCE(A.構成ID, A.ID)=B.親IDとすることで、製品の展開が行える事となる。
※上記は構成を示すことを主としいて、実際には、原料や商品といったマスタを別に持ち、外部参照する方が良いと思います。
↑イメージ合わせました

投稿2018/12/12 00:45

編集2018/12/12 08:04
sazi

総合スコア25173

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

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

yambejp

2018/12/12 01:51

部品表の場合、データ構造として子に対して親が一つではないので入れ子にできなくないでしょうか? (入れ子にすると同じ部品が違うものとして管理される?)
sazi

2018/12/12 02:25

入れ子モデルでは製品や原料は重複する事になるので、同じキーだけでは管理できません。 テーブルレイアウトを考えてみました。
TsunakiM

2018/12/12 06:29

ご回答ありがとうございます。 今回はMySQL5.7を利用するため、ご提示の方法はとれなさそうです。 ただ、再帰クエリという概念を知らなかったため、大変参考になりました。
sazi

2018/12/12 06:49 編集

再帰クエリは手段です。階層問い合わせは再帰クエリの別解です。 案の構造でも階層問い合わせで可能ですし、tacsheavenさんの提案されている構造も入れ子ですから、結局のところ基本は同じです。 私の案は再帰での問い合わせを意識したものなので連番等を生成しなくて済む分若干シンプルだと思いますが。
guest

0

SQL

1create table tbl(pid varchar(10),cid varchar(10)); 2insert into tbl values('A','X'),('A','Y'),('B','A'),('B','Z'),('C','B'),('C','N');

という状態だとして、仮にDが

SQL

1insert into tbl values('D','A'),('D','B'),('D','X'),('D','O');

だとする場合は存在するのでしょうか?
※AはBを部品とするが、DはAとBを部品とする

また、Dが仮に少量の自分自身を材料に使うなど循環する可能性はあるのでしょうか?

実際のところ、適当な階層を想定してleft joinするだけで処理はできますが
上記のような特殊な状況によってはデータのダブリがでてきたりループが発生したりします

  • 仮SQL

SQL

1select * from tbl t1 2left join tbl t2 on t1.cid=t2.pid 3left join tbl t3 on t2.cid=t3.pid 4left join tbl t4 on t3.cid=t4.pid 5left join tbl t5 on t4.cid=t5.pid 6left join tbl t6 on t5.cid=t6.pid 7left join tbl t7 on t6.cid=t7.pid 8left join tbl t8 on t7.cid=t8.pid 9left join tbl t9 on t8.cid=t9.pid

これを元に

SQL

1select t1.pid 2,concat_ws(',', 3group_concat(distinct t1.cid) 4,group_concat(distinct t2.cid) 5,group_concat(distinct t3.cid) 6,group_concat(distinct t4.cid) 7,group_concat(distinct t5.cid) 8,group_concat(distinct t6.cid) 9,group_concat(distinct t7.cid) 10,group_concat(distinct t8.cid) 11,group_concat(distinct t9.cid) 12) as cidx 13from tbl t1 14left join tbl t2 on t1.cid=t2.pid 15left join tbl t3 on t2.cid=t3.pid 16left join tbl t4 on t3.cid=t4.pid 17left join tbl t5 on t4.cid=t5.pid 18left join tbl t6 on t5.cid=t6.pid 19left join tbl t7 on t6.cid=t7.pid 20left join tbl t8 on t7.cid=t8.pid 21left join tbl t9 on t8.cid=t9.pid 22group by pid

上記もDのように使用部品の競合があるとユニークに拾えないので別処理が必要になります

procedureで処理

久しぶりに真面目に書いてみました

  • 元データ

SQL

1create table tbl(pname varchar(10),cname varchar(10)); 2insert into tbl values('A','X'),('A','Y'),('B','A'),('B','Z'),('C','B'),('C','N');
  • 追加データ

SQL

1insert into tbl values('D','A'),('D','B'),('D','X'),('D','O');
  • tbl
pnamecname
AX
AY
BA
BZ
CB
CN
DA
DB
DX
DO
  • ツリーテーブル作成

SQL

1create table tree (id int NOT NULL primary key auto_increment,pid INT null,cname varchar(64),level int NOT NULL default 0,l int not null default 0,r int not null default 0);
  • procedure作成

SQL

1drop procedure if exists set_tree_table; 2delimiter // 3create procedure set_tree_table() 4begin 5declare count int default 0; 6declare count_stop int default 5; 7declare lastid int default 0; 8declare done int default 0; 9declare a int default 0; 10declare cur cursor for 11select id from tree where level=0 order by pid asc,id desc; 12truncate tree; 13insert into tree(cname,level) 14select distinct pname,1 from tbl; 15insert into tree(pid,cname,level) 16select t2.id,t1.cname,2 from tbl as t1 17inner join tree as t2 on t1.pname=t2.cname; 18while count<count_stop and done=0 do 19insert into tree(pid,cname,level) 20select distinct t1.id,t3.cname,(count+3) from tree as t1 21inner join tree as t2 on t1.cname=t2.cname and t1.level=(count+2) and t2.level=(count+1) 22inner join tree as t3 on t2.id=t3.pid; 23select @lastid:=LAST_INSERT_ID(); 24set count=count+1; 25if lastid=@lastid then 26set done=1; 27else 28set lastid=@lastid; 29end if; 30end while; 31end 32// 33delimiter ;
  • procedure実行

tblのデータを追加、削除、更新したら都度procudureを実行します

SQL

1call set_tree_table;
  • treeテーブル
idpidcnamelevellr
1NULLA116
2NULLB1716
3NULLC11730
4NULLD13152
81X223
91Y245
102A2813
112Z21415
123B21827
133N22829
144A23237
154B23847
164X24849
174O25051
2310X3910
2414X33334
2510Y31112
2614Y33536
2712A31924
2815A33944
2912Z32526
3015Z34546
3827X42021
3928X44041
4027Y42223
4128Y44243

具体的なデータ

  • 全構成要素を表示

SQL

1select t1.cname,group_concat(distinct t2.cname) as all_child from tree as t1 2inner join tree as t2 on t2.l > t1.l and t2.l < t1.r 3where t1.pid is null 4group by cname;
  • 結果
cnameall_child
AY,X
BX,Y,Z,A
CB,A,Z,X,Y,N
DY,A,X,B,O,Z
  • 階層状況を視覚化

SQL

1select concat(repeat('__', level -1 ) , cname ) AS cname 2FROM tree 3order by l;
  • 結果
A __X __Y B __A ____X ____Y __Z C __B ____A ______X ______Y ____Z __N D __A ____X ____Y __B ____A ______X ______Y ____Z __X __O

投稿2018/12/12 01:44

編集2018/12/12 09:32
yambejp

総合スコア114779

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

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

TsunakiM

2018/12/12 06:35

ご回答ありがとうございます。 現状では、製品X=製品Y+製品Zのような状況はありえます。 一方、製品Xの原料として、製品X自身を使うことはまずありえないのですが、100%絶対にないとは言い切れない状況です。 そのため、ご提示の方法ですと危険があるため、今回はとれなさそうです。 ただ、JOINの使い方として大変勉強になるものでした。機会があればこの形式のSQL文を使ってみようと思います。
yambejp

2018/12/12 09:33

久しぶりにやる気をだしてみました 考え方はsaziさんの提案のとおりです
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問