JOINで行が増える理由と重複チェック(件数確認・1対多の見分け方)

JOINで行が増える理由と重複チェック(件数確認・1対多の見分け方)

この記事で分かること

  • JOIN後に「件数が合わない」「同じ行が何度も出る」とき、原因を切り分けられる
  • 結合と結合の件数を確認する手順が身につく
  • 1対多・多対多の関係を意識して、JOIN条件を足す・見直す判断ができる

結論:結合前後の件数を必ず確認する

JOINは「条件に合う組み合わせ」をすべて出します。1対多の「多」側を結合すると行数は増えます。意図した結合なら問題ありませんが、「条件不足で意図せず増えている」のか「1対多で増えているだけ」なのかを、結合前の各表の件数結合後の件数で確認すると原因が絞れます。

最小例:結合すると行が増える

顧客(customers)と注文(orders)は 1対多 です。1人に注文が複数あるため、JOINすると行数は「注文の数」になります。

-- 結合後の件数は「注文の数」(顧客数より多くなりがち)
SELECT c.name, o.total
FROM customers AS c
JOIN orders AS o ON o.customer_id = c.id;
  • 意図どおり: 「1行=1注文」で、顧客名と注文金額が欲しい → 上記でOK
  • 意図と違う: 「1行=1顧客」で、顧客ごとの合計が欲しい → 先に集計(GROUP BY)してから扱う

結合前後の件数確認(まずここをやる)

  1. 結合する各表を単体で SELECT COUNT(*) する
  2. 結合後の結果SELECT COUNT(*) する(または件数が分かる方法で確認)
  3. 結合後が「どれかの表より明らかに多い」→ 1対多の多側を結合している、または JOIN 条件が足りていない
-- 例:顧客10件、注文50件 → JOIN後は50行が自然
SELECT COUNT(*) FROM customers;  -- 10
SELECT COUNT(*) FROM orders;     -- 50
SELECT COUNT(*) FROM customers c JOIN orders o ON o.customer_id = c.id;  -- 50

結合後が 10 でも 50 でもなく、異常に大きい(例:500)場合は、JOIN条件の不足(同じキーで複数紐づく・条件が甘い)を疑います。

よくあるミス

  • 結合後の件数を見ずに「動いた」で終わる
  • 「1行=何か」を決めずにJOINする(顧客単位か注文単位かで、GROUP BY が必要かが変わる)
  • 複数回JOINするときに、途中の件数を確認しない(2つ目・3つ目のJOINで一気に増えることがある)

練習問題(2問)

  1. 顧客と注文をJOINしたとき、結合前(顧客数・注文数)と結合後の件数を出し、関係を確認する
  2. 「顧客ごとに注文数が2件以上の顧客」を取りたいとき、JOINだけでは足りず GROUP
    BY と HAVING が必要になる理由を、件数の観点で説明する

次に読む

インデックス入門

インデックス入門:どの条件・どの並び替えに効くか(最小限の考え方)

この記事で分かること

  • インデックスが「どの条件(WHERE)」「どの並び替え(ORDER BY)」に効くかの感覚がつかめる
  • 貼り方を間違えると逆効果になり得ることを知れる
  • 実行計画と組み合わせて「効いているか」を確認する流れが分かる

結論:インデックスは「列の並び」で効く

インデックスは「指定した列の順序」でデータを並べた構造です。そのため、「左からの連続した列」に対する条件や並び替えに効き、途中を飛ばした条件には効きにくい(または効かない)ことが多いです。

最小例:1列の条件に効く

-- customer_id にインデックスがあると、この条件に効きやすい
SELECT * FROM orders WHERE customer_id = 123;
  • 等価(=): その列のインデックスが使われやすい
  • 範囲(<, >, BETWEEN): その列までがインデックスの「左から」並んでいれば使われやすい
  • ORDER BY の先頭列: インデックスの並びと一致していると、ソートを省略できることがある

複数列インデックスの考え方

  • (A, B, C)
    の複数列インデックスなら、「A だけ」「A と B」「A と B と C」のように左から連続した条件に効く
  • B だけの条件では、多くのDBMSではこのインデックスは使われない(または効きが悪い)

