カテゴリー

  • プログラミング
  • 英語学習
  • 海外
  • キャリア
  • Kredo
Kredoオンラインキャンプ
KredoIT留学
無料カウンセリングはこちら
Kredoオンラインキャンプ体験談
KredoIT留学体験談
外貨を稼ぐ!海外フリーランス無料セミナー
未経験から即戦力ITグローバル人材 無料セミナー
Kredo Blog 英語・IT・AIで、人生の選択肢を広げる。
無料ウェビナー参加 ↗ 無料カウンセリング予約無料相談↗
MENU
EXPLORE KREDO BLOG

知りたいことから、世界が広がる。

ブログTOP ↗
留学・セブSTUDY & LIFE
留学・セブの記事をすべて読む ↗ 海外留学→海外移住→海外就職→
英語学習ENGLISH
英語学習の記事をすべて読む ↗ 英語学習法→英語フレーズ→
IT・AITECH & CREATIVITY
IT・AIの記事をすべて読む ↗ プログラミング学習→プログラミング知識→AI・テクノロジー→デザイン→
キャリアYOUR NEXT CHAPTER
キャリアの記事をすべて読む ↗ 転職情報→キャリアデザイン→
KredoOUR SCHOOL
Kredoの記事をすべて読む ↗ ニュース・キャンペーン→セブ島情報→インタビュー→
STUDENT DIARIES留学生のリアルな日記noteの体験記を読む ↗YouTube・Instagramで留学を知る ↗
記事を探す ブログTOP
留学・セブ
STUDY & LIFEセブで学ぶ。海外で暮らす。留学の準備から、現地の毎日まで。
留学・セブを知るすべての記事 ↗
海外留学費用・期間・学校選びを知る↗海外移住海外で暮らすためのヒント↗海外就職世界で働く選択肢を探す↗
FROM CEBU / note留学生の、
リアルな毎日。
写真とことばで読む留学日記日記を読む ↗
英語学習
ENGLISH英語が、世界を近くする。学び方と、今日から使える英語。
英語学習を知るすべての記事 ↗
英語学習法自分に合った学び方を見つける↗英語フレーズ会話で使える表現を読む↗
FROM CEBU / note留学生の、
リアルな毎日。
写真とことばで読む留学日記日記を読む ↗
IT・AI
TECH & CREATIVITYつくる力を、次の一歩に。プログラミングとAIをもっと身近に。
IT・AIを知るすべての記事 ↗
プログラミング学習はじめ方・学び方を知る↗プログラミング知識仕組みやスキルを深める↗AI・テクノロジーAIの活かし方と最新の話題↗デザイン伝わるものづくりのヒント↗
FROM CEBU / note留学生の、
リアルな毎日。
写真とことばで読む留学日記日記を読む ↗
キャリア
YOUR NEXT CHAPTER学びの先の、働き方。転職・海外就職・これからのキャリア。
キャリアを知るすべての記事 ↗
転職情報次の仕事へ向かう準備↗キャリアデザイン自分らしい働き方を考える↗
FROM CEBU / note留学生の、
リアルな毎日。
写真とことばで読む留学日記日記を読む ↗
Kredo
OUR SCHOOLKredoを、もっと知る。学校のこと、セブのこと、学ぶ人のこと。
Kredoを知るすべての記事 ↗
ニュース・キャンペーンKredoからのお知らせ↗セブ島情報現地の暮らしを知る↗インタビュー受講生・卒業生の声を読む↗
FROM CEBU / note留学生の、
リアルな毎日。
写真とことばで読む留学日記日記を読む ↗
留学日記 ↗

ENGLISH · IT · AI / STUDY IN CEBU

学ぶって、冒険だ。

英語・IT・AIで、人生の選択肢を広げよう。

