集計関数 COUNT/SUM/AVG/MAX/MIN
レビューは 100 件あるのに、平均の分母が 60 だった
レビューの表に 100 行入っています。「平均点は」と聞かれて AVG(score) を書き、返ってきた 4.2 をそのまま報告した。あとで「100 人の平均ですか」と確認されて、答えに詰まる。よくある事故です。
reviews テーブルの score 列は、点数を付けずコメントだけ残した人が空 (NULL) になっているとします。ここで数え方を 2 つ並べてみます。
SQL クエリ
SELECT
COUNT(*) AS row_count,
COUNT(score) AS scored_count,
AVG(score) AS avg_score
FROM reviews;| row_count | scored_count | avg_score |
|---|---|---|
| 100 | 60 | 4.2 |
COUNT(*) は行を数えます。中身が何であろうと、1 行は 1 行なので 100 です。COUNT(score) は score に値が入っている行だけを数えるので 60 です。そして AVG(score) の分母も 60 でした。4.2 は「100 人の平均」ではなく「点数を付けた 60 人の平均」だったわけです。
括弧の中が * か列名かで、意味が変わる
COUNT は、括弧の中に何を書くかで数える対象が変わります。* なら行そのもの、列名ならその列に値が入っている行です。SUM や AVG、MAX、MIN も同じで、値のない行は計算から外します。
この性質は、裏返すと列の埋まり具合を測る道具になります。
SQL クエリ
SELECT
COUNT(*) AS all_members,
COUNT(birthday) AS birthday_filled
FROM members;2 つの数字が離れているほど、その列は空のままです。集計を報告する前にこの形で 1 度確かめておくと、「平均が思ったより高い」といった相談の半分はここで説明が付きます。
1 行も無いとき、合計は 0 ではない
WHERE で絞った結果が 0 行だったとき、COUNT(*) は 0 を返します。ところが SUM は 0 ではなく空 (NULL) を返します。数える対象が 1 つもないので、合計という答え自体が存在しないからです。
合計 0 円のつもりで受け取った値が空で、アプリ側の計算が壊れる、という形で表に出ます。
SQL クエリ
SELECT COALESCE(SUM(amount), 0) AS total
FROM payments
WHERE paid_at >= DATE '2026-04-01';COALESCE は、左から順に見て最初に空でない値を返す関数です。金額の合計を必ず数値として受け取りたいときは、こうして包んでおきます。
テーブル構造
CREATE TABLE products (
id INT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
category VARCHAR(30) NOT NULL,
price INT NOT NULL,
stock INT NOT NULL
);
INSERT INTO products (id, name, category, price, stock) VALUES
(1, 'ノートPC', '家電', 128000, 5),
(2, 'マウス', '家電', 2980, 30),
(3, 'キーボード', '家電', 6800, 12),
(4, '小説A', '書籍', 1800, 50),
(5, '小説B', '書籍', 2200, 20),
(6, 'コーヒー豆', '食品', 1500, 40),
(7, '紅茶葉', '食品', 1200, 25);期待される出力
| cnt | total_price | avg_price | max_price | min_price |
|---|---|---|---|---|
| 7 | 144480 | 20640.000000000000 | 128000 | 1200 |