mirror of
https://github.com/sabazadam/Se302.git
synced 2025-12-31 20:31:22 +00:00
1214 lines
35 KiB
Markdown
1214 lines
35 KiB
Markdown
# DATABASE SCHEMA - EXAM SCHEDULING SYSTEM
|
||
|
||
**Project:** Exam Planner Desktop Application
|
||
**Database:** SQLite 3.44
|
||
**Version:** 1.0
|
||
**Last Updated:** November 26, 2025
|
||
|
||
---
|
||
|
||
## TABLE OF CONTENTS
|
||
|
||
1. [Overview](#overview)
|
||
2. [Entity-Relationship Diagram](#entity-relationship-diagram)
|
||
3. [Database Tables](#database-tables)
|
||
4. [Relationships](#relationships)
|
||
5. [Indexes](#indexes)
|
||
6. [Sample Data](#sample-data)
|
||
7. [SQL Schema Script](#sql-schema-script)
|
||
8. [Common Queries](#common-queries)
|
||
9. [Database Initialization](#database-initialization)
|
||
10. [Best Practices](#best-practices)
|
||
|
||
---
|
||
|
||
## OVERVIEW
|
||
|
||
### Database Purpose
|
||
|
||
The database stores all data required for the exam scheduling system:
|
||
- **Student records** - All students who will take exams
|
||
- **Course catalog** - All courses that need exams scheduled
|
||
- **Classroom inventory** - Available classrooms with capacities
|
||
- **Enrollment data** - Which students are enrolled in which courses
|
||
- **Generated schedules** - Complete exam schedules with metadata
|
||
- **Exam assignments** - Individual course-to-classroom-time assignments
|
||
|
||
### Database Technology
|
||
|
||
**SQLite 3.44** - Chosen for:
|
||
- **Zero configuration** - No server setup required
|
||
- **Embedded** - Single file database
|
||
- **Cross-platform** - Works on Windows, macOS, Linux
|
||
- **ACID compliant** - Full transaction support
|
||
- **Lightweight** - Perfect for desktop applications
|
||
- **Easy backup** - Just copy the .db file
|
||
|
||
### Database File Location
|
||
|
||
- **Development:** `./data/exam_scheduler.db`
|
||
- **Production:** User's Documents folder or application data directory
|
||
- **Backup:** Automatic export to CSV on schedule save
|
||
|
||
---
|
||
|
||
## ENTITY-RELATIONSHIP DIAGRAM
|
||
|
||
### High-Level ER Diagram
|
||
|
||
```
|
||
┌─────────────┐ ┌──────────────┐ ┌──────────────┐
|
||
│ Student │ │ Enrollment │ │ Course │
|
||
├─────────────┤ ├──────────────┤ ├──────────────┤
|
||
│ student_id │◄───────►│ enrollment_id│◄───────►│ course_code │
|
||
│ created_at │ M:N │ student_id │ M:N │ created_at │
|
||
└─────────────┘ │ course_code │ └──────┬───────┘
|
||
│ created_at │ │
|
||
└──────────────┘ │ 1:M
|
||
│
|
||
▼
|
||
┌─────────────┐ ┌──────────────────────────────────────┐
|
||
│ Classroom │ │ ExamAssignment │
|
||
├─────────────┤ ├──────────────────────────────────────┤
|
||
│classroom_id │◄────────│ exam_id │
|
||
│ capacity │ 1:M │ schedule_id (FK) │
|
||
│ created_at │ │ course_code (FK) │
|
||
└─────────────┘ │ classroom_id (FK) │
|
||
│ day │
|
||
│ time_slot │
|
||
│ created_at │
|
||
└──────────────┬───────────────────────┘
|
||
│ M:1
|
||
▼
|
||
┌──────────────────────────────────────┐
|
||
│ Schedule │
|
||
├──────────────────────────────────────┤
|
||
│ schedule_id │
|
||
│ created_date │
|
||
│ optimization_strategy │
|
||
│ num_days │
|
||
│ slots_per_day │
|
||
│ status │
|
||
└──────────────────────────────────────┘
|
||
```
|
||
|
||
### Relationship Summary
|
||
|
||
| Relationship | Type | Description |
|
||
|-------------|------|-------------|
|
||
| Student ↔ Enrollment | 1:M | One student has many enrollments |
|
||
| Course ↔ Enrollment | 1:M | One course has many enrollments |
|
||
| Student ↔ Course | M:N | Many-to-many through Enrollment |
|
||
| Course ↔ ExamAssignment | 1:M | One course can have multiple assignments (across schedules) |
|
||
| Classroom ↔ ExamAssignment | 1:M | One classroom hosts many exams |
|
||
| Schedule ↔ ExamAssignment | 1:M | One schedule contains many exam assignments |
|
||
|
||
---
|
||
|
||
## DATABASE TABLES
|
||
|
||
### 1. students
|
||
|
||
**Purpose:** Store all student records who will participate in exams.
|
||
|
||
**Table Definition:**
|
||
|
||
| Column | Type | Constraints | Description |
|
||
|--------|------|-------------|-------------|
|
||
| student_id | TEXT | PRIMARY KEY, NOT NULL | Unique student identifier (e.g., "Std_ID_001") |
|
||
| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | Record creation timestamp |
|
||
|
||
**Business Rules:**
|
||
- Student IDs must be unique
|
||
- Student IDs are imported from CSV
|
||
- Cannot be deleted if enrollments exist (protected by foreign key)
|
||
|
||
**Sample Data:**
|
||
```sql
|
||
INSERT INTO students (student_id) VALUES
|
||
('Std_ID_001'),
|
||
('Std_ID_002'),
|
||
('Std_ID_003');
|
||
```
|
||
|
||
**Expected Size:** 250-500 students (sample data has 250)
|
||
|
||
---
|
||
|
||
### 2. courses
|
||
|
||
**Purpose:** Store all courses that require exam scheduling.
|
||
|
||
**Table Definition:**
|
||
|
||
| Column | Type | Constraints | Description |
|
||
|--------|------|-------------|-------------|
|
||
| course_code | TEXT | PRIMARY KEY, NOT NULL | Unique course identifier (e.g., "CourseCode_01") |
|
||
| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | Record creation timestamp |
|
||
|
||
**Business Rules:**
|
||
- Course codes must be unique
|
||
- Course codes are imported from CSV
|
||
- Cannot be deleted if enrollments exist (protected by foreign key)
|
||
|
||
**Sample Data:**
|
||
```sql
|
||
INSERT INTO courses (course_code) VALUES
|
||
('CourseCode_01'),
|
||
('CourseCode_02'),
|
||
('CourseCode_03');
|
||
```
|
||
|
||
**Expected Size:** 20-50 courses (sample data has 20)
|
||
|
||
---
|
||
|
||
### 3. classrooms
|
||
|
||
**Purpose:** Store available classrooms with their seating capacities.
|
||
|
||
**Table Definition:**
|
||
|
||
| Column | Type | Constraints | Description |
|
||
|--------|------|-------------|-------------|
|
||
| classroom_id | TEXT | PRIMARY KEY, NOT NULL | Unique classroom identifier (e.g., "Classroom_01") |
|
||
| capacity | INTEGER | NOT NULL, CHECK(capacity > 0) | Maximum seating capacity |
|
||
| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | Record creation timestamp |
|
||
|
||
**Business Rules:**
|
||
- Classroom IDs must be unique
|
||
- Capacity must be positive integer
|
||
- Imported from CSV format: `ClassroomID;Capacity`
|
||
- Cannot be deleted if used in exam assignments (protected by foreign key)
|
||
|
||
**Sample Data:**
|
||
```sql
|
||
INSERT INTO classrooms (classroom_id, capacity) VALUES
|
||
('Classroom_01', 40),
|
||
('Classroom_02', 35),
|
||
('Classroom_03', 50);
|
||
```
|
||
|
||
**Expected Size:** 10-20 classrooms (sample data has 10, all capacity 40)
|
||
|
||
---
|
||
|
||
### 4. enrollments
|
||
|
||
**Purpose:** Many-to-many relationship between students and courses (which students are enrolled in which courses).
|
||
|
||
**Table Definition:**
|
||
|
||
| Column | Type | Constraints | Description |
|
||
|--------|------|-------------|-------------|
|
||
| enrollment_id | INTEGER | PRIMARY KEY AUTOINCREMENT | Auto-generated unique ID |
|
||
| student_id | TEXT | NOT NULL, FK → students(student_id) | Reference to student |
|
||
| course_code | TEXT | NOT NULL, FK → courses(course_code) | Reference to course |
|
||
| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | Record creation timestamp |
|
||
| UNIQUE | | (student_id, course_code) | Prevent duplicate enrollments |
|
||
|
||
**Indexes:**
|
||
```sql
|
||
CREATE INDEX idx_enrollments_student ON enrollments(student_id);
|
||
CREATE INDEX idx_enrollments_course ON enrollments(course_code);
|
||
```
|
||
|
||
**Business Rules:**
|
||
- Each student-course pair can only appear once
|
||
- Cannot enroll student in non-existent course
|
||
- Cannot enroll non-existent student
|
||
- Imported from attendance list CSV
|
||
|
||
**Sample Data:**
|
||
```sql
|
||
INSERT INTO enrollments (student_id, course_code) VALUES
|
||
('Std_ID_001', 'CourseCode_01'),
|
||
('Std_ID_001', 'CourseCode_05'),
|
||
('Std_ID_002', 'CourseCode_01'),
|
||
('Std_ID_002', 'CourseCode_03');
|
||
```
|
||
|
||
**Expected Size:** 800-10,000 records (sample: ~800 enrollments, ~40 students per course)
|
||
|
||
**Cascade Behavior:**
|
||
- If student deleted → all enrollments deleted (CASCADE)
|
||
- If course deleted → all enrollments deleted (CASCADE)
|
||
|
||
---
|
||
|
||
### 5. schedules
|
||
|
||
**Purpose:** Store metadata for each generated exam schedule.
|
||
|
||
**Table Definition:**
|
||
|
||
| Column | Type | Constraints | Description |
|
||
|--------|------|-------------|-------------|
|
||
| schedule_id | INTEGER | PRIMARY KEY AUTOINCREMENT | Auto-generated unique ID |
|
||
| created_date | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP, NOT NULL | When schedule was generated |
|
||
| optimization_strategy | TEXT | NOT NULL | Strategy used (see values below) |
|
||
| num_days | INTEGER | NOT NULL, CHECK(num_days > 0) | Number of exam days configured |
|
||
| slots_per_day | INTEGER | NOT NULL, CHECK(slots_per_day > 0) | Number of time slots per day |
|
||
| status | TEXT | DEFAULT 'generated', CHECK(status IN (...)) | Schedule status (see values below) |
|
||
|
||
**Optimization Strategy Values:**
|
||
- `'minimize_days'` - Pack exams into fewest days
|
||
- `'balance_distribution'` - Spread exams evenly across days
|
||
- `'minimize_classrooms'` - Use fewest classrooms
|
||
- `'balance_classrooms'` - Balance classroom usage per day
|
||
|
||
**Status Values:**
|
||
- `'generated'` - Just created
|
||
- `'saved'` - User explicitly saved
|
||
- `'archived'` - Old schedule kept for history
|
||
|
||
**Business Rules:**
|
||
- num_days and slots_per_day must be positive integers
|
||
- Total slots (num_days × slots_per_day) should be ≥ number of courses
|
||
- Cannot delete schedule if it's the only one (application logic)
|
||
|
||
**Sample Data:**
|
||
```sql
|
||
INSERT INTO schedules (optimization_strategy, num_days, slots_per_day, status) VALUES
|
||
('balance_distribution', 5, 4, 'saved'),
|
||
('minimize_days', 5, 4, 'generated'),
|
||
('minimize_classrooms', 6, 3, 'archived');
|
||
```
|
||
|
||
**Expected Size:** 10-100 records (historical schedules kept for comparison)
|
||
|
||
---
|
||
|
||
### 6. exam_assignments
|
||
|
||
**Purpose:** Store individual exam assignments (which course exam is in which classroom at what time).
|
||
|
||
**Table Definition:**
|
||
|
||
| Column | Type | Constraints | Description |
|
||
|--------|------|-------------|-------------|
|
||
| exam_id | INTEGER | PRIMARY KEY AUTOINCREMENT | Auto-generated unique ID |
|
||
| schedule_id | INTEGER | NOT NULL, FK → schedules(schedule_id) | Which schedule this belongs to |
|
||
| course_code | TEXT | NOT NULL, FK → courses(course_code) | Which course exam |
|
||
| classroom_id | TEXT | NOT NULL, FK → classrooms(classroom_id) | Which classroom |
|
||
| day | INTEGER | NOT NULL, CHECK(day > 0) | Which day (1, 2, 3, ...) |
|
||
| time_slot | INTEGER | NOT NULL, CHECK(time_slot > 0) | Which time slot (1, 2, 3, ...) |
|
||
| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | Record creation timestamp |
|
||
| UNIQUE | | (schedule_id, course_code) | Each course once per schedule |
|
||
| UNIQUE | | (schedule_id, classroom_id, day, time_slot) | No classroom double-booking |
|
||
|
||
**Indexes:**
|
||
```sql
|
||
CREATE INDEX idx_assignments_schedule ON exam_assignments(schedule_id);
|
||
CREATE INDEX idx_assignments_course ON exam_assignments(course_code);
|
||
CREATE INDEX idx_assignments_classroom_time ON exam_assignments(classroom_id, day, time_slot);
|
||
```
|
||
|
||
**Business Rules:**
|
||
- Each course appears exactly once per schedule
|
||
- Each {classroom, day, time_slot} combination used at most once per schedule
|
||
- Day and time_slot must be positive integers
|
||
- day must be ≤ num_days from schedule
|
||
- time_slot must be ≤ slots_per_day from schedule (enforced by application)
|
||
|
||
**Sample Data:**
|
||
```sql
|
||
INSERT INTO exam_assignments (schedule_id, course_code, classroom_id, day, time_slot) VALUES
|
||
(1, 'CourseCode_01', 'Classroom_01', 1, 1),
|
||
(1, 'CourseCode_02', 'Classroom_02', 1, 1),
|
||
(1, 'CourseCode_03', 'Classroom_01', 1, 2),
|
||
(1, 'CourseCode_04', 'Classroom_03', 1, 3);
|
||
```
|
||
|
||
**Expected Size:** 20-50 assignments per schedule (one per course)
|
||
|
||
**Cascade Behavior:**
|
||
- If schedule deleted → all assignments deleted (CASCADE)
|
||
- If course deleted → all assignments deleted (CASCADE)
|
||
- If classroom deleted → all assignments deleted (CASCADE)
|
||
|
||
---
|
||
|
||
## RELATIONSHIPS
|
||
|
||
### Primary Key - Foreign Key Relationships
|
||
|
||
#### 1. students → enrollments (1:M)
|
||
|
||
```sql
|
||
FOREIGN KEY (student_id) REFERENCES students(student_id) ON DELETE CASCADE
|
||
```
|
||
|
||
**Meaning:** Each student can have many enrollments. If a student is deleted, all their enrollments are automatically deleted.
|
||
|
||
**Query Example:**
|
||
```sql
|
||
-- Get all courses for a student
|
||
SELECT c.course_code
|
||
FROM courses c
|
||
JOIN enrollments e ON c.course_code = e.course_code
|
||
WHERE e.student_id = 'Std_ID_001';
|
||
```
|
||
|
||
---
|
||
|
||
#### 2. courses → enrollments (1:M)
|
||
|
||
```sql
|
||
FOREIGN KEY (course_code) REFERENCES courses(course_code) ON DELETE CASCADE
|
||
```
|
||
|
||
**Meaning:** Each course can have many enrollments. If a course is deleted, all enrollments in that course are automatically deleted.
|
||
|
||
**Query Example:**
|
||
```sql
|
||
-- Get all students in a course
|
||
SELECT s.student_id
|
||
FROM students s
|
||
JOIN enrollments e ON s.student_id = e.student_id
|
||
WHERE e.course_code = 'CourseCode_01';
|
||
```
|
||
|
||
---
|
||
|
||
#### 3. courses → exam_assignments (1:M)
|
||
|
||
```sql
|
||
FOREIGN KEY (course_code) REFERENCES courses(course_code) ON DELETE CASCADE
|
||
```
|
||
|
||
**Meaning:** Each course can have multiple exam assignments (across different schedules). If a course is deleted, all its exam assignments are deleted.
|
||
|
||
---
|
||
|
||
#### 4. classrooms → exam_assignments (1:M)
|
||
|
||
```sql
|
||
FOREIGN KEY (classroom_id) REFERENCES classrooms(classroom_id) ON DELETE CASCADE
|
||
```
|
||
|
||
**Meaning:** Each classroom can host many exams. If a classroom is deleted, all exam assignments using it are deleted.
|
||
|
||
---
|
||
|
||
#### 5. schedules → exam_assignments (1:M)
|
||
|
||
```sql
|
||
FOREIGN KEY (schedule_id) REFERENCES schedules(schedule_id) ON DELETE CASCADE
|
||
```
|
||
|
||
**Meaning:** Each schedule contains many exam assignments. If a schedule is deleted, all its assignments are automatically deleted.
|
||
|
||
---
|
||
|
||
## INDEXES
|
||
|
||
### Purpose of Indexes
|
||
|
||
Indexes speed up queries by creating a data structure that allows fast lookups. Critical for:
|
||
- JOIN operations
|
||
- WHERE clauses
|
||
- ORDER BY operations
|
||
|
||
### Index Definitions
|
||
|
||
#### 1. enrollments Indexes
|
||
|
||
```sql
|
||
CREATE INDEX idx_enrollments_student ON enrollments(student_id);
|
||
```
|
||
**Purpose:** Fast lookup of all courses for a student
|
||
**Used by:** Constraint validation (check if student has consecutive exams)
|
||
|
||
```sql
|
||
CREATE INDEX idx_enrollments_course ON enrollments(course_code);
|
||
```
|
||
**Purpose:** Fast lookup of all students in a course
|
||
**Used by:** Capacity validation, solver domain creation
|
||
|
||
---
|
||
|
||
#### 2. exam_assignments Indexes
|
||
|
||
```sql
|
||
CREATE INDEX idx_assignments_schedule ON exam_assignments(schedule_id);
|
||
```
|
||
**Purpose:** Fast retrieval of all assignments in a schedule
|
||
**Used by:** Loading schedules, display views
|
||
|
||
```sql
|
||
CREATE INDEX idx_assignments_course ON exam_assignments(course_code);
|
||
```
|
||
**Purpose:** Fast lookup of assignment for a specific course
|
||
**Used by:** Manual editing, validation
|
||
|
||
```sql
|
||
CREATE INDEX idx_assignments_classroom_time ON exam_assignments(classroom_id, day, time_slot);
|
||
```
|
||
**Purpose:** Fast check if classroom is available at specific time
|
||
**Used by:** Solver (check classroom availability), validation
|
||
|
||
---
|
||
|
||
### Index Performance
|
||
|
||
**Without indexes:**
|
||
- Query time for "students in course": O(n) - scan all enrollments
|
||
- Query time for "classroom availability": O(n) - scan all assignments
|
||
|
||
**With indexes:**
|
||
- Query time: O(log n) - binary search on index
|
||
- 10x-100x faster for typical dataset sizes
|
||
|
||
**Trade-off:**
|
||
- Indexes use disk space (~10-20% more)
|
||
- Inserts/updates slightly slower (must update indexes)
|
||
- For this application: Read-heavy, so indexes are beneficial
|
||
|
||
---
|
||
|
||
## SAMPLE DATA
|
||
|
||
### Sample Dataset Statistics
|
||
|
||
Based on provided sample data:
|
||
|
||
```
|
||
Students: 250 (Std_ID_001 to Std_ID_250)
|
||
Courses: 20 (CourseCode_01 to CourseCode_20)
|
||
Classrooms: 10 (Classroom_01 to Classroom_10)
|
||
Capacity: 40 seats per classroom (all classrooms)
|
||
Enrollments: ~800 total (~40 students per course average)
|
||
```
|
||
|
||
### Sample Enrollment Distribution
|
||
|
||
```
|
||
CourseCode_01: 40 students
|
||
CourseCode_02: 40 students
|
||
CourseCode_03: 38 students
|
||
...
|
||
CourseCode_20: 42 students
|
||
```
|
||
|
||
**Characteristics:**
|
||
- Fairly balanced enrollment across courses
|
||
- No course exceeds max classroom capacity (40)
|
||
- Average student takes 3-4 courses
|
||
- Solvable with 5 days × 4 slots = 20 total slots
|
||
|
||
---
|
||
|
||
## SQL SCHEMA SCRIPT
|
||
|
||
### Complete Schema (schema.sql)
|
||
|
||
This script creates all tables with proper constraints, indexes, and foreign keys.
|
||
|
||
```sql
|
||
-- ============================================================
|
||
-- EXAM SCHEDULING SYSTEM - DATABASE SCHEMA
|
||
-- Database: SQLite 3.44
|
||
-- Version: 1.0
|
||
-- Created: November 26, 2025
|
||
-- ============================================================
|
||
|
||
-- Enable foreign key constraints (required for SQLite)
|
||
PRAGMA foreign_keys = ON;
|
||
|
||
-- ============================================================
|
||
-- TABLE: students
|
||
-- Purpose: Store all student records
|
||
-- ============================================================
|
||
|
||
CREATE TABLE IF NOT EXISTS students (
|
||
student_id TEXT PRIMARY KEY NOT NULL,
|
||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||
);
|
||
|
||
-- ============================================================
|
||
-- TABLE: courses
|
||
-- Purpose: Store all course records
|
||
-- ============================================================
|
||
|
||
CREATE TABLE IF NOT EXISTS courses (
|
||
course_code TEXT PRIMARY KEY NOT NULL,
|
||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||
);
|
||
|
||
-- ============================================================
|
||
-- TABLE: classrooms
|
||
-- Purpose: Store classroom information with capacities
|
||
-- ============================================================
|
||
|
||
CREATE TABLE IF NOT EXISTS classrooms (
|
||
classroom_id TEXT PRIMARY KEY NOT NULL,
|
||
capacity INTEGER NOT NULL CHECK(capacity > 0),
|
||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||
);
|
||
|
||
-- ============================================================
|
||
-- TABLE: enrollments
|
||
-- Purpose: Many-to-many relationship between students and courses
|
||
-- ============================================================
|
||
|
||
CREATE TABLE IF NOT EXISTS enrollments (
|
||
enrollment_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
student_id TEXT NOT NULL,
|
||
course_code TEXT NOT NULL,
|
||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||
|
||
FOREIGN KEY (student_id) REFERENCES students(student_id) ON DELETE CASCADE,
|
||
FOREIGN KEY (course_code) REFERENCES courses(course_code) ON DELETE CASCADE,
|
||
|
||
UNIQUE(student_id, course_code)
|
||
);
|
||
|
||
-- Indexes for enrollments (for fast lookups)
|
||
CREATE INDEX IF NOT EXISTS idx_enrollments_student ON enrollments(student_id);
|
||
CREATE INDEX IF NOT EXISTS idx_enrollments_course ON enrollments(course_code);
|
||
|
||
-- ============================================================
|
||
-- TABLE: schedules
|
||
-- Purpose: Store metadata for generated exam schedules
|
||
-- ============================================================
|
||
|
||
CREATE TABLE IF NOT EXISTS schedules (
|
||
schedule_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||
optimization_strategy TEXT NOT NULL CHECK(
|
||
optimization_strategy IN (
|
||
'minimize_days',
|
||
'balance_distribution',
|
||
'minimize_classrooms',
|
||
'balance_classrooms'
|
||
)
|
||
),
|
||
num_days INTEGER NOT NULL CHECK(num_days > 0),
|
||
slots_per_day INTEGER NOT NULL CHECK(slots_per_day > 0),
|
||
status TEXT DEFAULT 'generated' CHECK(
|
||
status IN ('generated', 'saved', 'archived')
|
||
)
|
||
);
|
||
|
||
-- ============================================================
|
||
-- TABLE: exam_assignments
|
||
-- Purpose: Store individual exam assignments
|
||
-- ============================================================
|
||
|
||
CREATE TABLE IF NOT EXISTS exam_assignments (
|
||
exam_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
schedule_id INTEGER NOT NULL,
|
||
course_code TEXT NOT NULL,
|
||
classroom_id TEXT NOT NULL,
|
||
day INTEGER NOT NULL CHECK(day > 0),
|
||
time_slot INTEGER NOT NULL CHECK(time_slot > 0),
|
||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||
|
||
FOREIGN KEY (schedule_id) REFERENCES schedules(schedule_id) ON DELETE CASCADE,
|
||
FOREIGN KEY (course_code) REFERENCES courses(course_code) ON DELETE CASCADE,
|
||
FOREIGN KEY (classroom_id) REFERENCES classrooms(classroom_id) ON DELETE CASCADE,
|
||
|
||
-- Each course appears once per schedule
|
||
UNIQUE(schedule_id, course_code),
|
||
|
||
-- No classroom double-booking
|
||
UNIQUE(schedule_id, classroom_id, day, time_slot)
|
||
);
|
||
|
||
-- Indexes for exam_assignments (for fast queries)
|
||
CREATE INDEX IF NOT EXISTS idx_assignments_schedule ON exam_assignments(schedule_id);
|
||
CREATE INDEX IF NOT EXISTS idx_assignments_course ON exam_assignments(course_code);
|
||
CREATE INDEX IF NOT EXISTS idx_assignments_classroom_time ON exam_assignments(classroom_id, day, time_slot);
|
||
|
||
-- ============================================================
|
||
-- END OF SCHEMA
|
||
-- ============================================================
|
||
```
|
||
|
||
---
|
||
|
||
## COMMON QUERIES
|
||
|
||
### Query 1: Get All Courses for a Student
|
||
|
||
```sql
|
||
SELECT c.course_code
|
||
FROM courses c
|
||
JOIN enrollments e ON c.course_code = e.course_code
|
||
WHERE e.student_id = ?
|
||
ORDER BY c.course_code;
|
||
```
|
||
|
||
**Parameters:** `student_id` (e.g., 'Std_ID_001')
|
||
|
||
**Returns:** List of course codes the student is enrolled in
|
||
|
||
**Usage:** Constraint validation, student view display
|
||
|
||
---
|
||
|
||
### Query 2: Get All Students in a Course
|
||
|
||
```sql
|
||
SELECT s.student_id
|
||
FROM students s
|
||
JOIN enrollments e ON s.student_id = e.student_id
|
||
WHERE e.course_code = ?
|
||
ORDER BY s.student_id;
|
||
```
|
||
|
||
**Parameters:** `course_code` (e.g., 'CourseCode_01')
|
||
|
||
**Returns:** List of student IDs enrolled in the course
|
||
|
||
**Usage:** Capacity validation, solver domain creation
|
||
|
||
---
|
||
|
||
### Query 3: Get Enrollment Count for a Course
|
||
|
||
```sql
|
||
SELECT COUNT(*) as enrollment_count
|
||
FROM enrollments
|
||
WHERE course_code = ?;
|
||
```
|
||
|
||
**Parameters:** `course_code`
|
||
|
||
**Returns:** Number of students enrolled
|
||
|
||
**Usage:** Capacity validation (must be ≤ classroom capacity)
|
||
|
||
---
|
||
|
||
### Query 4: Get All Exam Assignments for a Schedule
|
||
|
||
```sql
|
||
SELECT
|
||
ea.course_code,
|
||
ea.classroom_id,
|
||
ea.day,
|
||
ea.time_slot,
|
||
cl.capacity,
|
||
COUNT(e.student_id) as enrolled_count
|
||
FROM exam_assignments ea
|
||
JOIN classrooms cl ON ea.classroom_id = cl.classroom_id
|
||
LEFT JOIN enrollments e ON ea.course_code = e.course_code
|
||
WHERE ea.schedule_id = ?
|
||
GROUP BY ea.exam_id
|
||
ORDER BY ea.day, ea.time_slot, ea.classroom_id;
|
||
```
|
||
|
||
**Parameters:** `schedule_id`
|
||
|
||
**Returns:** Complete schedule with capacities and enrollment counts
|
||
|
||
**Usage:** Display all views, validation
|
||
|
||
---
|
||
|
||
### Query 5: Get Student's Personal Exam Schedule
|
||
|
||
```sql
|
||
SELECT
|
||
c.course_code,
|
||
ea.day,
|
||
ea.time_slot,
|
||
ea.classroom_id
|
||
FROM courses c
|
||
JOIN enrollments e ON c.course_code = e.course_code
|
||
JOIN exam_assignments ea ON c.course_code = ea.course_code
|
||
WHERE e.student_id = ?
|
||
AND ea.schedule_id = ?
|
||
ORDER BY ea.day, ea.time_slot;
|
||
```
|
||
|
||
**Parameters:** `student_id`, `schedule_id`
|
||
|
||
**Returns:** Student's personal exam schedule
|
||
|
||
**Usage:** Student view display
|
||
|
||
---
|
||
|
||
### Query 6: Check if Classroom is Available
|
||
|
||
```sql
|
||
SELECT COUNT(*) as is_occupied
|
||
FROM exam_assignments
|
||
WHERE schedule_id = ?
|
||
AND classroom_id = ?
|
||
AND day = ?
|
||
AND time_slot = ?;
|
||
```
|
||
|
||
**Parameters:** `schedule_id`, `classroom_id`, `day`, `time_slot`
|
||
|
||
**Returns:** 0 if available, 1 if occupied
|
||
|
||
**Usage:** Solver domain filtering, validation
|
||
|
||
---
|
||
|
||
### Query 7: Get Exams for Specific Day and Time Slot
|
||
|
||
```sql
|
||
SELECT
|
||
ea.course_code,
|
||
ea.classroom_id,
|
||
COUNT(e.student_id) as student_count
|
||
FROM exam_assignments ea
|
||
LEFT JOIN enrollments e ON ea.course_code = e.course_code
|
||
WHERE ea.schedule_id = ?
|
||
AND ea.day = ?
|
||
AND ea.time_slot = ?
|
||
GROUP BY ea.exam_id
|
||
ORDER BY ea.classroom_id;
|
||
```
|
||
|
||
**Parameters:** `schedule_id`, `day`, `time_slot`
|
||
|
||
**Returns:** All exams in that time slot
|
||
|
||
**Usage:** Day view display
|
||
|
||
---
|
||
|
||
### Query 8: Get All Schedules (History)
|
||
|
||
```sql
|
||
SELECT
|
||
s.schedule_id,
|
||
s.created_date,
|
||
s.optimization_strategy,
|
||
s.num_days,
|
||
s.slots_per_day,
|
||
s.status,
|
||
COUNT(DISTINCT ea.course_code) as course_count,
|
||
COUNT(DISTINCT ea.classroom_id) as classroom_count
|
||
FROM schedules s
|
||
LEFT JOIN exam_assignments ea ON s.schedule_id = ea.schedule_id
|
||
GROUP BY s.schedule_id
|
||
ORDER BY s.created_date DESC;
|
||
```
|
||
|
||
**Returns:** All schedules with metadata and statistics
|
||
|
||
**Usage:** Schedule history view
|
||
|
||
---
|
||
|
||
### Query 9: Check for Student Conflicts (Consecutive Exams)
|
||
|
||
```sql
|
||
-- Check if student has exam in previous slot
|
||
SELECT COUNT(*) as has_conflict
|
||
FROM enrollments e1
|
||
JOIN exam_assignments ea1 ON e1.course_code = ea1.course_code
|
||
WHERE e1.student_id = ?
|
||
AND ea1.schedule_id = ?
|
||
AND (
|
||
-- Check previous slot same day
|
||
(ea1.day = ? AND ea1.time_slot = ? - 1)
|
||
OR
|
||
-- Check last slot of previous day
|
||
(ea1.day = ? - 1 AND ea1.time_slot = (
|
||
SELECT slots_per_day FROM schedules WHERE schedule_id = ?
|
||
))
|
||
);
|
||
```
|
||
|
||
**Parameters:** `student_id`, `schedule_id`, `day`, `time_slot`
|
||
|
||
**Returns:** Count of conflicts (should be 0)
|
||
|
||
**Usage:** Constraint validation
|
||
|
||
---
|
||
|
||
### Query 10: Delete Schedule and All Assignments
|
||
|
||
```sql
|
||
-- Due to CASCADE, this automatically deletes all exam_assignments
|
||
DELETE FROM schedules WHERE schedule_id = ?;
|
||
```
|
||
|
||
**Parameters:** `schedule_id`
|
||
|
||
**Effect:** Deletes schedule and all associated assignments
|
||
|
||
**Usage:** Schedule deletion
|
||
|
||
---
|
||
|
||
## DATABASE INITIALIZATION
|
||
|
||
### Step 1: Create Database File
|
||
|
||
```java
|
||
import java.sql.Connection;
|
||
import java.sql.DriverManager;
|
||
import java.sql.Statement;
|
||
|
||
public class DatabaseInitializer {
|
||
|
||
private static final String DB_URL = "jdbc:sqlite:./data/exam_scheduler.db";
|
||
|
||
public static void initializeDatabase() throws SQLException {
|
||
// Create data directory if it doesn't exist
|
||
new File("./data").mkdirs();
|
||
|
||
// Connect to database (creates file if doesn't exist)
|
||
try (Connection conn = DriverManager.getConnection(DB_URL);
|
||
Statement stmt = conn.createStatement()) {
|
||
|
||
// Enable foreign keys
|
||
stmt.execute("PRAGMA foreign_keys = ON;");
|
||
|
||
// Read schema.sql and execute
|
||
String schema = readFile("src/main/resources/database/schema.sql");
|
||
stmt.executeUpdate(schema);
|
||
|
||
System.out.println("Database initialized successfully!");
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### Step 2: Verify Schema
|
||
|
||
```sql
|
||
-- Check all tables exist
|
||
SELECT name FROM sqlite_master WHERE type='table';
|
||
|
||
-- Expected output:
|
||
-- students
|
||
-- courses
|
||
-- classrooms
|
||
-- enrollments
|
||
-- schedules
|
||
-- exam_assignments
|
||
|
||
-- Check indexes exist
|
||
SELECT name FROM sqlite_master WHERE type='index';
|
||
|
||
-- Check foreign keys are enabled
|
||
PRAGMA foreign_keys;
|
||
-- Should return: 1
|
||
```
|
||
|
||
---
|
||
|
||
### Step 3: Import Sample Data
|
||
|
||
```java
|
||
public void importSampleData() throws SQLException {
|
||
try (Connection conn = DriverManager.getConnection(DB_URL)) {
|
||
conn.setAutoCommit(false); // Start transaction
|
||
|
||
try {
|
||
importStudents(conn, "data/sample/students.csv");
|
||
importCourses(conn, "data/sample/courses.csv");
|
||
importClassrooms(conn, "data/sample/classrooms.csv");
|
||
importEnrollments(conn, "data/sample/enrollments.csv");
|
||
|
||
conn.commit(); // Commit transaction
|
||
System.out.println("Sample data imported successfully!");
|
||
} catch (Exception e) {
|
||
conn.rollback(); // Rollback on error
|
||
throw e;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## BEST PRACTICES
|
||
|
||
### 1. Always Use Transactions for Multiple Operations
|
||
|
||
```java
|
||
try (Connection conn = getConnection()) {
|
||
conn.setAutoCommit(false);
|
||
|
||
try {
|
||
// Multiple inserts/updates
|
||
insertStudent(conn, student1);
|
||
insertStudent(conn, student2);
|
||
insertEnrollment(conn, enrollment1);
|
||
|
||
conn.commit(); // Success - commit all
|
||
} catch (Exception e) {
|
||
conn.rollback(); // Error - rollback all
|
||
throw e;
|
||
}
|
||
}
|
||
```
|
||
|
||
**Why:** Ensures data consistency. If any operation fails, all are rolled back.
|
||
|
||
---
|
||
|
||
### 2. Use Prepared Statements (Prevent SQL Injection)
|
||
|
||
**❌ BAD (Vulnerable to SQL injection):**
|
||
```java
|
||
String sql = "SELECT * FROM students WHERE student_id = '" + studentId + "'";
|
||
Statement stmt = conn.createStatement();
|
||
ResultSet rs = stmt.executeQuery(sql);
|
||
```
|
||
|
||
**✅ GOOD (Safe):**
|
||
```java
|
||
String sql = "SELECT * FROM students WHERE student_id = ?";
|
||
PreparedStatement pstmt = conn.prepareStatement(sql);
|
||
pstmt.setString(1, studentId);
|
||
ResultSet rs = pstmt.executeQuery();
|
||
```
|
||
|
||
---
|
||
|
||
### 3. Close Resources Properly (Use Try-With-Resources)
|
||
|
||
**❌ BAD:**
|
||
```java
|
||
Connection conn = DriverManager.getConnection(DB_URL);
|
||
Statement stmt = conn.createStatement();
|
||
ResultSet rs = stmt.executeQuery(sql);
|
||
// If exception occurs, resources not closed!
|
||
```
|
||
|
||
**✅ GOOD:**
|
||
```java
|
||
try (Connection conn = DriverManager.getConnection(DB_URL);
|
||
Statement stmt = conn.createStatement();
|
||
ResultSet rs = stmt.executeQuery(sql)) {
|
||
// Process results
|
||
} // Resources automatically closed
|
||
```
|
||
|
||
---
|
||
|
||
### 4. Use Batch Inserts for Performance
|
||
|
||
**Slow (one at a time):**
|
||
```java
|
||
for (Student student : students) {
|
||
String sql = "INSERT INTO students (student_id) VALUES (?)";
|
||
PreparedStatement pstmt = conn.prepareStatement(sql);
|
||
pstmt.setString(1, student.getId());
|
||
pstmt.executeUpdate(); // 250 database round-trips!
|
||
}
|
||
```
|
||
|
||
**Fast (batch):**
|
||
```java
|
||
String sql = "INSERT INTO students (student_id) VALUES (?)";
|
||
PreparedStatement pstmt = conn.prepareStatement(sql);
|
||
for (Student student : students) {
|
||
pstmt.setString(1, student.getId());
|
||
pstmt.addBatch();
|
||
}
|
||
pstmt.executeBatch(); // 1 database round-trip!
|
||
```
|
||
|
||
**Performance:** Batch inserts are 10-100x faster for large datasets.
|
||
|
||
---
|
||
|
||
### 5. Enable Foreign Keys
|
||
|
||
SQLite doesn't enable foreign keys by default! Must enable:
|
||
|
||
```java
|
||
try (Connection conn = DriverManager.getConnection(DB_URL);
|
||
Statement stmt = conn.createStatement()) {
|
||
stmt.execute("PRAGMA foreign_keys = ON;");
|
||
}
|
||
```
|
||
|
||
**Without this:** Foreign key constraints are ignored (data corruption possible)
|
||
|
||
---
|
||
|
||
### 6. Use Indexes for Frequently Queried Columns
|
||
|
||
Already created in schema:
|
||
- `idx_enrollments_student` - Fast student lookup
|
||
- `idx_enrollments_course` - Fast course lookup
|
||
- `idx_assignments_schedule` - Fast schedule lookup
|
||
- `idx_assignments_classroom_time` - Fast availability check
|
||
|
||
**When to add more indexes:** If queries are slow, profile and add indexes to WHERE/JOIN columns.
|
||
|
||
---
|
||
|
||
### 7. Regular Database Maintenance
|
||
|
||
```sql
|
||
-- Analyze database for query optimization
|
||
ANALYZE;
|
||
|
||
-- Rebuild indexes (occasionally, if database grows large)
|
||
REINDEX;
|
||
|
||
-- Vacuum database to reclaim space (after many deletes)
|
||
VACUUM;
|
||
```
|
||
|
||
---
|
||
|
||
### 8. Backup Strategy
|
||
|
||
**Manual backup:**
|
||
```bash
|
||
# Just copy the database file
|
||
cp data/exam_scheduler.db data/exam_scheduler_backup.db
|
||
```
|
||
|
||
**Automatic backup on schedule save:**
|
||
```java
|
||
public void saveSchedule(Schedule schedule) {
|
||
// Save to database
|
||
scheduleDAO.save(schedule);
|
||
|
||
// Also export to CSV as backup
|
||
exportService.exportToCSV(schedule, "backups/");
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## DATABASE FILE STRUCTURE
|
||
|
||
### Database File Location
|
||
|
||
```
|
||
exam-scheduler/
|
||
├── data/
|
||
│ ├── exam_scheduler.db # Main database
|
||
│ ├── exam_scheduler_backup.db # Backup (optional)
|
||
│ └── sample/
|
||
│ ├── students.csv
|
||
│ ├── courses.csv
|
||
│ ├── classrooms.csv
|
||
│ └── enrollments.csv
|
||
```
|
||
|
||
### Database File Size Estimates
|
||
|
||
| Data Size | Students | Courses | Enrollments | DB Size |
|
||
|-----------|----------|---------|-------------|---------|
|
||
| Small | 100 | 10 | 200 | ~50 KB |
|
||
| Sample | 250 | 20 | 800 | ~100 KB |
|
||
| Medium | 500 | 50 | 2,500 | ~500 KB |
|
||
| Large | 1,000 | 100 | 10,000 | ~2 MB |
|
||
|
||
**Note:** SQLite is very efficient. Even with 10 schedules saved (10 × 100 assignments = 1,000 rows), database remains < 5 MB.
|
||
|
||
---
|
||
|
||
## TROUBLESHOOTING
|
||
|
||
### Issue 1: Foreign Key Constraint Violation
|
||
|
||
**Error:**
|
||
```
|
||
FOREIGN KEY constraint failed
|
||
```
|
||
|
||
**Causes:**
|
||
- Trying to insert enrollment for non-existent student/course
|
||
- Trying to insert exam assignment for non-existent course/classroom
|
||
|
||
**Solution:**
|
||
```java
|
||
// Always check references exist first
|
||
if (studentDAO.exists(studentId) && courseDAO.exists(courseCode)) {
|
||
enrollmentDAO.insert(enrollment);
|
||
} else {
|
||
throw new IllegalArgumentException("Student or course does not exist");
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### Issue 2: Unique Constraint Violation
|
||
|
||
**Error:**
|
||
```
|
||
UNIQUE constraint failed: enrollments.student_id, enrollments.course_code
|
||
```
|
||
|
||
**Cause:** Trying to enroll student in same course twice
|
||
|
||
**Solution:**
|
||
```java
|
||
// Check if enrollment exists before inserting
|
||
if (!enrollmentDAO.exists(studentId, courseCode)) {
|
||
enrollmentDAO.insert(enrollment);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### Issue 3: Database Locked
|
||
|
||
**Error:**
|
||
```
|
||
database is locked
|
||
```
|
||
|
||
**Cause:** Another connection has an open transaction
|
||
|
||
**Solution:**
|
||
```java
|
||
// Set busy timeout (wait instead of failing immediately)
|
||
try (Connection conn = DriverManager.getConnection(DB_URL)) {
|
||
Statement stmt = conn.createStatement();
|
||
stmt.execute("PRAGMA busy_timeout = 5000"); // Wait up to 5 seconds
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### Issue 4: Foreign Keys Not Enforced
|
||
|
||
**Symptom:** Can insert invalid foreign keys without error
|
||
|
||
**Cause:** Foreign keys not enabled
|
||
|
||
**Solution:**
|
||
```java
|
||
// Enable on EVERY connection
|
||
try (Connection conn = DriverManager.getConnection(DB_URL);
|
||
Statement stmt = conn.createStatement()) {
|
||
stmt.execute("PRAGMA foreign_keys = ON;");
|
||
// Now do queries
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## VERSION HISTORY
|
||
|
||
| Version | Date | Author | Changes |
|
||
|---------|------|--------|---------|
|
||
| 1.0 | 2025-11-26 | Claude Code | Initial schema design |
|
||
|
||
---
|
||
|
||
## REFERENCES
|
||
|
||
- SQLite Documentation: https://www.sqlite.org/docs.html
|
||
- SQLite JDBC: https://github.com/xerial/sqlite-jdbc
|
||
- SQL Tutorial: https://www.sqlitetutorial.net/
|
||
|
||
---
|
||
|
||
**END OF DATABASE SCHEMA DOCUMENTATION**
|