SELECTと列|テーブル・SELECT・条件抽出

このページはプロモーション(広告)を含みます。

テーブル・SELECT・条件抽出(初級・読む20分+演習30分)

この授業のゴール

仕事でどう使うか

SELECT は、表からどの列を持ってくるかを決める文です。列の選び方が雑だと、不要な個人情報を持ち出したり、列名があいまいで資料が読めなかったり、後で表に列が増えたときにレポートが壊れたりします。

路線 この授業の使いどころ
DS(データサイエンティスト) 分析に必要な列だけを抽出し、率や差の列を式で作る。列の意味が名前から読めるようにする
ML(機械学習エンジニア) 特徴量(予測に使う入力列)と正解ラベルを区別して取り出す。ID列や漏洩する列を誤って入れない
DX(DX推進・AI企画) 業務システムから出すCSVの列を要件として定義する。個人情報を含む列を最小限にする
RS(研究者) 解析に使う変数だけを取り出し、元データを書き換えずに再現できる手順にする

実務では「必要な列だけ、わかる名前で」が基本です。SELECT *(全列取得)は、探索時の確認用にとどめます。

前提

次の言葉を使います。不安があれば先に戻ってください。

この授業では、次の注文表 orders(8行、1行=注文1件)を使います。amount は円単位の注文金額で、注文6は未記録(NULL)、注文4は0円です。

身近な状況:「注文ID・日付・金額だけ」ほしい

営業部から「4月の注文について、注文ID・日付・金額だけの一覧がほしい」と頼まれました。注文表には顧客ID、チャネル(web/app/store)、クーポンなど、他にも列があります。

-- db: none
CREATE TABLE orders (
  order_id INTEGER PRIMARY KEY,
  customer_id INTEGER NOT NULL,
  order_date TEXT NOT NULL,
  status TEXT NOT NULL CHECK(status IN ('paid','cancelled','refunded')),
  channel TEXT NOT NULL,
  amount INTEGER,
  coupon_code TEXT
);
INSERT INTO orders VALUES
  (1,101,'2025-03-31','paid','web',3000,NULL),
  (2,102,'2025-04-01','paid','app',1200,'SPRING'),
  (3,101,'2025-04-01','cancelled','web',2500,NULL),
  (4,103,'2025-04-10','paid','store',0,NULL),
  (5,104,'2025-04-15','refunded','app',1800,'SPRING'),
  (6,102,'2025-04-20','paid','web',NULL,NULL),
  (7,105,'2025-04-30','paid','web',1200,''),
  (8,103,'2025-05-01','paid','app',4000,'VIP');
SELECT * FROM orders;

SELECT * は全列を返します。確認には便利ですが、依頼には次の問題があります。

必要な列だけを書き並べる SELECT 列名, 列名 ... を使います。

直感的な説明:表を縦に切り出す

表を紙の一覧表だとすると、SELECT は縦方向の切り取りです。必要な列に付箋を貼り、その列だけをコピーして新しい一覧を作るイメージです。

たとえ話と実際の違い:付箋のたとえでは「行は全部残る」ように見えます。実際、SELECT だけでは行は減りません。行を減らすのは次の授業の WHERE(WHEREと条件)の役目です。ただし DISTINCT を付けると、重複した行が1つにまとまり、行数が減ることがあります。

正確な定義:SELECT の書き方と DISTINCT

基本形は次のとおりです。

SELECT 列1, 列2 AS 別名, 式 AS 別名
FROM 表名;

NULLを含む式の規則:演算子の片方が NULL なら、結果は NULL です(「わからない値」に何を足してもわからない)。たとえば $\text{NULL} / 10 = \text{NULL}$ です。

数式で整理します。注文の手数料を「金額の10%」とすると、

$$\text{fee} = \frac{\text{amount}}{10}$$