留学の無料相談へ ↗Kredo Blogを読む →
JAPAN✈CEBU
Kredoで、講師と一緒に英語とITを学ぶ受講生
LEARN SOMETHING NEW.
セブの海を望む場所で学ぶ日常
HELLO, NEW WORLD.
  • TOP
  • プログラミング
  • Web・プログラミング(学習)
  • SQLのJOINで行数・合計が増える原因は?1対多と直し方を図解

SQLのJOINで行数・合計が増える原因は?1対多と直し方を図解

Kredo CEO横田猛夫さん
Takeo Yokota
公開日:2026.10.08
更新日:2026.10.08
Web・プログラミング(学習) |
机の上で色分けした注文カードと明細カードを並べ、結合後のカードを見比べる学習イメージ

こんにちは!KredoのTakeo Yokotaです。

表紙はこの記事専用に生成した学習イメージです。実在の受講生・授業の写真ではありません。正確な件数や対応関係は本文の表と図で確認します。

SQLで注文と明細をJOINしたら、注文数や合計金額が合わなくなった。エラーは出ないのに数字が違い、DISTINCTを付けても直らない。そんなときは、計算式より先に「結合後の1行は何を表すのか」を確認しましょう。

原因の一つは、1件の注文に複数の明細が対応し、注文の金額が結果の複数行に現れることです。そのまま足すと、同じ注文を何回も加算してしまいます。金額の合計だけが必要なら注文テーブルだけを集計し、明細件数も必要なら、明細を注文単位へ集計してから結合するのが今回の直し方です。

この記事では、注文3件・明細3件の小さな架空データで、正しい合計3,200円が4,400円になってしまう過程をたどります。結合結果と主要な集計結果を本文に載せているので、実行環境がなくても紙に書いて確かめられます。後半には、Python標準ライブラリのsqlite3で動くダウンロード教材と練習問題も用意しました。

Kredoは、英語×IT・AIを通じてグローバルに活躍・就職する人材を育成し、累計3,000名の卒業生を送り出してきました。この記事では、コードの意味を説明し、結果を自分で検算する練習を扱います。SQL教材の作成・実行確認は編集部で行っています。

データベースやMySQLの役割から確認したい方は、PHPとMySQLの入門記事も参照してください。ここではSELECTで表を読み取るところから進みます。

目次
  1. 1行の意味を確認する
  2. 3件が4行になる過程
  3. COUNTとSUMを切り分ける
  4. DISTINCTだけでは直らない理由
  5. 集計してから結合する
  6. 練習問題と実務前の確認

記事のもくじ

  • 1. JOIN後に合計が増えたら、まず「1行の意味」を確認する
    • 注文と明細は、1行が表すものが違う
    • 主キーがあるのに、なぜ注文IDが繰り返される?
  • 2. 3件の注文をJOINして、4行になる過程を見よう
    • INNER JOINに替えても、合計の問題は解決しない
  • 3. COUNTとSUMで何を数えているかを切り分ける
    • 注文IDごとに「何件と対応したか」を調べる
  • 4. DISTINCTを付けるだけでは直らない理由
    • SELECT DISTINCTでも、残す列が違えば答えが変わる
    • GROUP BYしてからSUMしても、重複した値は足される
  • 5. 必要な単位に集計してから結合する
    • 金額合計だけなら、JOINしない
    • 明細件数も添えるなら、明細を先に注文単位へまとめる
    • 最後の合計も、整えた結果から取る
  • 6. 自分で直す練習と、実務データに戻す前の確認
    • 教材の実行方法
    • 問題1:COUNTの4と3を説明しよう
    • 問題2:2,000円になったSQLを直そう
    • 問題3:注文104を追加したら、どこが変わる?
    • 実務のSQLへ戻す前のチェック
    • よくある疑問

1. JOIN後に合計が増えたら、まず「1行の意味」を確認する

注文と明細は、1行が表すものが違う

架空の注文記録を使います。ordersは「1注文につき1行」、order_itemsは「1明細につき1行」です。この「1行が表す単位」を、データの粒度と呼ぶことがあります。用語を覚えるより、「この行は注文なのか、明細なのか」と言い換えられることが大切です。

