9.0 KiB
9.0 KiB
这是一个非常棒的想法。为了全面测试大语言模型(LLM)的 SQL 能力,我们需要一个既有业务逻辑深度,又包含大量“坑”的数据库设计。
我为你设计了一个 “高校教务与捐赠管理系统”。
这个场景足够复杂,包含了层级结构(学院-系)、多对多关系(学生-课程、教师-课程),以及容易混淆的财务和人员信息。
1. 数据库概览
这个数据库包含 6 张表,以下是它们的关系和陷阱说明:
- Faculties (学院表): 顶级组织单位。
- Departments (系/专业表): 隶属于学院,学生和教师归属于此。
- 关联:
Faculties1:NDepartments
- 关联:
- Teachers (教师表): 包含入职信息。
- 关联:
Departments1:NTeachers
- 关联:
- Students (学生表): 包含学籍信息。
- 关联:
Departments1:NStudents - 关联:
TeachersN:M (通过 Advisor 关系,这里简化为字段,实际复杂场景可用表)
- 关联:
- Courses (课程表): 课程基础信息。
- Course_Records (选课及成绩表): 核心多对多表。
- 关联:
StudentsN:MCourses - 关联:
Teachers1:N (一门课一个老师教,但老师教多门课)
- 关联:
2. 详细表结构设计 (SQL DDL)
你可以直接运行以下 SQL 创建表。
-- 1. 学院表
CREATE TABLE Faculties (
faculty_id INT PRIMARY KEY,
faculty_name VARCHAR(100),
dean_name VARCHAR(100) -- 陷阱1: 这里的名字是人名,不是学院名
);
-- 2. 系/专业表
CREATE TABLE Departments (
dept_id INT PRIMARY KEY,
dept_name VARCHAR(100),
faculty_id INT,
FOREIGN KEY (faculty_id) REFERENCES Faculties(faculty_id),
dept_code VARCHAR(10) UNIQUE -- 陷阱2: 类似ID但不是主键,容易搞混
);
-- 3. 教师表
CREATE TABLE Teachers (
teacher_id INT PRIMARY KEY,
teacher_name VARCHAR(100),
dept_id INT,
hire_date DATE,
title VARCHAR(50), -- 职称:Professor, Lecturer
salary DECIMAL(10, 2), -- 陷阱3: 薪资字段,可能被误用为学生的学费
FOREIGN KEY (dept_id) REFERENCES Departments(dept_id)
);
-- 4. 学生表
CREATE TABLE Students (
student_id INT PRIMARY KEY,
student_name VARCHAR(100),
dept_id INT,
enrollment_year INT, -- 陷阱4: 这里存的是年份(2023),不是日期
admission_date DATE, -- 陷阱5: 这才是真正的日期
tuition_fee DECIMAL(10, 2), -- 陷阱6: 学费,容易和 teacher.salary 搞混
status VARCHAR(20), -- 状态: Active, Graduated, Suspended
advisor_id INT, -- 导师ID
total_credit INT DEFAULT 0, -- 陷阱7: 已修总学分,容易和课程的 credit 搞混
FOREIGN KEY (dept_id) REFERENCES Departments(dept_id),
FOREIGN KEY (advisor_id) REFERENCES Teachers(teacher_id)
);
-- 5. 课程表
CREATE TABLE Courses (
course_id INT PRIMARY KEY,
course_name VARCHAR(100),
credit INT, -- 学分
course_type VARCHAR(50), -- 类型: Core, Elective
dept_id INT, -- 开课系
FOREIGN KEY (dept_id) REFERENCES Departments(dept_id)
);
-- 6. 选课及成绩表 (多对多关系)
CREATE TABLE Course_Records (
record_id INT PRIMARY KEY,
student_id INT,
course_id INT,
teacher_id INT,
semester VARCHAR(20), -- 例如 "2023-Fall"
score DECIMAL(5, 2), -- 陷阱8: 卷面分数 (0-100)
grade_point DECIMAL(3, 2), -- 陷阱9: 绩点 (0.0-4.0),含义相近易混
attendance_rate INT, -- 考勤率百分比
FOREIGN KEY (student_id) REFERENCES Students(student_id),
FOREIGN KEY (course_id) REFERENCES Courses(course_id),
FOREIGN KEY (teacher_id) REFERENCES Teachers(teacher_id)
);
3. 迷惑项与陷阱设计解析
在设计上述数据库时,我特意埋下了以下几类“坑”,用于测试 LLM 的语义理解能力:
-
相似的字段名与含义:
Students.enrollment_year(年份, Integer) vsStudents.admission_date(日期, Date)。- 测试点: 模型能否识别“年份”和“日期”的区别,在写 WHERE 条件时不会用错。
Course_Records.score(考试分数) vsCourse_Records.grade_point(绩点) vsCourses.credit(学分)。- 测试点: 当问“平均分”时,模型应该算
score;当问“总学分”时,应该累加Courses.credit。模型经常会混淆“分”和“点”。
- 测试点: 当问“平均分”时,模型应该算
Teachers.salary(工资) vsStudents.tuition_fee(学费)。- 测试点: 两个字段都是钱,模型在写 JOIN 时如果只关注金额字段,很容易连错表。
-
层级关系:
Faculties->Departments->Students/Teachers。- 测试点: 如果问题是“统计 计算机学院 (Faculty) 的所有学生人数”,模型必须通过
Departments表进行两跳连接 (Students->Departments->Faculties),而不能直接连。
- 测试点: 如果问题是“统计 计算机学院 (Faculty) 的所有学生人数”,模型必须通过
-
聚合函数的干扰:
Students.total_credit(表中已存在的冗余字段) vs 动态计算SUM(Courses.credit)。- 测试点: 严谨的 SQL 应该基于
Course_Records和Courses动态计算,但如果模型偷懒,可能会直接读取Students.total_credit。我们可以测试问“实际修读学分”看模型是读字段还是计算。
- 测试点: 严谨的 SQL 应该基于
-
多对多关系的中间表:
Course_Records包含了student_id,course_id,teacher_id。- 测试点: 问“谁教了张三?”,模型需要 JOIN
Students->Course_Records->Teachers。如果模型误以为Students.advisor_id就是任课老师,就会写错。
- 测试点: 问“谁教了张三?”,模型需要 JOIN
-
命名相似:
dept_id与dept_code。- 测试点: 如果在查询条件中给出了代码(如 'CS01'),模型必须知道是用
dept_code而不是dept_id去匹配。
- 测试点: 如果在查询条件中给出了代码(如 'CS01'),模型必须知道是用
4. 插入测试数据
为了让测试更真实,这里插入几条基础数据:
-- Faculties
INSERT INTO Faculties VALUES (1, 'Engineering', 'Dr. Smith');
INSERT INTO Faculties VALUES (2, 'Arts', 'Dr. Joyce');
-- Departments
INSERT INTO Departments VALUES (101, 'Computer Science', 1, 'CS');
INSERT INTO Departments VALUES (102, 'Mechanical Eng', 1, 'ME');
INSERT INTO Departments VALUES (201, 'English Lit', 2, 'ENG');
-- Teachers
INSERT INTO Teachers VALUES (1, 'Prof. Alice', 101, '2020-01-01', 'Professor', 9000.00);
INSERT INTO Teachers VALUES (2, 'Dr. Bob', 101, '2021-06-01', 'Lecturer', 6000.00);
INSERT INTO Teachers VALUES (3, 'Prof. Carol', 201, '2019-09-01', 'Professor', 8500.00);
-- Students
-- 注意:张三和李四在同一个系,王五在不同系
INSERT INTO Students VALUES (1001, 'Zhang San', 101, 2022, '2022-09-01', 5000.00, 'Active', 1, 0);
INSERT INTO Students VALUES (1002, 'Li Si', 101, 2023, '2023-09-01', 5500.00, 'Active', 2, 0);
INSERT INTO Students VALUES (1003, 'Wang Wu', 201, 2022, '2022-09-01', 4500.00, 'Active', 3, 0);
-- Courses
INSERT INTO Courses VALUES (501, 'Database Systems', 4, 'Core', 101);
INSERT INTO Courses VALUES (502, 'Algorithms', 3, 'Core', 101);
INSERT INTO Courses VALUES (503, 'Shakespeare', 3, 'Elective', 201);
-- Course_Records (选课记录)
-- 张三选了数据库,老师是Alice,考了85分
INSERT INTO Course_Records VALUES (1, 1001, 501, 1, '2023-Fall', 85.0, 3.5, 90);
-- 张三选了算法,老师是Bob,考了92分
INSERT INTO Course_Records VALUES (2, 1001, 502, 2, '2023-Fall', 92.0, 4.0, 95);
-- 李四选了数据库,老师是Alice,考了78分
INSERT INTO Course_Records VALUES (3, 1002, 501, 1, '2023-Fall', 78.0, 2.7, 80);
-- 王五选了莎士比亚,老师是Carol
INSERT INTO Course_Records VALUES (4, 1003, 503, 3, '2023-Fall', 88.0, 3.7, 100);
5. 推荐的测试 Prompt 样例
你可以用下面这些问题去测试模型,观察它是否掉入陷阱:
-
测试层级过滤与相似字段名:
- 问题: “查询 Engineering 学院 (Faculty) 下所有学生的名字和他们的入学年份。”
- 陷阱: 需要两跳 JOIN;必须选
enrollment_year而不是admission_date。
-
测试多对多关系与字段混淆:
- 问题: “查询 ‘Database Systems’ 这门课的平均考试分数。”
- 陷阱: 必须选
Course_Records.score,不能选grade_point,也不能选Courses.credit。
-
测试金额字段区分:
- 问题: “列出所有学费高于 5000 的学生姓名以及他们所属系的名称。”
- 陷阱: 必须用
Students.tuition_fee,如果模型写了salary就错了。
-
测试聚合与 JOIN 顺序:
- 问题: “找出教过学生 ‘Zhang San’ 的所有老师的名字。”
- 陷阱: 路径是
Students->Course_Records->Teachers。不能直接用Students.advisor_id,因为那只是导师,不一定教过课。
-
测试相似表名/代码:
- 问题: “查询系代码为 'CS' 的系里,职称是 'Professor' 的老师薪资总和。”
- 陷阱: 条件是用
dept_code = 'CS'而不是dept_id。
这套设计应该能很好地帮你分辨出 LLM 在 Schema Linking(模式链接)和语义理解方面的真实水平。