ここで fee は手数料(円)、amount は注文金額(円)です。注文1なら $3000 / 10 = 300$ 円、注文2なら $1200 / 10 = 120$ 円です。ただし、SQLiteでは整数どうしの割り算は整数に切り捨てになる点に注意が必要です(詳しくは 型・日付・式)。今回の金額は10で割り切れる値なので問題ありません。

実行順序の注意:SQLは書いた順ではなく、おおよそ FROM(どの表か)→ SELECT(どの列を出すか)の順に処理されます。表が先に決まるので、SELECT では表に実在する列だけを使えます。

段階付き例題:手数料の列を作り、注文金額の一覧を整える

問題:全注文について、注文ID、日付、金額、手数料(金額の10%)を、わかりやすい列名で出す。

ステップ1:必要な列を選ぶ。order_id、order_date、amount。

ステップ2:式で列を作る。手数料は amount / 10。

ステップ3:列名を付ける。order_id AS id、amount / 10 AS fee。式のままだと、列名が amount / 10 になり読みにくい。

ステップ4:実行して読む。

-- db: none
CREATE TABLE orders (
  order_id INTEGER PRIMARY KEY,
  customer_id INTEGER NOT NULL,
  order_date TEXT NOT NULL,
  status TEXT NOT NULL CHECK(status IN ('paid','cancelled','refunded')),
  channel TEXT NOT NULL,
  amount INTEGER,
  coupon_code TEXT
);
INSERT INTO orders VALUES
  (1,101,'2025-03-31','paid','web',3000,NULL),
  (2,102,'2025-04-01','paid','app',1200,'SPRING'),
  (3,101,'2025-04-01','cancelled','web',2500,NULL),
  (4,103,'2025-04-10','paid','store',0,NULL),
  (5,104,'2025-04-15','refunded','app',1800,'SPRING'),
  (6,102,'2025-04-20','paid','web',NULL,NULL),
  (7,105,'2025-04-30','paid','web',1200,''),
  (8,103,'2025-05-01','paid','app',4000,'VIP');
SELECT order_id AS id,
       order_date,
       amount,
       amount / 10 AS fee
FROM orders
ORDER BY order_id;

結果の fee は、注文1で300、注文2で120、注文3で250、注文4で0、注文5で180、注文6でNULL、注文7で120、注文8で400です。

ステップ5:結果の意味を確認する。注文4(金額0円)の手数料は 0 です。注文6(金額が未記録)の手数料は NULL で、「わからない」のまま伝わります。NULLを黙って0にしないことが重要です。0にすると、手数料の合計や平均が実際より小さく見えます。

(ORDER BY order_id は並べ替えの指定で、ORDER BYとLIMIT で詳しく学びます。ここでは結果の行を毎回同じ順にするために付けています。)

操作して理解する:DISTINCT は何に効くか

同じ表で、DISTINCT の効き方を確かめます。実行する前に、それぞれ何行になるか予想してください。

-- db: none
CREATE TABLE orders (
  order_id INTEGER PRIMARY KEY,
  customer_id INTEGER NOT NULL,
  order_date TEXT NOT NULL,
  status TEXT NOT NULL CHECK(status IN ('paid','cancelled','refunded')),
  channel TEXT NOT NULL,
  amount INTEGER,
  coupon_code TEXT
);
INSERT INTO orders VALUES
  (1,101,'2025-03-31','paid','web',3000,NULL),
  (2,102,'2025-04-01','paid','app',1200,'SPRING'),
  (3,101,'2025-04-01','cancelled','web',2500,NULL),
  (4,103,'2025-04-10','paid','store',0,NULL),
  (5,104,'2025-04-15','refunded','app',1800,'SPRING'),
  (6,102,'2025-04-20','paid','web',NULL,NULL),
  (7,105,'2025-04-30','paid','web',1200,''),
  (8,103,'2025-05-01','paid','app',4000,'VIP');
