Yimaru-BackEnd/db/migrations/000006_unified_questions.up.sql

196 lines
8.3 KiB
SQL

-- Unified Question System Migration
-- Replaces: practice_questions, assessment_questions, assessment_question_options, assessment_short_answers
-- 1. Create unified questions table
CREATE TABLE IF NOT EXISTS questions (
id BIGSERIAL PRIMARY KEY,
question_text TEXT NOT NULL,
question_type VARCHAR(20) NOT NULL CHECK (question_type IN ('MCQ', 'TRUE_FALSE', 'SHORT_ANSWER')),
difficulty_level VARCHAR(20) CHECK (difficulty_level IN ('EASY', 'MEDIUM', 'HARD')),
points INT NOT NULL DEFAULT 1,
explanation TEXT,
tips TEXT,
voice_prompt TEXT,
sample_answer_voice_prompt TEXT,
status VARCHAR(20) NOT NULL DEFAULT 'DRAFT' CHECK (status IN ('DRAFT', 'PUBLISHED', 'INACTIVE', 'ARCHIVED')),
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ
);
CREATE INDEX idx_questions_type ON questions(question_type);
CREATE INDEX idx_questions_status ON questions(status);
CREATE INDEX idx_questions_difficulty ON questions(difficulty_level);
-- 2. Create question options table (for MCQ and TRUE_FALSE)
CREATE TABLE IF NOT EXISTS question_options (
id BIGSERIAL PRIMARY KEY,
question_id BIGINT NOT NULL REFERENCES questions(id) ON DELETE CASCADE,
option_text TEXT NOT NULL,
option_order INT NOT NULL DEFAULT 0,
is_correct BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_question_options_question_id ON question_options(question_id);
-- 3. Create question short answers table (for SHORT_ANSWER type)
CREATE TABLE IF NOT EXISTS question_short_answers (
id BIGSERIAL PRIMARY KEY,
question_id BIGINT NOT NULL REFERENCES questions(id) ON DELETE CASCADE,
acceptable_answer TEXT NOT NULL,
match_type VARCHAR(20) NOT NULL DEFAULT 'EXACT' CHECK (match_type IN ('EXACT', 'CONTAINS', 'CASE_INSENSITIVE')),
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_question_short_answers_question_id ON question_short_answers(question_id);
-- 4. Create question sets table (replaces practices for grouping questions)
CREATE TABLE IF NOT EXISTS question_sets (
id BIGSERIAL PRIMARY KEY,
title VARCHAR(255) NOT NULL,
description TEXT,
set_type VARCHAR(30) NOT NULL CHECK (set_type IN ('PRACTICE', 'INITIAL_ASSESSMENT', 'QUIZ', 'EXAM', 'SURVEY')),
owner_type VARCHAR(30), -- SUB_COURSE, COURSE, CATEGORY, STANDALONE
owner_id BIGINT, -- References the owning entity
banner_image TEXT,
persona VARCHAR(100),
time_limit_minutes INT, -- Optional time limit
passing_score INT, -- Optional passing percentage
shuffle_questions BOOLEAN NOT NULL DEFAULT FALSE,
status VARCHAR(20) NOT NULL DEFAULT 'DRAFT' CHECK (status IN ('DRAFT', 'PUBLISHED', 'INACTIVE', 'ARCHIVED')),
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ
);
CREATE INDEX idx_question_sets_type ON question_sets(set_type);
CREATE INDEX idx_question_sets_owner ON question_sets(owner_type, owner_id);
CREATE INDEX idx_question_sets_status ON question_sets(status);
-- 5. Create question set items table (links questions to sets)
CREATE TABLE IF NOT EXISTS question_set_items (
id BIGSERIAL PRIMARY KEY,
set_id BIGINT NOT NULL REFERENCES question_sets(id) ON DELETE CASCADE,
question_id BIGINT NOT NULL REFERENCES questions(id) ON DELETE CASCADE,
display_order INT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE(set_id, question_id)
);
CREATE INDEX idx_question_set_items_set_id ON question_set_items(set_id);
CREATE INDEX idx_question_set_items_question_id ON question_set_items(question_id);
-- 6. Migrate data from assessment_questions to new questions table
INSERT INTO questions (id, question_text, question_type, difficulty_level, points, status, created_at)
SELECT
id,
title,
CASE question_type
WHEN 'MULTIPLE_CHOICE' THEN 'MCQ'
WHEN 'SHORT_ANSWER' THEN 'SHORT_ANSWER'
WHEN 'TRUE_FALSE' THEN 'TRUE_FALSE'
ELSE 'MCQ'
END,
CASE difficulty_level
WHEN 'EASY' THEN 'EASY'
WHEN 'MEDIUM' THEN 'MEDIUM'
WHEN 'HARD' THEN 'HARD'
ELSE 'MEDIUM'
END,
points,
CASE WHEN is_active THEN 'PUBLISHED' ELSE 'INACTIVE' END,
created_at
FROM assessment_questions;
-- 7. Migrate assessment_question_options to question_options
INSERT INTO question_options (question_id, option_text, option_order, is_correct, created_at)
SELECT question_id, option_text, option_order, is_correct, created_at
FROM assessment_question_options;
-- 8. Migrate assessment_short_answers to question_short_answers
INSERT INTO question_short_answers (question_id, acceptable_answer, match_type, created_at)
SELECT question_id, correct_answer, 'EXACT', created_at
FROM assessment_short_answers;
-- 9. Create initial assessment question set from existing data
INSERT INTO question_sets (title, description, set_type, owner_type, status, created_at)
VALUES ('Initial Assessment', 'Default initial assessment for new users', 'INITIAL_ASSESSMENT', 'STANDALONE', 'PUBLISHED', CURRENT_TIMESTAMP);
-- Link existing assessment questions to the initial assessment set
INSERT INTO question_set_items (set_id, question_id, display_order)
SELECT
(SELECT id FROM question_sets WHERE set_type = 'INITIAL_ASSESSMENT' LIMIT 1),
id,
id -- Use ID as initial display order
FROM questions
WHERE id IN (SELECT id FROM assessment_questions);
-- 10. Migrate practice_questions to new structure
-- First, get max ID from questions to avoid conflicts
DO $$
DECLARE
max_q_id BIGINT;
practice_rec RECORD;
new_set_id BIGINT;
new_question_id BIGINT;
BEGIN
SELECT COALESCE(MAX(id), 0) INTO max_q_id FROM questions;
-- For each practice in the old system, create a question_set
FOR practice_rec IN
SELECT DISTINCT p.id, p.sub_course_id, p.title, p.description, p.banner_image, p.persona, p.status
FROM practices p
LOOP
-- Create question set for this practice
INSERT INTO question_sets (title, description, set_type, owner_type, owner_id, banner_image, persona, status, created_at)
VALUES (
practice_rec.title,
practice_rec.description,
'PRACTICE',
'SUB_COURSE',
practice_rec.sub_course_id,
practice_rec.banner_image,
practice_rec.persona,
practice_rec.status,
CURRENT_TIMESTAMP
)
RETURNING id INTO new_set_id;
-- Migrate questions from this practice
FOR new_question_id IN
INSERT INTO questions (question_text, question_type, tips, sample_answer_voice_prompt, voice_prompt, status, created_at)
SELECT
pq.question,
pq.type,
pq.tips,
pq.sample_answer_voice_prompt,
pq.question_voice_prompt,
'PUBLISHED',
CURRENT_TIMESTAMP
FROM practice_questions pq
WHERE pq.practice_id = practice_rec.id
RETURNING id
LOOP
-- Link question to set
INSERT INTO question_set_items (set_id, question_id, display_order)
VALUES (new_set_id, new_question_id, new_question_id);
END LOOP;
END LOOP;
END $$;
-- 11. Reset sequences
SELECT setval(pg_get_serial_sequence('questions', 'id'), COALESCE((SELECT MAX(id) FROM questions), 1), true);
SELECT setval(pg_get_serial_sequence('question_options', 'id'), COALESCE((SELECT MAX(id) FROM question_options), 1), true);
SELECT setval(pg_get_serial_sequence('question_short_answers', 'id'), COALESCE((SELECT MAX(id) FROM question_short_answers), 1), true);
SELECT setval(pg_get_serial_sequence('question_sets', 'id'), COALESCE((SELECT MAX(id) FROM question_sets), 1), true);
SELECT setval(pg_get_serial_sequence('question_set_items', 'id'), COALESCE((SELECT MAX(id) FROM question_set_items), 1), true);
-- 12. Drop old tables
DROP TABLE IF EXISTS practice_questions CASCADE;
DROP TABLE IF EXISTS practices CASCADE;
DROP TABLE IF EXISTS assessment_attempt_answers CASCADE;
DROP TABLE IF EXISTS assessment_attempt_questions CASCADE;
DROP TABLE IF EXISTS assessment_attempts CASCADE;
DROP TABLE IF EXISTS assessment_short_answers CASCADE;
DROP TABLE IF EXISTS assessment_question_options CASCADE;
DROP TABLE IF EXISTS assessment_questions CASCADE;