Files
ruoyi-vue-pro/sql/postgresql/education/008-education-catalog.sql

531 lines
30 KiB
SQL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- =============================================
-- 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 '父节点 IDNULL=根节点)';
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;