-- (1) チャネルの種類
SELECT DISTINCT channel FROM orders ORDER BY channel;
-- (2) 顧客IDの種類
SELECT DISTINCT customer_id FROM orders ORDER BY customer_id;
-- (3) 顧客IDとチャネルの組み合わせの種類
SELECT DISTINCT customer_id, channel FROM orders ORDER BY customer_id, channel;

予想と答え:

(2) より (3) の行数が多いのは、DISTINCT が「顧客IDだけ」ではなく顧客IDとチャネルの組み合わせに効くからです。列を足すと、まとまりにくくなり、行数は増えるか同じです。

試してみよう:(3) の channel を status に変えるとどうなるでしょう。顧客101は paid と cancelled の両方があるので、101の行は2行になります。

発展:SELECT DISTINCT と集計、SELECT * の位置づけ

DISTINCT と GROUP BY の関係:SELECT DISTINCT a, b FROM t は、SELECT a, b FROM t GROUP BY a, b と同じ結果になります。GROUP BY は集計(件数・合計)と組み合わせて使うので、種類の一覧だけがほしいときは DISTINCT のほうが意図が伝わります。

DISTINCT の代償:重複を除くために、データベースは行を比較・整理する必要があり、行数が多い表では処理が重くなりえます。また、重複が出る原因(結合で行が増えた等)を直さずに DISTINCT で隠すと、本物の重複まで消えます。「なぜ重複したか」を確認してから使います。

列名の規則:列名や別名に空白や日本語を使う場合は、"注文 ID" のように二重引用符で囲む方法があります。ただし、BIツールやPythonに渡したときの扱いが方言や環境で異なるため、分析の現場では英数字と下線(snake_case:order_amount)を使うのが無難です。

SELECT * の使いどころ:表の中身を初めて見るとき、LIMIT と組み合わせて確認するときは便利です。一方、レポートの元になるSQL、システムが毎日実行するSQL、ML の特徴量作成では、列を明示します。列の追加・順序変更の影響を受けず、必要な列が一目でわかるからです。

よくある失敗

失敗1:カンマを忘れて、列名が意図しない別名になる

SQLでは AS を省略して、列の直後に別名を書けます。これが、カンマ忘れを「エラーにならない誤り」に変えます。

-- db: none
CREATE TABLE orders (
  order_id INTEGER PRIMARY KEY,
  customer_id INTEGER NOT NULL,
  order_date TEXT NOT NULL,
  status TEXT NOT NULL CHECK(status IN ('paid','cancelled','refunded')),
  channel TEXT NOT NULL,
  amount INTEGER,
  coupon_code TEXT
);
INSERT INTO orders VALUES
  (1,101,'2025-03-31','paid','web',3000,NULL),
  (2,102,'2025-04-01','paid','app',1200,'SPRING'),
  (3,101,'2025-04-01','cancelled','web',2500,NULL),
  (4,103,'2025-04-10','paid','store',0,NULL),
  (5,104,'2025-04-15','refunded','app',1800,'SPRING'),
  (6,102,'2025-04-20','paid','web',NULL,NULL),
  (7,105,'2025-04-30','paid','web',1200,''),
  (8,103,'2025-05-01','paid','app',4000,'VIP');
-- 誤り:order_id と amount の間にカンマがない
SELECT order_id amount, order_date FROM orders ORDER BY order_id LIMIT 3;
-- 正しい
SELECT order_id, amount, order_date FROM orders ORDER BY order_id LIMIT 3;

誤りのほうは、order_id が amount という名前に変わり、金額の列がなくなります。表示された「amount」の値は 1、2、3(注文ID)です。エラーが出ないので、気づかずに資料に載せる危険があります。直し方:列は必ずカンマで区切り、別名を付けるときは AS を省略しない。結果の列名と先頭行を目で確認する。

失敗2:DISTINCT を「1列だけ」に効くと思い込む

