【SQL】SQLのCOUNT関数の使い方と解説

データベース データベース
主にデータベースに関する記事

SQLの集計関数のひとつであるCOUNT関数。COUNT(*)しか使ったことないので、実は良く知らない方もいるのではないでしょうか。このCOUNT関数の引数の違いを理解して、テーブル内のレコード数を正しくカウントできるように解説をします。そして、それぞれをいつ使うべきかの指針も合わせて解説していきます。

COUNT関数とは

SELECTステートメントで取得された行に含まれる引数の非NULL値のレコード数を返します。 結果はBIGINTになります。
また、COUNT(*) は、NULL値が含まれるかどうかに関係なく、取得されたレコード数を返します。

COUNT関数の基本

COUNT関数の基本構文

SELECT COUNT(引数) FROM テーブル名;

COUNT関数の引数

COUNT関数は指定したの引数を何にするかによって結果が変わってきます。

COUNT関数の引数説明
COUNT(*)どの列にNULL値があっても、テーブル内の全てのレコード数1もほぼ同義。
COUNT(カラム名)指定した列に含まれる非NULL値のレコード数
COUNT(DISTINCT カラム名)NULLと重複を除いたレコード数。カラム名は複数指定可能

よくある勘違い

COUNT(*)は各行のすべての列を読み取る必要があるが、 COUNT(カラム名) では特定の列のみを読み取るため、COUNT(カラム名) の方が高速だと思っている方がいたりします。
ですが、これは正しい場合もありますが、全然逆の場合もあります。こちらは詳細な解説にて最後に説明します。

COUNT関数の実際の使い方と応用

基本的な使い方を解説したので、実例を通して使い方を解説していきます。

テスト用のサンプルテーブルとレコード

実例を解説するにあたり、利用するサンプルのテーブルを用意します。こちらを元に解説していきます。
ご自身で試したい場合はSQL文も用意していますので、ご利用ください。

ID: id商品名: productカテゴリ: category価格: price
1AppleFruitNULL
2TomatoVegetable25
3PotatoVegetable25
4HoneyNULL40
5ChocolateNULLNULL
6HoneyNULL40
7CarrotVegetable40
テーブル作成
CREATE TABLE IF NOT EXISTS `orders` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `product` VARCHAR(255) NOT NULL,
  `category` VARCHAR(255),
  `price` INT,
  PRIMARY KEY(`id`)
);
レコード作成
INSERT INTO `orders` (`product`, `category`, `price`) VALUES
  ("Apple", "Fruit", null),
  ("Tomato", "Vegetable", 25),
  ("Potato", "Vegetable", 25),
  ("Honey", null, 40),
  ("Chocolate", null, null),
  ("Honey", null, 40),
  ("Carrot", "Vegetable", 40);

実用例

レコード数の取得

SELECT COUNT(*) FROM テーブル名;
SELECT COUNT(*) from `orders`;
+----------+
| count(*) |
+----------+
|        7 |
+----------+

NULLを含まないレコード数の取得

SELECT COUNT(カラム名) FROM テーブル名;
SELECT COUNT(category) from `orders`;
+-----------------+
| COUNT(category) |
+-----------------+
|               4 |
+-----------------+

重複を除いたレコード数の取得

SELECT COUNT(DISTINCT カラム名) FROM テーブル名;

指定したカラムの値が非NULLのレコード、かつカラム名の値の重複を除去したレコード数を取得します。

SELECT COUNT(DISTINCT category) FROM `orders`;
+--------------------------+
| COUNT(DISTINCT category) |
+--------------------------+
|                        2 |
+--------------------------+

categoryがNULLを除いた種類数を取得できます。

重複を除いたレコード数の取得(複数のカラム)

SELECT COUNT(DISTINCT カラム名,カラム名...) FROM テーブル名;

指定したカラムの値がどれも非NULLのレコード、かつカラム名の値の重複を除去したレコード数を取得します。

SELECT COUNT(DISTINCT category,price) FROM `orders`;
+--------------------------------+
| COUNT(DISTINCT category,price) |
+--------------------------------+
|                              2 |
+--------------------------------+

