forked from wangziqi/ruoyi-vue-pro
531 lines
30 KiB
SQL
531 lines
30 KiB
SQL
-- =============================================
|
||
-- Education 模块 — 题库目录原生表 DDL (PostgreSQL)
|
||
-- Ticket #20: 原生实现题库目录浏览,替换 Scalar adapter
|
||
-- Migration: 008
|
||
-- Prerequisites: 000-education-schema.sql (database creation)
|
||
-- 001-education-tenant-seed.sql (tenant seed data)
|
||
-- =============================================
|
||
|
||
-- =============================================
|
||
-- Preconditions
|
||
-- =============================================
|
||
-- Verify none of these tables already exist:
|
||
-- SELECT table_name FROM information_schema.tables
|
||
-- WHERE table_catalog = current_database()
|
||
-- AND table_name IN (
|
||
-- 'education_region', 'education_school', 'education_major',
|
||
-- 'education_subject', 'education_category',
|
||
-- 'education_content_entry', 'education_content_node',
|
||
-- 'education_question_collection', 'education_question',
|
||
-- 'education_practice_blueprint', 'education_question_collection_question'
|
||
-- );
|
||
-- Result MUST be 0 before executing this migration.
|
||
|
||
-- =============================================
|
||
-- 1. 地区表
|
||
-- =============================================
|
||
-- Indexes:
|
||
-- idx_tenant — tenant-scoped queries (tenant-aware interceptor default)
|
||
-- idx_tenant_active_order — active region listing ordered by display order
|
||
CREATE TABLE education_region (
|
||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||
tenant_id BIGINT NOT NULL,
|
||
legacy_id VARCHAR(64) DEFAULT NULL,
|
||
name VARCHAR(100) NOT NULL,
|
||
code VARCHAR(32) DEFAULT NULL,
|
||
short_name VARCHAR(50) DEFAULT NULL,
|
||
full_name VARCHAR(200) DEFAULT NULL,
|
||
icon VARCHAR(500) DEFAULT NULL,
|
||
pinyin VARCHAR(200) DEFAULT NULL,
|
||
is_hot BOOLEAN NOT NULL DEFAULT false,
|
||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||
sort_order INT NOT NULL DEFAULT 0,
|
||
creator VARCHAR(64) DEFAULT '',
|
||
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
updater VARCHAR(64) DEFAULT '',
|
||
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
deleted BOOLEAN NOT NULL DEFAULT false
|
||
);
|
||
COMMENT ON TABLE education_region IS '教育-地区';
|
||
COMMENT ON COLUMN education_region.id IS '主键';
|
||
COMMENT ON COLUMN education_region.tenant_id IS '租户编号';
|
||
COMMENT ON COLUMN education_region.legacy_id IS '旧系统地区 ID';
|
||
COMMENT ON COLUMN education_region.name IS '地区名称';
|
||
COMMENT ON COLUMN education_region.code IS '地区编码';
|
||
COMMENT ON COLUMN education_region.short_name IS '地区简称';
|
||
COMMENT ON COLUMN education_region.full_name IS '地区全称';
|
||
COMMENT ON COLUMN education_region.icon IS '地区图标 URL';
|
||
COMMENT ON COLUMN education_region.pinyin IS '地区拼音';
|
||
COMMENT ON COLUMN education_region.is_hot IS '是否热门地区';
|
||
COMMENT ON COLUMN education_region.is_active IS '是否启用';
|
||
COMMENT ON COLUMN education_region.sort_order IS '显示排序值';
|
||
COMMENT ON COLUMN education_region.creator IS '创建者';
|
||
COMMENT ON COLUMN education_region.create_time IS '创建时间';
|
||
COMMENT ON COLUMN education_region.updater IS '更新者';
|
||
COMMENT ON COLUMN education_region.update_time IS '更新时间';
|
||
COMMENT ON COLUMN education_region.deleted IS '是否删除';
|
||
CREATE INDEX idx_tenant ON education_region (tenant_id);
|
||
CREATE INDEX idx_tenant_active_order ON education_region (tenant_id, is_active, sort_order);
|
||
|
||
-- =============================================
|
||
-- 2. 院校表
|
||
-- =============================================
|
||
CREATE TABLE education_school (
|
||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||
tenant_id BIGINT NOT NULL,
|
||
legacy_id VARCHAR(64) DEFAULT NULL,
|
||
region_id BIGINT DEFAULT NULL,
|
||
module_id BIGINT DEFAULT NULL,
|
||
name VARCHAR(200) NOT NULL,
|
||
professional_exam_date VARCHAR(200) DEFAULT NULL,
|
||
metadata JSONB DEFAULT NULL,
|
||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||
sort_order INT NOT NULL DEFAULT 0,
|
||
creator VARCHAR(64) DEFAULT '',
|
||
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
updater VARCHAR(64) DEFAULT '',
|
||
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
deleted BOOLEAN NOT NULL DEFAULT false
|
||
);
|
||
COMMENT ON TABLE education_school IS '教育-院校';
|
||
COMMENT ON COLUMN education_school.id IS '主键';
|
||
COMMENT ON COLUMN education_school.tenant_id IS '租户编号';
|
||
COMMENT ON COLUMN education_school.legacy_id IS '旧系统院校 ID';
|
||
COMMENT ON COLUMN education_school.region_id IS '地区 ID';
|
||
COMMENT ON COLUMN education_school.module_id IS '地区模块 ID';
|
||
COMMENT ON COLUMN education_school.name IS '院校名称';
|
||
COMMENT ON COLUMN education_school.professional_exam_date IS '专业考试日期说明';
|
||
COMMENT ON COLUMN education_school.metadata IS '院校扩展数据';
|
||
COMMENT ON COLUMN education_school.is_active IS '是否启用';
|
||
COMMENT ON COLUMN education_school.sort_order IS '显示排序值';
|
||
COMMENT ON COLUMN education_school.creator IS '创建者';
|
||
COMMENT ON COLUMN education_school.create_time IS '创建时间';
|
||
COMMENT ON COLUMN education_school.updater IS '更新者';
|
||
COMMENT ON COLUMN education_school.update_time IS '更新时间';
|
||
COMMENT ON COLUMN education_school.deleted IS '是否删除';
|
||
CREATE INDEX idx_tenant ON education_school (tenant_id);
|
||
CREATE INDEX idx_tenant_region ON education_school (tenant_id, region_id);
|
||
|
||
-- =============================================
|
||
-- 3. 专业表
|
||
-- =============================================
|
||
CREATE TABLE education_major (
|
||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||
tenant_id BIGINT NOT NULL,
|
||
legacy_id VARCHAR(64) DEFAULT NULL,
|
||
region_id BIGINT DEFAULT NULL,
|
||
school_id BIGINT DEFAULT NULL,
|
||
name VARCHAR(200) NOT NULL,
|
||
description VARCHAR(500) DEFAULT NULL,
|
||
study_tips VARCHAR(500) DEFAULT NULL,
|
||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||
sort_order INT NOT NULL DEFAULT 0,
|
||
creator VARCHAR(64) DEFAULT '',
|
||
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
updater VARCHAR(64) DEFAULT '',
|
||
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
deleted BOOLEAN NOT NULL DEFAULT false
|
||
);
|
||
COMMENT ON TABLE education_major IS '教育-专业';
|
||
COMMENT ON COLUMN education_major.id IS '主键';
|
||
COMMENT ON COLUMN education_major.tenant_id IS '租户编号';
|
||
COMMENT ON COLUMN education_major.legacy_id IS '旧系统专业 ID';
|
||
COMMENT ON COLUMN education_major.region_id IS '地区 ID';
|
||
COMMENT ON COLUMN education_major.school_id IS '院校 ID';
|
||
COMMENT ON COLUMN education_major.name IS '专业名称';
|
||
COMMENT ON COLUMN education_major.description IS '专业说明';
|
||
COMMENT ON COLUMN education_major.study_tips IS '学习建议';
|
||
COMMENT ON COLUMN education_major.is_active IS '是否启用';
|
||
COMMENT ON COLUMN education_major.sort_order IS '显示排序值';
|
||
COMMENT ON COLUMN education_major.creator IS '创建者';
|
||
COMMENT ON COLUMN education_major.create_time IS '创建时间';
|
||
COMMENT ON COLUMN education_major.updater IS '更新者';
|
||
COMMENT ON COLUMN education_major.update_time IS '更新时间';
|
||
COMMENT ON COLUMN education_major.deleted IS '是否删除';
|
||
CREATE INDEX idx_tenant ON education_major (tenant_id);
|
||
CREATE INDEX idx_tenant_school ON education_major (tenant_id, school_id);
|
||
|
||
-- =============================================
|
||
-- 4. 科目表
|
||
-- =============================================
|
||
CREATE TABLE education_subject (
|
||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||
tenant_id BIGINT NOT NULL,
|
||
region_id BIGINT DEFAULT NULL,
|
||
school_id BIGINT DEFAULT NULL,
|
||
major_id BIGINT DEFAULT NULL,
|
||
module_id BIGINT DEFAULT NULL,
|
||
name VARCHAR(200) NOT NULL,
|
||
type VARCHAR(50) DEFAULT NULL,
|
||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||
sort_order INT NOT NULL DEFAULT 0,
|
||
creator VARCHAR(64) DEFAULT '',
|
||
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
updater VARCHAR(64) DEFAULT '',
|
||
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
deleted BOOLEAN NOT NULL DEFAULT false
|
||
);
|
||
COMMENT ON TABLE education_subject IS '教育-科目';
|
||
COMMENT ON COLUMN education_subject.id IS '主键';
|
||
COMMENT ON COLUMN education_subject.tenant_id IS '租户编号';
|
||
COMMENT ON COLUMN education_subject.region_id IS '地区 ID';
|
||
COMMENT ON COLUMN education_subject.school_id IS '院校 ID';
|
||
COMMENT ON COLUMN education_subject.major_id IS '专业 ID';
|
||
COMMENT ON COLUMN education_subject.module_id IS '地区模块 ID';
|
||
COMMENT ON COLUMN education_subject.name IS '科目名称';
|
||
COMMENT ON COLUMN education_subject.type IS '科目类型';
|
||
COMMENT ON COLUMN education_subject.is_active IS '是否启用';
|
||
COMMENT ON COLUMN education_subject.sort_order IS '显示排序值';
|
||
COMMENT ON COLUMN education_subject.creator IS '创建者';
|
||
COMMENT ON COLUMN education_subject.create_time IS '创建时间';
|
||
COMMENT ON COLUMN education_subject.updater IS '更新者';
|
||
COMMENT ON COLUMN education_subject.update_time IS '更新时间';
|
||
COMMENT ON COLUMN education_subject.deleted IS '是否删除';
|
||
CREATE INDEX idx_tenant ON education_subject (tenant_id);
|
||
CREATE INDEX idx_tenant_major ON education_subject (tenant_id, major_id);
|
||
|
||
-- =============================================
|
||
-- 5. 题目分类表
|
||
-- =============================================
|
||
CREATE TABLE education_category (
|
||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||
tenant_id BIGINT NOT NULL,
|
||
subject_id BIGINT DEFAULT NULL,
|
||
legacy_node_id VARCHAR(64) DEFAULT NULL,
|
||
name VARCHAR(200) NOT NULL,
|
||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||
sort_order INT NOT NULL DEFAULT 0,
|
||
creator VARCHAR(64) DEFAULT '',
|
||
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
updater VARCHAR(64) DEFAULT '',
|
||
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
deleted BOOLEAN NOT NULL DEFAULT false
|
||
);
|
||
COMMENT ON TABLE education_category IS '教育-题目分类';
|
||
COMMENT ON COLUMN education_category.id IS '主键';
|
||
COMMENT ON COLUMN education_category.tenant_id IS '租户编号';
|
||
COMMENT ON COLUMN education_category.subject_id IS '科目 ID';
|
||
COMMENT ON COLUMN education_category.legacy_node_id IS '旧导航节点 ID';
|
||
COMMENT ON COLUMN education_category.name IS '分类名称';
|
||
COMMENT ON COLUMN education_category.is_active IS '是否启用';
|
||
COMMENT ON COLUMN education_category.sort_order IS '显示排序值';
|
||
COMMENT ON COLUMN education_category.creator IS '创建者';
|
||
COMMENT ON COLUMN education_category.create_time IS '创建时间';
|
||
COMMENT ON COLUMN education_category.updater IS '更新者';
|
||
COMMENT ON COLUMN education_category.update_time IS '更新时间';
|
||
COMMENT ON COLUMN education_category.deleted IS '是否删除';
|
||
CREATE INDEX idx_tenant ON education_category (tenant_id);
|
||
CREATE INDEX idx_tenant_subject ON education_category (tenant_id, subject_id);
|
||
|
||
-- =============================================
|
||
-- 6. 内容入口表
|
||
-- =============================================
|
||
-- Indexes:
|
||
-- uk_tenant_entry_key — per-tenant uniqueness for entry key
|
||
CREATE TABLE education_content_entry (
|
||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||
tenant_id BIGINT NOT NULL,
|
||
legacy_id VARCHAR(64) DEFAULT NULL,
|
||
region_id BIGINT DEFAULT NULL,
|
||
entry_key VARCHAR(100) NOT NULL,
|
||
name VARCHAR(200) NOT NULL,
|
||
entry_type VARCHAR(50) NOT NULL,
|
||
icon VARCHAR(500) DEFAULT NULL,
|
||
route VARCHAR(200) DEFAULT NULL,
|
||
description VARCHAR(500) DEFAULT NULL,
|
||
visibility VARCHAR(50) NOT NULL DEFAULT 'PUBLIC',
|
||
access_rules JSONB DEFAULT NULL,
|
||
layout_config JSONB DEFAULT NULL,
|
||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||
sort_order INT NOT NULL DEFAULT 0,
|
||
creator VARCHAR(64) DEFAULT '',
|
||
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
updater VARCHAR(64) DEFAULT '',
|
||
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
deleted BOOLEAN NOT NULL DEFAULT false
|
||
);
|
||
COMMENT ON TABLE education_content_entry IS '教育-内容入口';
|
||
COMMENT ON COLUMN education_content_entry.id IS '主键';
|
||
COMMENT ON COLUMN education_content_entry.tenant_id IS '租户编号';
|
||
COMMENT ON COLUMN education_content_entry.legacy_id IS '旧系统内容入口 ID';
|
||
COMMENT ON COLUMN education_content_entry.region_id IS '地区 ID';
|
||
COMMENT ON COLUMN education_content_entry.entry_key IS '内容入口唯一键';
|
||
COMMENT ON COLUMN education_content_entry.name IS '内容入口名称';
|
||
COMMENT ON COLUMN education_content_entry.entry_type IS '内容入口类型';
|
||
COMMENT ON COLUMN education_content_entry.icon IS '图标';
|
||
COMMENT ON COLUMN education_content_entry.route IS '前端路由';
|
||
COMMENT ON COLUMN education_content_entry.description IS '入口说明';
|
||
COMMENT ON COLUMN education_content_entry.visibility IS '可见性';
|
||
COMMENT ON COLUMN education_content_entry.access_rules IS '访问规则 JSON';
|
||
COMMENT ON COLUMN education_content_entry.layout_config IS '布局配置 JSON';
|
||
COMMENT ON COLUMN education_content_entry.is_active IS '是否启用';
|
||
COMMENT ON COLUMN education_content_entry.sort_order IS '显示排序值';
|
||
COMMENT ON COLUMN education_content_entry.creator IS '创建者';
|
||
COMMENT ON COLUMN education_content_entry.create_time IS '创建时间';
|
||
COMMENT ON COLUMN education_content_entry.updater IS '更新者';
|
||
COMMENT ON COLUMN education_content_entry.update_time IS '更新时间';
|
||
COMMENT ON COLUMN education_content_entry.deleted IS '是否删除';
|
||
CREATE INDEX idx_tenant ON education_content_entry (tenant_id);
|
||
CREATE UNIQUE INDEX uk_tenant_entry_key ON education_content_entry (tenant_id, entry_key);
|
||
|
||
-- =============================================
|
||
-- 7. 内容节点表(树形结构)
|
||
-- =============================================
|
||
-- Indexes:
|
||
-- idx_entry_parent — supports tree navigation: find children of a parent within an entry
|
||
-- idx_tenant_entry — tenant-scoped queries by entry
|
||
CREATE TABLE education_content_node (
|
||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||
tenant_id BIGINT NOT NULL,
|
||
entry_id BIGINT NOT NULL,
|
||
parent_id BIGINT DEFAULT NULL,
|
||
name VARCHAR(200) NOT NULL,
|
||
title VARCHAR(200) DEFAULT NULL,
|
||
node_type VARCHAR(50) NOT NULL,
|
||
marker_type VARCHAR(50) DEFAULT NULL,
|
||
depth INT NOT NULL DEFAULT 0,
|
||
is_leaf BOOLEAN NOT NULL DEFAULT false,
|
||
is_selectable BOOLEAN NOT NULL DEFAULT true,
|
||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||
sort_order INT NOT NULL DEFAULT 0,
|
||
metadata JSONB DEFAULT NULL,
|
||
creator VARCHAR(64) DEFAULT '',
|
||
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
updater VARCHAR(64) DEFAULT '',
|
||
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
deleted BOOLEAN NOT NULL DEFAULT false
|
||
);
|
||
COMMENT ON TABLE education_content_node IS '教育-内容节点';
|
||
COMMENT ON COLUMN education_content_node.id IS '主键';
|
||
COMMENT ON COLUMN education_content_node.tenant_id IS '租户编号';
|
||
COMMENT ON COLUMN education_content_node.entry_id IS '内容入口 ID';
|
||
COMMENT ON COLUMN education_content_node.parent_id IS '父节点 ID(NULL=根节点)';
|
||
COMMENT ON COLUMN education_content_node.name IS '节点名称';
|
||
COMMENT ON COLUMN education_content_node.title IS '节点标题';
|
||
COMMENT ON COLUMN education_content_node.node_type IS '节点类型';
|
||
COMMENT ON COLUMN education_content_node.marker_type IS '节点标记类型';
|
||
COMMENT ON COLUMN education_content_node.depth IS '树深度';
|
||
COMMENT ON COLUMN education_content_node.is_leaf IS '是否叶子节点';
|
||
COMMENT ON COLUMN education_content_node.is_selectable IS '是否可被选择';
|
||
COMMENT ON COLUMN education_content_node.is_active IS '是否启用';
|
||
COMMENT ON COLUMN education_content_node.sort_order IS '显示排序值';
|
||
COMMENT ON COLUMN education_content_node.metadata IS '扩展元数据';
|
||
COMMENT ON COLUMN education_content_node.creator IS '创建者';
|
||
COMMENT ON COLUMN education_content_node.create_time IS '创建时间';
|
||
COMMENT ON COLUMN education_content_node.updater IS '更新者';
|
||
COMMENT ON COLUMN education_content_node.update_time IS '更新时间';
|
||
COMMENT ON COLUMN education_content_node.deleted IS '是否删除';
|
||
CREATE INDEX idx_tenant ON education_content_node (tenant_id);
|
||
CREATE INDEX idx_entry_parent ON education_content_node (entry_id, parent_id);
|
||
CREATE INDEX idx_tenant_entry ON education_content_node (tenant_id, entry_id);
|
||
|
||
-- =============================================
|
||
-- 8. 题集表
|
||
-- =============================================
|
||
CREATE TABLE education_question_collection (
|
||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||
tenant_id BIGINT NOT NULL,
|
||
entry_id BIGINT DEFAULT NULL,
|
||
node_id BIGINT DEFAULT NULL,
|
||
name VARCHAR(200) NOT NULL,
|
||
title VARCHAR(200) DEFAULT NULL,
|
||
collection_type VARCHAR(50) NOT NULL,
|
||
question_count INT NOT NULL DEFAULT 0,
|
||
duration_minutes INT DEFAULT NULL,
|
||
access_rules JSONB DEFAULT NULL,
|
||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||
sort_order INT NOT NULL DEFAULT 0,
|
||
metadata JSONB DEFAULT NULL,
|
||
creator VARCHAR(64) DEFAULT '',
|
||
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
updater VARCHAR(64) DEFAULT '',
|
||
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
deleted BOOLEAN NOT NULL DEFAULT false
|
||
);
|
||
COMMENT ON TABLE education_question_collection IS '教育-题集';
|
||
COMMENT ON COLUMN education_question_collection.id IS '主键';
|
||
COMMENT ON COLUMN education_question_collection.tenant_id IS '租户编号';
|
||
COMMENT ON COLUMN education_question_collection.entry_id IS '内容入口 ID';
|
||
COMMENT ON COLUMN education_question_collection.node_id IS '内容节点 ID';
|
||
COMMENT ON COLUMN education_question_collection.name IS '题集名称';
|
||
COMMENT ON COLUMN education_question_collection.title IS '题集标题';
|
||
COMMENT ON COLUMN education_question_collection.collection_type IS '题集类型';
|
||
COMMENT ON COLUMN education_question_collection.question_count IS '题目数量';
|
||
COMMENT ON COLUMN education_question_collection.duration_minutes IS '建议作答时长(分钟)';
|
||
COMMENT ON COLUMN education_question_collection.access_rules IS '访问规则 JSON';
|
||
COMMENT ON COLUMN education_question_collection.is_active IS '是否启用';
|
||
COMMENT ON COLUMN education_question_collection.sort_order IS '显示排序值';
|
||
COMMENT ON COLUMN education_question_collection.metadata IS '扩展元数据';
|
||
COMMENT ON COLUMN education_question_collection.creator IS '创建者';
|
||
COMMENT ON COLUMN education_question_collection.create_time IS '创建时间';
|
||
COMMENT ON COLUMN education_question_collection.updater IS '更新者';
|
||
COMMENT ON COLUMN education_question_collection.update_time IS '更新时间';
|
||
COMMENT ON COLUMN education_question_collection.deleted IS '是否删除';
|
||
CREATE INDEX idx_tenant ON education_question_collection (tenant_id);
|
||
CREATE INDEX idx_tenant_entry ON education_question_collection (tenant_id, entry_id);
|
||
CREATE INDEX idx_tenant_node ON education_question_collection (tenant_id, node_id);
|
||
|
||
-- =============================================
|
||
-- 9. 题目表(核心表)
|
||
-- =============================================
|
||
-- Indexes:
|
||
-- idx_tenant_published — 查询已发布题目(主查询路径)
|
||
-- idx_tenant_collection — 按题集筛选
|
||
-- idx_tenant_subject — 按科目筛选
|
||
-- idx_tenant_node — 按内容节点筛选
|
||
-- idx_tenant_type_diff — 按题型和难度筛选
|
||
-- Security note: correct_answer 和 explanation 列存在但需在 service 层过滤;
|
||
-- 学生端 DTO 绝不得包含这两列。
|
||
-- Options JSONB: 存储 label, content, isCorrect, order — 完整选项数据。
|
||
-- Service 层在学生端返回前剥离 isCorrect。
|
||
CREATE TABLE education_question (
|
||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||
tenant_id BIGINT NOT NULL,
|
||
content_version INT NOT NULL DEFAULT 1,
|
||
stem TEXT NOT NULL,
|
||
type VARCHAR(32) NOT NULL,
|
||
type_label VARCHAR(50) DEFAULT NULL,
|
||
difficulty VARCHAR(32) DEFAULT NULL,
|
||
question_content JSONB DEFAULT NULL,
|
||
options JSONB NOT NULL,
|
||
correct_answer VARCHAR(500) DEFAULT NULL,
|
||
explanation TEXT DEFAULT NULL,
|
||
analysis TEXT DEFAULT NULL,
|
||
status VARCHAR(20) NOT NULL DEFAULT 'PUBLISHED',
|
||
is_published BOOLEAN NOT NULL DEFAULT true,
|
||
collection_id BIGINT DEFAULT NULL,
|
||
subject_id BIGINT DEFAULT NULL,
|
||
node_id BIGINT DEFAULT NULL,
|
||
tags JSONB DEFAULT NULL,
|
||
sort_order INT NOT NULL DEFAULT 0,
|
||
metadata JSONB DEFAULT NULL,
|
||
creator VARCHAR(64) DEFAULT '',
|
||
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
updater VARCHAR(64) DEFAULT '',
|
||
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
deleted BOOLEAN NOT NULL DEFAULT false
|
||
);
|
||
COMMENT ON TABLE education_question IS '教育-题目';
|
||
COMMENT ON COLUMN education_question.id IS '主键';
|
||
COMMENT ON COLUMN education_question.tenant_id IS '租户编号';
|
||
COMMENT ON COLUMN education_question.content_version IS '内容版本号(递增,保证历史报告稳定性)';
|
||
COMMENT ON COLUMN education_question.stem IS '题干';
|
||
COMMENT ON COLUMN education_question.type IS '题型';
|
||
COMMENT ON COLUMN education_question.type_label IS '题型显示名称';
|
||
COMMENT ON COLUMN education_question.difficulty IS '难度';
|
||
COMMENT ON COLUMN education_question.question_content IS '题干结构化内容(兼容复杂题型)';
|
||
COMMENT ON COLUMN education_question.options IS '选项 JSONB [{label, content, isCorrect, order}]';
|
||
COMMENT ON COLUMN education_question.correct_answer IS '正确答案(敏感字段,学生端不可返回)';
|
||
COMMENT ON COLUMN education_question.explanation IS '答案解析(敏感字段,学生端不可返回)';
|
||
COMMENT ON COLUMN education_question.analysis IS '深度解析(敏感字段)';
|
||
COMMENT ON COLUMN education_question.status IS '题目状态:PUBLISHED-已发布, HIDDEN-已隐藏, DRAFT-草稿, INACTIVE-停用';
|
||
COMMENT ON COLUMN education_question.is_published IS '是否已发布';
|
||
COMMENT ON COLUMN education_question.collection_id IS '所属题集 ID';
|
||
COMMENT ON COLUMN education_question.subject_id IS '所属科目 ID';
|
||
COMMENT ON COLUMN education_question.node_id IS '所属内容节点 ID';
|
||
COMMENT ON COLUMN education_question.tags IS '标签 JSONB 数组';
|
||
COMMENT ON COLUMN education_question.sort_order IS '显示排序值';
|
||
COMMENT ON COLUMN education_question.metadata IS '扩展元数据';
|
||
COMMENT ON COLUMN education_question.creator IS '创建者';
|
||
COMMENT ON COLUMN education_question.create_time IS '创建时间';
|
||
COMMENT ON COLUMN education_question.updater IS '更新者';
|
||
COMMENT ON COLUMN education_question.update_time IS '更新时间';
|
||
COMMENT ON COLUMN education_question.deleted IS '是否删除';
|
||
CREATE INDEX idx_tenant ON education_question (tenant_id);
|
||
CREATE INDEX idx_tenant_published ON education_question (tenant_id, is_published, status);
|
||
CREATE INDEX idx_tenant_collection ON education_question (tenant_id, collection_id);
|
||
CREATE INDEX idx_tenant_subject ON education_question (tenant_id, subject_id);
|
||
CREATE INDEX idx_tenant_node ON education_question (tenant_id, node_id);
|
||
CREATE INDEX idx_tenant_type_diff ON education_question (tenant_id, type, difficulty);
|
||
|
||
-- =============================================
|
||
-- 10. 练习蓝图表
|
||
-- =============================================
|
||
CREATE TABLE education_practice_blueprint (
|
||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||
tenant_id BIGINT NOT NULL,
|
||
mode VARCHAR(50) NOT NULL,
|
||
entry_id BIGINT DEFAULT NULL,
|
||
node_id BIGINT DEFAULT NULL,
|
||
collection_id BIGINT DEFAULT NULL,
|
||
question_limit INT DEFAULT NULL,
|
||
duration_minutes INT DEFAULT NULL,
|
||
eligible_count INT NOT NULL DEFAULT 0,
|
||
total_count INT NOT NULL DEFAULT 0,
|
||
available_types JSONB DEFAULT NULL,
|
||
available_difficulties JSONB DEFAULT NULL,
|
||
min_questions INT NOT NULL DEFAULT 1,
|
||
max_questions INT NOT NULL DEFAULT 200,
|
||
suggested_count INT NOT NULL DEFAULT 20,
|
||
creator VARCHAR(64) DEFAULT '',
|
||
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
updater VARCHAR(64) DEFAULT '',
|
||
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
deleted BOOLEAN NOT NULL DEFAULT false
|
||
);
|
||
COMMENT ON TABLE education_practice_blueprint IS '教育-练习蓝图';
|
||
COMMENT ON COLUMN education_practice_blueprint.id IS '主键';
|
||
COMMENT ON COLUMN education_practice_blueprint.tenant_id IS '租户编号';
|
||
COMMENT ON COLUMN education_practice_blueprint.mode IS '练习模式';
|
||
COMMENT ON COLUMN education_practice_blueprint.entry_id IS '内容入口 ID';
|
||
COMMENT ON COLUMN education_practice_blueprint.node_id IS '内容节点 ID';
|
||
COMMENT ON COLUMN education_practice_blueprint.collection_id IS '题集 ID';
|
||
COMMENT ON COLUMN education_practice_blueprint.question_limit IS '题目数量限制';
|
||
COMMENT ON COLUMN education_practice_blueprint.duration_minutes IS '建议作答时长(分钟)';
|
||
COMMENT ON COLUMN education_practice_blueprint.eligible_count IS '符合条件的题目数';
|
||
COMMENT ON COLUMN education_practice_blueprint.total_count IS '题库总数';
|
||
COMMENT ON COLUMN education_practice_blueprint.available_types IS '可用题型列表 JSONB';
|
||
COMMENT ON COLUMN education_practice_blueprint.available_difficulties IS '可用难度列表 JSONB';
|
||
COMMENT ON COLUMN education_practice_blueprint.min_questions IS '最少题目数';
|
||
COMMENT ON COLUMN education_practice_blueprint.max_questions IS '最多题目数';
|
||
COMMENT ON COLUMN education_practice_blueprint.suggested_count IS '建议题目数';
|
||
COMMENT ON COLUMN education_practice_blueprint.creator IS '创建者';
|
||
COMMENT ON COLUMN education_practice_blueprint.create_time IS '创建时间';
|
||
COMMENT ON COLUMN education_practice_blueprint.updater IS '更新者';
|
||
COMMENT ON COLUMN education_practice_blueprint.update_time IS '更新时间';
|
||
COMMENT ON COLUMN education_practice_blueprint.deleted IS '是否删除';
|
||
CREATE INDEX idx_tenant ON education_practice_blueprint (tenant_id);
|
||
CREATE INDEX idx_tenant_collection ON education_practice_blueprint (tenant_id, collection_id);
|
||
|
||
-- =============================================
|
||
-- 11. 题集-题目关联表(多对多)
|
||
-- =============================================
|
||
CREATE TABLE education_question_collection_question (
|
||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||
collection_id BIGINT NOT NULL,
|
||
question_id BIGINT NOT NULL,
|
||
sort_order INT NOT NULL DEFAULT 0
|
||
);
|
||
COMMENT ON TABLE education_question_collection_question IS '教育-题集题目关联';
|
||
COMMENT ON COLUMN education_question_collection_question.id IS '主键';
|
||
COMMENT ON COLUMN education_question_collection_question.collection_id IS '题集 ID';
|
||
COMMENT ON COLUMN education_question_collection_question.question_id IS '题目 ID';
|
||
COMMENT ON COLUMN education_question_collection_question.sort_order IS '排序值';
|
||
CREATE UNIQUE INDEX uk_collection_question ON education_question_collection_question (collection_id, question_id);
|
||
CREATE INDEX idx_question ON education_question_collection_question (question_id);
|
||
|
||
-- =============================================
|
||
-- Post-migration verification queries
|
||
-- =============================================
|
||
-- Verify all tables exist:
|
||
-- SELECT table_name FROM information_schema.tables
|
||
-- WHERE table_catalog = current_database()
|
||
-- AND table_name LIKE 'education_%'
|
||
-- ORDER BY table_name;
|
||
-- Verify indexes (sample):
|
||
-- -- PostgreSQL equivalent of SHOW INDEX:
|
||
-- SELECT * FROM pg_indexes WHERE tablename = 'education_question' AND indexname IN ('idx_tenant_published', 'idx_tenant_collection');
|
||
-- SELECT * FROM pg_indexes WHERE tablename = 'education_content_node' AND indexname = 'idx_entry_parent';
|
||
-- Verify no orphan data (should be 0 after fresh migration for catalog tables):
|
||
-- SELECT 'region' AS tbl, COUNT(*) FROM education_region
|
||
-- UNION ALL SELECT 'school', COUNT(*) FROM education_school
|
||
-- UNION ALL SELECT 'major', COUNT(*) FROM education_major
|
||
-- UNION ALL SELECT 'subject', COUNT(*) FROM education_subject
|
||
-- UNION ALL SELECT 'category', COUNT(*) FROM education_category
|
||
-- UNION ALL SELECT 'content_entry', COUNT(*) FROM education_content_entry
|
||
-- UNION ALL SELECT 'content_node', COUNT(*) FROM education_content_node
|
||
-- UNION ALL SELECT 'question_collection', COUNT(*) FROM education_question_collection
|
||
-- UNION ALL SELECT 'question', COUNT(*) FROM education_question
|
||
-- UNION ALL SELECT 'practice_blueprint', COUNT(*) FROM education_practice_blueprint
|
||
-- UNION ALL SELECT 'collection_question', COUNT(*) FROM education_question_collection_question;
|