注文テーブルordersは次の3行です。amount_yenは、その注文に記録された金額です。会計上の売上や消費税の計算を教える例ではなく、整数の記録値を足す教材です。

order_id(注文ID) amount_yen(円)
101 1200
102 1200
103 800

注文101と102は、たまたまどちらも1,200円ですが、別の注文です。元の合計は1,200+1,200+800=3,200円。注文IDは3種類あります。

明細テーブルorder_itemsは次の3行です。

item_id(明細ID) order_id(注文ID) label(品名)
1 101 Notebook
2 101 Pen
3 102 Folder

注文101には明細1・2が、注文102には明細3が対応します。注文103の明細は、教材ではまだ取り込まれていない設定です。「明細が見つからないから注文額も0円」とは考えません。明細別の金額や数量は、この教材には持たせていません。

主キーがあるのに、なぜ注文IDが繰り返される?

ordersのorder_idは主キーで、注文を一意に識別します。一方、order_itemsのorder_idは、対応する注文を指す外部キーです。1つの注文へ明細が複数つながるので、この列の101が繰り返されること自体は正常です。明細側で一意なのはitem_idです。

主キーが一意でも、JOINの結果にその値が1回だけ出るとは限りません。 JOINは、条件に合う行の組合せを結果へ並べるからです。1注文と複数明細という「1対多」の関係を、次の節で実際に展開してみましょう。

なお、SELECTで結果の行が増えても、保存されているordersやorder_itemsの行が書き換わったわけではありません。SQLiteのSELECT公式資料でも、SELECTはデータベースへ変更を加えず結果を返す文として説明されています。

2. 3件の注文をJOINして、4行になる過程を見よう

JOINの結果を確認するときは、最初からSUMで1つの数字にまとめず、注文IDと明細IDを一緒に表示します。次のSQLを実行してみてください。

SELECT o.order_id, o.amount_yen, i.item_id
FROM orders AS o
LEFT JOIN order_items AS i
  ON o.order_id = i.order_id
ORDER BY o.order_id, i.item_id;

FROM orders AS oは「ordersをoという短い名前で読む」、order_items AS iは「明細をiという名前で読む」という意味です。o.order_idのように表の別名を付けると、どちらの列かが明確になります。ONでは、注文IDが一致する行どうしを対応させています。

LEFT JOINは、左側のordersの行を残す結合です。対応する明細があれば、その組合せを返します。明細がなければ、右側の列がNULLの行を返します。ここでのNULLは、対応する明細の値がないことを示し、数字の0とは異なります。ORDER BYは表示順を指定するものです。これがないSELECTの行順を、いつも同じとは考えないようにしましょう。

注文101は明細1と2の2行、注文102は明細3の1行、注文103は対応明細なしの1行になる水彩図
青の101は2組、橙の102は1組、緑の103は未対応として1行。色だけでなく注文IDでも対応を確認します。

実行結果は、次の4行です。

order_id amount_yen item_id
101 1200 1
101 1200 2
102 1200 3
103 800 NULL

101の注文金額1,200円が2回現れました。これは、注文101を新しく追加したためではなく、「101と明細1」「101と明細2」という2つの組合せができたためです。結合後の行は、もう「1注文1行」ではありません。

この結果でamount_yenを足すと、1,200+1,200+1,200+800=4,400円です。元の3,200円との差1,200円は、注文101を余分に1回足した金額と一致します。結果の101の2行に印を付けると、どの注文が増加分を作ったのかを説明できます。

INNER JOINに替えても、合計の問題は解決しない

同じSQLのLEFT JOINをINNER JOINへ替えると、一致する明細のない注文103が結果から消えます。残るのは101が2行、102が1行の計3行です。合計は3,600円、注文IDは2種類となります。