categoryもpriceもNULLではなく、重複を除去したレコード数を取得しています。

グループ単位でのレコード数の取得

SELECT COUNT(*) FROM テーブル名 GROUP BY カラム名;

カラム名でグルーピングした後に、それぞれのレコード数をカウントします。

SELECT category,COUNT(*) FROM `orders` GROUP BY category;
+-----------+----------+
| category  | COUNT(*) |
+-----------+----------+
| Fruit     |        1 |
| Vegetable |        3 |
| NULL      |        3 |
+-----------+----------+

categoryでグルーピングして、カテゴリ毎のレコード数を取得しています。
分析などでよく利用するので、是非覚えておきましょう。

条件を指定してレコード数を取得

SELECTCOUNT(*) FROM `orders` WHERE 条件;
SELECTCOUNT(*) FROM `orders` WHERE category="Vegetable";
+----------+
| COUNT(*) |
+----------+
|        3 |
+----------+

条件を指定して、その結果のレコード数を取得しています。
こちらもGROUP BYを利用したものと合わせて使うことで、よく分析などで利用します。

COUNT関数の詳細な挙動の解説

MySQLは行全体を読まずに1つの列の内容だけを読むことはできないので、SQLはデータを含む行をディスク上の約8KBのデータページに保存します。これらのページは1つ以上の行を含み、内部メモリ (RAM) に配置されます。
1つの列(または複数)の値を確認するには、ページ全体をディスクから読み取ってメモリに配置する必要があります。

テーブルの中に何行あるかを調べたいだけなのに、データページ全体を読むのは非効率的です。そこでSQLは、インデックス存在する場合、代わりにそのインデックスを使用するようにします。

列名にインデックスがある場合、COUNT(カラム名)はそのインデックスを使用してレコード数をカウントします。
しかし、COUNT(カラム名)で指定した列名にインデックスがない場合は、データページを読んでNULL値をチェックし、レコード数をカウントします。

一方COUNT(*)は、インデックスが他の列で指定されていても、そのインデックスを使ってレコード数をカウントします。

つまり、 COUNT(カラム名) は指定したカラムにインデックスがある場合のみ使用し、COUNT(*)はすべての列をスキャンしてインデックスがある場合に使用します。

そのため、列名にインデックスがない場合、COUNT(*)の方がCOUNT(カラム名)よりも高速になる場合があります。

まとめ

COUNT関数を解説しました。引数の指定方法による違いをよく理解し、誤った利用をしないように注意して、パフォーマンスにも注意しながら利用しましょう。

参考資料

MySQL 8.0 リファレンスマニュアル 集計関数の説明 COUNT()

MySQL 8.0 リファレンスマニュアル 集計関数の説明 COUNT(DISTINCT)

MySQL 8.0 リファレンスマニュアル GROUP BYの処理

役にたったと思ったら応援をお願いします m(._.)m

著者

Webエンジニア歴30年、フリーランスバックエンドエンジニア。

PHP歴約30年(Laravel 7年・FuelPHP 5年・CakePHP・自作FW)、
JavaScript歴約20年(React・Vue各4年)。
AWS(EC2 / CloudFront / RDS / API Gateway など)・
GCP(BigQuery)を使ったバックエンド開発を中心に、
複数の事業会社・受託案件でシステム設計から実装・運用まで担当しています。

PHPがバージョン4の時代から書いており、
Laravelが普及する前のフレームワーク乱立期も経験しています。
「昔はこう書いていたが今はこう」という変遷を肌で知っているエンジニアとして、
単なるコマンドの使い方だけでなく、なぜそうするのかの背景まで伝えることを意識して書いています。

このブログでは、実務で実際に詰まった箇所・調べたこと・気づいたことを
そのまま記事にしています。誰かの「詰まり」が解決するきっかけになれば幸いです。

千原 耕司をフォローする

役にたったと思ったら応援をお願いします m(._.)m

データベース
スポンサーリンク
シェアする
千原 耕司をフォローする
タイトルとURLをコピーしました