SQL 速查表

SQL 速查:SELECT、JOIN、GROUP BY、INSERT、UPDATE、DELETE、聚合函数与约束等常用语句的语法与示例,写查询时快速查阅。

原理解析

SQL 是操作关系型数据库的标准声明式语言:你描述「要什么」,优化器决定「怎么取」。DML(SELECT/INSERT/UPDATE/DELETE)处理数据,DDL(CREATE/ALTER/DROP)定义结构,DCL(GRANT)管权限。

数据以表(行/列)组织,表间靠外键关联,JOIN 把多表拼成结果集。索引加速查询但拖慢写入;事务以 ACID(原子/一致/隔离/持久)保证并发安全。写查询时优先用参数化语句防 SQL 注入。

什么时候该查这张表

这张表在你要查 SQL 语法但不想开文档站时有用。它的价值是覆盖面:DDL、DML、聚合、窗口函数、事务控制按类别排好,缺什么能立刻定位。但 SQL 方言差异极大,同一句「取前 N 行」在 MySQL 是 LIMIT、在 SQL Server 是 TOP n、在 Oracle 是 ROWNUM,跨库时必须转换。

不该查的情况:你在调查询性能。索引类型、执行计划、代价模型属于数据库调优范畴,一条语法正确但全表扫描的查询比语法错误慢得多。本表不提供任何性能判据。

判断标准:语法拼接、报表查询、一次性导出用本表就够了;涉及事务隔离、锁行为、并发写入时,要看具体数据库的文档而不是通用语法——同样一句 UPDATE 在不同隔离级别下的行为可能完全不同。

特别注意 SQL 注入:字符串拼接而非参数绑定是后端最常见的漏洞来源。本表列出的语法本身没问题,但任何含变量占位的写法都应当用参数化查询(Prepared Statement),不要用引号包裹变量。

边界与常见陷阱

  • NULL 参与任何比较都返回 NULL

    这是最反直觉的一点:NULL <> 1 的结果不是 true 而是 NULL,因此 WHERE 会把它过滤掉。需要显式写成 IS NULL / IS NOT NULL。聚合函数同样忽略 NULL(COUNT(列) 不计空值但 COUNT(*) 计行数),而 AVG 与 SUM 对全 NULL 返回 NULL 而非 0。

  • DELETE 与 TRUNCATE 的语义差别很大

    DELETE 是逐行删除并写事务日志,可回滚、有 WHERE 时只删部分;TRUNCATE 是整体清空,通常不可回滚(依数据库与引擎而定)、重置自增主键、不触发 DELETE 触发器。在有外键约束时 TRUNCATE 往往直接失败,而 DELETE 可以先删子表。误用会在生产上丢数据或失败。

  • UPDATE 忘了写 WHERE 会清空整表

    这条每年都在发生。工程上可用的防护:执行 UPDATE/DELETE 前先跑同样条件的 SELECT 看影响行数;数据库层面对无 WHERE 的 UPDATE 拒绝执行或要求显式确认;权限上把 UPDATE 与 DELETE 收窄到必须带条件的视图或存储过程。单靠「小心」不可靠。

  • JOIN 的 NULL 语义容易被忽略

    LEFT JOIN 后右表的条件写在 WHERE 里会把结果退化成 INNER JOIN(因为 NULL <> x 为 NULL,不成立)。条件应写在 ON 子句中。此外 COUNT(右表.列) 在 LEFT JOIN 下不统计未匹配的行,统计「总行数」要用 COUNT(*) 而不是 COUNT(列),这是报表数字对不上的常见根因。

与相邻内容的分工

要查某个具体数据库(MySQL / PostgreSQL / SQLite / SQL Server)的方言差异与版本特性,应查该数据库官方文档,本表只给通用写法。

要格式化或校验 SQL 语句,用站内 SQL 工具;本表不含 SQL 解析与语法高亮能力。

要理解索引为何不生效、EXPLAIN 怎么看,需数据库调优知识,超出本表范围。

若目的是把查询结果安全导出(处理引号、编码分隔符),那是导出流程问题,不是 SQL 语法问题。

实战片段

要点:① UPDATE/DELETE 前先跑同条件 SELECT 看影响行数,事务包裹并在核对后提交;② NULL 只能用 IS NULL 判断,= NULL 恒为 NULL;③ LEFT JOIN 的右表条件必须放 ON,放 WHERE 会退化成 INNER JOIN,且统计行数用 COUNT(*) 而非 COUNT(列);④ 变量一律参数绑定,字符串拼接是 SQL 注入的主要入口。

-- ① 改写前先看影响面:把 UPDATE 的 WHERE 套进 SELECT
SELECT id, name, status
FROM   orders
WHERE  status = 'pending' AND created_at < '2024-01-01';
-- 确认行数合理,再执行 UPDATE;行数异常就是条件写错了

BEGIN;
UPDATE orders SET status = 'expired'
WHERE  status = 'pending' AND created_at < '2024-01-01';
-- 核对受影响行数与上一步一致
COMMIT;  -- 不满意就 ROLLBACK;

-- ② NULL 必须显式判断
SELECT name FROM users WHERE manager_id IS NULL;      -- 不是 = NULL
SELECT name FROM users WHERE manager_id IS NOT NULL;

