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

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

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

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

SQL

SQL(Structured Query Language)は、リレーショナルデータベース管理システム (RDBMS)のデータベース言語です。大きく分けて、データ定義言語(DDL)、データ操作言語(DML)、データ制御言語(DCL)の3つで構成されており、プログラム上でSQL文を生成して、RDBMSに命令を出し、RDBに必要なデータを格納できます。また、格納したデータを引き出すことも可能です。

データベース

データベースとは、データの集合体を指します。また、そのデータの集合体の共用を可能にするシステムの意味を含めます

データベース設計

データベース設計はデータベースの論理的や物理的な部分を特定する工程です。

Q&A

解決済

2回答

8315閲覧

複数テーブルを外部キーで結合する方法

zico_teratail

総合スコア907

MySQL

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

SQL

SQL(Structured Query Language)は、リレーショナルデータベース管理システム (RDBMS)のデータベース言語です。大きく分けて、データ定義言語(DDL)、データ操作言語(DML)、データ制御言語(DCL)の3つで構成されており、プログラム上でSQL文を生成して、RDBMSに命令を出し、RDBに必要なデータを格納できます。また、格納したデータを引き出すことも可能です。

データベース

データベースとは、データの集合体を指します。また、そのデータの集合体の共用を可能にするシステムの意味を含めます

データベース設計

データベース設計はデータベースの論理的や物理的な部分を特定する工程です。

0グッド

0クリップ

投稿2016/12/30 13:02

編集2016/12/30 13:10

初歩的な質問ですみません。
MySQLで3つ以上のテーブルを外部キーでどんどん結合していく方法について教えてください。

二つまでならJOINすることが出来たのですが、二つめのテーブル内にあるキーを元にさらに連結させて取り出すことが出来ません。

具体的には下記のようなテーブルと得たい結果があるとします。

◆member ------------ id | name | ability ------------ 1 | tanaka | 4 2 | suzuki | 3 3 | satou | 2 4 | honda | 4 ------------ ◆ability ------------ id | spec1 | spec2 | ...(略) | spec10 ------------ 1 | 4 | 1 | ...(略) | 50 2 | 2 | 8 | ...(略) | 25 3 | 18 | 3 | ...(略) | 10 4 | 1 | 7 | ...(略) | 9 ------------ ◆spec ------------ id | spec ------------ 1 | PHP 2 | MySQL 3 | Perl 4 | Ruby (略) 50 | C++ ------------ 得たい結果は、 ------------ id | spec1 | spec2 | ...(略) | spec10 ------------ tanaka PHP Ruby (略) C++ ・・・という感じです。

このテーブルに対して以下のようなクエリでいけるかと思ったけどダメでした。

sql

1SELECT * 2FROM `member` AS a 3LEFT JOIN `ability` AS b ON b.`id` = a.`ability_id` 4LEFT JOIN `spec` AS c ON c.`id` = b.`spec1` AND c.`id` = b.`spec2` (中略) AND c.`id` = b.`spec10` 5WHERE `id` = 1 6

どうダメだったのかというと、以下のようにspec部分がNULLになってしまうのです。

------------ id | spec1 | spec2 | ...(略) | spec10 | spec ------------ tanaka 4 1 (略)50 NULL

このような場合にどんなクエリを書いたらいいのしょうか。

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

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

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

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

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

guest

回答2

0

こんな感じでは、(略は同じにしていますが。。。)

sql

1select m.id, t1.* from member m join 2( select a.id, 3max(case when spec1=s.id then spec else null end) spec1, 4max(case when spec2=s.id then spec else null end) spec2, 5...(略) 6max(case when spec10=s.id then spec else null end) spec10 7 from ability a cross join specs s where s.id in (spec1,spec2,...(略),spec10) 8 group by 1) t1 9on ability_id=t1.id 10where m.id=1; 11 12+------+-------+-------+--------+ 13| id | spec1 | spec2 | spec10 | 14+------+-------+-------+--------+ 15| 1 | ruby | php | c++ | 16+------+-------+-------+--------+ 171 row in set (0.01 sec) 18