元のordersも3行なので、行数だけを見ると正しく見えるかもしれません。しかし101を重ねて数え、103を落としています。行数が同じことは、正しい注文が1回ずつ残った証明にはなりません。 LEFTかINNERかは、「対応のない注文も含めるか」という目的で選び、重複集計の応急処置として替えないことが大切です。

結合と未対応行の基本は、PostgreSQL公式のJOIN入門でも確認できます。本記事の実行検証はSQLiteで行っており、PostgreSQLの実行画面を示したものではありません。

3. COUNTとSUMで何を数えているかを切り分ける

次は4行の結果を、いくつかの集計関数で調べます。COUNTは「数を数える」関数ですが、かっこの中に何を書くかで対象が変わります。

SELECT COUNT(*) AS joined_rows,
       COUNT(i.item_id) AS matched_items,
       COUNT(DISTINCT o.order_id) AS orders_count,
       SUM(o.amount_yen) AS naive_sum
FROM orders AS o
LEFT JOIN order_items AS i
  ON o.order_id = i.order_id;
SQLiteの実行値を組版した結果。結合後4行、一致した明細3件、注文ID3種類、そのままの金額合計4400円
付属教材をSQLiteで実行して得た値を、読みやすく組版した図です。アプリの画面キャプチャや生成AIによる疑似実行画面ではありません。
式 結果 今回数えているもの
COUNT(*) 4 結合後の全行
COUNT(i.item_id) 3 NULLでない明細ID
COUNT(DISTINCT o.order_id) 3 異なる注文ID
SUM(o.amount_yen) 4400 4行に現れた注文金額の合計

COUNT(*)は103の未対応行も含めます。COUNT(i.item_id)は、103の行にあるNULLを数えません。この教材ではitem_idがNULLにならない主キーなので、一致した明細の件数として使えます。NULLを許す備考などの列を数えると、「一致したけれど、その値がNULL」という行も除かれるため、同じ意味にはなりません。

COUNT(DISTINCT o.order_id)は101を1種類にまとめます。今回の目的が注文数なら3で正解です。一方、これでSUMの計算対象まで直るわけではありません。それぞれの関数は、指定された値を独立して集計します。SQLiteの集計関数資料も、count(*)とcount(X)、DISTINCTを指定した集計の違いを説明しています。

注文IDごとに「何件と対応したか」を調べる

全体の差が分かったら、どの注文が複数行になったかを絞り込みます。

SELECT o.order_id, COUNT(i.item_id) AS item_count
FROM orders AS o
LEFT JOIN order_items AS i
  ON o.order_id = i.order_id
GROUP BY o.order_id
ORDER BY o.order_id;

出力は「101:2件、102:1件、103:0件」です。GROUP BYは、指定した値ごとに行をまとめて集計します。このSQLでは注文IDごとに明細の数を確認しています。103を0件にしたいので、COUNT(*)ではなく明細IDを数えている点も見直してください。

明細が複数あること自体は誤りではありません。想定どおりの1対多なのか、結合条件が足りず別の注文まで一致したのかを区別する材料になります。実務で注文IDが店舗内だけで一意なら、店舗IDも結合条件に必要かもしれません。教材の単一キーを、そのまま実データへ当てはめないようにしましょう。

4. DISTINCTを付けるだけでは直らない理由

DISTINCTは「重複を除く」という言葉から万能に見えますが、何を同じとみなすかが重要です。今回、残したいのは別々の注文101と102です。金額が同じだからといって、片方を消してはいけません。

SELECT SUM(DISTINCT o.amount_yen) AS wrong_sum
FROM orders AS o
LEFT JOIN order_items AS i
  ON o.order_id = i.order_id;

この答えは2,000円です。SUM(DISTINCT amount_yen)は、金額の異なる値だけを足すので、1,200と800の2種類になります。101の繰り返しだけでなく、同額の別注文102まで1つにまとめてしまいました。

