196 lines
8.3 KiB
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;
|