Skip to content

33|SQL 连表查询与聚合函数

数据通常分散在多张表中:服务器信息在 servers 表,所属项目在 projects 表,任务记录在 tasks 表。连表查询(JOIN)把相关数据从多张表组合到一起。聚合函数则对一组记录做统计:计数、求和、平均值。

一、表结构示例

sql
CREATE TABLE projects (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL
);

CREATE TABLE servers (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    hostname TEXT NOT NULL,
    project_id INTEGER,
    status TEXT DEFAULT 'running'
);

CREATE TABLE tasks (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    server_id INTEGER,
    action TEXT,
    created_at TEXT
);

servers.project_id 关联 projects.idtasks.server_id 关联 servers.id

二、JOIN 连表查询

INNER JOIN

只返回两张表中都匹配的记录:

python
cursor.execute("""
    SELECT s.hostname, p.name as project_name
    FROM servers s
    INNER JOIN projects p ON s.project_id = p.id
""")

如果某台服务器没有设置 project_id(或为 NULL),这条记录不会出现在结果中。

LEFT JOIN

返回左表的所有记录,右表不匹配时填 NULL

python
cursor.execute("""
    SELECT s.hostname, p.name as project_name
    FROM servers s
    LEFT JOIN projects p ON s.project_id = p.id
""")

所有服务器都会出现在结果中,没有项目的那些行 project_nameNULL

JOIN 类型结果
INNER JOIN只返回两表匹配的行
LEFT JOIN返回左表所有行,右表不匹配填 NULL
RIGHT JOIN返回右表所有行,左表不匹配填 NULL(SQLite 不支持)
FULL OUTER JOIN返回两表所有行,不匹配填 NULL(SQLite 不支持)

SQLite 只支持 INNER JOINLEFT JOIN。需要 RIGHT JOINFULL JOIN 时,调整 SQL 写法或换数据库。

多表 JOIN

python
cursor.execute("""
    SELECT s.hostname, p.name, COUNT(t.id) as task_count
    FROM servers s
    LEFT JOIN projects p ON s.project_id = p.id
    LEFT JOIN tasks t ON t.server_id = s.id
    GROUP BY s.id
""")

三、聚合函数

python
# 统计服务器数量
cursor.execute("SELECT COUNT(*) FROM servers")

# 按状态分组统计
cursor.execute("""
    SELECT status, COUNT(*) as count
    FROM servers
    GROUP BY status
""")

# 有任务的服务器数量(DISTINCT 去重)
cursor.execute("SELECT COUNT(DISTINCT server_id) FROM tasks")
函数作用
COUNT(*)统计行数
COUNT(列)统计该列非 NULL 的行数
SUM(列)求和
AVG(列)平均值
MAX(列)最大值
MIN(列)最小值

四、GROUP BY 与 HAVING

GROUP BY 按指定列分组,聚合函数对每个组单独计算:

python
cursor.execute("""
    SELECT project_id, COUNT(*) as server_count
    FROM servers
    WHERE status = 'running'
    GROUP BY project_id
    HAVING COUNT(*) >= 2
""")
子句作用执行时机
WHERE过滤原始行分组前
GROUP BY按列分组过滤后
HAVING过滤分组结果分组后

上面的 SQL:先过滤出 status = 'running' 的服务器,再按 project_id 分组,最后只保留服务器数量大于等于 2 的组。

五、子查询

子查询是嵌套在另一个查询中的查询:

python
# 查询有任务的服务器
cursor.execute("""
    SELECT * FROM servers
    WHERE id IN (SELECT DISTINCT server_id FROM tasks)
""")
python
# 查询每台服务器的最新任务
cursor.execute("""
    SELECT s.hostname, t.action, t.created_at
    FROM servers s
    JOIN tasks t ON t.id = (
        SELECT id FROM tasks
        WHERE server_id = s.id
        ORDER BY created_at DESC
        LIMIT 1
    )
""")

子查询可以出现在 SELECTFROMWHERE 中。复杂的子查询可能影响性能,必要时用 JOIN 替代。

六、常见错误

SELECT 中包含未聚合的非分组列

sql
-- 错误:hostname 没有出现在 GROUP BY 中,结果不确定
SELECT hostname, status, COUNT(*) FROM servers GROUP BY status;

-- 正确:只选分组列和聚合函数
SELECT status, COUNT(*) FROM servers GROUP BY status;

-- 或把 hostname 也加入 GROUP BY
SELECT hostname, status, COUNT(*) FROM servers GROUP BY hostname, status;

SQLite 对这种写法比较宽松(返回每组的任意一行),但 MySQL/PostgreSQL 会报错。

WHERE 和 HAVING 混用

sql
-- 错误:HAVING 不能过滤原始行
SELECT status, COUNT(*) FROM servers HAVING status = 'running' GROUP BY status;

-- 正确:原始行过滤用 WHERE
SELECT status, COUNT(*) FROM servers WHERE status = 'running' GROUP BY status;

JOIN 忘记 ON 条件

sql
-- 错误:笛卡尔积,结果行数 = 表A行数 × 表B行数
SELECT * FROM servers JOIN projects;

-- 正确
SELECT * FROM servers JOIN projects ON servers.project_id = projects.id;