🇯🇵 日本語 | 🇺🇸 English | 🇪🇸 Español | 🇵🇹 Português | 🇹🇭 ไทย | 🇨🇳 中文

【可复制运行】全面解析 SQL 的 COUNT 函数:从基础到进阶

在开发网站或应用程序时,经常会遇到这样的问题:“用户总数是多少?”、“某个商品分类中有多少种商品?”这时候,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, 'Taro Yamada', 'Tokyo', 'yamada@example.com'),
(2, 'Hanako Suzuki', 'Osaka', 'suzuki@example.com'),
(3, 'Jiro Sato', 'Tokyo', NULL),
(4, 'Sakura Ito', 'Fukuoka', 'ito@example.com'),
(5, 'Saburo Watanabe', 'Hokkaido', 'watanabe@example.com'),
(6, 'Shiro Takahashi', 'Tokyo', 'takahashi@example.com'),
(7, 'Misaki Tanaka', 'Osaka', NULL);

该表包含 ID、姓名、地区和电子邮件字段。注意:Sato(ID 3)和 Tanaka(ID 7)的 emailNULL(即为空),这将在后文讲解 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”要少。这是因为有 2 条记录(Sato 和 Tanaka)在 email 字段为 NULL,因此未被统计。

请记住: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)`

接下来是分析类操作中常用的 COUNT(DISTINCT)DISTINCT 表示“去重”,可用于统计唯一值的数量。

比如我们想知道:“网站用户来自多少个不同的地区?”

users 表中,Tokyo 有 3 人,Osaka 有 2 人。我们来统计地区(prefecture)的唯一值数量。

SELECT COUNT(DISTINCT prefecture) AS unique_prefectures FROM users;

执行结果:

unique_prefectures
------------------
4

结果是“4”。表中包含 Tokyo、Osaka、Fukuoka、Hokkaido 共 4 个不同的地区,统计无误。

这在电商网站统计商品分类数、日志中统计独立访客数量等场景中都有广泛应用。


【进阶 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
-----------|------------
Osaka      | 2
Hokkaido   | 1
Tokyo      | 3
Fukuoka    | 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
-----------|------------
Osaka      | 2
Tokyo      | 3

通过添加 HAVING user_count >= 2 条件,仅返回了用户数量不少于 2 人的地区:Osaka 和 Tokyo。

请记住: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>用于 COUNT() 的 SQL 运行环境</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();
        
        // 设置初始表和数据
        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, 'Taro Yamada', 'Tokyo', 'yamada@example.com'),
          (2, 'Hanako Suzuki', 'Osaka', 'suzuki@example.com'),
          (3, 'Jiro Sato', 'Tokyo', NULL),
          (4, 'Sakura Ito', 'Fukuoka', 'ito@example.com'),
          (5, 'Saburo Watanabe', 'Hokkaido', 'watanabe@example.com'),
          (6, 'Shiro Takahashi', 'Tokyo', 'takahashi@example.com'),
          (7, 'Misaki Tanaka', 'Osaka', 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(*) vs COUNT(1) vs COUNT(column_name)

有时可以看到 COUNT(1) 的写法,它的意思是为每一行赋值 “1” 并进行计数。在大多数数据库中,COUNT(*)COUNT(1) 的行为和性能几乎没有区别,都统计所有行。而 COUNT(column_name) 不统计 NULL 值,请再次确认其目的是否不同。如有疑问,建议使用语义明确的 COUNT(*)(统计所有行)或 COUNT(column_name)(统计非 NULL 值)。

性能方面

如果在拥有数百万甚至上千万条记录的巨大表上执行 COUNT(*),可能会比较耗时。尤其是当不指定任何条件时,需要扫描整张表的数据。如果你频繁需要获取总记录数,可以考虑其他方法(例如准备汇总表),但只要掌握基本用法就没有问题。


相关功能:其他聚合函数

学会了 COUNT() 之后,其它聚合函数也能很容易掌握。与 GROUP BY 结合使用时,效果尤为强大。

例如,如果你想知道用户 ID 的最大值,可以这样写:

SELECT MAX(id) AS latest_user_id FROM users;

总结

本文从基础到进阶,快速介绍了如何使用 COUNT() 函数来统计记录数量。

COUNT() 是分析数据并获取洞察的第一步。请多尝试本文的代码,掌握“统计数据”的感觉。当你能熟练运用 SQL 时,你将看到一个全新的世界。让我们继续愉快地学习吧!