子查询必须用括号包裹,否则直接报语法错误;必须返回与使用位置匹配的结果(标量子查询需单值,WHERE中多行需用IN/EXISTS);NULL值导致结果为NULL而非0,须用COALESCE显式处理。

子查询必须用括号包裹,否则语法直接报错
SQL里子查询不是可选的装饰,而是独立语句块,不加括号会被解析器当成语法错误。常见报错是 ERROR: syntax error at or near "SELECT",本质就是缺了外层括号。
实际写法必须是:(SELECT SUM(amount) FROM orders WHERE DATE(created_at) = CURRENT_DATE),而不是裸写 SELECT SUM(amount) FROM orders WHERE ...。
- 子查询出现在 SELECT 列表中时,必须返回单值(标量),否则会报
more than one row returned by a subquery used as an expression - WHERE 或 HAVING 中的子查询可以返回多行,但要用
IN、EXISTS等适配操作符 - PostgreSQL 和 MySQL 对子查询位置限制略有不同:MySQL 允许在 FROM 中用子查询当临时表,而 PostgreSQL 要求别名(
AS t)
当日汇总和当月汇总不能共用同一个 WHERE 条件
想在一个 SELECT 里同时算“今天”和“本月”,关键不是拼条件,而是把时间范围拆开——每个子查询各自负责自己的时间逻辑。
错误做法是试图用 WHERE created_at >= '2024-06-01' AND created_at 套住两个统计,这会导致当日值被当月条件污染(比如把昨天的也混进去了)。
- 当日汇总用:
DATE(created_at) = CURRENT_DATE(兼容性好,避开时区问题) - 当月汇总用:
created_at >= DATE_TRUNC('month', CURRENT_DATE) AND created_at (PostgreSQL) - MySQL 用户用:
YEAR(created_at) = YEAR(NOW()) AND MONTH(created_at) = MONTH(NOW()),但注意它会包含未来日期(如果数据有误)
避免在子查询里重复扫描大表
如果订单表有千万级数据,两个子查询各自全表扫描一遍,性能会明显下降。这不是语法问题,而是执行计划隐患。
优化思路不是“怎么写更短”,而是“怎么让优化器少走几步”。最直接的方式是把基础数据先过滤再聚合:
- 用 CTE 预筛一次:
WITH base AS (SELECT amount, created_at FROM orders WHERE created_at >= DATE_TRUNC('month', CURRENT_DATE)),再从base里分别算当日/当月 - 确保
created_at字段有索引,且类型是TIMESTAMP WITH TIME ZONE或DATETIME,避免函数索引失效(比如DATE(created_at)在某些版本无法走索引) - 如果只是读取展示用,且数据更新不频繁,考虑物化当日/当月视图或定时刷新汇总表,比实时子查询更稳
NULL 值会让子查询结果变成 NULL,而不是 0
如果当天没订单,(SELECT SUM(amount) FROM ...) 返回的是 NULL,不是 0。前端显示可能变成空格或报错,后端处理也容易漏判。
必须显式处理:COALESCE((SELECT SUM(amount) ...), 0)。这个细节在联查多个子查询时特别容易忽略——一个 NULL 就可能让整行计算崩掉。
-
COALESCE是标准 SQL,所有主流数据库都支持 - 不要用
IFNULL(MySQL)或ISNULL(SQL Server)替代,会降低可移植性 - 如果子查询还涉及 JOIN 或 GROUP BY,记得在子查询内部也做
COALESCE(SUM(...), 0),而不是只在外层包一层

















