テーブルと1行の単位|テーブル・SELECT・条件抽出
このページはプロモーション(広告)を含みます。
テーブル・SELECT・条件抽出(初級・読む20分+演習30分)
この授業のゴール
- 表の1行が何を表すか(1行の単位=粒度)を、列名とキーから言葉で説明できる
- 主キーと外部キーの役割を説明し、行数・種類数(重複を除いた数)の違いを使い分けられる
- SELECT COUNT(*) と COUNT(DISTINCT 列) で、行数と対象の種類数を別々に数えられる
- 「顧客数」「注文数」「明細行数」を混同した数字を、粒度から見抜ける
仕事でどう使うか
SQLを書く前に、まず表を読みます。表の1行が何を表すかを取り違えると、正しい構文で書いても、数字の意味が変わります。「注文数」のつもりが「商品の行数」になっていた、という事故は実務で頻繁に起きます。
| 路線 | この授業の使いどころ |
|---|---|
| DS(データサイエンティスト) | 分析表の「1行=何か」を最初に確認する。ここがずれると、率の分母・分子がずれ、分析レポートの数字が誤る |
| ML(機械学習エンジニア) | 学習データの1行が「顧客」か「注文」かで、予測の対象が変わる。同じ顧客が複数行あると、評価データに情報が漏れる |
| DX(DX推進・AI企画) | KPI(Key Performance Indicator:重要業績評価指標)の定義書に「何を1件と数えるか」を書く。システム担当との認識合わせに使う |
| RS(研究者) | 観測単位(被験者か、測定1回か)を明記する。同じ人の複数測定を独立な標本と数えると、結論が過信になる |
この授業の目的は、SQLの文法よりも前に「表を正しく読む目」を作ることです。
前提
次の言葉を使います。不安があれば先に戻ってください。
- 表・行・列(縦と横に並んだ数値や文字の集まり)→ 行と列と1行の単位
- 単位(数字が何の数かを表す)と 割合 → 単位と桁と丸め、比率・割合・パーセントポイント
SQL(Structured Query Language:データベースに問い合わせる言語)の経験は不要です。この授業のSQLは、実行 ボタンで画面内で動かせます。
身近な状況:「注文は何件?」に答えが3つある
ネットショップの担当者が「先月の注文は何件ですか」と聞かれました。手元には次の表があります。
注文表 orders(8行)の先頭部分:
| order_id | customer_id | order_date | status | amount |
|---|---|---|---|---|
| 1 | 101 | 2025-03-31 | paid | 3000 |
| 2 | 102 | 2025-04-01 | paid | 1200 |
| 3 | 101 | 2025-04-01 | cancelled | 2500 |
明細表 order_items(注文の中の商品ごとに1行)には、注文1のために2行(商品p1とp2)があります。
この状況で「件数」と言えそうな数字は3つあります。
- 注文表の行数:注文が何件あったか(8)
- 明細表の行数:商品の行が何行あるか(8)
- 注文した顧客の人数:何人が買ったか(5)
偶然、1と2はどちらも8ですが、意味は別物です。2は「注文1に商品が2行ある」ぶん増え、「明細のない注文(注文6)」ぶん減っています。どの表の何を数えるのかを決めないと、答えが決まりません。
下を実行して、表を眺めてみましょう。-- db: none は「空のデータベースから始める」印です。
-- db: none
CREATE TABLE customers (
customer_id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
prefecture TEXT
);
INSERT INTO customers VALUES
(101,'佐藤','東京都'),(102,'鈴木','大阪府'),(103,'高橋','東京都'),
(104,'田中','愛知県'),(105,'伊藤','東京都'),(106,'渡辺','北海道');
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');
CREATE TABLE order_items (
order_id INTEGER NOT NULL,
line_no INTEGER NOT NULL,
product_id TEXT NOT NULL,
quantity INTEGER NOT NULL,
unit_price INTEGER NOT NULL,
PRIMARY KEY (order_id, line_no)
);
INSERT INTO order_items VALUES
(1,1,'p1',1,2000),(1,2,'p2',2,500),(2,1,'p3',1,1200),(3,1,'p4',1,2500),
(4,1,'p5',1,0),(5,1,'p6',1,1800),(7,1,'p3',1,1200),(8,1,'p7',2,2000);
SELECT * FROM customers;
SELECT * FROM orders;
SELECT * FROM order_items;
直感的な説明:表は名簿、1行は名簿の1人分
表(テーブル)は、同じ項目をもつ記録を並べた名簿のようなものです。
- 横の1本が行(レコード)。名簿の1人分。
- 縦の1本が列(カラム)。名簿の1つの項目(名前、住所など)。
- 名簿には「この名簿の1人分は誰を表すか」が決まっています。顧客名簿なら1行=顧客1人、注文名簿なら1行=注文1件です。
この「1行が表す単位」を粒度(りゅうど、grain)と呼びます。粒度が違う表は、数え方も違います。
| 表 | 1行の単位(粒度) | 同じ顧客は何行? |
|---|---|---|
| customers | 顧客1人 | 1行だけ |
| orders | 注文1件 | 何度も注文すれば複数行 |
| order_items | 注文の中の商品1行 | 1注文に複数行もありえる |
粒度の見分け方は、表の名前、主キー(行を1つに決める列)、列の名前を読むことです。order_items の主キーが (order_id, line_no) の組なら、「注文番号と行番号が決まれば1行に決まる」ので、1行=注文の中の1行だとわかります。
正確な定義:主キー・外部キー・行数と種類数
この授業で使う言葉を、正確に定義します。
- 主キー(primary key):その表の行を1つに特定する列(または列の組)。値は重複せず、NULL(値がない印)にならない。
- 外部キー(foreign key):別の表の主キーを指す列。
orders.customer_idはcustomers.customer_idを指す。これにより、注文から顧客の名前をたどれる。 - 行数:条件を付けないときの、表の行の総数。
COUNT(*)で数える。 - 種類数:重複を除いた値の数。
COUNT(DISTINCT 列)で数える。
$$\text{行数} \ge \text{種類数}$$
主キーの列なら、行数と種類数は一致します。主キーではない列(外部キー customer_id など)では、同じ値が複数行に現れるので、行数のほうが大きくなりえます。
関数の整理:
| 式 | 引数 | 戻り値 | NULLの扱い |
|---|---|---|---|
COUNT(*) |
なし(* は全列の意味) |
整数:行の数 | すべての行を数える |
COUNT(列) |
1つの列 | 整数:その列がNULLでない行の数 | NULLは数えない |
COUNT(DISTINCT 列) |
1つの列 | 整数:NULLを除いた値の種類数 | NULLは数えない |
実行して確認します。
-- db: none
CREATE TABLE customers (
customer_id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
prefecture TEXT
);
INSERT INTO customers VALUES
(101,'佐藤','東京都'),(102,'鈴木','大阪府'),(103,'高橋','東京都'),
(104,'田中','愛知県'),(105,'伊藤','東京都'),(106,'渡辺','北海道');
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');
CREATE TABLE order_items (
order_id INTEGER NOT NULL,
line_no INTEGER NOT NULL,
product_id TEXT NOT NULL,
quantity INTEGER NOT NULL,
unit_price INTEGER NOT NULL,
PRIMARY KEY (order_id, line_no)
);
INSERT INTO order_items VALUES
(1,1,'p1',1,2000),(1,2,'p2',2,500),(2,1,'p3',1,1200),(3,1,'p4',1,2500),
(4,1,'p5',1,0),(5,1,'p6',1,1800),(7,1,'p3',1,1200),(8,1,'p7',2,2000);
SELECT COUNT(*) AS order_rows,
COUNT(DISTINCT order_id) AS order_kinds,
COUNT(DISTINCT customer_id) AS customer_kinds
FROM orders;
結果は order_rows=8、order_kinds=8、customer_kinds=5 です。order_id は主キーなので行数と種類数が同じ、customer_id は同じ顧客が複数回注文するので8行に対して5種類です。
段階付き例題:「注文した顧客は何人?」を3つの表で数える
問題:注文を1件以上した顧客の人数を求める。また、一度も注文していない顧客の人数も求める。
ステップ1:数える対象を決める。「人数」なので数えるのは顧客。顧客の一覧がある表は customers(1行=顧客1人)。
ステップ2:全顧客の人数を数える。customers は主キーが customer_id なので、行数がそのまま人数になる。
ステップ3:注文した顧客の人数を数える。orders は1行=注文1件で、同じ顧客が複数行に現れる。行数(8)を使うと注文件数になってしまうので、COUNT(DISTINCT customer_id) で種類数を数える。
ステップ4:差を求める。注文なしの顧客 = 全顧客 − 注文した顧客。
-- db: none
CREATE TABLE customers (
customer_id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
prefecture TEXT
);
INSERT INTO customers VALUES
(101,'佐藤','東京都'),(102,'鈴木','大阪府'),(103,'高橋','東京都'),
(104,'田中','愛知県'),(105,'伊藤','東京都'),(106,'渡辺','北海道');
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');
CREATE TABLE order_items (
order_id INTEGER NOT NULL,
line_no INTEGER NOT NULL,
product_id TEXT NOT NULL,
quantity INTEGER NOT NULL,
unit_price INTEGER NOT NULL,
PRIMARY KEY (order_id, line_no)
);
INSERT INTO order_items VALUES
(1,1,'p1',1,2000),(1,2,'p2',2,500),(2,1,'p3',1,1200),(3,1,'p4',1,2500),
(4,1,'p5',1,0),(5,1,'p6',1,1800),(7,1,'p3',1,1200),(8,1,'p7',2,2000);
SELECT (SELECT COUNT(*) FROM customers) AS all_customers,
(SELECT COUNT(DISTINCT customer_id) FROM orders) AS ordered_customers,
(SELECT COUNT(*) FROM customers)
- (SELECT COUNT(DISTINCT customer_id) FROM orders) AS never_ordered;
ステップ5:結果を読む。全顧客6人、注文した顧客5人、一度も注文していない顧客1人(106 渡辺)です。計算の式は $6 - 5 = 1$ です。
ステップ6:意味を確認する。購入率(顧客のうち購入した人の割合)は $5 \div 6 \approx 0.833$、つまり83.3%です。もし誤って注文の行数8を人数として使うと $8 \div 6 = 133\%$ となり、100%を超えるため誤りに気づけます。割合が100%を超えたら、分子の数え方を疑うのが検算の基本です。
((SELECT ...) は、小さな問い合わせの結果を1つの値として使う書き方です。詳しくは 「サブクエリ」(有料版) で学びます。)
操作して理解する:表の「キー」と粒度を自分で確かめる
次のSQLを実行し、結果を実行前に予想してから確認してください。
-- db: none
CREATE TABLE customers (
customer_id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
prefecture TEXT
);
INSERT INTO customers VALUES
(101,'佐藤','東京都'),(102,'鈴木','大阪府'),(103,'高橋','東京都'),
(104,'田中','愛知県'),(105,'伊藤','東京都'),(106,'渡辺','北海道');
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');
CREATE TABLE order_items (
order_id INTEGER NOT NULL,
line_no INTEGER NOT NULL,
product_id TEXT NOT NULL,
quantity INTEGER NOT NULL,
unit_price INTEGER NOT NULL,
PRIMARY KEY (order_id, line_no)
);
INSERT INTO order_items VALUES
(1,1,'p1',1,2000),(1,2,'p2',2,500),(2,1,'p3',1,1200),(3,1,'p4',1,2500),
(4,1,'p5',1,0),(5,1,'p6',1,1800),(7,1,'p3',1,1200),(8,1,'p7',2,2000);
-- (1) 明細表の行数と、明細のある注文の種類数
SELECT COUNT(*) AS item_rows, COUNT(DISTINCT order_id) AS orders_with_items
FROM order_items;
-- (2) 明細が1行もない注文はどれ?
SELECT order_id FROM orders
WHERE order_id NOT IN (SELECT order_id FROM order_items);
-- (3) 同じ (order_id, line_no) が2行以上ある? 0行なら主キーが守られている
SELECT order_id, line_no, COUNT(*) AS n
FROM order_items GROUP BY order_id, line_no HAVING COUNT(*) > 1;
答えと読み方:
- (1) 明細8行、明細のある注文は7種類。注文表は8行なので、1注文ぶんは明細がありません。
- (2) 注文6です(金額が未記録の注文)。明細のない注文があるので、「注文表と明細表を結合した行数」は注文数と一致しません。
- (3) 0行です。主キーが守られているので、(注文番号, 行番号) で1行に決まります。
自分でも試してみましょう:(1) の COUNT(DISTINCT order_id) を COUNT(DISTINCT product_id) に変えると、商品の種類数(p1〜p7で7)が出ます。同じ表でも、数える列を変えると数えている対象が変わります。
発展:キーが守られない表と「見かけの1行」
実際の業務データでは、主キーを宣言していない表や、宣言していても意味の上で重複している表があります。
- 注文の取込を2回実行して、同じ注文が2行ずつ入っている。
order_idが主キーでなければ登録できてしまう。 - 履歴型の表(顧客の住所変更を、変更のたびに新しい行で追加する)。1行=「顧客のある時点の状態」であり、顧客1人が複数行になる。
- 集計済みの表(日次売上)。1行=1日であり、注文や顧客の数をこの表から直接は数えられない。
どの場合も、使う前に次の3点を確認する習慣を付けます。
- 主キー(またはそれに相当する列)で行数と種類数が一致するか。
- 1行=何かを、列名とサンプル行から言葉にできるか。
- 期間や状態(取消など)で、行が追加・上書き・削除されるのか。
なお、データベースの方言(MySQL、PostgreSQL、SQLiteなど)によって主キーの扱い(NULLの許容など)に細かな違いがあります。ここではSQLiteで説明しています。
よくある失敗
失敗1:COUNT(*) を「顧客数」として報告する
-- db: none
CREATE TABLE customers (
customer_id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
prefecture TEXT
);
INSERT INTO customers VALUES
(101,'佐藤','東京都'),(102,'鈴木','大阪府'),(103,'高橋','東京都'),
(104,'田中','愛知県'),(105,'伊藤','東京都'),(106,'渡辺','北海道');
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');
CREATE TABLE order_items (
order_id INTEGER NOT NULL,
line_no INTEGER NOT NULL,
product_id TEXT NOT NULL,
quantity INTEGER NOT NULL,
unit_price INTEGER NOT NULL,
PRIMARY KEY (order_id, line_no)
);
INSERT INTO order_items VALUES
(1,1,'p1',1,2000),(1,2,'p2',2,500),(2,1,'p3',1,1200),(3,1,'p4',1,2500),
(4,1,'p5',1,0),(5,1,'p6',1,1800),(7,1,'p3',1,1200),(8,1,'p7',2,2000);
-- 誤り:注文表の行数は「注文件数」。顧客数ではない
SELECT COUNT(*) AS customers_wrong FROM orders;
-- 正しい:顧客の種類数
SELECT COUNT(DISTINCT customer_id) AS customers_right FROM orders;
誤りは8、正しくは5です。注文表の1行は注文1件なので、同じ顧客(101、102、103)が複数回数えられています。直し方:数えたい対象(顧客)の種類数を COUNT(DISTINCT customer_id) で数えるか、顧客表の行数を使う。
失敗2:粒度の違う表をそのまま足し算の元にする
注文表の amount(注文の合計金額)と、明細表の unit_price(商品1個の単価)は粒度も意味も違います。明細表の unit_price をそのまま合計しても、売上にはなりません。
-- db: none
CREATE TABLE customers (
customer_id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
prefecture TEXT
);
INSERT INTO customers VALUES
(101,'佐藤','東京都'),(102,'鈴木','大阪府'),(103,'高橋','東京都'),
(104,'田中','愛知県'),(105,'伊藤','東京都'),(106,'渡辺','北海道');
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');
CREATE TABLE order_items (
order_id INTEGER NOT NULL,
line_no INTEGER NOT NULL,
product_id TEXT NOT NULL,
quantity INTEGER NOT NULL,
unit_price INTEGER NOT NULL,
PRIMARY KEY (order_id, line_no)
);
INSERT INTO order_items VALUES
(1,1,'p1',1,2000),(1,2,'p2',2,500),(2,1,'p3',1,1200),(3,1,'p4',1,2500),
(4,1,'p5',1,0),(5,1,'p6',1,1800),(7,1,'p3',1,1200),(8,1,'p7',2,2000);
-- 誤り:単価の合計。数量を掛けていないので、注文1の金額が合わない
SELECT SUM(unit_price) AS wrong_total FROM order_items WHERE order_id = 1;
-- 正しい:数量×単価の合計
SELECT SUM(quantity * unit_price) AS right_total FROM order_items WHERE order_id = 1;
誤りは2500(2000+500)、正しくは3000(2000×1 + 500×2)で、注文表の amount(3000)と一致します。直し方:明細の売上は「数量×単価」の合計で出し、注文表の金額と照合する。
失敗3:0・NULL・空欄を「同じ未入力」とみなす
注文4の金額は 0(無料サンプル)、注文6の金額は NULL(未記録)、注文7のクーポン欄は空文字 '' です。この3つは別の意味です。
-- db: none
CREATE TABLE customers (
customer_id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
prefecture TEXT
);
INSERT INTO customers VALUES
(101,'佐藤','東京都'),(102,'鈴木','大阪府'),(103,'高橋','東京都'),
(104,'田中','愛知県'),(105,'伊藤','東京都'),(106,'渡辺','北海道');
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');
CREATE TABLE order_items (
order_id INTEGER NOT NULL,
line_no INTEGER NOT NULL,
product_id TEXT NOT NULL,
quantity INTEGER NOT NULL,
unit_price INTEGER NOT NULL,
PRIMARY KEY (order_id, line_no)
);
INSERT INTO order_items VALUES
(1,1,'p1',1,2000),(1,2,'p2',2,500),(2,1,'p3',1,1200),(3,1,'p4',1,2500),
(4,1,'p5',1,0),(5,1,'p6',1,1800),(7,1,'p3',1,1200),(8,1,'p7',2,2000);
SELECT order_id, amount, coupon_code,
amount IS NULL AS amount_is_null,
coupon_code IS NULL AS coupon_is_null,
coupon_code = '' AS coupon_is_empty
FROM orders WHERE order_id IN (4, 6, 7);
注文4は金額0(NULLではない)、注文6は金額NULL、注文7のクーポンは空文字(NULLではない)です。「0」「欠損」「空欄」を一括りに集計すると、平均やデータ品質の数字が狂います。直し方:列ごとに「0は有効な値か」「空文字を欠損とみなすか」を決め、その定義を書いておく。詳しくは NULLと3値論理。
まとめ
- 表の1行が何を表すか(粒度)を最初に確認する。名前、主キー、列名、サンプル行から読み取る。
- 主キーは行を1つに決める列で、重複もNULLもない。外部キーは別の表の主キーを指す。
- 行数は COUNT(*)、種類数は COUNT(DISTINCT 列)。数えたい対象に合わせて使い分ける。
- 0・NULL・空文字は別の意味。集計前に区別して定義する。
- 割合が100%を超えたら、分子と分母の数え方を疑う。
用語
- テーブル(表)(table)
同じ項目(列)をもつ記録(行)を並べたもの。データベースの基本単位。
- 粒度(grain)
表の1行が表す単位(顧客1人、注文1件、明細1行など)。
- 主キー(primary key)
表の行を1つに特定する列(または列の組)。重複もNULLも許されない。
- 外部キー(foreign key)
別の表の主キーを指す列。表どうしの関係を表す。
- COUNT(DISTINCT 列)(COUNT DISTINCT)
重複を除いた値の種類数を数える。NULLは数えない。
よくある質問
なぜSQLを書く前に「1行の単位」を確認するのですか?
数える対象が決まらないと、COUNTやSUMの結果がどの量を表すのかが決まらないからです。構文が正しくても、顧客数のつもりで注文件数を出す事故が起きます。
COUNT(*) と COUNT(列) は何が違いますか?
COUNT(*) は行の総数、COUNT(列) はその列がNULLでない行の数です。NULLがない列なら同じ値になりますが、NULLがあると差が出ます。未入力の割合を調べるときにも使えます。
COUNT(DISTINCT 列) はいつも使えばよいですか?
いいえ。主キーの列では行数と同じなので不要です。重複の原因(結合で行が増えた等)を隠すために使うと、本物の重複まで消えて誤りの原因になります。何を数えたいかが「種類数」のときだけ使います。
主キーが宣言されていない表では、どうやって1行の単位を確認しますか?
列名とサンプル行から仮説を立て、COUNT(*) と COUNT(DISTINCT 候補の列) を比べます。一致すればその列は一意である可能性が高いです。それでも業務の担当者に定義を確認するのが確実です。
外部キーを宣言しておくと何が良いのですか?
存在しない顧客IDを持つ注文を登録できなくなり、データの整合性が守られます。また、表どうしの関係が読み手にわかり、結合の条件を間違えにくくなります。ただし分析用に取り込んだ表では宣言がないこともあるため、確認が必要です。
仕事の数字の「件数」はどう確認すべきですか?
報告する前に「何の1件か」を言葉にします。KPI定義書に『注文件数=注文表の取消を除く行数』のように、表・条件・粒度を書いておけば、担当者が変わっても同じ数字になります。
確認問題
問1 注文表(1行=注文1件)で、同じ顧客が複数回注文している。「顧客数」を最も適切に数える式はどれか。
- ① SUM(amount)
- ② COUNT(*)
- ③ COUNT(DISTINCT customer_id)
- ④ COUNT(order_id)
解答と解説
正解:③
同じ顧客が複数行に現れるので、重複を除いた種類数を数えます。
問2 「粒度(1行の単位)」の説明として最も適切なものはどれか。
- ① 表の1行が何を表すか
- ② 表に入れられる最大行数
- ③ 数値の小数点以下の桁数
- ④ 列の数
解答と解説
正解:①
粒度は、1行が顧客1人なのか注文1件なのかなど、行が表す単位のことです。
問3 主キーの性質として正しいものはどれか。
- ① 必ず数値でなければならない
- ② 同じ値が複数行にあってよい
- ③ NULLでもよい
- ④ 行を1つに特定でき、値が重複しない
解答と解説
正解:④
主キーは行を一意に決める列で、重複もNULLも許されません。
問4 明細表(1行=注文の中の商品1行)の行数が8、COUNT(DISTINCT order_id) が7だった。ここから言えることはどれか。
- ① 注文は8件ある
- ② 注文は7件で、そのうち1つ以上の注文に複数行の明細がある
- ③ 明細表には主キーがない
- ④ 顧客は7人いる
解答と解説
正解:②
行数が種類数より多いのは、同じ注文の明細が複数行あるためです。7は明細のある注文の種類数です。
問5 閲覧ログの user_id 列に NULL(未ログイン)が含まれる。COUNT(DISTINCT user_id) の結果について正しいものはどれか。
- ① エラーになる
- ② NULLを0として数える
- ③ NULLも1種類として数える
- ④ NULLは数えない
解答と解説
正解:④
COUNT(列) と COUNT(DISTINCT 列) はNULLを数えません。
問6 購入者数が5、全顧客数が6のとき購入率はいくつか。また、誤って注文の行数8を分子にするとどうなるか。
- ① 83.3%。誤ると133%になり、100%超で誤りに気づける
- ② 16.7%。誤ると75%になる
- ③ 50%。誤ると100%になる
- ④ 83.3%。誤りでも100%以下なので気づけない
解答と解説
正解:①
5÷6≒0.833。8÷6≒1.33で、割合が100%を超えるのは分子が違う印です。
次に学ぶこと
次は SELECTと列 で、SELECTで必要な列だけを取り出し、列名を付け、式で新しい列を作ります。条件で行を選ぶ方法は WHEREと条件 で扱います。
この授業の確認問題だけで習得とはしません。モジュール テーブル・SELECT・条件抽出 の総合テストと、別データの自力課題で判定します。
キャリア学習:データサイエンティスト・機械学習エンジニア・DX推進・研究者を目指す人の学習アプリ。164モジュール・984授業を、ブラウザで動くSQLとPythonの演習で。基礎と各分野の最初のモジュールは無料。全部は2,980円の買い切り。
アプリを無料で開く有料版と無料版の違い