金額の種類では1200円と800円の2つで2000円、注文単位では101と102と103の3つで3200円になる比較図
「同じ金額」と「同じ注文」は別の判定です。101と102はともに1,200円でも、合計にそれぞれ1回必要です。

MySQL 8.4のSUM公式説明でも、DISTINCTを付けたSUMは異なる式の値を足すものとされています。注文IDを暗黙に見てくれる機能ではありません。

SELECT DISTINCTでも、残す列が違えば答えが変わる

SELECT DISTINCT o.order_id, o.amount_yen, i.item_idと書いても、今回の4行は残ります。101の2行はitem_idが1と2で違い、全列が同じ行ではないためです。

一方、注文IDと金額だけを選び、SELECT DISTINCT o.order_id, o.amount_yenとすれば、今回のデータでは3注文の組になります。その結果を外側でSUMする方法も成立します。ただし、後から明細列を追加すれば、再び1注文が複数行になる可能性があります。「DISTINCTは使ってはいけない」ではなく、「どの列の組を1つとみなしたいか」を説明できる状態で使いましょう。

GROUP BYしてからSUMしても、重複した値は足される

結合した後に注文IDでGROUP BYし、SUM(o.amount_yen)を求めると、101は2,400円、102は1,200円、103は800円になります。3行にまとまっても、101の金額は直っていません。GROUP BYは、すでに重複した注文金額を自動で元に戻す仕組みではないからです。

件数で割る、MINやMAXで1つだけ取り出す、LIMITで行を切る、といった処理にも、成り立つ条件が必要です。欠落した注文や結合条件の誤りは隠せません。さらに、正当な明細をDELETEして行数を合わせると、元データを壊してしまいます。今回直すのは読み取りの集計方法であり、元の明細ではありません。

5. 必要な単位に集計してから結合する

修正方法を選ぶ前に、欲しい結果を一文で書きます。「全注文の金額合計だけ」なのか、「注文ごとの金額と明細件数」なのかで、必要なSQLは違います。

金額合計だけなら、JOINしない

ordersに注文金額が1注文1行で記録されているので、今回の全注文合計はこれだけで求まります。

SELECT SUM(amount_yen) AS total_yen
FROM orders;

結果は3,200円です。明細の条件で注文を絞る必要も、明細の情報を表示する必要もないなら、明細テーブルを結合する理由がありません。まず最も単純な正解を残しておくと、複雑な集計結果との比較にも使えます。

明細件数も添えるなら、明細を先に注文単位へまとめる

最初に、明細だけを注文IDで集計します。

SELECT order_id, COUNT(*) AS item_count
FROM order_items
GROUP BY order_id
ORDER BY order_id;

中間結果は次の2行です。

order_id item_count
101 2
102 1

ここでは元のorder_itemsを直接数えているので、COUNT(*)で明細件数になります。注文103の明細行は存在しないため、中間結果にも103は出ません。

明細3行を注文IDごとの2行へ先に集計し、それを注文3行へLEFT JOINする工程図
右側を注文IDごとに1行へ整えてから結合します。103の中間行はないので、最後に件数だけ0と表示します。

このSELECTをかっこで囲み、FROMの中で一時的な表として使います。これをサブクエリ、またはこの位置では派生テーブルと呼びます。見慣れない構文でも、まず「さっきの2行の表をここへ置いた」と読めば大丈夫です。

SELECT o.order_id, o.amount_yen,
       COALESCE(i.item_count, 0) AS item_count
FROM orders AS o
LEFT JOIN (
    SELECT order_id, COUNT(*) AS item_count
    FROM order_items
    GROUP BY order_id
) AS i ON o.order_id = i.order_id
ORDER BY o.order_id;
order_id amount_yen item_count
101 1200 2
102 1200 1
103 800 0

101の右側は、明細そのものの2行ではなく、件数2を持つ1行です。そのため注文金額が繰り返されません。結果は3行、注文金額の合計は3,200円、明細件数の合計は3です。

