理解数据库结构定义、数据完整性约束、表间查询关系
Schema 描述数据长什么样,不涉及数据本身。它包含以下所有元素:
| 组成部分 | 作用 | 示例 |
|---|---|---|
| 表 (Table) | 定义有哪些表、每个表的列和列类型 | CREATE TABLE guests (id VARCHAR, name TEXT, ...) |
| 约束 (Constraint) | 对数据的规则限制 | PRIMARY KEY, FOREIGN KEY, UNIQUE, NOT NULL, CHECK |
| 索引 (Index) | 加速查询的数据结构 | CREATE INDEX idx_appointments_date ON appointments(date) |
| 触发器 (Trigger) | 数据变动时自动执行的函数 | AFTER INSERT ON schedules_daily EXECUTE fn_generate_times() |
| 视图 (View) | 虚拟表,基于查询定义 | CREATE VIEW v_active_employees AS SELECT ... |
| 约束 | 作用 | 示例 |
|---|---|---|
PRIMARY KEY |
唯一标识一行,不能重复、不能为空 | id VARCHAR(255) PRIMARY KEY |
FOREIGN KEY |
引用另一张表的主键,保证引用有效 | guest_id REFERENCES guests(id) |
UNIQUE |
列值不能重复 | email VARCHAR(255) UNIQUE |
NOT NULL |
列值不能为空 | name TEXT NOT NULL |
CHECK |
自定义条件 | CHECK (price > 0) |
public schema、tenant_spa001 schema,每个 schema 里有一套独立的表。但本文讨论的是广义的"数据库结构定义"。
这是两个完全不同的概念,但非常容易混淆。
FK 只在你 INSERT / UPDATE / DELETE 的时候起作用——它阻止你写入非法数据。
-- 定义:appointments 的 guest_id 必须指向 guests 表里存在的 id ALTER TABLE appointments ADD CONSTRAINT fk_appointments_guest FOREIGN KEY (guest_id) REFERENCES guests(id);
加了这个约束后:
violates foreign key constraintJOIN 是查询时你主动告诉数据库怎么把两张表关联起来读取。
-- 查询:把 appointments 和 guests 关联起来读取 SELECT a.id, g.name FROM appointments a JOIN guests g ON a.guest_id = g.id;
JOIN 不需要 FK 约束存在——只要两张表有可以匹配的列,你就能 JOIN。
| FK 约束 | JOIN 查询 | |
|---|---|---|
| 作用时机 | 写入数据时(INSERT / UPDATE / DELETE) | 读取数据时(SELECT) |
| 目的 | 保证数据完整性(拒绝非法数据) | 关联查询多张表的数据 |
| 是否必须 | 可选(没有也能跑) | 需要关联数据时才写 |
| 谁触发 | 数据库自动检查 | 开发者手动写 SQL |
| 性能影响 | 写入变慢(每次 INSERT 需校验) | 读取时按需消耗 |
| Schema 声明 FK | DB 强制 FK | |
|---|---|---|
| 含义 | SQL 文件里写了 REFERENCES 语句 |
information_schema 里能查到的约束 |
| 位置 | database/schema/tables/*.sql 文件中 |
PostgreSQL 数据库内部 |
| 是否强制执行 | 不强制(只是代码文件) | 强制(数据库拒绝违规写入) |
| Celoria 示例 | appointments, employees, guests 等核心表 |
day_end_closeouts, campaign_*, tip_distributions 等后期模块 |
具体来说:
database/schema/tables/05_appointments.sql 文件里写了:
guest_id VARCHAR(255) REFERENCES guests(id) ON DELETE CASCADE
但数据库里 appointments 表实际上没有这个 FK 约束。
这意味着你可以执行:
INSERT INTO appointments (guest_id) VALUES ('random_garbage');
-- 不会报错!因为没有 FK 约束来阻止
早期核心业务表建表时可能没有跑那些 SQL 文件,或者建表脚本没包含 FK 部分。后来通过迁移脚本(migration)添加的模块(财务、营销等)正确部署了 FK 约束。
| 数据源 | FK 边数 | 说明 |
|---|---|---|
| 数据库实际 FK | 64 | 主要在财务/运营/营销模块 |
| Schema 文件声明的 FK | 36 | 主要在核心业务表(未部署到 DB) |
| 依赖图中标记的 FK | 25 | 大部分是 Schema 声明的,非 DB 强制的 |
guest_id,数据库不会报错,产生的孤儿记录会导致 JOIN 查询时数据"消失"。
| 机制 | 作用 | 层级 | 是否自动 |
|---|---|---|---|
| FK 约束 | 写入时校验引用有效性 | 数据库层 | 自动(数据库强制) |
| JOIN 查询 | 读取时关联多张表 | SQL 查询层 | 手动(开发者写 SQL) |
| 触发器 | 数据变动时自动执行逻辑 | 数据库层 | 自动(绑定事件触发) |
| 索引 | 加速查询(尤其是 JOIN 的匹配列) | 数据库层 | 自动(查询优化器选择) |
| 应用层校验 | 代码中检查数据合法性 | 应用层 | 手动(开发者写代码) |
| ORM 关联 | 框架自动生成 JOIN 查询 | 应用层 | 半自动(配置后框架生成) |