RDBは、配列形式の項目が得意ではないので、テーブルを変更されるのは良いと思います。
上記はちょっと面倒なのでもう少し簡単なのを(大量件数だとちょっと遅いですが)

sql

1select m.id, 2(select spec from specs where specs.id=spec1) spec1, 3(select spec from specs where specs.id=spec2) spec2, 4...(略) 5(select spec from specs where specs.id=spec10) spec10 6 from member m join ability a on ability_id=a.id where m.id=1;

投稿2016/12/30 14:15

編集2016/12/30 15:15
A.Ichi

総合スコア4070

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

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

zico_teratail

2016/12/30 14:19

ありがとうございます。 自分では思いつかないクエリで、とても参考になります。 しかしこの構成だとけっこう複雑なクエリになってしまいますね・・・。 やはりテーブル定義から見直すしかないかもしれません。
guest

0

ベストアンサー

ability テーブルは

id, member_id, spec_id で構成する方が王道ですね。


#テーブル定義

sql

1CREATE TABLE `abilities` ( 2 `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 3 `member_id` int(11) DEFAULT NULL, 4 `spec_id` int(11) DEFAULT NULL, 5 PRIMARY KEY (`id`) 6) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 7 8INSERT INTO `abilities` (`id`, `member_id`, `spec_id`) 9VALUES 10 (1,1,1), 11 (2,1,2), 12 (3,2,1), 13 (4,2,3), 14 (5,2,4); 15 16CREATE TABLE `member` ( 17 `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 18 `name` varchar(641) NOT NULL DEFAULT '', 19 PRIMARY KEY (`id`) 20) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 21 22INSERT INTO `member` (`id`, `name`) 23VALUES 24 (1,'tanaka'), 25 (2,'suzuki'); 26 27CREATE TABLE `spec` ( 28 `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 29 `spec` varchar(64) NOT NULL DEFAULT '', 30 PRIMARY KEY (`id`) 31) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 32 33INSERT INTO `spec` (`id`, `spec`) 34VALUES 35 (1,'MySQL'), 36 (2,'PHP'), 37 (3,'LINUX'), 38 (4,'C#');

#SELECT

sql

1SELECT 2 m.id, m.name, group_concat(s.spec) AS specs 3FROM member m 4LEFT JOIN abilities a ON m.id=a.member_id 5LEFT JOIN spec s ON a.spec_id = s.id 6GROUP BY m.id

#テーブル定義(改)

優先順位の概念を追加
本来、正規化して以下のようにすべきかと思います。

sql

1CREATE TABLE `abilities` ( 2 `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 3 `member_id` int(11) DEFAULT NULL, 4 `spec_id` int(11) DEFAULT NULL, 5 `priority` int(11) DEFAULT NULL, 6 PRIMARY KEY (`id`) 7) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4; 8 9SELECT 10 m.id 11 , m.name 12 , group_concat(s.spec ORDER BY a.priority) AS specs 13FROM member m 14LEFT JOIN abilities a ON m.id = a.member_id 15LEFT JOIN spec s ON a.spec_id = s.id 16GROUP BY m.id

#事情を考慮した取得例

sql

1SELECT 2 a.name 3 , sp1.spec as spec1 4 , sp2.spec as spec2 5 , sp3.spec as spec3 6 , sp4.spec as spec4 7 , sp5.spec as spec5 8 , sp6.spec as spec6 9 , sp7.spec as spec7 10 , sp8.spec as spec8 11 , sp9.spec as spec9 12 , sp10.spec as spec10 13FROM `member` AS a 14LEFT JOIN `ability` AS b ON b.`id` = a.`ability_id` 15LEFT JOIN `spec` AS sp1 ON sp1.`id` = b.`spec1` 16LEFT JOIN `spec` AS sp2 ON sp2.`id` = b.`spec2` 17LEFT JOIN `spec` AS sp3 ON sp3.`id` = b.`spec3` 18LEFT JOIN `spec` AS sp4 ON sp4.`id` = b.`spec4` 19LEFT JOIN `spec` AS sp5 ON sp5.`id` = b.`spec5` 20LEFT JOIN `spec` AS sp6 ON sp6.`id` = b.`spec6` 21LEFT JOIN `spec` AS sp7 ON sp7.`id` = b.`spec7` 22LEFT JOIN `spec` AS sp8 ON sp8.`id` = b.`spec8` 23LEFT JOIN `spec` AS sp9 ON sp9.`id` = b.`spec9` 24LEFT JOIN `spec` AS sp10 ON sp10.`id` = b.`spec10` 25WHERE `id` = 1 26group by a.id