COALESCE(i.item_count, 0)は、対応する集計行がなくNULLになった件数を0で表示しています。「この明細テーブルに該当行が0件」という意味であり、注文が無効・未購入という意味ではありません。NULLなら何でも0円にしてよい、という一般ルールでもありません。

最後の合計も、整えた結果から取る

明細件数と注文金額の全体合計を一緒に表示したい場合は、前集計した表との結合から計算できます。

SELECT SUM(o.amount_yen) AS total_yen,
       SUM(COALESCE(i.item_count, 0)) AS total_items
FROM orders AS o
LEFT JOIN (
    SELECT order_id, COUNT(*) AS item_count
    FROM order_items
    GROUP BY order_id
) AS i ON o.order_id = i.order_id;

結果はtotal_yenが3200、total_itemsが3です。右側の集計結果が注文IDにつき最大1行だからこそ、このSUMは注文金額を1回ずつ足せます。別の明細表をこの後にそのまま追加結合すれば、また複数行になることもあります。修正後も、出力の1行の意味を確認する習慣は残してください。

6. 自分で直す練習と、実務データに戻す前の確認

説明を読んで分かったつもりになったら、今度は結果を見る前に予想を書いてみましょう。「元データ」「予想」「実測」「理由」の4欄を埋めると、数字を偶然当てただけなのか、結合の仕組みを説明できるのかが分かります。

紙のワークシートに予想と確認結果を書こうとする手元の学習イメージ
練習用の生成イメージです。実際の問題と解答欄は本文・ダウンロード教材にあります。

教材の実行方法

SQL JOIN実習パックをダウンロードする。架空データのsetup.sql、確認用queries.sql、問題exercise.sql、answers.md、期待値CSV、実行スクリプトrun_practice.py、READMEをまとめています。外部サービスへの登録や会社のアカウントは不要です。

  1. ZIPを展開し、READMEを読みます。SQLやスクリプトは、知らないものをそのまま実行せず内容を確認してください。
  2. Python 3を使える端末で、展開したフォルダーをターミナルで開きます。
  3. 下のコマンドを実行します。環境によってpython3ではなくpythonまたはpy -3を使います。
  4. 表示された結果を本文とexpected-results.csvで照合し、なぜ違う数字になるかを説明します。
python3 run_practice.py

付属スクリプトはPython標準のsqlite3を使い、メモリ内に練習専用データベースを作ります。会社のデータベースへ接続せず、外部への通信も行いません。2026年10月8日(日本時間)にPython 3.12.14/SQLite 3.53.1で検証しました。MySQLやPostgreSQLのサーバーでは実行していません。Pythonが使えない場合は、本文の表を紙に転記して各問題を解いても、同じ考え方を練習できます。

問題1:COUNTの4と3を説明しよう

第2節の4行を見ながら、次の2点を自分の言葉で書いてください。

  • なぜ101は2回現れるのでしょうか。
  • なぜCOUNT(*)は4なのに、COUNT(i.item_id)は3なのでしょうか。

解答:101には明細1と2が対応し、それぞれとの組合せが1行ずつ出るからです。COUNT(*)は103の未対応行も含む4行を数えます。COUNT(i.item_id)は103のNULLを除くため3になります。「SQLが勝手に注文を増やした」「COUNTが不正確」という説明では、結合結果の単位を捉えられていません。

問題2:2,000円になったSQLを直そう

第4節のSUM(DISTINCT o.amount_yen)を、次の2つの目的ごとに直してください。

  • A:全注文の金額合計だけを知りたい
  • B:注文ごとの金額と明細件数を並べたい

解答A:第5節の、ordersだけをSUMするSQLです。結果は3,200円になります。解答B:明細をorder_idで先にCOUNTし、その結果をordersへLEFT JOINするSQLです。結果は101が「1200・2」、102が「1200・1」、103が「800・0」です。

DISTINCTを外すだけでは4,400円へ戻ります。INNER JOINへ替えると103が消え、金額は3,600円になります。「3,200に近いか」ではなく、「全注文が1回ずつ含まれているか」で解答を評価しましょう。

