INNER JOIN(内部結合)の使い方
juchu受注
| juchu_id | kokyaku_id |
|---|---|
| 1001 | 1 |
| 1002 | 2 |
| … | |
kokyaku顧客
| kokyaku_id | kokyaku_name |
|---|---|
| 1 | 青葉工業 |
| 2 | 白樺物産 |
| 5 | 三田商事✕ |
| … | |
INNER JOINつないだ結果
| juchu_id | kokyaku_id | kokyaku_name |
|---|---|---|
| 1001 | 1 | 青葉工業 |
| 1002 | 2 | 白樺物産 |
| … | ||
- kokyaku_nameのように、右の表の列が左の表にくっつくのが結合です。
- 受注 10 行 → つないだ結果 10 行(行数は変わりません)
- 取り消し線= 相手がいないので結果に出てこない行
- 受注側に取り消し線が無いのは、必ず顧客に紐づいているためです。
INNER JOIN は、2 つの表の「対応する行どうし」をつなぐ書き方です。 対応する相手がいない行は結果に出てきません。この「出てこない」性質が、便利さと事故の両方の原因になります。
主要 7 製品で通用 そのまま使える: PostgreSQL / MySQL / MariaDB / SQLite / SQL Server / Oracle Database / IBM Db2
このページの実行結果と図は、すべて本物のデータベースに同じ SQL を流して得たものです。手で書いた「たぶんこうなる」ではありません。
01なぜ結合が必要なのか
受注の表には、顧客が kokyaku_id という番号でしか入っていません。表を分けて番号で参照し合うのは、顧客名が変わったときに 1 か所だけ直せば済むようにするためです。
しかし画面や帳票に出したいのは番号ではなく顧客名です。この「番号から名前を引き当てる」操作が結合(JOIN)です。
まず受注表そのものを見てみます。kokyaku_id はあっても顧客名はどこにもありません。
SELECT juchu_id, kokyaku_id, juchu_on, status
FROM juchu
ORDER BY juchu_id;| juchu_id | kokyaku_id | juchu_on | status |
|---|---|---|---|
| 1001 | 1 | 2025-11-05 | done |
| 1002 | 2 | 2025-11-18 | done |
| 1003 | 1 | 2025-12-02 | done |
| 1004 | 3 | 2025-12-09 | cancel |
| 1005 | 4 | 2025-12-24 | done |
(10 行 … うち先頭 5 行を表示)
02基本形は FROM … JOIN … ON
結論: `FROM 表A JOIN 表B ON 表A.列 = 表B.列` と書きます。
ON に書くのは「どの列どうしが同じものを指しているか」です。ここでは受注表の kokyaku_id と顧客表の kokyaku_id が同じ顧客を指しているので、それを等号で結びます。
juchu j の j は別名(エイリアス)です。どの表の列かを毎回フルネームで書くと長くなるので、短い名前を付けています。付けなくても動きますが、表が増えるほど読みづらくなるので最初から付ける習慣にしてください。
SELECT j.juchu_id, k.kokyaku_name何を出すかFROM juchu j主となる表(別名 j)JOIN kokyaku kつなぐ相手(別名 k)ON k.kokyaku_id = j.kokyaku_idどの列どうしが同じかORDER BY j.juchu_id並び順
上から順に読むのではなく、FROM と JOIN で「どの表を見るか」を決め、ON で「どうつなぐか」を決め、SELECT で「何を出すか」を選ぶ、という関係です。
SELECT j.juchu_id, k.kokyaku_name, j.juchu_on
FROM juchu j
JOIN kokyaku k ON k.kokyaku_id = j.kokyaku_id
ORDER BY j.juchu_id;| juchu_id | kokyaku_name | juchu_on |
|---|---|---|
| 1001 | 青葉工業 | 2025-11-05 |
| 1002 | 白樺物産 | 2025-11-18 |
| 1003 | 青葉工業 | 2025-12-02 |
| 1004 | 黒松システム | 2025-12-09 |
| 1005 | 赤坂商会 | 2025-12-24 |
(10 行 … うち先頭 5 行を表示)
03INNER は省略できる
JOIN とだけ書くと INNER JOIN の意味になります。どちらで書いても結果は同じです。
読み手に親切なのは明示する方ですが、実務では省略形もよく使われます。大事なのは同じ文の中で書き方を混ぜないことです。
SELECT j.juchu_id, k.kokyaku_name
FROM juchu j
INNER JOIN kokyaku k ON k.kokyaku_id = j.kokyaku_id
ORDER BY j.juchu_id;| juchu_id | kokyaku_name |
|---|---|
| 1001 | 青葉工業 |
| 1002 | 白樺物産 |
| 1003 | 青葉工業 |
(10 行 … うち先頭 3 行を表示)
04内部結合は「相手がいない行」を落とす
ここが内部結合でいちばん重要な性質です。
このデータには顧客が 5 社いますが、受注が 1 件も無い会社が 1 社あります。内部結合すると、その会社は結果から消えます。エラーにも警告にもなりません。黙って消えます。
「顧客の一覧を出したら 1 社足りない」「合計は合っているのに件数が合わない」という不具合は、たいていこれが原因です。
消したくない場合は外部結合(LEFT OUTER JOIN)を使います。
ON k.kokyaku_id = j.kokyaku_id
左は顧客ごとに代表 1 件だけ載せています。右は全 5 社です。線が引かれていない行(点線・✕)は、内部結合の結果に出てきません。
SELECT kokyaku_id, kokyaku_name
FROM kokyaku
ORDER BY kokyaku_id;| kokyaku_id | kokyaku_name |
|---|---|
| 1 | 青葉工業 |
| 2 | 白樺物産 |
| 3 | 黒松システム |
| 4 | 赤坂商会 |
| 5 | 三田商事 |
(5 行)
SELECT DISTINCT k.kokyaku_id, k.kokyaku_name
FROM kokyaku k
JOIN juchu j ON j.kokyaku_id = k.kokyaku_id
ORDER BY k.kokyaku_id;| kokyaku_id | kokyaku_name |
|---|---|
| 1 | 青葉工業 |
| 2 | 白樺物産 |
| 3 | 黒松システム |
| 4 | 赤坂商会 |
(4 行)
①と②を見比べてください。三田商事(kokyaku_id = 5)が抜けています。受注が 1 件も無いためです。エラーも警告も出ません。
05⚠️ よくある間違い:ON を書き忘れると件数が爆発する
FROM juchu, kokyaku のようにカンマで並べただけで結合条件を書かないと、すべての組み合わせが返ります。これをクロス結合(直積)といいます。
ここでは受注 10 行 × 顧客 5 行 = 50 行。数が小さいので気づけますが、実務の表は数万行あります。数万 × 数万は数十億行で、返ってくる前にサーバーが悲鳴を上げます。
「結果の件数が想定よりやたら多い」ときは、まず結合条件の書き忘れを疑ってください。
顧客 5 行
- 本当に欲しい組み合わせ
- 10 通り
- ON を書かないと返ってくる
- 50 通り
1 行につき正しい相手は 1 つだけ。ON を書かないと、その行以外の赤いマスも全部返ってきます。
SELECT count(*) AS kensu
FROM juchu, kokyaku;| kensu |
|---|
| 50 |
(1 行)
SELECT j.juchu_id, k.kokyaku_name
FROM juchu j, kokyaku k
ORDER BY j.juchu_id, k.kokyaku_id;| juchu_id | kokyaku_name |
|---|---|
| 1001 | 青葉工業 |
| 1001 | 白樺物産 |
| 1001 | 黒松システム |
| 1001 | 赤坂商会 |
| 1001 | 三田商事 |
| 1002 | 青葉工業 |
(50 行 … うち先頭 6 行を表示)
受注 1001 に 5 社ぜんぶが結び付いています。正しいのはこのうち 1 行だけです。
06⚠️ よくある間違い:同じ名前の列は、どちらの表か指定しないとエラーになる
両方の表に kokyaku_id があるとき、kokyaku_id とだけ書くとデータベースはどちらか判断できません。あいまい(ambiguous)というエラーになります。
直し方は簡単で、j.kokyaku_id のように表名(または別名)を前に付けるだけです。
このエラーは初心者がいちばん多く出会うものですが、メッセージが原因をそのまま言ってくれているので、読めばすぐ直せます。
SELECT kokyaku_id, kokyaku_name
FROM juchu
JOIN kokyaku ON kokyaku.kokyaku_id = juchu.kokyaku_id;先頭の kokyaku_id が juchu のものか kokyaku のものか決まらないため。
07列名が同じなら USING でも書ける
つなぐ列の名前が両方の表でまったく同じときに限り、ON k.kokyaku_id = j.kokyaku_id を `USING (kokyaku_id)` と短く書けます。
USING を使うと、結合列は 1 本にまとめられます(SELECT * したときに kokyaku_id が 2 回出てこない)。
ただし使えない製品があります。迷ったら ON を選んでください。ON は調べたすべての製品で通ります。
SELECT j.juchu_id, k.kokyaku_name
FROM juchu j
JOIN kokyaku k USING (kokyaku_id)
ORDER BY j.juchu_id;| juchu_id | kokyaku_name |
|---|---|
| 1001 | 青葉工業 |
| 1002 | 白樺物産 |
| 1003 | 青葉工業 |
| 1004 | 黒松システム |
(10 行 … うち先頭 4 行を表示)
⚠️ この書き方は SQL Server では使えません(JOIN ... USING (列))。製品ごとの対応表
083 つ以上の表をつなぐ
JOIN は続けて書けます。読み方は「左から順に、ここまでつないだ結果に次の表をつないでいく」です。
受注(juchu)→ 明細(juchu_meisai)→ 商品(shohin)とたどると、「どの受注で・何を・いくつ買ったか」が 1 枚の表になります。
表が増えても考え方は同じで、それぞれの `JOIN` に対応する `ON` を 1 つずつ書くだけです。JOIN が 2 つあるのに ON が 1 つしかなければ、そこで件数が爆発します。
- juchu受注juchu_id
- juchu_meisai明細shohin_id
- shohin商品
矢印に書いてあるのが、その JOIN の ON で使う列です。
SELECT j.juchu_id, s.shohin_name, m.suryo, m.tanka
FROM juchu j
JOIN juchu_meisai m ON m.juchu_id = j.juchu_id
JOIN shohin s ON s.shohin_id = m.shohin_id
WHERE j.status = 'done'
ORDER BY j.juchu_id, m.gyo_no;| juchu_id | shohin_name | suryo | tanka |
|---|---|---|---|
| 1001 | ノート | 20 | 180 |
| 1001 | ボールペン | 30 | 120 |
| 1002 | デスクチェア | 2 | 23800 |
| 1002 | 書棚 | 1 | 18500 |
| 1003 | デスクライト | 4 | 4200 |
| 1003 | キーボード | 2 | 8900 |
| 1005 | ノート | 10 | 180 |
| 1005 | ボールペン | 0 | 120 |
(15 行 … うち先頭 8 行を表示)
自分で打ってみる
このページの例と同じデータが入った本物のデータベースを、この場(あなたのブラウザの中)で起動できます。打った SQL がサーバーへ送られることはありません。
押すと初回だけ約 9MB を読み込みます(2 回目以降はキャッシュされます)。
解いてみる
読んだだけでは書けるようになりません。実行結果で採点します。
関連するトピック
- LEFT / RIGHT OUTER JOIN内部結合で消えてしまう行を残したいときに使います。
- ON 句と USING 句ON と USING の違い、結合条件と絞り込み条件の使い分け。
- CROSS JOINON を書き忘れたときに起きるのが、これを明示的に書いた状態です。
根拠(一次情報)
- PostgreSQL 18 マニュアル: 問い合わせ — 結合テーブル一次情報・確認 2026-07-27
- Oracle Database SQL Language Reference: SELECT(join_clause)一次情報・確認 2026-07-27
- SQLite: SELECT(join-clause)一次情報・確認 2026-07-27