# 第 4 章 配套代码

> 演示 PG 的「数据质量守护者」：6 大约束（PK / UNIQUE / NOT NULL / CHECK / FK / EXCLUDE）、视图、物化视图、SERIAL vs IDENTITY。配套表统一以 `ch4_` 前缀，视图以 `ch4_v_` / `ch4_mv_` 前缀。

## 准备工作

1. 跑初始化脚本：
   ```bash
   psql -h 127.0.0.1 -U postgres -d learn_pg -f ../init.sql
   ```
   > `EXCLUDE USING gist` 依赖 `btree_gist` 扩展，脚本里已 `CREATE EXTENSION IF NOT EXISTS btree_gist;`
2. 安装依赖：
   ```bash
   pip install "psycopg[binary]>=3.1"
   ```
3. （可选）通过环境变量覆盖默认连接：
   ```bash
   export PG_DSN="host=127.0.0.1 port=5432 dbname=learn_pg user=postgres"
   ```

## 脚本一览（推荐运行顺序）

| 脚本 | 一句话说明 | 关键 PG 特性 |
|------|------------|--------------|
| `01_constraints.py` | 6 大约束被故意触发 → 看真实报错信息 | `PRIMARY KEY` / `UNIQUE` / `CHECK` / `FK ON DELETE` / `EXCLUDE USING gist` / `tstzrange` |
| `02_materialized_view.py` | 普通视图 vs 物化视图查询性能 + REFRESH CONCURRENTLY | `CREATE MATERIALIZED VIEW` / `REFRESH … CONCURRENTLY` / UNIQUE 索引前置条件 |
| `03_identity_serial.py` | `SERIAL` vs `IDENTITY ALWAYS` vs `IDENTITY DEFAULT` 行为差异 | `GENERATED ALWAYS AS IDENTITY` / `OVERRIDING SYSTEM VALUE` / `pg_get_serial_sequence()` |

## 预期输出

`01_constraints.py` 跑完会看到约 6 段「故意触发约束 → 抛错」的输出，关键片段：

```
[Demo 5] EXCLUDE 约束：同房间时段不能重叠
  尝试给 ch4_rooms[1] 再插一段 10:30-11:30 …
  ✅ 预期失败：conflicting key value violates exclusion constraint "no_overlap_in_room"
                Key (room_id, period)=(1, …) conflicts with existing key …
```

`02_materialized_view.py` 会对比同一个聚合的两种实现，常规输出：

```
[Demo 1] 普通视图   ch4_v_sales_by_region   平均 38.41 ms (每次都算 GROUP BY)
        物化视图   ch4_mv_daily_sales 聚合 平均 1.23 ms (查表 + 小聚合)
[Demo 2] REFRESH MATERIALIZED VIEW (FULL):       42.7 ms (期间 SELECT 阻塞)
        REFRESH MATERIALIZED VIEW CONCURRENTLY: 88.3 ms (期间 SELECT 不阻塞)
```

## 常见报错

- `connection refused` → PG 没起 / 端口不对
- `relation "ch4_rooms" does not exist` → 没跑 `../init.sql`
- `extension "btree_gist" is not available` → 系统包未安装 `postgresql-XX-btree-gist`，先 `apt install`
- `cannot refresh materialized view "ch4_mv_no_unique" concurrently` → CONCURRENTLY 必须有 UNIQUE 索引，这是脚本故意演示的失败场景
- `cannot insert a non-DEFAULT value into column "id"` → 用 `IDENTITY ALWAYS` 时直接 `INSERT id` 会被禁止，需 `OVERRIDING SYSTEM VALUE`