問題3:注文104を追加したら、どこが変わる?

新しい注文104・600円と、それに対応する明細2件を追加します。初期データへ一度だけ追加するSQLです。教材は毎回新しいメモリ内データベースを作るため、再実行で同じIDを重ねて登録しません。手作業で同じDBへ繰り返すと、主キーの重複エラーになります。

INSERT INTO orders (order_id, amount_yen)
VALUES (104, 600);

INSERT INTO order_items (item_id, order_id, label)
VALUES (4, 104, 'Marker'), (5, 104, 'Paper');

先に、注文数、正しい金額合計、明細数、素のLEFT JOINの行数とSUMを予想してください。追加ケースは、次のコマンドで実行します。初期データから作り直して注文104を一度だけ追加するため、先にINSERTを手動実行する必要はありません。

python3 run_practice.py --scenario added104
注文104追加後の解答用紙。注文4件、正しい合計3800円、明細5件、素の結合6行と5600円、前集計後4行と3800円
SQLiteで実行した追加ケースの値を使った解答欄です。理由欄まで説明できれば、別の件数にも応用できます。
確認対象 追加後の正解
元の注文件数 4
元の注文金額合計 3800円
明細件数 5
素のLEFT JOINの行数 6
素のLEFT JOIN後のSUM 5600円
SUM(DISTINCT amount_yen) 2600円
前集計してから結合した行数 4
前集計してから結合した金額合計 3800円

101と104がそれぞれ2行になり、102と103が1行ずつなので、素の結合は6行です。金額は101の1,200円と104の600円が1回ずつ余分に足され、3,800+1,200+600=5,600円になります。DISTINCTなら金額の種類1,200・800・600だけを足して2,600円です。

前集計後の結果へ増えるのは「104、600、2」という1行です。4注文の金額を1回ずつ足せば3,800円になります。数字を替えても同じ説明ができることが、この練習の到達点です。

実務のSQLへ戻す前のチェック

  • 元の表と出力の「1行の意味」をそれぞれ書けるか
  • キーが本当に一意か。会社・店舗・日付などを含む複合キーではないか
  • 対象期間、キャンセル、対象注文の条件が比較元と一致しているか
  • 未対応の注文を残す必要があるか。LEFT JOIN後に右表の列をWHEREで絞り、未対応行を落としていないか
  • 注文IDごとの対応件数と、残ったID・消えたIDを確認したか
  • 合計が一致するだけでなく、同額の別注文や明細0件のケースも確認したか
  • 元データの重複や取込漏れが疑わしいとき、仕様と担当者へ確認したか

今回の教材は、1注文に複数明細がある場面へ絞っています。多対多の関係、複数の明細表を同時に結ぶケース、性能改善、実際の売上認識や税計算は扱っていません。実務の数値を報告する前には、集計の対象と定義を担当者とそろえましょう。

よくある疑問

JOINしただけで元データが増えたのですか?

ここで使ったSELECTでは元テーブルを変更していません。増えたのは、その条件で返された結果の行です。INSERTなどの書き込み処理とは区別します。

LEFT JOINなら左の行数と同じになるのでは?

左の行が残ることと、1行だけになることは別です。右側で2行が一致すれば、その左行は2組になります。右側を結合キーにつき最大1行へ整えると、今回のように左の3行を保てます。

INNER JOINに替えれば直りますか?

一致しない注文が落ちるだけで、1対多は残ります。今回の3行・3,600円は、101の重複と103の欠落が同時に起きた結果です。

MySQLでも同じ考え方が使えますか?

1対多で結果が複数行になること、COUNTの対象、SUM(DISTINCT)が金額の値をまとめることは、MySQLの公式資料でも確認できます。ただし、同梱の実行スクリプトとスキーマ検証はSQLite用です。MySQLでの実行確認済みとはしていません。型や制約、接続方法などは利用するバージョンで確認してください。

