【コピペで動く】SQLのCOUNT関数を徹底解説!基本から応用まで
Webサイトやアプリケーションを開発していると、「ユーザーの総数は?」「特定の商品カテゴリには何種類の商品がある?」といった、データの「個数」を知りたい場面が頻繁に訪れます。そんな時に大活躍するのが、SQLのCOUNT()関数です。
COUNT()は、テーブル内のレコード(行)数を数えるための集計関数。一見シンプルですが、使い方次第で様々な情報を引き出すことができる、非常に奥が深い関数でもあります。この記事では、Webクリエーターを目指す初心者の方でもすぐに使えるように、基本的な使い方から実用的な応用例まで、たくさんの「コピペで動くコード」と共に解説していきます。
この記事の目標は、皆さんに「SQLって、書いたら本当に動くんだ!」という感動を体験してもらうことです。難しい理論は後回し!まずはコードをコピーして、動かして、結果を確かめながらSQLの面白さを感じていきましょう。
まずは準備運動!サンプルデータを用意しよう
SQLを試すには、データが入ったテーブルが必要です。今回は、簡単な「ユーザーリスト」を想定したusersテーブルを作成しましょう。以下のSQL文は、テーブルを作成し、サンプルデータを挿入するためのものです。
この記事で紹介するコードは、すべてこのusersテーブルを対象としています。
-- テーブルが存在すれば削除(何回も試せるように)
DROP TABLE IF EXISTS users;
-- usersテーブルを作成
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
prefecture TEXT NOT NULL,
email TEXT
);
-- データを挿入
INSERT INTO users (id, name, prefecture, email) VALUES
(1, '山田 太郎', '東京', 'yamada@example.com'),
(2, '鈴木 花子', '大阪', 'suzuki@example.com'),
(3, '佐藤 次郎', '東京', NULL),
(4, '伊藤 さくら', '福岡', 'ito@example.com'),
(5, '渡辺 三郎', '北海道', 'watanabe@example.com'),
(6, '高橋 四郎', '東京', 'takahashi@example.com'),
(7, '田中 美咲', '大阪', NULL);
このテーブルには、ID、名前、出身地、そしてメールアドレスのカラムがあります。ポイントは、IDが3番の佐藤さんと7番の田中さんのemailがNULL(つまり、データが空)になっている点です。これが後々、COUNT()関数の挙動を理解する上で重要になります。
【基本編1】すべてのレコード数を数える `COUNT(*)`
まずは最も基本的な使い方、COUNT(*)です。アスタリスク(*)は「すべてのカラム」を意味し、COUNT(*)はテーブル内の全レコード数を返します。シンプルに「このテーブルにデータは何件ある?」と聞きたいときに使います。
早速、usersテーブルの全ユーザー数を数えてみましょう。
SELECT COUNT(*) FROM users;
実行結果:
7
先ほど7人分のデータを挿入したので、正しく「7」という結果が返ってきました。これがCOUNT()の基本の「き」です。
【基本編2】特定のカラムを指定して数える `COUNT(column_name)`
次に、カラム名を指定して数える方法COUNT(column_name)を見ていきましょう。これは、指定したカラムに「値が入っている」レコードの数を数えます。
ここが重要なポイントなのですが、COUNT(column_name)はNULL値を無視します。つまり、データが空のレコードはカウントしません。
先ほどのusersテーブルで、メールアドレスが登録されているユーザーの数だけを数えてみましょう。
SELECT COUNT(email) FROM users;
実行結果:
5
結果は「5」となりました。COUNT(*)の「7」と数が合いませんね。これは、emailカラムがNULLである2人(佐藤さんと田中さん)がカウントから除外されたためです。
このように、COUNT(*)は単純な行数、COUNT(column_name)は「そのカラムにデータが存在する行数」を返す、という違いをしっかり覚えておきましょう。
【応用編1】結果に別名を付ける `AS`
COUNT()の結果は、デフォルトだとCOUNT(*)やCOUNT(email)といった、少し分かりにくいカラム名で表示されます。これでは、後でデータを扱う際に不便なことがあります。
そんな時はASを使って、結果のカラムに分かりやすい別名(エイリアス)を付けましょう。
SELECT COUNT(*) AS total_users FROM users;
実行結果:
total_users
-----------
7
AS total_usersと記述したことで、結果のカラム名がtotal_usersに変わりました。これで、この結果が「全ユーザー数」であることが一目瞭然ですね。プログラムで結果を利用する際にも、result['total_users']のように直感的にアクセスできるようになり、非常に便利です。
【応用編2】重複を除いて数える `COUNT(DISTINCT column_name)`
次に、Webサイト分析などで非常によく使うCOUNT(DISTINCT)です。DISTINCTは「重複を除外する」という意味のキーワードで、これを使うと「ユニークなデータの種類」を数えることができます。
例えば、「このサイトのユーザーは、全部で何種類の都道府県からアクセスしているんだろう?」という疑問に答えることができます。
usersテーブルには「東京」が3人、「大阪」が2人いますが、出身地の「種類」として数えてみましょう。
SELECT COUNT(DISTINCT prefecture) AS unique_prefectures FROM users;
実行結果:
unique_prefectures
------------------
4
結果は「4」となりました。usersテーブルの出身地は「東京」「大阪」「福岡」「北海道」の4種類なので、正しく種類だけを数えられていることが分かります。
ECサイトで「取り扱い商品のカテゴリ数」を調べたり、アクセスログから「ユニークな訪問者数」を割り出したりと、用途は無限大です。
【応用編3】グループごとに集計する `GROUP BY`
COUNT()の真価が発揮されるのが、GROUP BY句との組み合わせです。GROUP BYを使うと、指定したカラムの値が同じレコードをグループ化し、そのグループごとにCOUNT()を適用できます。
「出身地ごとのユーザー数を知りたい」――これはまさにGROUP BYの出番です。
SELECT
prefecture,
COUNT(*) AS user_count
FROM
users
GROUP BY
prefecture;
実行結果:
prefecture | user_count
-----------|------------
大阪 | 2
北海道 | 1
東京 | 3
福岡 | 1
見事に、出身地ごとのユーザー数を一覧で取得できました!prefectureカラムでグループを作り、それぞれのグループに含まれるレコード数をCOUNT(*)で数えています。
「どの地域のユーザーが多いのか?」といったサイト分析の第一歩となる、非常に重要なテクニックです。
【発展編】集計結果に条件を付ける `HAVING`
GROUP BYで集計した結果に対して、さらに条件を絞り込みたい場合もあります。例えば、「ユーザーが2人以上いる都道府県だけを表示したい」といったケースです。
ここで注意が必要なのは、レコードを絞り込むWHERE句はGROUP BYによる集計前に処理されるため、集計結果(例: `user_count`)に対しては使えない、という点です。
集計後の結果に条件を付けたい場合は、HAVING句を使います。
SELECT
prefecture,
COUNT(*) AS user_count
FROM
users
GROUP BY
prefecture
HAVING
user_count >= 2;
実行結果:
prefecture | user_count
-----------|------------
大阪 | 2
東京 | 3
HAVING user_count >= 2という条件を追加したことで、ユーザー数が2人以上である「大阪」と「東京」だけが抽出されました。
WHEREは「集計する前の個々のレコード」に対する条件、HAVINGは「GROUP BYで集計した後のグループ」に対する条件、と覚えておきましょう。
【体験コーナー】ブラウザでSQLを動かしてみよう!
お待たせしました!ここで、これまで学んだSQLを実際にあなたの手で動かせる環境を用意しました。
以下のHTMLコードを丸ごとコピーして、sql_test.htmlのような名前でファイルに保存し、ブラウザで開いてみてください。記事で紹介したusersテーブルがすでに用意されているので、色々なSQLを試して結果の変化を楽しんでみましょう!
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SQL実行環境 for COUNT()</title>
<style>
body { font-family: sans-serif; line-height: 1.6; color: #333; max-width: 800px; margin: 2rem auto; padding: 0 1rem; }
h1 { color: #444; }
textarea { width: 100%; height: 150px; font-family: monospace; font-size: 16px; padding: 10px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; margin-bottom: 1rem; }
button { background-color: #007bff; color: white; border: none; padding: 10px 20px; font-size: 16px; border-radius: 4px; cursor: pointer; }
button:hover { background-color: #0056b3; }
button:disabled { background-color: #ccc; cursor: not-allowed; }
#result-container { margin-top: 2rem; border: 1px solid #ddd; padding: 1rem; border-radius: 4px; background: #f9f9f9; min-height: 50px; }
#error-message { color: #d9534f; font-weight: bold; }
table { border-collapse: collapse; width: 100%; margin-top: 1rem; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #f2f2f2; }
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.10.3/sql-wasm.js"></script>
</head>
<body>
<h1>SQLを試してみよう!</h1>
<p>下のテキストエリアにSQL文を入力して「実行」ボタンを押してください。記事で紹介した色々なSQLを試してみましょう!</p>
<textarea id="sql-input">SELECT prefecture, COUNT(*) AS user_count
FROM users
GROUP BY prefecture
HAVING user_count >= 2;</textarea>
<button id="execute-btn">実行</button>
<div id="result-container">
<p id="error-message"></p>
<div id="result-output"></div>
</div>
<script>
const sqlInput = document.getElementById('sql-input');
const executeBtn = document.getElementById('execute-btn');
const errorMsg = document.getElementById('error-message');
const resultOutput = document.getElementById('result-output');
let db;
async function initDb() {
executeBtn.disabled = true;
executeBtn.textContent = '準備中...';
try {
const SQL = await initSqlJs({
locateFile: file => `https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.10.3/${file}`
});
db = new SQL.Database();
// Setup initial table and data
const setupSql = `
DROP TABLE IF EXISTS users;
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
prefecture TEXT NOT NULL,
email TEXT
);
INSERT INTO users (id, name, prefecture, email) VALUES
(1, '山田 太郎', '東京', 'yamada@example.com'),
(2, '鈴木 花子', '大阪', 'suzuki@example.com'),
(3, '佐藤 次郎', '東京', NULL),
(4, '伊藤 さくら', '福岡', 'ito@example.com'),
(5, '渡辺 三郎', '北海道', 'watanabe@example.com'),
(6, '高橋 四郎', '東京', 'takahashi@example.com'),
(7, '田中 美咲', '大阪', NULL);
`;
db.run(setupSql);
executeBtn.disabled = false;
executeBtn.textContent = '実行';
resultOutput.innerHTML = '<p>準備完了!SQLを入力して実行してください。</p>';
} catch (err) {
errorMsg.textContent = 'データベースの初期化に失敗しました: ' + err.message;
console.error(err);
}
}
function executeSql() {
if (!db) return;
const sql = sqlInput.value;
errorMsg.textContent = '';
resultOutput.innerHTML = '';
try {
const results = db.exec(sql);
if (results.length === 0) {
resultOutput.innerHTML = '<p>クエリは成功しましたが、結果セットは返されませんでした。(例: INSERT, UPDATEなど)</p>';
return;
}
results.forEach(result => {
const table = document.createElement('table');
const thead = document.createElement('thead');
const tbody = document.createElement('tbody');
const headerRow = document.createElement('tr');
result.columns.forEach(colName => {
const th = document.createElement('th');
th.textContent = colName;
headerRow.appendChild(th);
});
thead.appendChild(headerRow);
result.values.forEach(row => {
const bodyRow = document.createElement('tr');
row.forEach(cellValue => {
const td = document.createElement('td');
td.textContent = cellValue === null ? 'NULL' : cellValue;
bodyRow.appendChild(td);
});
tbody.appendChild(bodyRow);
});
table.appendChild(thead);
table.appendChild(tbody);
resultOutput.appendChild(table);
});
} catch (err) {
errorMsg.textContent = 'SQLエラー: ' + err.message;
console.error(err);
}
}
executeBtn.addEventListener('click', executeSql);
initDb();
</script>
</body>
</html>
【試してみよう!】
- メールアドレスが登録されていないユーザーの数を数えてみましょう。(ヒント: `COUNT(*)` と `COUNT(email)` の差)
- 出身地が「東京」のユーザーだけを数えてみましょう。(ヒント: `WHERE`句を使います)
- 自分で新しい`INSERT`文を実行してデータを追加し、その後に`COUNT(*)`で数が増えるか確認してみましょう。
気をつけるべき点と豆知識
COUNT(*)vsCOUNT(1)vsCOUNT(column_name)-
COUNT(1)という書き方を見かけることがあります。これは、各行に対して「1」という定数を割り当てて、その数を数えるという意味です。ほとんどのデータベースでは、COUNT(*)とCOUNT(1)の動作やパフォーマンスに違いはありません。どちらも全行を数えます。COUNT(column_name)は前述の通りNULLを数えないので、目的が違うことを再度確認しましょう。迷ったら、意図が明確なCOUNT(*)(全行を数えたい場合)かCOUNT(column_name)(NULL以外を数えたい場合)を使うのがおすすめです。 - パフォーマンス
-
数百万、数千万件といった巨大なテーブルに対して
COUNT(*)を実行すると、時間がかかることがあります。特に条件を指定しない全件カウントは、テーブルの全データをスキャンする必要があるためです。頻繁に全件数を取得する必要がある場合は、別の方法(サマリーテーブルを別途用意するなど)が検討されることもありますが、まずは基本的な使い方として覚えておけば問題ありません。
関連する仲間たち:他の集計関数
COUNT()を覚えたら、他の集計関数を学ぶのも簡単です。これらもGROUP BYと組み合わせて使うと非常に強力です。
SUM(column_name): 数値カラムの合計値を計算します。(例: 売上合計)AVG(column_name): 数値カラムの平均値を計算します。(例: 平均年齢、平均単価)MAX(column_name): カラムの最大値を取得します。(例: 最高気温、最高得点)MIN(column_name): カラムの最小値を取得します。(例: 最低価格)
例えば、ユーザーIDの最大値を知りたい場合は、以下のようになります。
SELECT MAX(id) AS latest_user_id FROM users;
まとめ
今回は、レコード数を数えるCOUNT()関数について、基本的な使い方から応用テクニックまでを駆け足で見てきました。
COUNT(*): テーブルの全レコード数を数える。COUNT(column_name): 指定したカラムのNULLではないレコード数を数える。COUNT(DISTINCT column_name): 重複を除いたユニークなデータの種類を数える。AS alias: 結果のカラムに分かりやすい別名を付ける。GROUP BY: グループごとに集計する際にCOUNT()と組み合わせる。
COUNT()は、データを分析し、インサイトを得るための第一歩です。この記事のコードを何度も試して、「データを数える」感覚をぜひ掴んでみてください。SQLが使えるようになると、見える世界がぐっと広がります。これからも楽しみながら学習を続けていきましょう!