-- db: none
CREATE TABLE orders (
  order_id INTEGER PRIMARY KEY,
  customer_id INTEGER NOT NULL,
  order_date TEXT NOT NULL,
  status TEXT NOT NULL CHECK(status IN ('paid','cancelled','refunded')),
  channel TEXT NOT NULL,
  amount INTEGER,
  coupon_code TEXT
);
INSERT INTO orders VALUES
  (1,101,'2025-03-31','paid','web',3000,NULL),
  (2,102,'2025-04-01','paid','app',1200,'SPRING'),
  (3,101,'2025-04-01','cancelled','web',2500,NULL),
  (4,103,'2025-04-10','paid','store',0,NULL),
  (5,104,'2025-04-15','refunded','app',1800,'SPRING'),
  (6,102,'2025-04-20','paid','web',NULL,NULL),
  (7,105,'2025-04-30','paid','web',1200,''),
  (8,103,'2025-05-01','paid','app',4000,'VIP');
-- 誤解:顧客ごとに1行になるはず、と思って書く
SELECT DISTINCT customer_id, channel FROM orders ORDER BY customer_id, channel;

7行が返り、顧客102(app と web)、顧客103(store と app)が2行ずつ出ます。顧客数は5人なので、「顧客ごとに1行」にはなっていません。DISTINCT は customer_id, channel の組み合わせで重複を判定します。直し方:顧客ごとに1行がほしいなら、SELECT DISTINCT customer_id と顧客IDだけを選ぶ。チャネルも必要なら、「顧客ごとに最も多いチャネル」など、追加の定義を決めてから集計する。

失敗3:NULLを含む式を、0として扱ってしまう

-- db: none
CREATE TABLE orders (
  order_id INTEGER PRIMARY KEY,
  customer_id INTEGER NOT NULL,
  order_date TEXT NOT NULL,
  status TEXT NOT NULL CHECK(status IN ('paid','cancelled','refunded')),
  channel TEXT NOT NULL,
  amount INTEGER,
  coupon_code TEXT
);
INSERT INTO orders VALUES
  (1,101,'2025-03-31','paid','web',3000,NULL),
  (2,102,'2025-04-01','paid','app',1200,'SPRING'),
  (3,101,'2025-04-01','cancelled','web',2500,NULL),
  (4,103,'2025-04-10','paid','store',0,NULL),
  (5,104,'2025-04-15','refunded','app',1800,'SPRING'),
  (6,102,'2025-04-20','paid','web',NULL,NULL),
  (7,105,'2025-04-30','paid','web',1200,''),
  (8,103,'2025-05-01','paid','app',4000,'VIP');
SELECT order_id, amount, amount / 10 AS fee
FROM orders
WHERE order_id IN (4, 6)
ORDER BY order_id;

注文4は金額0なので fee も 0、注文6は金額が NULL なので fee も NULL です。この2つを「どちらも手数料なし」とまとめると、注文6の手数料が未記録であることを見落とします。

直し方:式の結果が NULL になる行があるかを確認する。0とみなしてよい(未記録=0円)という業務ルールがあるときだけ COALESCE(amount, 0) / 10 のように明示する(COALESCEは NULLと3値論理)。ルールを決めずに0にすると、合計や平均が実際と合わなくなります。

まとめ

用語

SELECT(SELECT)

表から取り出す列(または式)を指定するSQLの文。

別名(エイリアス)(alias)

結果の列や表に付ける名前。AS で付ける。

式(expression)

列や数値を演算子でつないだもの。行ごとに計算され、新しい列を作る。

DISTINCT(DISTINCT)

選んだ列の組み合わせが同じ行を1行にまとめるキーワード。

よくある質問

なぜ列に別名(AS)を付けるのですか?

式の結果の列名は amount / 10 のようになり、読みにくく、後続のBIツールやPythonで参照しづらいからです。意味のある名前を付けると、資料の列見出しとしてそのまま使えます。

AS は省略してもよいですか?

文法上は省略できますが、おすすめしません。カンマの付け忘れが「エラーにならない別名」になるためです。別名を付けるときは常に AS を書くと、この事故を防げます。

