跳到主要内容

PostgreSQL SELECT 实用教程(常用语法 + 易错坑点)

SELECT 完整语法顺序(最重要!执行顺序≠书写顺序)​

✅ 书写顺序(你写SQL的顺序)

SELECT 列
FROM 表
WHERE 行过滤
GROUP BY 分组
HAVING 分组后过滤
ORDER BY 排序
LIMIT / OFFSET 分页

⚠️ 数据库【执行顺序】(非常容易踩坑)

信息

FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT
关键点: 1. WHERE 在SELECT之前执行,WHERE里不能使用select别名 2. HAVING在GROUP之后,可以使用聚合函数;WHERE不能用聚合函数

最常用基础 SELECT 写法​

-- 查询指定字段(推荐!不要用 select *)
SELECT id, name, create_time FROM users;
-- 带别名 AS,AS可以省略
SELECT id AS user_id, name AS uname FROM users;
-- 条件过滤 WHERE
SELECT * FROM users WHERE age >= 18 AND status = 1;
-- 模糊匹配 LIKE,%代表任意字符
SELECT * FROM users WHERE name LIKE '张%';
-- IN 多值匹配
SELECT * FROM users WHERE id IN (1,3,5,7);
-- 范围 BETWEEN ... AND 包含两端边界
SELECT * FROM orders WHERE amount BETWEEN 100 AND 1000;
注意

❌ 易错:BETWEEN 是闭区间!BETWEEN 100 AND 1000 等价 >=100 AND <=1000,时间查询经常踩坑。

GROUP BY + 聚合函数(高频,坑最多)​

常用聚合:COUNT / SUM / AVG / MAX / MIN

-- 统计每个状态用户数量
SELECT status, COUNT(*) AS cnt FROM users GROUP BY status;
注意
❌ 经典错误:GROUP BY 后,select 只能写分组字段或者聚合函数!

PostgreSQL严格校验,下面SQL直接报错,MySQL旧版本会放行产生错误数据

-- 错误!name既不是分组字段,也不是聚合函数
SELECT status, name, COUNT(*) FROM users GROUP BY status;

HAVING:对分组结果过滤,可以使用聚合函数

-- 查询分组数量大于10的分组
SELECT status, COUNT(*) AS cnt FROM users GROUP BY status HAVING COUNT(*) >10;

NULL 的大坑(PostgreSQL 最容易踩坑!)​

信息

PostgreSQL 中 NULL 不等于任何值,包括 NULL

  • 判断空:col IS NULL
  • 判断非空:col IS NOT NULL
  • ❌ col = NULL 永远返回 null,不会匹配任何行!

-- ✅正确
SELECT * FROM users WHERE phone IS NULL;
-- ❌错误,查不出任何数据
SELECT * FROM users WHERE phone = NULL;

聚合函数规则:COUNT(列名) 自动忽略NULL;COUNT(*) 统计全部行(包含NULL行)

COUNT(*) → 统计总行,无视null COUNT(phone) → 统计phone不为null的行数

DISTINCT 去重​

-- 对name整列去重
SELECT DISTINCT name FROM users;
-- 多字段DISTINCT:组合去重,是【多个字段组合唯一】,不是单个字段
SELECT DISTINCT name, age FROM users;
注意

DISTINCT作用于后面全部字段,不是只作用在第一个字段! 数据量大的时候DISTINCT性能很差,优先考虑GROUP BY代替。

ORDER BY 排序 & 分页 LIMIT/OFFSET​

-- 升序ASC默认,可省略;DESC降序
SELECT id,name FROM users ORDER BY create_time DESC;
-- 多字段排序:先按创建时间降序,相同时间按id升序
SELECT id,name FROM users ORDER BY create_time DESC, id ASC;
-- 分页:取第2页,每页10条
SELECT * FROM users ORDER BY id LIMIT 10 OFFSET 10;
注意

❌易错:OFFSET大的时候性能极差。OFFSET不是跳过索引,数据库会扫描前面所有数据。 大数据分页不要用OFFSET,推荐【主键游标分页】:

-- 游标分页(推荐,高性能)
SELECT id,name FROM users WHERE id > 100 ORDER BY id LIMIT 10;

另外:ORDER BY 字段存在相同值,不指定唯一排序字段,分页会出现重复/丢失数据!

子查询 & IN / EXISTS 对比​

写法特点适用场景
IN子查询返回集合,子查询有NULL直接返回空结果子查询数据量小
EXISTS找到第一条匹配立刻终止,性能更好,不受NULL影响子查询数据量大,推荐优先使用EXISTS
-- EXISTS 示例(推荐)
SELECT * FROM users u WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id);
注意

IN子查询如果返回包含NULL的集合,整个查询直接返回空结果,极其隐蔽的bug!优先EXISTS。

字符串、时间查询常见坑​

1. 大小写:PostgreSQL 默认区分大小写!​

-- name='Tom' 和 name='tom' 不相等
SELECT * FROM users WHERE name = 'tom';
-- 不区分大小写查询:ILIKE
SELECT * FROM users WHERE name ILIKE 'tom';

2. 时间戳timestamp查询坑​

-- timestamp带时分秒,'2025-01-01'等价'2025-01-01 00:00:00'
SELECT * FROM orders WHERE create_time >= '2025-01-01' AND create_time < '2025-01-02';
注意

❌不要写 create_time <= '2025-01-01 23:59:59',会漏掉毫秒级数据,推荐用 < 下一天。

JOIN 连接查询(最常用,内连接、左连接)​

-- INNER JOIN 内连接:两边匹配才返回
SELECT u.id, o.order_no FROM users u INNER JOIN orders o ON u.id = o.user_id;
-- LEFT JOIN 左连接:保留左表全部,右表无匹配则右表字段为NULL
SELECT u.id, o.order_no FROM users u LEFT JOIN orders o ON u.id = o.user_id;
注意
LEFT JOIN 经典大坑:WHERE里过滤右表字段,会把左连接降级变成内连接!
-- ❌错误!orders.status=1写在WHERE,右表null行会被过滤,变成inner join
SELECT u.id, o.order_no FROM users u LEFT JOIN orders o ON u.id = o.user_id WHERE o.status = 1;
-- ✅正确,过滤条件写在ON里面
SELECT u.id, o.order_no FROM users u LEFT JOIN orders o ON u.id = o.user_id AND o.status = 1;

总结:PostgreSQL SELECT 高频易错清单​

  • WHERE 不能引用SELECT别名;聚合函数只能放在HAVING,不能放WHERE
  • NULL 判断只能 IS NULL,不能用 = NULL
  • GROUP BY严格校验,select字段必须是分组字段/聚合函数
  • LEFT JOIN 的右表过滤条件写在ON,不要写WHERE,否则丢失左表数据
  • 大分页不要OFFSET,用游标分页
  • IN子查询存在NULL会返回空结果,大数据优先EXISTS
  • 字符串默认区分大小写,ILIKE忽略大小写
  • 时间查询避免 <= '23:59:59',使用 < 次日
  • BETWEEN是闭区间,包含两端边界
  • 禁止生产环境写 SELECT *,减少网络IO,增加索引命中概率