よくあるミス

  • インデックスをたくさん貼りすぎて、更新(INSERT/UPDATE/DELETE)が遅くなる
  • 「効いているはず」で実行計画を見ずに終わる(実際はフルスキャンになっていることもある)

練習問題(2問)

  1. 手元の表に1本、インデックスを付けて EXPLAIN で「Index Scan」などになるか確認する
  2. 複数列(例:customer_id,
    ordered_at)のインデックスを作り、WHERE customer_id = ? AND ordered_at > ?
    で計画がどう変わるか見る

次に読む

実行計画入門

実行計画入門:遅いSQLを直す前に「何が重いか」を見る

この記事で分かること

  • 「なぜ遅いのか」を勘で当てにせず、DBが実際にどう実行しているかを確認できる
  • 実行計画(EXPLAIN)の見方の基本が分かる
  • 「どの表のアクセスが重いか」「インデックスが効いているか」を判断する入口が持てる

結論:書き換えの前に実行計画を見る

遅いSQLを闇雲に書き換えても、原因が違えば速くなりません。まず実行計画で「どの処理に時間がかかっているか」を確認し、その部分(フルスキャン・重いJOIN・ソートなど)を狙って直すのが基本です。

最小例:EXPLAIN を出す

DBMSによって構文が少し違いますが、多くの場合 EXPLAIN(または
EXPLAIN ANALYZE)で計画と実測が得られます。

-- PostgreSQL の例
EXPLAIN ANALYZE
SELECT * FROM orders WHERE customer_id = 123;

-- MySQL の例
EXPLAIN SELECT * FROM orders WHERE customer_id = 123;
  • Seq Scan / Full Table Scan: 表全体を読んでいる → 条件に合う列にインデックスがあるか確認
  • Index Scan / Index Range Scan: インデックスを使って絞り込んでいる → 期待どおり
  • Nested Loop / Hash Join: 結合のやり方。行数が多くなっていないか確認

どこを直すかの当たりの付け方

  • rows(見積もり行数)が極端に大きい → その段階で絞り込めていない
  • 実際の実行時間が長いステップ → その処理(スキャン・JOIN・Sort)を減らす・インデックスを検討
  • インデックスが使われていない → WHERE / JOIN の条件とインデックスの列の順序・型を確認

よくあるミス

  • 実行計画を見ずに「とりあえずサブクエリをJOINに変えた」などで終わる
  • 1回だけ実行してキャッシュの影響を見ていない(複数回・本番に近いデータ量で確認)

練習問題(2問)

  1. 手元のDBで、簡単な SELECT に EXPLAIN を付けて実行し、「どのスキャンになっているか」を読む
  2. WHERE の条件列にインデックスを付けて、計画が「Index Scan」などに変わるか確認する

次に読む

GROUP BYのよくあるエラー

GROUP BYでエラーになる理由と直し方(「何を1行にしたいか」から逆算)

この記事で分かること

  • GROUP BY のエラーが出る典型パターンと、直す手順が分かる
  • 「集計の単位」を日本語で決めてからSQLに落とすコツが身につく
  • 集計後にJOINして行が増える事故を避けられる

結論:最初に「最終的に1行を何にするか」を決める

GROUP BY は「どの単位で1行に潰すか」を決める構文です。先に日本語で決めると、GROUP BY
に入れる列が自然に決まります。

最小例:顧客ごとの注文数

SELECT
  customer_id,
  COUNT(*) AS cnt
FROM
  orders
GROUP BY
  customer_id;

典型エラー1:GROUP BYにない列をSELECTしている

症状

  • 「この列はGROUP BYに含まれていない」系のエラー

直し方

  • その列を GROUP BYに追加する(=粒度を変える)
  • もしくは 集計関数をかけるMAX など)
  • あるいは、別クエリに分ける(まず集計→後でJOIN)

典型エラー2:意図と違う集計になっている(行が増える)

原因

  • 集計前にJOINして、1対多の「多側」を結合してしまっている

回避策

  • 先に集計で粒度を揃える→必要なら後でJOINする

