# SQL 速查

以下示例基于通用 SQL，MySQL 8 与 PostgreSQL 均可参考。

## JOIN

内连接只返回两表都匹配的行，左连接保留左表全部行，右表没匹配上的列补空值；连接条件写 ON，结果过滤写 WHERE。把右表条件放进 WHERE 会让左连接退化成内连接，这是常见坑。

```sql
SELECT * FROM orders WHERE id = 1;
```

## 聚合与分组

聚合函数忽略空值，WHERE 在分组前过滤原始行，HAVING 在分组后过滤结果，排序可以直接使用聚合别名。统计行数用 COUNT(*)，只数非空值用 COUNT 加列名，两者别混用。

```sql
SELECT COUNT(*) FROM orders;
```

## 窗口函数

OVER 子句不压缩行数，分区子句分组、排序子句定序，适合做组内排名、累计求和和同比计算。连续序号与并列排名遇到相同值会给出不同结果，取前三名要先想清楚用哪一个。

```sql
SELECT ROW_NUMBER() OVER () AS rn FROM t;
```

## CTE

WITH 把子查询提成命名结果集，多段引用比嵌套子查询好读；递归 CTE 还能展开树形结构，比如组织架构。复杂报表可以串多个 CTE，每段只做一件事，排查时逐段单独执行即可。

```sql
WITH p AS (SELECT 1) SELECT * FROM p;
```

## 索引

联合索引遵循最左前缀原则，用 EXPLAIN 确认是否命中；在索引列上套函数会让索引失效，范围条件尽量往后面放。索引不是越多越好，写入变慢和占用空间都要算进成本里。

```sql
CREATE INDEX i ON orders (id);
```

## 事务

事务保证一组操作要么全成功要么全回滚，转账场景先用 FOR UPDATE 锁住要改的行，校验余额无误后再提交。锁的范围尽量小，长事务会拖住其它请求，出错时记得执行回滚释放。

```sql
START TRANSACTION;
```