最後に、困ったら「SUMを直す前に、注文IDと明細IDを並べる」ことを思い出してください。1行の意味と対応関係が見えれば、JOINが必要か、先に集計すべきかを選びやすくなります。

単一ファイルから集計の考え方を練習したい方はPythonでCSVを集計する実習、実資料を使わず別の題材を作りたい方はAIで架空の業務データを作る練習へ進んでみてください。

参考資料(2026年10月8日・日本時間確認)

  • SQLite:SELECT — 結合、LEFT JOIN、表示順、DISTINCT
  • SQLite:集計関数 — COUNT、SUM、DISTINCT
  • MySQL 8.4:JOIN — 結合条件と未対応行
  • MySQL 8.4:集計関数 — SUM(DISTINCT)など
  • MySQL 8.4:派生テーブル — FROM内のサブクエリ
  • PostgreSQL:JOIN入門 — 表の結合と外部結合

数値、注文・品名、図解は本記事用の架空教材です。実行結果はこの教材の出力であり、実際の売上や学習者の成果ではありません。

英語でIT・AIを学べるKredo

英語×IT・AIのスキルを身につけてグローバルに活躍しませんか?

当メディアを運営しているKredoは、英語×ITをオンラインで学ぶ「Kredoオンラインキャンプ」と、フィリピンのセブ島で英語とIT・AIを学ぶ「KredoIT留学」「KredoAI留学」を提供しています。これまでの卒業生は3,000名以上。卒業生の多くが、国内外のIT企業への転職、フリーランスなどへのキャリアチェンジを実現しています。これからの時代に必要な英語×IT・AIのスキルを身につけてグローバルに活躍しませんか?

\ セブ島でIT×英語を学ぶ /IT留学の無料カウンセリングを予約する
\ オンラインでIT✕英語を学ぶ / Kredoオンラインキャンプの詳細をみる >>
  • ツイート
  • シェア
  • はてな
  • ポケット
この記事を書いた人
Kredo CEO横田猛夫さん
Takeo Yokota

セブ島IT×英語留学のKredo代表の横田です。 2012年 国費にてアフリカの大学院へ留学。その後、2013年にセブ島留学を経験し、現地の語学学校でマネージャーを3年間務める。2014年同社でIT留学を立ち上げ、2016年8月Kredoを開校。これまでに700名以上の日本人卒業生を輩出し、スタッフは100名体制で学校運営を行っている。

関連記事

  • sort()は元のリストを並べ替えて戻り値がNone、sorted()は元のデータを残して新しいリストを返す比較図
    Web・プログラミング(学習)

    Pythonのsortとsortedの違い|英語の公式仕様で元データ・戻り値を確認

  • 追加した項目の動きをノートで整理する学習者の写真イメージ
    プログラミング Web・プログラミング(学習)

    JavaScriptのイベント委譲とは?追加したボタンが動く仕組みを実験

    2026.10.06
  • フォームの入力条件をノートで整理する学習者の写真イメージ
    プログラミング Web・プログラミング(学習)

    フォームの入力チェックの作り方|HTMLとJavaScriptで空白・文字数を確認

  • fetchのエラーをノートとパソコンで調べる学習者の写真イメージ
    プログラミング Web・プログラミング(学習)

    JavaScriptのfetchエラーの調べ方|404・JSON・通信失敗を切り分ける

新規CTA
KREDO JAPAN株式会社

\ SNSで留学の様子を更新中! /

  • Instagram
  • LINE
  • X
  • YouTube
©KREDO JAPAN Inc. 2024 All rights reserved.
Kredoのサービス
セブ島で学びたい方はこちら KredoIT留学
自宅で学びたい方はこちら Kredo Online 英語×アプリ開発コース
運営会社 会社概要 採用情報 お問い合わせ
利用規約 プライバシーポリシー 特定商品取引に基づく表示 資料請求