練習問題(2問)

  1. 「日付ごとの注文数」を集計する(ordered_at は日付に丸める想定)
  2. 「顧客ごとの売上合計」を集計する(SUM(total)

次に読む

NULLの3値論理

NULLの3値論理でWHEREが外れる理由(= / <> が通じないケース)

この記事で分かること

  • NULLが「値」ではなく「不明」である感覚がつかめる
  • WHERE col <> 'x' に NULL が含まれない理由(3値論理)を説明できる
  • IS NULL / COALESCE / OR col IS NULL を使い分けられる

結論:NULLは比較するとUNKNOWNになり得る

SQLの条件式は TRUE /
FALSE だけではなく、UNKNOWN(不明)があります。WHEREは TRUE の行だけを残すため、UNKNOWNは落ちます。

最小例(この挙動を覚える)

-- NULLを探す
SELECT *
FROM t
WHERE col IS NULL;

-- 「x以外」には、NULLは含まれない(NULLは不明扱い)
SELECT *
FROM t
WHERE col <> 'x';

-- NULLも含めて「x以外」にしたいなら、明示的にORする
SELECT *
FROM t
WHERE col <> 'x' OR col IS NULL;

よくあるミス

  • = NULL を書く(正しくは IS NULL
  • NULLを含む列で NOT IN を使って事故る(EXISTS/NOT EXISTSを検討)

練習問題(2問)

  1. 「x以外(NULLも含む)」を正しく抽出する
  2. NULLを空文字として扱って比較する(COALESCE(col, '') <> 'x' など)

次に読む

ROW_NUMBERで「グループごとに最新1件」

ROW_NUMBERで「グループごとに最新1件」を安定して取る(同率・タイブレークまで)

この記事で分かること

  • 「ユーザーごとの最新注文」「端末ごとの最新ログ」など、実務で頻出の"最新1件"を安定して書ける
  • ROW_NUMBER() のテンプレをそのまま使える
  • 同率や同時刻でも結果が揺れないように、タイブレークを入れられる

結論:最新1件は ROW_NUMBER + タイブレーク

LIMIT 1 だけでは「最新」が定義できません。「何を最新とするか」を ORDER BY
に書き、同時刻などの揺れをIDで止めるのが基本です。

最小テンプレ(まずこれだけ覚える)

SELECT *
FROM (
  SELECT
    t.*,
    ROW_NUMBER() OVER (
      PARTITION BY group_key
      ORDER BY sort_key DESC, tie_breaker DESC
    ) AS rn
  FROM t
) x
WHERE x.rn = 1;

例:ユーザーごとの最新注文を取る

SELECT *
FROM (
  SELECT
    o.*,
    ROW_NUMBER() OVER (
      PARTITION BY o.user_id
      ORDER BY o.ordered_at DESC, o.order_id DESC
    ) AS rn
  FROM orders AS o
) x
WHERE x.rn = 1;

ここが重要(タイブレーク)

  • ordered_at が同時刻の行があり得るなら、必ず order_id などを足して揺れを止めます

よくあるミス

  • ORDER BY が弱くて結果が揺れる(同時刻・同率)
  • JOINした後に ROW_NUMBER() を振って行が増える(1対多の多側を結合している)

練習問題(3問)

  1. ユーザーごとの最新注文を取る(同時刻のタイブレーク込み)
  2. 端末ごとの最新ログを取る(device_id / logged_at / log_id を想定)
  3. 「最新2件」まで取りたい(rn <= 2

次に読む

EXISTS入門:存在チェックの基本と落とし穴(IN・NULL・重複)

EXISTS入門:存在チェックの基本と落とし穴(IN・NULL・重複)

この記事で分かること

  • EXISTS の基本形(相関サブクエリ)を、最小パターンで理解できる
  • INEXISTS の「まずはこう選ぶ」判断基準が分かる
  • NULLや重複で「動くけど間違う」事故を避けられる

前提

  • SELECT / WHERE / JOIN の基本は知っている
  • まだSQLに慣れていなくてもOK(コードは短めにする)

結論:迷ったらまずは EXISTS

「条件を満たす行が"あるかどうか"」だけを確認したいなら、まず EXISTS を使うのが安全です。特に
IN (subquery) は、サブクエリ結果にNULLが混ざると意図しない挙動になりやすいので注意が必要です。

EXISTSの最小形(まずこれだけ)

「注文が1件でもある顧客」だけを取りたい例です。

SELECT
  c.*
FROM
  customers AS c
WHERE
  EXISTS (
    SELECT
      1
    FROM
      orders AS o
    WHERE
      o.customer_id = c.id
  );

つまずきポイント

  • 相関条件(o.customer_id = c.id)を書き忘れると、全件TRUEになって事故ります

INとEXISTSの使い分け(まずはこの基準)

  • 存在チェックEXISTS
  • 候補リストに含まれるかIN(ただしNULLを意識)

INで起きやすいNULL事故(要注意)

NOT IN
とNULLが組み合わさると、直感に反する結果になりやすいです。このパターンに出会ったら、NOT EXISTS
を優先的に検討してください。

NOT EXISTSの例(「注文がない顧客」)

SELECT
  c.*
FROM
  customers AS c
WHERE
  NOT EXISTS (
    SELECT
      1
    FROM
      orders AS o
    WHERE
      o.customer_id = c.id
  );

練習問題(3問)

  1. 注文が1件でもある顧客を抽出する(EXISTS
  2. 注文がない顧客を抽出する(NOT EXISTS
  3. 「特定の商品を買った顧客」だけを抽出する(EXISTSordersorder_items がある想定)

次に読む

データベースエンジニアはAIに駆逐されるか。AIを飲み込むのか。

AIという用語を聞く機会が多くなりました。AI(Artificial Intelligence)つまり人工知能ですね。

AIで働き方が変わったり、AIで仕事がなくなったりするとも言われています。

データベースエンジニアはAIにとってかわられるのでしょうか。あるいはAIを使いこなし生産性をあげていくのでしょうか。

身近なAI

データベースやデータベースエンジニアにとってのAIを考える前に、どんなものがAI化されているか確認してみましょう。

siriとalexa

いわゆる音声アシスタントです。スマートフォンのくくりでいうと、iPhone搭載のsiri、Android搭載のグーグルアシスタントです。
例えばカップラーメンの時間をはかるために
「Hey siri 3分タイマー」とsiriに依頼したとします。
siriは以下の処理を行います。

  1. 音声認識
  2. 内容解釈
  3. 指示の実行

1.「Hey siri 3分タイマー」という音声の塊を意味のある日本語として聞き取ります。声の個人差、高低、抑揚などの違いを乗り越えて発声者の意図した日本語に置き換えるのは意外と難しく、AIで各段に進歩した分野です。

2.では置き換えた日本語の依頼内容を解釈します。ここも表現違い理解する必要がありAIが活躍しています。

3.では指示に基づき、スマートフォンの設定を行います。

AIで実施している処理で重要となるポイントは、人間のように考えられるかという部分です。

自動運転

目的地と現在位置の情報から経路を割り出すことは、カーナビで実現できています。
自動運転では各種センサー情報をもとに、前方車両との距離や障害物、自車速度に加え、人や車の飛び出しなど予期しない動きに即応して安全に目的地まで運転します。

ここでも重要なのは人のように判断し車を操作できるかです。

自分が運転している時にしている事を想像してください。

  • きょろきょろしている歩行者が急に道路を横切らないか。
  • 信号が赤になりそうだ。
  • 対向の右折車が急に曲がってこないか。

などリアルタイムで判断しアクセルを調節したりブレーキやハンドル操作をしています。

まだまだ完全な自動運転は普及していませんが、AIの優れた点は疲労や居眠り、飲酒による判断ミスや判断の遅れがない点です。

レントゲン、CT、MRIの画像診断

レントゲン、CT、MRIの画像から癌や腫瘍を見つけます。医師や技師が長年の経験で培ってきた判断と同等の判断ができるようになりつつあります。

ここでもAIのアドバンテージは疲労知らずでうっかり見逃すことがない点と圧倒的な処理スピードです。

データベース業務とAI

データベースエンジニアが行うデータベースの業務の中でAIで代替できる作業にはどんなものがあるのでしょうか。

DB設計や構築のAI

現在主流のリレーショナルデータベースはお客様の業種ごとに論理データモデルが存在します。
ほとんどの業種では、顧客情報のテーブルが存在します。

また、金融機関であれば口座の情報であったり、流通業であれば商品コードや単価の情報、売り上げの情報などがあります。

データベースに提供されるデータから、それらの中身や定義情報から論理モデルのどのテーブルに該当するかあてはめ、ほかのデータとの関連(リレーション)も読み取って外部キーの設定を自動で設定できるようになるでしょう。

分析のAI

データベースからデータを取り出し、分析する業務は、データエンジニアやデータサイエンティストの仕事とされますが、小さなプロジェクトではデータベースエンジニアが兼任するケースが多くあります。

データ分析ではこれまで中心であった統計手法を用いた分析に加えAIによる分析が可能になります。

まとめ

データベースエンジニアの仕事が、AIによってどのように変わっていくか。あるいは変わっていかないのか。
についてお話してきました。

データベースの設計・構築フェーズにおいて、ある程度の知見が必要ながらも単純作業や大量作業についてはAIに置き換わっていく可能性があります。

また、データ分析においては分析手法が増え、役に立つ分析結果を提示できる機会が増えます。

データベースを導入する顧客はデータベースを構築したいのではなく、データベースから得られる知見を欲しています。

AIはデータベース構築までの作業負荷を減らし、より高度な分析する助けとなるでしょう。

次に読む

データベースエンジニアとデータエンジニアの違い

最近耳にするようになったデータエンジニアとはどんな職種でしょうか。データベースエンジニアの違いはなんでしょう。またデータサイエンティストとの関係はどう考えればよいでしょうか。

データエンジニアとはデータ分析のためにデータを整理し管理する仕事です。その際、必要に応じてデータをどう取得しどういう形で保存するのか決めることもします。

結論を先に提示するパターンで構成すると
データエンジニアとデータベースエンジニアの違い
はどこに記述するのか序文か本文最初か
後者の場合現在の見出し構成で可能か

データエンジニアとは

データエンジニアは、おおざっぱに言えばデータ基盤の設計・構築・管理運用を行います。
具体的には

  1. 膨大なデータを分析するためのデータの整理や管理を行う。
  2. 複雑で大規模なデータを活用できるように情報基盤の構築や運用を行います。
  3. データの欠落部分を補い、重複部分を取り除き、表記ゆらぎを統一して分析できるようデータを整えます。
  4. データをグラフにするなど可視化する業務を行います。
  5. また収集されるデータからデータベースを作成し分析する人が必要な形で取り出せるよう情報インフラを開発します。
  6. 各種センサーやIoTを駆使したスマート工場の情報システムの開発も行います。
  7. 人工知能の開発ではAIに学習させるために必要な教師データの作成整理を行います。

少し抽象的ですね。3「データの欠落と補完」と4「データの可視化」を例に具体的な内容を見ていきましょうい。

データの欠落と補完

実際にあった例を挙げて説明します。
あるお客様から性別のデータを分析すると件数が合わない。と相談がありました。

そこで性別データ項目の値を確認すると「男性」「男」「M」といったデータが混在している事が分かりました。

件数が合わないとおっしゃったお客様は、性別項目=「男」という条件だけで分析していたようです。

この場合の男性データ抽出条件は
性別項目=「男性」or 性別項目=「男」or 性別項目=「M」という長い記述をしないと正しい分析はできません。

分析のたびに毎回長い抽出条件を指定するのは面倒であり、他の条件と組み合わせるときに注意が注意が必要になります。

そこでお客様と相談の上、表記の揺らぎをを統一する事にしました。具体的は3つの値を統一し「M」としました。

この変更を実施後、男性データを抽出には性別項目=「M」という条件だけで済むようになりました。

データの可視化

データをグラフ化する作業も実際にデータベースエンジニアである私が、経験しています。

店舗別の売り上げ推移や商品別売り上げ構成比について、企業経営に必要な指標としてデータを抽出し
てグラフ化する要望がありました。

そこで抽出に必要なSQLとそのデータをグラフ化するエクセルのテンプレートを作成しお渡ししました。
これによっていつでも新しいデータでグラフを作成する事ができます。

助力を請われた時、魚を渡すのではなく釣り方を教えたという事ですね。

私がデータベースエンジニアの仕事を始めた頃はデータエンジニアは存在しませんでした。
そもそもデータべースエンジニアという呼称も一般的でなく、データベースを取り扱うシステムエンジニアという位置づけでした。

データエンジニアは秀吉か

天下餅の歌をご存知でしょうか。
「織田がつき、羽柴がこねし天下餅、座りしままに、食ふは徳川」

データ分析に当てはめると

  1. データベースエンジニア(織田)は情報をデータベースとして構築する。
  2. データエンジニア(羽柴)はデータベースを整理し分析用にデータを抽出加工する。
  3. データサイエンティスト(徳川)は分析用のデータを分析する。

という流れになります。

データサイエンティストが餅を食らっている(労せず分析結果を得ている)わけではありませんが、データサイエンティストがもてはやされている昨今、縁の下の力持ちとなっているデータベースエンジニアやデータエンジニアの立ち位置は天下餅の歌に近いものがあります。

また、徳川が企業内の分析ユーザであれば、「座りしままに、食ふ」事は当然の事ですね。何しろそのためにお金を払っているわけですから。

まとめ

データエンジニアの仕事は多岐にわたりますが、一番重要なのはデータを整える作業と私は考えています。

現在はデータ分析結果を確実に利益につなげている企業は多くありません。

そのためデータを整える作業に人を雇うことはなく、データベースエンジニアか分析者であるユーザやデータサイエンティストが行っています。

今後、データ分析結果を企業の施策に反映させ、確実に利益につなげられる時代になると私は確信しています。

そういう時代にはデータ分析の需要が爆発的に拡大すると私は考えています。

その際、一般の分析ユーザとデータサイエンティストが分析に専念できるようデータを整える作業をデータエンジニアに選任させるようになると考えています。

データベースエンジニアとデータエンジニアは、広義の意味では仕事内容に重複する部分も多くあります。

しかしすべての基本は、データベースエンジニアとしてのスキルにあります。

データベースエンジニアとしてキャリアを積んでデータエンジニアとしての仕事もこなせるよう意識してスキルアップをしていく事が大事だと私は考えています。

次に読む

SQLで1件だけを複数検索結果から取り出すには?

SQLで内容確認のため1件だけ取り出したい時ありませんか。
また、ある項目でグルーピングして、その代表として1件を取り出したいことはありませんか。

この記事では、3つの方法をお教えします。場合に応じて使い分けて見てください。

SQLで1件だけ①単純に検索結果を1件にする

これから分析するにあたり、カラム名やカラム内にどんな値が入っているか知りたい場合は「sample」句が使えます。(DBによる)

書式は以下の通りです。

SELECT * FROM テーブル名 sample 1 ;

つまりSLECT文の最後に「sample 1」とつけるだけです。
これで検索対象のデータのサンプルを1件取得する事ができます。

当然「sample 10」とすると10件の行を取得する事ができます。

データの傾向を見るためには100件ぐらい取得するとよいでしょう。

「sample」の代わりに「TOP」句が使えるDBMSもあります。

SELECT TOP 10 * FROM テーブル名 ;

SQLで1件だけ②「ROWNUM」使う

「ROWNUM」はSQLで使用できる予約語で、抽出された行に振られる番号です。
(DBMSによる)

例えば次のような検索では

SELECT *
FROM
(SELECT カラム名1,カラム名2 …
FROM テーブル名
WHERE カラム1 >= 100 条件
ORDER BY カラム1)
WHERE ROWNUM <= 1

カラム1の値が100以上である行を抽出する副問い合わせで
結果をカラム1で順番に並べます。

そして順番に並べたことで振られるROWNUMを使って1行抽出しています。
この方法では特定のカラムの値に条件を与えたり、ORDER BYすることで取得する事で、取得行をある程度選べます。

SQLで1件だけ③「TOP RANK() PARTITION」で1件取得する

「RANK() PARTITION」でGROUP BYのようなくくりを作ってその中での「TOP 1」を表示させます。

select TOP 1 カラム1,カラム2 ,
rank() over(partition by カラム1 order by カラム2 desc) 順位
from テーブル名
order by カラム1;

まとめ

SQLで1件だけ表示させる方法をいくつか紹介しました。

  • 「SAMPLE」や「TOP」を使う方法
  • 「ROWNUM」を使う方法
  • 「RANK() PARTITION」を使う方法

それぞれ、特徴があり、使えるDBMSも異なるのでいろいろ試して確認しておきましょう。

次に読む