feat(education): complete Flyway migration and atomic submit
This commit is contained in:
156
sql/postgresql/education/005-education-wrong-question.sql
Normal file
156
sql/postgresql/education/005-education-wrong-question.sql
Normal file
@@ -0,0 +1,156 @@
|
||||
-- =============================================
|
||||
-- Education 模块 — 错题本 DDL
|
||||
-- Ticket #9: 错题自动收集、复习练习创建
|
||||
-- Migration: 005
|
||||
-- Prerequisites: 004-education-submit-report.sql (report + detail tables)
|
||||
-- =============================================
|
||||
|
||||
-- =============================================
|
||||
-- Preconditions
|
||||
-- =============================================
|
||||
-- Operator is expected to verify:
|
||||
-- SELECT COUNT(*) FROM information_schema.tables
|
||||
-- WHERE table_schema = current_database()
|
||||
-- AND table_name IN ('education_wrong_question',
|
||||
-- 'education_wrong_question_idempotency');
|
||||
-- Result MUST be 0 before executing this migration.
|
||||
--
|
||||
-- Verify prerequisite tables exist:
|
||||
-- SELECT COUNT(*) FROM information_schema.tables
|
||||
-- WHERE table_schema = current_database()
|
||||
-- AND table_name IN ('education_practice_report',
|
||||
-- 'education_practice_report_detail');
|
||||
-- Result MUST be 2.
|
||||
|
||||
-- =============================================
|
||||
-- PracticeReportDetailDO: add content_version + options snapshot columns
|
||||
-- =============================================
|
||||
-- Purpose: At submit time, snapshot question content version and options
|
||||
-- (without isCorrect) so wrong-question book and review sessions have
|
||||
-- stable display data. The options are already stripped of isCorrect
|
||||
-- by the submit flow.
|
||||
ALTER TABLE education_practice_report_detail
|
||||
ADD COLUMN content_version VARCHAR(64) NOT NULL DEFAULT '' /* 题目内容版本快照 */,
|
||||
ADD COLUMN options TEXT DEFAULT NULL /* 选项快照 JSON(不含 isCorrect) */;
|
||||
|
||||
-- =============================================
|
||||
-- PracticeSessionDO: add review fingerprint column
|
||||
-- =============================================
|
||||
-- Purpose: Persist the canonical fingerprint of wrong-question IDs used
|
||||
-- to create a review session. On idempotent replay, the fingerprint
|
||||
-- is compared: same tenant+clientSessionId+sameUser+sortedIDs match
|
||||
-- returns the existing session; different ID set returns
|
||||
-- SESSION_IDEMPOTENCY_MISMATCH.
|
||||
ALTER TABLE education_practice_session
|
||||
ADD COLUMN review_fingerprint VARCHAR(64) DEFAULT NULL /* 复习会话题目指纹(SHA-256 of sorted unique wrongQuestionIds) */;
|
||||
|
||||
-- 错题表
|
||||
-- =============================================
|
||||
-- Purpose: Persistent wrong-question book per student.
|
||||
-- Each (tenant, user, question) is a unique entry.
|
||||
-- Repeated wrong answers on the SAME question increment wrong_count
|
||||
-- and update last_wrong_time. The idempotency guard table ensures
|
||||
-- each (tenant, user, question, report) can upsert at most once.
|
||||
--
|
||||
-- master_status values: 'PENDING' (default) | 'MASTERED'
|
||||
-- Marking mastered retains the full history and count; it does NOT
|
||||
-- delete or archive the record. Students can optionally un-master.
|
||||
--
|
||||
-- Snapshot fields (stem, type, difficulty, options, content_version):
|
||||
-- populated from the latest report detail that touched this question.
|
||||
-- These are for listing/detail display without joining report details.
|
||||
--
|
||||
-- latest_correct_answer, latest_explanation:
|
||||
-- also from the latest report detail; available for detail display
|
||||
-- post-submit (not exposed in review session creation pre-submit).
|
||||
--
|
||||
-- Indexes:
|
||||
-- uk_tenant_user_question — per-tenant, per-user, per-question uniqueness.
|
||||
-- INSERT ... ON CONFLICT ... DO UPDATE is the primary write path.
|
||||
-- idx_tenant_user_status — covers filtered list queries (page with status filter).
|
||||
-- idx_tenant_user_last_wrong — covers time-sorted listing.
|
||||
CREATE TABLE education_wrong_question (
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY /* 主键 */,
|
||||
tenant_id BIGINT NOT NULL /* 租户编号 */,
|
||||
user_id BIGINT NOT NULL /* 学生用户编号 */,
|
||||
question_id VARCHAR(64) NOT NULL /* 原始题目 ID */,
|
||||
-- snapshot fields for listing / detail (from latest report detail)
|
||||
stem TEXT NOT NULL /* 题干快照(最新) */,
|
||||
type VARCHAR(32) NOT NULL /* 题型快照 */,
|
||||
difficulty VARCHAR(32) DEFAULT NULL /* 难度快照 */,
|
||||
options JSONB NOT NULL /* 选项快照 JSON(不含 isCorrect) */,
|
||||
content_version VARCHAR(64) NOT NULL DEFAULT '' /* 题目内容版本 */,
|
||||
latest_correct_answer TEXT DEFAULT NULL /* 正确答案快照(最新,供详情展示) */,
|
||||
latest_explanation TEXT DEFAULT NULL /* 解析快照(最新,供详情展示) */,
|
||||
-- timing & count
|
||||
first_wrong_time TIMESTAMP NOT NULL /* 首次错误时间 */,
|
||||
last_wrong_time TIMESTAMP NOT NULL /* 最近错误时间 */,
|
||||
wrong_count INT NOT NULL DEFAULT 1 /* 累计错误次数 */,
|
||||
-- mastery
|
||||
master_status VARCHAR(20) NOT NULL DEFAULT 'PENDING'
|
||||
/* 掌握状态:PENDING-待掌握, MASTERED-已掌握 */,
|
||||
mastered_time TIMESTAMP DEFAULT NULL /* 标记掌握时间 */,
|
||||
-- provenance
|
||||
last_report_id BIGINT DEFAULT NULL /* 最近关联的报告 ID */,
|
||||
last_session_id BIGINT DEFAULT NULL /* 最近关联的会话 ID */,
|
||||
-- audit
|
||||
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_wrong_question IS '教育-错题本';
|
||||
|
||||
CREATE UNIQUE INDEX uk_tenant_user_question ON education_wrong_question (tenant_id, user_id, question_id);
|
||||
CREATE INDEX idx_tenant_user_status ON education_wrong_question (tenant_id, user_id, master_status);
|
||||
CREATE INDEX idx_tenant_user_last_wrong ON education_wrong_question (tenant_id, user_id, last_wrong_time);
|
||||
|
||||
-- =============================================
|
||||
-- 错题流水幂等表
|
||||
-- =============================================
|
||||
-- Purpose: Ensure each (tenant, user, question, report) upserts the
|
||||
-- wrong-question book exactly once. The submitSession transaction
|
||||
-- INSERT ... ON CONFLICT DO NOTHING into this table BEFORE the
|
||||
-- wrong question upsert; a duplicate means this report already
|
||||
-- contributed to the count. This guards against:
|
||||
-- - Replayed submit (idempotent resubmit) double-counting
|
||||
-- - Concurrent submit races where both threads evaluate the
|
||||
-- same report details
|
||||
--
|
||||
-- Indexes:
|
||||
-- uk_tenant_user_question_report — per (tenant, user, question, report) uniqueness.
|
||||
-- INSERT ... ON CONFLICT DO NOTHING provides the idempotency guard
|
||||
-- BEFORE upserting. wrong_question_id is filled after upsert for
|
||||
-- audit purposes.
|
||||
-- idx_report — fast lookup by report for audit/debug.
|
||||
CREATE TABLE education_wrong_question_idempotency (
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY /* 主键 */,
|
||||
tenant_id BIGINT NOT NULL /* 租户编号 */,
|
||||
user_id BIGINT NOT NULL /* 学生用户编号 */,
|
||||
wrong_question_id BIGINT DEFAULT NULL /* 错题记录 ID(upsert 后填充) */,
|
||||
report_id BIGINT NOT NULL /* 报告 ID */,
|
||||
question_id VARCHAR(64) NOT NULL /* 题目 ID */,
|
||||
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_wrong_question_idempotency IS '教育-错题流水幂等';
|
||||
|
||||
CREATE UNIQUE INDEX uk_tenant_user_question_report ON education_wrong_question_idempotency (tenant_id, user_id, question_id, report_id);
|
||||
CREATE INDEX idx_report ON education_wrong_question_idempotency (report_id);
|
||||
|
||||
-- =============================================
|
||||
-- Post-migration verification queries
|
||||
-- =============================================
|
||||
-- Verify new tables exist:
|
||||
-- \d education_wrong_question
|
||||
-- \d education_wrong_question_idempotency
|
||||
-- Verify unique keys are enforced:
|
||||
-- SELECT * FROM pg_indexes WHERE tablename = 'education_wrong_question' AND indexname = 'uk_tenant_user_question';
|
||||
-- SELECT * FROM pg_indexes WHERE tablename = 'education_wrong_question_idempotency' AND indexname = 'uk_tenant_user_question_report';
|
||||
-- Verify no orphan data (should be 0 after fresh migration):
|
||||
-- SELECT COUNT(*) FROM education_wrong_question;
|
||||
-- SELECT COUNT(*) FROM education_wrong_question_idempotency;
|
||||
Reference in New Issue
Block a user