-- ③ LEFT JOIN 的条件写在 ON,不要写 WHERE
SELECT c.name AS customer, o.total
FROM   customers c
LEFT JOIN orders o
       ON o.customer_id = c.id
      AND o.status = 'paid';          -- 放在 ON 里才保留未付费客户
-- 统计总行数用 COUNT(*),不是 COUNT(o.total)
SELECT COUNT(*) AS customers, COUNT(o.total) AS paid_rows
FROM   customers c
LEFT JOIN orders o ON o.customer_id = c.id AND o.status = 'paid';

-- ④ 用参数绑定,不要拼字符串(SQL 注入防线)
PREPARE stmt FROM 'SELECT * FROM users WHERE email = ?';
SET @email = 'a@example.com';
EXECUTE stmt USING @email;
DEALLOCATE PREPARE stmt;

实践注解

  • NULL 参与的比较一律返回 UNKNOWNWHERE col = NULL 永不成立,必须写 IS NULL。同理 NOT IN 子查询若结果集含 NULL,整个条件会变成 UNKNOWN 导致返回空集——这是「明明有数据却查不到」的经典原因。
  • COUNT(*) 与 COUNT(col) 语义不同COUNT(*) 数行数,COUNT(col) 只数该列非 NULL 的行。做「非空率」统计时若用错,会把有 NULL 的列算成偏小;COUNT(DISTINCT col) 又会忽略 NULL。
  • 隐式类型转换会让索引失效WHERE varchar_col = 123 会让多数数据库把列侧转成数字,索引无法使用,全表扫描。字段类型与传入参数类型必须一致,尤其注意字符集与排序规则也要匹配。

数据来源

  • ISO/IEC 9075 (SQL)
  • Database vendor dialect documentation

上述编号、名称与定义均来自公开规范,本站不主张对规范数据本身的独创性;本页的编排方式、中文释义与实践注解由本站撰写。

语句说明示例
SELECT查询列(* 表示全部)SELECT id, name FROM users
FROM指定数据来源表FROM orders
WHERE行过滤条件WHERE status = 'paid'
GROUP BY按列分组聚合GROUP BY category
HAVING对分组结果再过滤HAVING COUNT(*) > 1
ORDER BY排序(ASC/DESC)ORDER BY created_at DESC
LIMIT限制返回行数LIMIT 10
OFFSET跳过前 N 行(分页)OFFSET 20
DISTINCT去重返回SELECT DISTINCT city
JOIN内连接两表FROM a JOIN b ON a.id = b.a_id
LEFT JOIN左连接,保留左表全部行FROM a LEFT JOIN b ON a.id = b.a_id
RIGHT JOIN右连接,保留右表全部行FROM a RIGHT JOIN b ON a.id = b.a_id
INNER JOIN内连接(同 JOIN)FROM a INNER JOIN b ON a.id = b.a_id
ON连接条件ON a.id = b.a_id
AS表或列别名SELECT name AS 姓名
INSERT INTO插入行INSERT INTO users (name) VALUES ('Tom')
UPDATE更新行UPDATE users SET name = 'Tom' WHERE id = 1
DELETE删除行DELETE FROM users WHERE id = 1
CREATE TABLE建表CREATE TABLE t (id INT PRIMARY KEY)
ALTER TABLE修改表结构ALTER TABLE t ADD COLUMN age INT
DROP TABLE删除表DROP TABLE t
PRIMARY KEY主键约束id INT PRIMARY KEY
FOREIGN KEY外键约束FOREIGN KEY (a_id) REFERENCES a(id)
INDEX创建索引加速查询CREATE INDEX idx_name ON users(name)
UNION合并两个查询结果(去重)SELECT a FROM t1 UNION SELECT a FROM t2
LIKE模糊匹配(% 任意,_ 单字)WHERE name LIKE 'T%'
IN值属于集合WHERE status IN ('paid','done')
BETWEEN范围闭区间WHERE age BETWEEN 18 AND 30
NULL / IS NULL空值判断(不能用 =)WHERE deleted_at IS NULL
COUNT / SUM / AVG聚合函数SELECT COUNT(*) FROM users
CASE WHEN条件表达式CASE WHEN age>18 THEN 'adult' ELSE 'minor' END
EXPLAIN查看执行计划EXPLAIN SELECT * FROM users

常见问题

INNER JOIN 和 LEFT JOIN 有什么区别?

INNER JOIN 只返回两表都匹配的行;LEFT JOIN 返回左表全部行,右表无匹配时对应列填 NULL。需要"保留主表所有记录"时用 LEFT JOIN,只需交集时用 INNER JOIN。

WHERE 和 HAVING 有什么区别?

WHERE 在分组前过滤行;HAVING 在 GROUP BY 之后过滤分组结果,且可引用聚合函数(如 COUNT(*))。不能在 WHERE 中使用聚合函数。

LIKE 的 % 和 _ 有什么区别?

% 匹配任意长度(含零)字符;_ 只匹配单个字符。如 'T%' 匹配以 T 开头任意串,'T_m' 只匹配 T 后一个任意字符再接 m(如 Tom)。

如何判断 NULL?

NULL 表示未知,不能用 = NULL 判断,必须用 IS NULL / IS NOT NULL。在多数数据库中 NULL 与任何值比较都返回未知(非真),容易踩坑。