数据库大作业 — 数据库设计与实施
沉浸式剧本杀平台的完整数据库设计——从 E-R 建模到 3NF 规范化,覆盖 10 张业务表、触发器、存储过程、RBAC 权限体系。
- 沉浸式剧本平台数据库设计(10 张表)
- 用户/剧本/角色/线索/打卡点/场次/订单/排班/进度/评价
- 触发器:订单支付自动更新场次人数 + NPC 排班冲突检测
- 视图:场次概览 + 剧本评分汇总
- 存储过程:场次运营报表生成
- RBAC 四级角色权限体系(游客/NPC/运营/管理员)
还有 2 项
- 50+ 条测试数据覆盖全部业务场景
- 复杂查询:嵌套子查询 / EXISTS / GROUP BY HAVING
数据库设计与实施
项目概述
一个完整的沉浸式剧本杀业务数据库设计,涵盖用户管理、剧本管理、角色分配、线索链、打卡点、场次排期、订单支付、NPC 排班、游玩进度追踪和评价系统等 10 张核心表。同时包含一个独立的 SQL 实战操练部分,覆盖 20+ 道 SQL 查询练习。
架构设计
数据库以 Script(剧本)为业务核心,向外辐射关联 9 张表:Role 和 Clue 属于剧本的内容资产,CheckPoint 将虚拟线索映射到物理 GPS 位置,Session 和 Order 构成商家的运营和营收模块,NpcSchedule 管理人力排班,PlayerProgress 追踪玩家状态,Review 收集反馈。
外键约束确保数据完整性:Role/Clue 通过 script_id 级联删除,Order 通过 user_id 和 session_id 关联用户和场次,PlayerProgress 通过 uk_user_session 唯一约束保证同一用户在同一场次只能有一条进度记录。
视图与存储过程:View_SessionOverview 聚合场次概览(含余位和已支付订单数),View_ScriptRating 计算剧本评分统计。proc_session_report 存储过程接受剧本 ID 和日期范围参数,输出总订单数、总营收和逐场次明细。
技术亮点
触发器实现业务自动化:trg_order_payment_update_session 在订单支付状态变更时自动更新场次的 current_players 计数,无需应用层处理。trg_npcschedule_conflict_check 在插入排班前通过时间重叠检测防止 NPC 排班冲突,使用 SIGNAL SQLSTATE '45000' 抛出自定义错误。
RBAC 权限体系:使用 MySQL 的角色机制创建了 role_tourist/role_npc/role_operator/role_admin 四个数据库角色,每个角色精确控制可操作的表和列。tourist 只能查询和下单,npc 可更新游玩进度,operator 管理剧本内容,admin 拥有全部权限。
CHECK 约束与索引优化:通过 CHECK 约束确保角色类型、评分范围、难度等级的数据合法性。为常用查询字段建立 12 个索引,覆盖用户角色查询、订单状态查询、场次时间范围扫描等场景。
设计决策
命名规范遵循"表名+ID"的主键命名惯例,外键显式命名(fk_来源_目标)便于维护和错误定位。GPS 坐标使用 DECIMAL(10,7) 保证精度,price 使用 DECIMAL(10,2) 避免浮点误差。AR 相关内容预留 url 字段,为未来扩展留有余地。
关键代码解读
CREATE TRIGGER trg_npcschedule_conflict_check
BEFORE INSERT ON NpcSchedule
FOR EACH ROW
BEGIN
SELECT COUNT(*) INTO conflict_count
FROM NpcSchedule ns
JOIN `Session` s ON ns.session_id = s.session_id
WHERE ns.npc_id = NEW.npc_id
AND (
(s.start_time <= s_new.start_time AND s.end_time > s_new.start_time)
OR (s_new.start_time <= s.start_time AND s_new.end_time > s.start_time)
);
IF conflict_count > 0 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '排班时间冲突';
END IF;
END;
该触发器利用两段时间重叠的判断逻辑(时间段 A 的开始在 B 结束之前且 A 的结束在 B 开始之后),在插入排班前自动检测时间冲突。相比应用层检查,触发器从数据库层面保证排班数据的绝对一致性。
sql核心表结构(建表 DDL)
沉浸式剧本平台的核心数据模型,包含用户、剧本、角色、线索、打卡点 5 张主表。
-- =============================================
-- 沉浸式剧本平台 — 核心表结构
-- 作者: 童国睿
-- =============================================
-- 1. 用户表
CREATE TABLE User (
user_id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
phone VARCHAR(11) NOT NULL UNIQUE,
nickname VARCHAR(50) NOT NULL,
role_type TINYINT NOT NULL DEFAULT 0
COMMENT '0-游客 1-运营 2-NPC 3-管理员',
status TINYINT NOT NULL DEFAULT 0,
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 2. 剧本表
CREATE TABLE Script (
script_id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL COMMENT '剧本名称',
description TEXT,
difficulty TINYINT NOT NULL DEFAULT 1,
duration INT NOT NULL COMMENT '时长(分)',
min_players INT NOT NULL DEFAULT 1,
max_players INT NOT NULL,
cover_url VARCHAR(255),
status TINYINT NOT NULL DEFAULT 0,
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 3. 角色表
CREATE TABLE Role (
role_id INT AUTO_INCREMENT PRIMARY KEY,
script_id INT NOT NULL,
name VARCHAR(50) NOT NULL,
description TEXT,
is_key_role TINYINT NOT NULL DEFAULT 0,
sort_order INT NOT NULL DEFAULT 0,
FOREIGN KEY (script_id) REFERENCES Script(script_id)
ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 4. 线索表
CREATE TABLE Clue (
clue_id INT AUTO_INCREMENT PRIMARY KEY,
script_id INT NOT NULL,
name VARCHAR(100) NOT NULL,
content TEXT NOT NULL,
clue_type TINYINT NOT NULL DEFAULT 0,
trigger_type TINYINT NOT NULL DEFAULT 0,
sequence_num INT NOT NULL,
FOREIGN KEY (script_id) REFERENCES Script(script_id)
ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 5. 打卡点表
CREATE TABLE CheckPoint (
point_id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
latitude DECIMAL(10,7) NOT NULL,
longitude DECIMAL(10,7) NOT NULL,
ar_content_url VARCHAR(255),
binding_clue_id INT,
is_required TINYINT NOT NULL DEFAULT 0,
FOREIGN KEY (binding_clue_id) REFERENCES Clue(clue_id)
ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;sql触发器:订单支付与排班冲突检测
两个关键触发器:支付状态变更自动更新场次报名人数,NPC 排班时间冲突检测防止重复调度。
-- 触发器1:订单支付 -> 场次人数自动更新
DELIMITER //
CREATE TRIGGER trg_order_payment_update_session
AFTER UPDATE ON `Order`
FOR EACH ROW
BEGIN
IF OLD.payment_status = 0 AND NEW.payment_status = 1 THEN
UPDATE `Session`
SET current_players = current_players + 1
WHERE session_id = NEW.session_id;
END IF;
IF OLD.payment_status = 1 AND NEW.payment_status = 2 THEN
UPDATE `Session`
SET current_players = current_players - 1
WHERE session_id = NEW.session_id;
END IF;
END//
DELIMITER ;
-- 触发器2:NPC排班时间冲突检测
DELIMITER //
CREATE TRIGGER trg_npcschedule_conflict_check
BEFORE INSERT ON NpcSchedule
FOR EACH ROW
BEGIN
DECLARE conflict_count INT;
SELECT COUNT(*) INTO conflict_count
FROM NpcSchedule ns
JOIN `Session` s ON ns.session_id = s.session_id
JOIN `Session` s_new ON s_new.session_id = NEW.session_id
WHERE ns.npc_id = NEW.npc_id
AND ns.schedule_date = NEW.schedule_date
AND (
(s.start_time <= s_new.start_time AND s.end_time > s_new.start_time)
OR
(s_new.start_time <= s.start_time AND s_new.end_time > s.start_time)
);
IF conflict_count > 0 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '该NPC在此时间段已有排班冲突';
END IF;
END//
DELIMITER ;sql存储过程:场次运营报表
按剧本和时间范围生成运营数据,包含总订单数、总收入、逐场次明细、上座率。
DELIMITER //
CREATE PROCEDURE proc_session_report(
IN p_script_id INT,
IN p_start_date DATE,
IN p_end_date DATE
)
BEGIN
DECLARE v_total_orders INT DEFAULT 0;
DECLARE v_total_revenue DECIMAL(12,2) DEFAULT 0;
SELECT COUNT(o.order_id), COALESCE(SUM(o.amount), 0)
INTO v_total_orders, v_total_revenue
FROM `Session` se
LEFT JOIN `Order` o
ON se.session_id = o.session_id AND o.payment_status = 1
WHERE (p_script_id IS NULL OR se.script_id = p_script_id)
AND DATE(se.start_time) BETWEEN p_start_date AND p_end_date;
-- 汇总输出
SELECT p_script_id AS script_id,
p_start_date AS date_from, p_end_date AS date_to,
v_total_orders AS paid_orders,
v_total_revenue AS revenue;
-- 逐场次明细
SELECT s.name AS script_name, se.session_id,
se.start_time,
CONCAT(se.current_players, '/', se.max_players) AS occupancy,
ROUND(se.current_players / se.max_players * 100, 1) AS rate,
se.price,
COUNT(o.order_id) AS order_count,
COALESCE(SUM(o.amount), 0) AS session_revenue
FROM `Session` se
JOIN Script s ON se.script_id = s.script_id
LEFT JOIN `Order` o
ON se.session_id = o.session_id AND o.payment_status = 1
WHERE (p_script_id IS NULL OR se.script_id = p_script_id)
AND DATE(se.start_time) BETWEEN p_start_date AND p_end_date
GROUP BY se.session_id
ORDER BY se.start_time;
END//
DELIMITER ;
-- 调用:CALL proc_session_report(1, '2026-06-01', '2026-06-30');| 路径 | 说明 | 行数 |
|---|---|---|
大作业二.sql | 完整数据库设计(建表/视图/触发器/存储过程/权限/测试数据) | 709 |