SELECT DISTINCT と COUNT(DISTINCT) は何が違いますか?

SELECT DISTINCT 列 は種類の一覧(行)を返し、COUNT(DISTINCT 列) は種類の数(1つの数)を返します。一覧を見たいか、数だけほしいかで使い分けます。

列の順序は結果に影響しますか?

値は変わりませんが、受け取る側の資料やプログラムが列の位置で参照していると影響します。列名で参照できる形にし、SELECT の列を明示しておくと安全です。

SELECT で列を選ぶと、元の表も変わりますか?

いいえ。SELECT は読み取りだけで、元の表は変わりません。表を書き換えるのは UPDATE や DELETE ですが、この教材の演習では使いません。

個人情報の列は取り出してよいですか?

分析に必要か、社内のルールで許可されているかで判断します。必要な列だけを明示的に選ぶ習慣は、不要な個人情報を持ち出さないことにも役立ちます。最終的な可否は所属組織の規程に従ってください。

確認問題

問1 SELECT order_id amount FROM orders のようにカンマを忘れて書いた。結果はどうなるか。

  1. ① 構文エラーになる
  2. ② order_id と amount の2列が出る
  3. ③ order_id の列が出るが、列名が amount になる
  4. ④ amount の列だけが出る
解答と解説

正解:③

ASを省略した別名の書き方と解釈され、order_id に amount という名前が付きます。

問2 SELECT DISTINCT customer_id, channel FROM orders は、何に対して重複を除くか。

  1. ① customer_id と channel の組み合わせ
  2. ② 表全体の全列
  3. ③ customer_id だけ
  4. ④ channel だけ
解答と解説

正解:①

DISTINCT は SELECT で選んだすべての列の組み合わせで判定します。

問3 amount / 10 で amount が NULL の行の結果はどうなるか。

  1. ① 10
  2. ② エラーで停止する
  3. ③ 0
  4. ④ NULL
解答と解説

正解:④

NULLを含む四則演算の結果はNULLになります。

問4 レポート用のSQLで SELECT * を避ける理由として最も適切なものはどれか。

  1. ① SELECT * はエラーになるから
  2. ② 表に列が追加・並べ替えされても結果の列が変わらないよう、必要な列を明示するため
  3. ③ SELECT * は行数が減るから
  4. ④ SELECT * では WHERE が使えないから
解答と解説

正解:②

列を明示すると、表の変更の影響を受けず、必要な列が読み手にも明確です。

問5 注文表で SELECT DISTINCT channel, status を実行したところ、本文のデータでは何行になるか。

  1. ① 7行
  2. ② 8行
  3. ③ 3行
  4. ④ 5行
解答と解説

正解:④

(app,paid)、(app,refunded)、(store,paid)、(web,cancelled)、(web,paid) の5種類です。

問6 注文4(amount=0)と注文6(amount=NULL)の手数料 amount / 10 を、同じ「手数料なし」として扱ってよいか。

  1. ① よくない。0は0円と観測された値、NULLは未記録で、意味が違う
  2. ② よい。NULLは自動的に0になるから
  3. ③ よくない。どちらもエラーになるから
  4. ④ よい。0もNULLも同じ意味だから
解答と解説

正解:①

0は有効な値、NULLは未記録・不明です。業務ルールなしに同一視すると集計が狂います。

次に学ぶこと

次は WHEREと条件 で、WHERE を使って必要な行を選びます。前の授業 テーブルと1行の単位 の粒度の考え方は、条件を考えるときも使います。

この授業の確認問題だけで習得とはしません。モジュール テーブル・SELECT・条件抽出 の総合テストと自力課題で判定します。

キャリア学習:データサイエンティスト・機械学習エンジニア・DX推進・研究者を目指す人の学習アプリ。164モジュール・984授業を、ブラウザで動くSQLとPythonの演習で。基礎と各分野の最初のモジュールは無料。全部は2,980円の買い切り。

アプリを無料で開く有料版と無料版の違い