投稿2016/12/30 13:11

編集2016/12/30 14:23
退会済みユーザー

退会済みユーザー

総合スコア0

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

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

zico_teratail

2016/12/30 13:21

ありがとうございます。 ability テーブルを参照するのが member テーブルだけであればそれで良いのですが、事情により他のテーブルからもability テーブルを参照することがあり、このようなテーブル構成になっております。
退会済みユーザー

退会済みユーザー

2016/12/30 13:25

DBの正規化が全くなされていないですよ。
zico_teratail

2016/12/30 13:29

正規化はされていると思っているのですが、具体的にどこがどうダメなのでしょうか?
退会済みユーザー

退会済みユーザー

2016/12/30 13:30

DBの定義を追記してます。
zico_teratail

2016/12/30 13:46

ありがとうございます。 おっしゃっている意味が分かりました。 ability テーブルにspec1~spec10まで並んでいる件が正規化できていない、という意味ですね? これにつきましては、前述のように他のテーブルからの参照などいろいろ事情がありまして、苦肉の策でこのような形態になっております。
退会済みユーザー

退会済みユーザー

2016/12/30 13:48

経験上、どんな要件でも、正規化できないということを経験したことが全くないのですが、一体どんな事情があるのでしょう。全くイメージできないのでよろしければお聞かせください。
zico_teratail

2016/12/30 13:54

私が質問文として例示した内容では分かりづらく、誤解を招く例で不適切だったかもしれません。 申し訳ありません。 実際のテーブル内容は spec1~spec10の「並び順」というか「位置」も重要でして、たとえば tanakaさんの spec1=4 spec2=1 と hogeさんの spec1=1 spec2=4 は等価ではなく、全く違う意味となります。
退会済みユーザー

退会済みユーザー

2016/12/30 13:57

spec1 〜 spec10 には優先順位があるということですか?
zico_teratail

2016/12/30 14:10

そうですね、優先順位と言ってもいいかもしれませんが、 たとえばspec1に1が入っている場合とspec2に1が入っている場合とでは意味合いが変わってきます。
退会済みユーザー

退会済みユーザー

2016/12/30 14:12

恐れ入りますが、テーブルの定義を私がやったように CREATE 文で提示してくれませんか? show create table テーブル名; で取得できます。
zico_teratail

2016/12/30 14:27

回答への追記ありがとうございます。 このままだとスマートな解決ができなさそうなので、Kosuke_Shibuyaさんのご指摘通り、正規化も含めてテーブル定義全体の見直しをしてみようと思います。
Panzer_vor

2017/01/04 15:05

横槍ですが。。 テーブル構造をいじれる内なら皆様の語指摘のとおり正規化するのがベストです。 そもそも同一項目を列もちして何がまずいかというと、  ①下手をすれば項目増減が発生するたびにテーブル定義の変更が発生する  ②今ケースの用に他テーブルを参照する必要がある場合横持ち分だけテーブル結合が必要 の2点のデメリットが大きいです。 特に①のようにテーブル定義をちょくちょく変える機会が発生する事態は、 下手な事故を避ける意味でも必要最小限に抑えることが望ましいです。 どうしても元のテーブルをいじれない状況の場合は、 素直に項目数分テーブルを結合するクエリを書くか、 列→行展開を行うビューを作成して逃げ道を作るかのどちらかになりますね。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.49%

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

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

質問する

関連した質問