SQL基础语法入门:SELECT查询与多表JOIN连接示例
SQLPro 是一款macOS平台上数据库管理应用,避免使用非法,享受更全面的支持和更新。它更贴近日常 macOS 使用场景,处理高频操作时整体会更顺手一些。
本文以PostgreSQL语法为主,讲解SQL基础SELECT查询与多表JOIN连接。通过users和orders表实例,演示WHERE过滤、ORDER BY排序、LIMIT限制,重点解析INNER JOIN与LEFT JOIN的结果差异、ON与WHERE条件放置对结果的影响,以及GROUP BY统计与结果异常排查方法。适合刚接触SQL的开发者快速掌握多表查询核心逻辑。
很多刚接触 SQL 的开发者在从单表查询转向多表连接时,常遇到“为什么数据变少了”或“为什么出现了 NULL”的困惑。这通常是因为没有理清 JOIN 的匹配机制以及过滤条件的位置。掌握 SELECT 的基础执行顺序,明确 INNER JOIN 与 LEFT JOIN 保留行的规则,并将过滤条件放在正确的位置,是写出准确多表查询的关键。
先用 SELECT 查清单表数据
在连接多表之前,必须熟悉单表查询的基本结构。SQL 查询的逻辑执行顺序并非按照书写顺序,而是先确定数据来源(FROM),再过滤行(WHERE),接着投影列(SELECT),最后排序(ORDER BY)和限制数量(LIMIT)。
假设我们有一张 users 表和一张 orders 表。users 表存储用户信息,orders 表存储订单记录,两者通过 user_id 关联。
-- 查询前5名活跃用户的姓名和邮箱,按注册日期倒序排列
SELECT
id,
username,
email
FROM users
WHERE status = 'active'
ORDER BY created_at DESC
LIMIT 5;
这段代码中:
FROM users确定数据源。WHERE status = 'active'在投影前先过滤掉非活跃用户,减少处理数据量。SELECT id, username, email只提取需要的列,避免使用SELECT *,这在多表连接时能显著减少网络传输和内存占用。ORDER BY和LIMIT最后执行,确保返回的是排序后的前几条记录。

SELECT、WHERE、ORDER BY 和 LIMIT 逐步缩小结果集
给表或列起别名也是好习惯。例如 SELECT u.username FROM users AS u,在多表查询中,别名能明确区分来自不同表的同名列(如两个表都有 id)。
INNER JOIN 为什么会少行
当我们需要同时查看用户及其订单时,需要使用 JOIN。INNER JOIN(内连接)只返回两个表中关联条件匹配的行。如果左表(users)中的某用户在右表(orders)中没有订单,或者右表中的订单对应的用户 ID 在左表中不存在,这些行都会被丢弃。
SELECT
u.username,
o.order_no,
o.amount
FROM users u
INNER JOIN orders o ON u.id = o.user_id;
为什么结果行数可能少于 users 表的总行数?
- 无匹配记录:某些用户尚未下单,
o.user_id无法匹配到u.id,因此这些用户不会出现在结果中。 - NULL 值处理:如果
orders表中的user_id存在 NULL 值,由于 NULL 不等于任何值(包括 NULL),这些订单也不会被匹配。
如果你发现查询结果比预期的少,首先检查是否有用户没有订单,或者是否存在脏数据(如孤儿订单)。
LEFT JOIN 保留谁以及条件放哪里
如果我们希望列出所有用户,无论他们是否有订单,就需要使用 LEFT JOIN(左连接)。LEFT JOIN 会保留左表(users)的所有行。如果右表(orders)中没有匹配行,右表的列将填充为 NULL。
SELECT
u.username,
o.order_no,
o.amount
FROM users u
LEFT JOIN orders o ON u.id = o.user_id;
找出没有订单的用户
利用 LEFT JOIN 的特性,我们可以轻松找出“零订单”用户:
SELECT
u.username
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.order_no IS NULL;
这里 WHERE o.order_no IS NULL 筛选出了那些在 orders 表中没有匹配记录的用户。
ON 与 WHERE 的关键差异
在使用 LEFT JOIN 时,过滤条件放在 ON 子句还是 WHERE 子句,结果截然不同。
- 放在 ON 中:仅影响右表的匹配过程。如果条件不满足,右表对应列为 NULL,但左表行依然保留。
- 放在 WHERE 中:在连接完成后对最终结果集进行过滤。如果条件涉及右表且要求非 NULL,会导致 LEFT JOIN 退化为 INNER JOIN。
错误示例:
-- 意图:列出所有用户,并只显示金额大于100的订单(若无订单则显示NULL)
-- 错误写法:
SELECT u.username, o.amount
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.amount > 100; -- 这会过滤掉 amount 为 NULL 的行,即丢失了无订单或订单金额<=100的用户
正确写法:
SELECT u.username, o.amount
FROM users u
LEFT JOIN orders o ON u.id = o.user_id AND o.amount > 100;
将 o.amount > 100 放入 ON 子句,确保只有金额大于100的订单才会被关联,其他用户依然保留,只是订单字段为 NULL。

INNER JOIN 只保留匹配行,LEFT JOIN 保留左表记录
多表统计与结果排查
在实际业务中,我们经常需要统计每个用户的订单总数或总金额。这时需要结合 GROUP BY 和聚合函数。
SELECT
u.username,
COUNT(o.id) AS order_count,
COALESCE(SUM(o.amount), 0) AS total_amount
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.username;
注意以下几点:
- GROUP BY 规则:在 PostgreSQL 等严格模式下,SELECT 列表中所有非聚合列(如
username)都必须出现在 GROUP BY 子句中,或者依赖于主键(如u.id)。 - 处理 NULL:
SUM对 NULL 值结果为 NULL,使用COALESCE(..., 0)将其转换为 0,更符合业务直觉。 - COUNT 的选择:
COUNT(o.id)只统计非 NULL 的订单 ID,而COUNT(*)会统计所有行(包括无订单时的 NULL 行,结果为1)。对于 LEFT JOIN,使用COUNT(o.id)才能准确反映订单数量(无订单时为0)。
结果异常排查步骤
如果统计结果不符合预期,按以下步骤排查:
- 单独运行每张表查询:先确认
users和orders单独查询的数据是否正确。 - 检查连接键重复:如果
orders表中存在重复的user_id或其他导致一对多关系膨胀的情况,JOIN 后行数会增加,导致 SUM 或 COUNT 偏大。可以通过SELECT user_id, COUNT(*) FROM orders GROUP BY user_id HAVING COUNT(*) > 1检查是否有重复数据。 - 验证过滤条件位置:确认是否误将右表过滤条件放在 WHERE 中,导致左表数据丢失。
- 分页排序稳定性:如果在分页查询中使用
ORDER BY,确保排序列具有唯一性(如加上id),否则分页结果可能出现重复或遗漏。
掌握这些基础语法和逻辑,就能应对绝大多数日常的多表查询需求。建议在编写复杂查询时,先从小规模数据测试 JOIN 结果,再逐步添加过滤和聚合逻辑。
参考资料
- PostgreSQL SELECT
- MySQL JOIN Syntax
- SQL Server FROM clause









































