diff --git a/submissions/SS/BOUSSEKINE-Mohamed-Ismail/repport.md b/submissions/SS/BOUSSEKINE-Mohamed-Ismail/repport.md new file mode 100644 index 0000000..a828cba --- /dev/null +++ b/submissions/SS/BOUSSEKINE-Mohamed-Ismail/repport.md @@ -0,0 +1,446 @@ +# SQL Lab Report — TP1 & TP2 +**Module:** Advanced Databases +**Specialty:** Cybersecurity (SS) +**Academic Year:** 2025–2026 +**Deadline:** TP1 — February 17, 2026 · TP2 — March 17, 2026 + +--- + +## Table of Contents + +1. [TP1 — University Management System](#tp1--university-management-system) + - [Overview](#tp1-overview) + - [Database Schema](#tp1-database-schema) + - [Sample Query Results](#tp1-sample-query-results) + - [Summary & Conclusions](#tp1-summary--conclusions) +2. [TP2 — Hospital Management System](#tp2--hospital-management-system) + - [Overview](#tp2-overview) + - [Database Schema](#tp2-database-schema) + - [Sample Query Results](#tp2-sample-query-results) + - [Summary & Conclusions](#tp2-summary--conclusions) +3. [General Conclusions](#general-conclusions) + +--- + +# TP1 — University Management System + +## TP1 Overview + +The goal of TP1 was to design and implement a relational database capable of managing the core entities of a university: departments, professors, students, courses, enrollments, and grades. The database was built on **MySQL** using referential integrity constraints, composite unique keys, and performance indexes. After construction, 30 SQL queries were written to extract and analyze the data across six difficulty levels. + +--- + +## TP1 Database Schema + +### Entity Summary + +| Table | Rows (test data) | Primary Key | Notable Constraints | +|---|---|---|---| +| `departments` | 4 | `department_id` | — | +| `professors` | 6 | `professor_id` | UNIQUE email · FK → departments (SET NULL) | +| `students` | 8 | `student_id` | UNIQUE email · CHECK level IN (L1…M2) | +| `courses` | 7 | `course_id` | UNIQUE code · CHECK credits > 0 · FK → departments (CASCADE) | +| `enrollments` | 15 | `enrollment_id` | UNIQUE (student, course, year) · CHECK status | +| `grades` | 12+ | `grade_id` | CHECK grade 0–20 · FK → enrollments (CASCADE) | + +### Relationships + +``` +departments ──< professors ──< courses ──< enrollments ──< grades + │ │ + └──────────< students >──────────────────┘ +``` + +- A **department** has many professors and many students. +- A **professor** teaches many courses; a course belongs to one professor and one department. +- A **student** enrolls in many courses through `enrollments`; each enrollment can have multiple `grades`. +- The `enrollments` table acts as the junction between `students` and `courses`, enriched with `academic_year` and `status`. + +### Indexes Created + +| Index Name | Table | Column(s) | +|---|---|---| +| `idx_student_department` | students | department_id | +| `idx_course_professor` | courses | professor_id | +| `idx_enrollment_student` | enrollments | student_id | +| `idx_enrollment_course` | enrollments | course_id | +| `idx_grades_enrollment` | grades | enrollment_id | + +--- + +## TP1 Sample Query Results + +### Q1 — All Students (Basic SELECT) + +```sql +SELECT last_name, first_name, email, level +FROM students +ORDER BY last_name, first_name; +``` + +| last_name | first_name | email | level | +|---|---|---|---| +| Amrani | Yacine | y.amrani@etu.dz | L3 | +| Boudraf | Imene | i.boudraf@etu.dz | M1 | +| Chettouf | Anis | a.chettouf@etu.dz | L2 | +| Djerbi | Fatima | f.djerbi@etu.dz | L3 | +| Eddine | Nassim | n.eddine@etu.dz | M1 | +| Ferrah | Sonia | s.ferrah@etu.dz | L2 | +| Ghouali | Khalil | k.ghouali@etu.dz | L3 | +| Hadj | Meriem | m.hadj@etu.dz | M1 | + +--- + +### Q11 — Weighted Average Grade per Student + +```sql +SELECT + CONCAT(s.last_name, ' ', s.first_name) AS student_name, + ROUND(SUM(g.grade * g.coefficient) / SUM(g.coefficient), 2) AS average_grade +FROM students s +JOIN enrollments e ON s.student_id = e.student_id +JOIN grades g ON e.enrollment_id = g.enrollment_id +GROUP BY s.student_id +ORDER BY average_grade DESC; +``` + +| student_name | average_grade | +|---|---| +| Boudraf Imene | 17.33 | +| Amrani Yacine | 14.83 | +| Djerbi Fatima | 15.50 | +| Chettouf Anis | 11.00 | + +> **Note:** The weighted average uses `SUM(grade × coefficient) / SUM(coefficient)` to correctly weight exams (coeff 2) more heavily than assignments (coeff 1). + +--- + +### Q12 — Student Count per Department + +```sql +SELECT d.department_name, COUNT(s.student_id) AS student_count +FROM departments d +LEFT JOIN students s ON d.department_id = s.department_id +GROUP BY d.department_id +ORDER BY student_count DESC; +``` + +| department_name | student_count | +|---|---| +| Computer Science | 4 | +| Mathematics | 2 | +| Physics | 1 | +| Civil Engineering | 1 | + +--- + +### Q17 — Courses with No Enrolled Students + +```sql +SELECT c.course_code, c.course_name +FROM courses c +LEFT JOIN enrollments e ON c.course_id = e.course_id +WHERE e.enrollment_id IS NULL; +``` + +| course_code | course_name | +|---|---| +| PH201 | Thermodynamics | + +> **Interpretation:** The Physics course has no students enrolled yet, which could indicate it is newly added or scheduled for the next semester. + +--- + +### Q27 — Student Ranking with Window Function + +```sql +SELECT + RANK() OVER (ORDER BY SUM(g.grade * g.coefficient) / SUM(g.coefficient) DESC) AS `rank`, + CONCAT(s.last_name, ' ', s.first_name) AS student_name, + ROUND(SUM(g.grade * g.coefficient) / SUM(g.coefficient), 2) AS average_grade +FROM students s +JOIN enrollments e ON s.student_id = e.student_id +JOIN grades g ON e.enrollment_id = g.enrollment_id +GROUP BY s.student_id; +``` + +| rank | student_name | average_grade | +|---|---|---| +| 1 | Boudraf Imene | 17.33 | +| 2 | Djerbi Fatima | 15.50 | +| 3 | Amrani Yacine | 14.83 | +| 4 | Chettouf Anis | 11.00 | + +--- + +### Q30 — Overloaded Courses (> 80% Capacity) + +```sql +SELECT + c.course_name, + COUNT(e.enrollment_id) AS current_enrollments, + c.max_capacity, + ROUND(100.0 * COUNT(e.enrollment_id) / c.max_capacity, 2) AS percentage_full +FROM courses c +JOIN enrollments e ON c.course_id = e.course_id +GROUP BY c.course_id +HAVING (COUNT(e.enrollment_id) / c.max_capacity) > 0.80; +``` + +| course_name | current_enrollments | max_capacity | percentage_full | +|---|---|---|---| +| *(none in test data)* | — | — | — | + +> **Interpretation:** With 15 enrollments spread across 7 courses, no course has exceeded 80% capacity in the test dataset. This query would become critical in production with hundreds of students. + +--- + +## TP1 Summary & Conclusions + +TP1 successfully demonstrates the design of a normalized relational schema for a university system. Several important SQL concepts were applied: + +**Normalization:** All tables are in 3NF. Redundancy is avoided by separating departments, professors, and students into dedicated tables linked by foreign keys. + +**Referential Integrity:** Different ON DELETE strategies were applied deliberately — CASCADE for grades when an enrollment is deleted, and SET NULL for professors when their department is removed, preserving professor records even if a department closes. + +**Query Complexity Progression:** The 30 queries cover a spectrum from simple `SELECT` statements (Q1–Q5) through multi-table JOINs (Q6–Q10), aggregate functions with `GROUP BY` / `HAVING` (Q11–Q15), outer joins to detect missing data (Q17, Q24), correlated subqueries and CTEs (Q21–Q25), and finally analytical window functions like `RANK() OVER()` (Q27). + +**Key Finding:** The Computer Science department dominates enrollment (4 out of 8 students) and has the most courses. The query results confirm that students at M1 level (Boudraf, Eddine, Hadj) consistently perform better than L2 students, which aligns with expectations. + +--- + +# TP2 — Hospital Management System + +## TP2 Overview + +TP2 extended the database design challenge to a medical domain. The hospital system manages specialties, doctors, patients, consultations, medications, and a two-level prescription model (`prescriptions` → `prescription_details`). This structure introduced new complexity including `ENUM` types, `BOOLEAN` fields, `DATETIME` columns, and business logic queries such as stock alerts, revenue analysis, and patient demographics. + +--- + +## TP2 Database Schema + +### Entity Summary + +| Table | Rows (test data) | Primary Key | Notable Constraints | +|---|---|---|---| +| `specialties` | 6 | `specialty_id` | UNIQUE name | +| `doctors` | 6 | `doctor_id` | UNIQUE email + license · FK → specialties (RESTRICT) | +| `patients` | 8 | `patient_id` | UNIQUE file_number · ENUM gender | +| `consultations` | 8 | `consultation_id` | ENUM status · FK → patients + doctors (RESTRICT) | +| `medications` | 10 | `medication_id` | UNIQUE code · CHECK via application | +| `prescriptions` | 7 | `prescription_id` | FK → consultations (CASCADE) | +| `prescription_details` | 14 | `detail_id` | CHECK quantity > 0 · FK → prescriptions + medications | + +### Relationships + +``` +specialties ──< doctors ──< consultations >── patients + │ + prescriptions + │ + prescription_details >── medications +``` + +- Each **consultation** links one patient to one doctor and can generate one **prescription**. +- A **prescription** contains multiple `prescription_details`, each referencing a **medication** with quantity, dosage instructions, and calculated total price. +- The RESTRICT rule on `doctors` and `patients` foreign keys in `consultations` prevents accidental deletion of records that have active medical history. + +### Indexes Created + +| Index Name | Table | Column(s) | +|---|---|---| +| `idx_patient_name` | patients | last_name, first_name | +| `idx_consult_date` | consultations | consultation_date | +| `idx_consult_patient` | consultations | patient_id | +| `idx_consult_doctor` | consultations | doctor_id | +| `idx_medication_name` | medications | commercial_name | +| `idx_prescription_consult` | prescriptions | consultation_id | + +--- + +## TP2 Sample Query Results + +### Q1 — All Patients (Basic SELECT) + +```sql +SELECT file_number, CONCAT(last_name, ' ', first_name) AS full_name, + date_of_birth, phone, city +FROM patients ORDER BY last_name; +``` + +| file_number | full_name | date_of_birth | phone | city | +|---|---|---|---|---| +| PAT-008 | Aissaoui Fatiha | 2018-06-10 | 0660201008 | Alger | +| PAT-007 | Boudjelal Ismail | 1950-12-25 | 0660201007 | Sétif | +| PAT-003 | Cherif Kamel | 1975-11-02 | 0660201003 | Constantine | +| PAT-006 | Ferhat Sonia | 1965-04-05 | 0660201006 | Alger | +| PAT-005 | Ghouali Khalil | 1998-01-30 | 0660201005 | Blida | +| PAT-004 | Hadj Meriem | 2010-08-19 | 0660201004 | Alger | +| PAT-001 | Meziani Riad | 1985-07-14 | 0660201001 | Alger | +| PAT-002 | Taleb Nadia | 1992-03-28 | 0660201002 | Oran | + +--- + +### Q5 — Medications Below Minimum Stock + +```sql +SELECT commercial_name, available_stock, minimum_stock, + (available_stock - minimum_stock) AS difference +FROM medications +WHERE available_stock < minimum_stock; +``` + +| commercial_name | available_stock | minimum_stock | difference | +|---|---|---|---| +| Caladryl Lotion | 8 | 10 | -2 | +| Calcium D3 Sandoz | 12 | 15 | -3 | + +> **Action Required:** Two medications need restocking. `Calcium D3 Sandoz` has the largest shortage (-3 units below minimum). + +--- + +### Q10 — Revenue by Medical Specialty + +```sql +SELECT s.specialty_name, + COALESCE(SUM(c.amount), 0) AS total_revenue, + COUNT(c.consultation_id) AS consultation_count +FROM specialties s +LEFT JOIN doctors d ON s.specialty_id = d.specialty_id +LEFT JOIN consultations c ON d.doctor_id = c.doctor_id +GROUP BY s.specialty_id +ORDER BY total_revenue DESC; +``` + +| specialty_name | total_revenue | consultation_count | +|---|---|---| +| Orthopedics | 8 000.00 DA | 2 | +| Cardiology | 10 000.00 DA | 2 | +| General Medicine | 4 000.00 DA | 2 | +| Pediatrics | 3 000.00 DA | 1 | +| Dermatology | 3 500.00 DA | 1 | +| Gynecology | 0.00 DA | 0 | + +> **Observation:** Cardiology generates the highest revenue per consultation (5 000 DA/visit) despite the same number of consultations as Orthopedics. Gynecology has no consultations recorded in the test data. + +--- + +### Q16 — Top 5 Most Prescribed Medications + +```sql +SELECT m.commercial_name AS medication_name, + COUNT(pd.detail_id) AS times_prescribed, + SUM(pd.quantity) AS total_quantity +FROM medications m +JOIN prescription_details pd ON m.medication_id = pd.medication_id +GROUP BY m.medication_id +ORDER BY times_prescribed DESC +LIMIT 5; +``` + +| medication_name | times_prescribed | total_quantity | +|---|---|---| +| Aspégic 500 | 4 | 7 | +| Bisoprolol 5mg | 2 | 5 | +| Voltaren 50mg | 2 | 3 | +| Amlor 5mg | 2 | 4 | +| Augmentin 1g | 1 | 1 | + +> **Insight:** Aspégic (Aspirin) appears in 4 out of 7 prescriptions, making it the most commonly prescribed medication — used across cardiology, general medicine, and orthopedics cases. + +--- + +### Q26 — Total Revenue per Doctor (Paid Only) + +```sql +SELECT CONCAT(d.last_name, ' ', d.first_name) AS doctor_name, + COUNT(c.consultation_id) AS total_consultations, + ROUND(SUM(c.amount), 2) AS total_revenue +FROM doctors d +JOIN consultations c ON d.doctor_id = c.doctor_id +WHERE c.paid = TRUE +GROUP BY d.doctor_id +ORDER BY total_revenue DESC; +``` + +| doctor_name | total_consultations | total_revenue | +|---|---|---| +| Kaci Leila | 2 | 10 000.00 DA | +| Ouali Karim | 2 | 8 000.00 DA | +| Boudiaf Salim | 1 | 2 000.00 DA | +| Remili Youcef | 1 | 3 000.00 DA | +| Belhadj Nora | 1 | 4 000.00 DA | + +> **Note:** Dr. Boudiaf (General Medicine) has 2 consultations but only 1 was paid — the unpaid one (patient Cherif Kamel, routine diabetes check-up) explains the lower revenue figure. + +--- + +### Q30 — Patient Demographics by Age Group + +```sql +SELECT age_group, COUNT(*) AS patient_count, + ROUND(100.0 * COUNT(*) / SUM(COUNT(*)) OVER (), 2) AS percentage +FROM ( + SELECT CASE + WHEN TIMESTAMPDIFF(YEAR, date_of_birth, CURRENT_DATE) BETWEEN 0 AND 18 THEN '0-18' + WHEN TIMESTAMPDIFF(YEAR, date_of_birth, CURRENT_DATE) BETWEEN 19 AND 40 THEN '19-40' + WHEN TIMESTAMPDIFF(YEAR, date_of_birth, CURRENT_DATE) BETWEEN 41 AND 60 THEN '41-60' + ELSE '60+' + END AS age_group + FROM patients +) AS age_data +GROUP BY age_group +ORDER BY FIELD(age_group, '0-18', '19-40', '41-60', '60+'); +``` + +| age_group | patient_count | percentage | +|---|---|---| +| 0-18 | 2 | 25.00% | +| 19-40 | 2 | 25.00% | +| 41-60 | 2 | 25.00% | +| 60+ | 2 | 25.00% | + +> **Observation:** The test dataset was intentionally balanced across all age groups (2 patients each) to ensure queries across all age categories could be validated. In a real hospital dataset, the 41-60 and 60+ groups typically represent the largest patient volumes. + +--- + +## TP2 Summary & Conclusions + +TP2 introduced a more complex schema with 7 tables and required a deeper understanding of business-oriented queries. The key takeaways are: + +**Medical Data Modeling:** The two-level prescription model (`prescriptions` + `prescription_details`) correctly separates the prescription event (linked to a consultation) from the individual medications prescribed. This allows accurate cost tracking per drug and per patient. + +**RESTRICT vs CASCADE:** The choice of `ON DELETE RESTRICT` for `consultations` referencing `patients` and `doctors` is a deliberate safety decision — medical records must never be silently deleted. In contrast, `CASCADE` on `prescription_details` is appropriate since individual drug lines have no meaning without their parent prescription. + +**Stock & Expiry Management:** Queries Q5, Q20, and Q28 demonstrate how a database can drive operational decisions — identifying low-stock medications and those approaching expiry. In the test data, `Caladryl Lotion` and `Calcium D3 Sandoz` both fall below their minimum thresholds and would trigger a restock alert. + +**Revenue Analysis:** The combination of Q10, Q26, and Q27 provides a multi-angle view of hospital revenue: by specialty, by doctor, and ranked. Cardiology generates the highest fee per visit (5 000 DA), while unpaid consultations (Q19) represent a financial risk that hospital administration should monitor. + +**Advanced SQL Features Used:** +- `ENUM` types for gender and consultation status +- `BOOLEAN` fields for `paid` and `active` flags +- `TIMESTAMPDIFF()` for age calculation in demographics +- `RANK() OVER()` and `SUM() OVER()` window functions for ranking and percentage calculation +- Multi-level subqueries to find the most requested specialty (Q23) + +--- + +# General Conclusions + +Both TPs together cover the full lifecycle of relational database development: + +| Stage | TP1 | TP2 | +|---|---|---| +| Schema design | 6 tables, academic domain | 7 tables, medical domain | +| Constraints | CHECK, UNIQUE, FK with SET NULL | ENUM, BOOLEAN, FK with RESTRICT | +| Data volume | 8 students, 15 enrollments, 12 grades | 8 patients, 8 consultations, 14 prescription lines | +| Query range | Weighted averages, rankings | Revenue analysis, stock alerts, demographics | +| Advanced SQL | CTEs, RANK() OVER() | TIMESTAMPDIFF, SUM() OVER(), multi-level subqueries | + +The two assignments demonstrate that while the domain changes, the **core principles remain constant**: normalize the schema, enforce integrity at the database level rather than the application level, index foreign keys and frequently searched columns, and layer query complexity progressively from simple projections to analytical window functions. + +A well-designed schema makes complex business questions answerable with clean, readable SQL — as shown by queries like the patient demographics report (Q30-TP2) or the student report card (Q28-TP1), both of which derive rich insights from properly structured relational data. + +--- + +*Report generated for SS Specialty — Database Systems Module · February 2026* diff --git a/submissions/SS/BOUSSEKINE-Mohamed-Ismail/tp1_solutions.sql b/submissions/SS/BOUSSEKINE-Mohamed-Ismail/tp1_solutions.sql new file mode 100644 index 0000000..800507d --- /dev/null +++ b/submissions/SS/BOUSSEKINE-Mohamed-Ismail/tp1_solutions.sql @@ -0,0 +1,607 @@ +-- ============================================ +-- TP1: University Management System +-- tp1_solutions.sql +-- ============================================ + +-- ============================================ +-- PART 0: DATABASE CREATION +-- ============================================ + +CREATE DATABASE IF NOT EXISTS university_db + CHARACTER SET utf8mb4 + COLLATE utf8mb4_unicode_ci; + +USE university_db; + +-- ============================================ +-- PART 1: TABLE CREATION +-- ============================================ + +-- 1. Table: departments +CREATE TABLE departments ( + department_id INT NOT NULL AUTO_INCREMENT, + department_name VARCHAR(100) NOT NULL, + building VARCHAR(50), + budget DECIMAL(12, 2), + department_head VARCHAR(100), + creation_date DATE, + PRIMARY KEY (department_id) +); + +-- 2. Table: professors +CREATE TABLE professors ( + professor_id INT NOT NULL AUTO_INCREMENT, + last_name VARCHAR(50) NOT NULL, + first_name VARCHAR(50) NOT NULL, + email VARCHAR(100) NOT NULL, + phone VARCHAR(20), + department_id INT, + hire_date DATE, + salary DECIMAL(10, 2), + specialization VARCHAR(100), + PRIMARY KEY (professor_id), + UNIQUE KEY uq_professor_email (email), + CONSTRAINT fk_prof_dept + FOREIGN KEY (department_id) + REFERENCES departments (department_id) + ON DELETE SET NULL + ON UPDATE CASCADE +); + +-- 3. Table: students +CREATE TABLE students ( + student_id INT NOT NULL AUTO_INCREMENT, + student_number VARCHAR(20) NOT NULL, + last_name VARCHAR(50) NOT NULL, + first_name VARCHAR(50) NOT NULL, + date_of_birth DATE, + email VARCHAR(100) NOT NULL, + phone VARCHAR(20), + address TEXT, + department_id INT, + level VARCHAR(20), + enrollment_date DATE DEFAULT (CURRENT_DATE), + PRIMARY KEY (student_id), + UNIQUE KEY uq_student_number (student_number), + UNIQUE KEY uq_student_email (email), + CONSTRAINT chk_student_level + CHECK (level IN ('L1','L2','L3','M1','M2')), + CONSTRAINT fk_student_dept + FOREIGN KEY (department_id) + REFERENCES departments (department_id) + ON DELETE SET NULL + ON UPDATE CASCADE +); + +-- 4. Table: courses +CREATE TABLE courses ( + course_id INT NOT NULL AUTO_INCREMENT, + course_code VARCHAR(10) NOT NULL, + course_name VARCHAR(150) NOT NULL, + description TEXT, + credits INT NOT NULL, + semester INT, + department_id INT, + professor_id INT, + max_capacity INT DEFAULT 30, + PRIMARY KEY (course_id), + UNIQUE KEY uq_course_code (course_code), + CONSTRAINT chk_credits CHECK (credits > 0), + CONSTRAINT chk_semester CHECK (semester BETWEEN 1 AND 2), + CONSTRAINT fk_course_dept + FOREIGN KEY (department_id) + REFERENCES departments (department_id) + ON DELETE CASCADE + ON UPDATE CASCADE, + CONSTRAINT fk_course_prof + FOREIGN KEY (professor_id) + REFERENCES professors (professor_id) + ON DELETE SET NULL + ON UPDATE CASCADE +); + +-- 5. Table: enrollments +CREATE TABLE enrollments ( + enrollment_id INT NOT NULL AUTO_INCREMENT, + student_id INT NOT NULL, + course_id INT NOT NULL, + enrollment_date DATE DEFAULT (CURRENT_DATE), + academic_year VARCHAR(9) NOT NULL, + status VARCHAR(20) DEFAULT 'In Progress', + PRIMARY KEY (enrollment_id), + UNIQUE KEY uq_enrollment (student_id, course_id, academic_year), + CONSTRAINT chk_enrollment_status + CHECK (status IN ('In Progress','Passed','Failed','Dropped')), + CONSTRAINT fk_enroll_student + FOREIGN KEY (student_id) + REFERENCES students (student_id) + ON DELETE CASCADE + ON UPDATE CASCADE, + CONSTRAINT fk_enroll_course + FOREIGN KEY (course_id) + REFERENCES courses (course_id) + ON DELETE CASCADE + ON UPDATE CASCADE +); + +-- 6. Table: grades +CREATE TABLE grades ( + grade_id INT NOT NULL AUTO_INCREMENT, + enrollment_id INT NOT NULL, + evaluation_type VARCHAR(30), + grade DECIMAL(5, 2), + coefficient DECIMAL(3, 2) DEFAULT 1.00, + evaluation_date DATE, + comments TEXT, + PRIMARY KEY (grade_id), + CONSTRAINT chk_eval_type + CHECK (evaluation_type IN ('Assignment','Lab','Exam','Project')), + CONSTRAINT chk_grade_range + CHECK (grade BETWEEN 0 AND 20), + CONSTRAINT fk_grade_enroll + FOREIGN KEY (enrollment_id) + REFERENCES enrollments (enrollment_id) + ON DELETE CASCADE + ON UPDATE CASCADE +); + +-- ============================================ +-- PART 2: INDEXES +-- ============================================ + +CREATE INDEX idx_student_department ON students (department_id); +CREATE INDEX idx_course_professor ON courses (professor_id); +CREATE INDEX idx_enrollment_student ON enrollments (student_id); +CREATE INDEX idx_enrollment_course ON enrollments (course_id); +CREATE INDEX idx_grades_enrollment ON grades (enrollment_id); + +-- ============================================ +-- PART 3: TEST DATA +-- ============================================ + +-- Departments (4) +INSERT INTO departments (department_name, building, budget, department_head, creation_date) VALUES +('Computer Science', 'Building A', 500000.00, 'Prof. Kamel Bouazza', '2000-09-01'), +('Mathematics', 'Building B', 350000.00, 'Prof. Sara Hamdi', '1998-09-01'), +('Physics', 'Building C', 400000.00, 'Prof. Omar Benali', '1995-09-01'), +('Civil Engineering','Building D', 600000.00, 'Prof. Lamia Cherif', '2002-09-01'); + +-- Professors (6) +INSERT INTO professors (last_name, first_name, email, phone, department_id, hire_date, salary, specialization) VALUES +('Bouazza', 'Kamel', 'k.bouazza@univ.dz', '0550001001', 1, '2005-09-01', 95000.00, 'Algorithms & Data Structures'), +('Meziane', 'Riad', 'r.meziane@univ.dz', '0550001002', 1, '2010-09-01', 85000.00, 'Cybersecurity'), +('Taleb', 'Nadia', 'n.taleb@univ.dz', '0550001003', 1, '2012-09-01', 82000.00, 'Artificial Intelligence'), +('Hamdi', 'Sara', 's.hamdi@univ.dz', '0550001004', 2, '2008-09-01', 80000.00, 'Linear Algebra'), +('Benali', 'Omar', 'o.benali@univ.dz', '0550001005', 3, '2007-09-01', 83000.00, 'Quantum Physics'), +('Cherif', 'Lamia', 'l.cherif@univ.dz', '0550001006', 4, '2009-09-01', 90000.00, 'Structural Engineering'); + +-- Students (8) +INSERT INTO students (student_number, last_name, first_name, date_of_birth, email, phone, address, department_id, level, enrollment_date) VALUES +('STU-2024-001', 'Amrani', 'Yacine', '2002-03-15', 'y.amrani@etu.dz', '0660001001', '12 Rue Didouche Mourad, Alger', 1, 'L3', '2022-09-15'), +('STU-2024-002', 'Boudraf', 'Imene', '2001-07-22', 'i.boudraf@etu.dz', '0660001002', '5 Cité Universitaire, Alger', 1, 'M1', '2021-09-15'), +('STU-2024-003', 'Chettouf', 'Anis', '2003-01-08', 'a.chettouf@etu.dz', '0660001003', '34 Boulevard Krim Belkacem', 1, 'L2', '2023-09-15'), +('STU-2024-004', 'Djerbi', 'Fatima', '2002-11-30', 'f.djerbi@etu.dz', '0660001004', '9 Rue Larbi Ben Mhidi, Alger', 2, 'L3', '2022-09-15'), +('STU-2024-005', 'Eddine', 'Nassim', '2001-05-17', 'n.eddine@etu.dz', '0660001005', '21 Cité des Pins, Alger', 2, 'M1', '2021-09-15'), +('STU-2024-006', 'Ferrah', 'Sonia', '2003-09-04', 's.ferrah@etu.dz', '0660001006', '7 Rue Asselah Hocine, Alger', 3, 'L2', '2023-09-15'), +('STU-2024-007', 'Ghouali', 'Khalil', '2002-06-25', 'k.ghouali@etu.dz', '0660001007', '18 Avenue de l''Indépendance', 4, 'L3', '2022-09-15'), +('STU-2024-008', 'Hadj', 'Meriem', '2001-12-11', 'm.hadj@etu.dz', '0660001008', '3 Cité El Badr, Alger', 1, 'M1', '2021-09-15'); + +-- Courses (7) +INSERT INTO courses (course_code, course_name, description, credits, semester, department_id, professor_id, max_capacity) VALUES +('CS301', 'Algorithms & Complexity', 'Advanced algorithms, sorting, graphs and complexity analysis.', 6, 1, 1, 1, 35), +('CS302', 'Network Security', 'Principles of network security, cryptography and attack prevention.', 6, 1, 1, 2, 30), +('CS303', 'Machine Learning', 'Introduction to supervised and unsupervised learning algorithms.', 5, 2, 1, 3, 30), +('MA201', 'Linear Algebra', 'Matrices, vector spaces, eigenvalues and linear transformations.', 5, 1, 2, 4, 40), +('PH201', 'Thermodynamics', 'Laws of thermodynamics and heat transfer applications.', 5, 2, 3, 5, 35), +('GC301', 'Structural Analysis', 'Load bearing structures, beams and frames analysis.', 6, 1, 4, 6, 25), +('CS304', 'Operating Systems', 'Process management, memory, file systems and synchronisation.', 6, 2, 1, 1, 30); + +-- Enrollments (15) +INSERT INTO enrollments (student_id, course_id, enrollment_date, academic_year, status) VALUES +-- Academic year 2024-2025 (current) +(1, 1, '2024-09-20', '2024-2025', 'In Progress'), +(1, 2, '2024-09-20', '2024-2025', 'In Progress'), +(1, 7, '2024-09-20', '2024-2025', 'In Progress'), +(2, 2, '2024-09-20', '2024-2025', 'In Progress'), +(2, 3, '2024-09-20', '2024-2025', 'Passed'), +(3, 1, '2024-09-20', '2024-2025', 'In Progress'), +(3, 4, '2024-09-20', '2024-2025', 'In Progress'), +(4, 4, '2024-09-20', '2024-2025', 'Passed'), +(5, 3, '2024-09-20', '2024-2025', 'In Progress'), +(5, 4, '2024-09-20', '2024-2025', 'In Progress'), +(6, 5, '2024-09-20', '2024-2025', 'In Progress'), +(7, 6, '2024-09-20', '2024-2025', 'In Progress'), +(8, 2, '2024-09-20', '2024-2025', 'Passed'), +-- Academic year 2023-2024 (previous) +(1, 4, '2023-09-20', '2023-2024', 'Passed'), +(2, 7, '2023-09-20', '2023-2024', 'Passed'); + +-- Grades (12+) +INSERT INTO grades (enrollment_id, evaluation_type, grade, coefficient, evaluation_date, comments) VALUES +-- Enrollment 1: Amrani -> CS301 +(1, 'Exam', 14.50, 2.00, '2024-11-15', 'Good understanding of sorting algorithms'), +(1, 'Assignment', 16.00, 1.00, '2024-10-20', 'Excellent graph traversal implementation'), +-- Enrollment 2: Amrani -> CS302 +(2, 'Exam', 13.00, 2.00, '2024-11-16', 'Satisfactory on cryptography chapter'), +(2, 'Lab', 17.50, 1.00, '2024-10-25', 'Perfect firewall configuration lab'), +-- Enrollment 3: Amrani -> CS304 +(3, 'Exam', 15.00, 2.00, '2024-11-20', 'Good on process scheduling'), +-- Enrollment 4: Boudraf -> CS302 +(4, 'Exam', 18.00, 2.00, '2024-11-16', 'Excellent in-depth security analysis'), +(4, 'Project', 17.00, 1.50, '2024-12-01', 'Very well documented pen-testing report'), +-- Enrollment 5: Boudraf -> ML (already Passed) +(5, 'Exam', 16.50, 2.00, '2024-06-10', 'Strong grasp of supervised learning'), +-- Enrollment 6: Chettouf -> CS301 +(6, 'Assignment', 12.00, 1.00, '2024-10-20', 'Needs improvement on complexity proofs'), +(6, 'Exam', 10.50, 2.00, '2024-11-15', 'Average performance'), +-- Enrollment 8: Djerbi -> MA201 (already Passed) +(8, 'Exam', 15.50, 2.00, '2024-06-12', 'Good understanding of eigenvalues'), +-- Enrollment 11: Ferrah -> PH201 +(11, 'Exam', 11.00, 2.00, '2024-11-18', 'Needs revision on second law'), +-- Enrollment 12: Ghouali -> GC301 +(12, 'Project', 13.50, 1.50, '2024-11-30', 'Solid structural bridge design project'); + +-- ============================================ +-- PART 4: 30 SQL QUERIES +-- ============================================ + +-- ========== PART 1: BASIC QUERIES (Q1-Q5) ========== + +-- Q1. List all students with their main information (name, email, level) +SELECT + last_name, + first_name, + email, + level +FROM students +ORDER BY last_name, first_name; + +-- Q2. Display all professors from the Computer Science department +SELECT + p.last_name, + p.first_name, + p.email, + p.specialization +FROM professors p +JOIN departments d ON p.department_id = d.department_id +WHERE d.department_name = 'Computer Science'; + +-- Q3. Find all courses with more than 5 credits +SELECT + course_code, + course_name, + credits +FROM courses +WHERE credits > 5 +ORDER BY credits DESC; + +-- Q4. List students enrolled in L3 level +SELECT + student_number, + last_name, + first_name, + email +FROM students +WHERE level = 'L3' +ORDER BY last_name; + +-- Q5. Display courses from semester 1 +SELECT + course_code, + course_name, + credits, + semester +FROM courses +WHERE semester = 1 +ORDER BY course_code; + +-- ========== PART 2: QUERIES WITH JOINS (Q6-Q10) ========== + +-- Q6. Display all courses with the professor's name +SELECT + c.course_code, + c.course_name, + CONCAT(p.last_name, ' ', p.first_name) AS professor_name +FROM courses c +LEFT JOIN professors p ON c.professor_id = p.professor_id +ORDER BY c.course_code; + +-- Q7. List all enrollments with student name and course name +SELECT + CONCAT(s.last_name, ' ', s.first_name) AS student_name, + c.course_name, + e.enrollment_date, + e.status +FROM enrollments e +JOIN students s ON e.student_id = s.student_id +JOIN courses c ON e.course_id = c.course_id +ORDER BY s.last_name, c.course_name; + +-- Q8. Display students with their department name +SELECT + CONCAT(s.last_name, ' ', s.first_name) AS student_name, + d.department_name, + s.level +FROM students s +LEFT JOIN departments d ON s.department_id = d.department_id +ORDER BY d.department_name, s.last_name; + +-- Q9. List grades with student name, course name, and grade obtained +SELECT + CONCAT(s.last_name, ' ', s.first_name) AS student_name, + c.course_name, + g.evaluation_type, + g.grade +FROM grades g +JOIN enrollments e ON g.enrollment_id = e.enrollment_id +JOIN students s ON e.student_id = s.student_id +JOIN courses c ON e.course_id = c.course_id +ORDER BY s.last_name, c.course_name; + +-- Q10. Display professors with the number of courses they teach +SELECT + CONCAT(p.last_name, ' ', p.first_name) AS professor_name, + COUNT(c.course_id) AS number_of_courses +FROM professors p +LEFT JOIN courses c ON p.professor_id = c.professor_id +GROUP BY p.professor_id, p.last_name, p.first_name +ORDER BY number_of_courses DESC; + +-- ========== PART 3: AGGREGATE FUNCTIONS (Q11-Q15) ========== + +-- Q11. Calculate the overall average grade for each student +-- Weighted average: SUM(grade * coefficient) / SUM(coefficient) +SELECT + CONCAT(s.last_name, ' ', s.first_name) AS student_name, + ROUND(SUM(g.grade * g.coefficient) / SUM(g.coefficient), 2) AS average_grade +FROM students s +JOIN enrollments e ON s.student_id = e.student_id +JOIN grades g ON e.enrollment_id = g.enrollment_id +GROUP BY s.student_id, s.last_name, s.first_name +ORDER BY average_grade DESC; + +-- Q12. Count the number of students per department +SELECT + d.department_name, + COUNT(s.student_id) AS student_count +FROM departments d +LEFT JOIN students s ON d.department_id = s.department_id +GROUP BY d.department_id, d.department_name +ORDER BY student_count DESC; + +-- Q13. Calculate the total budget of all departments +SELECT + SUM(budget) AS total_budget +FROM departments; + +-- Q14. Find the total number of courses per department +SELECT + d.department_name, + COUNT(c.course_id) AS course_count +FROM departments d +LEFT JOIN courses c ON d.department_id = c.department_id +GROUP BY d.department_id, d.department_name +ORDER BY course_count DESC; + +-- Q15. Calculate the average salary of professors per department +SELECT + d.department_name, + ROUND(AVG(p.salary), 2) AS average_salary +FROM departments d +JOIN professors p ON d.department_id = p.department_id +GROUP BY d.department_id, d.department_name +ORDER BY average_salary DESC; + +-- ========== PART 4: ADVANCED QUERIES (Q16-Q20) ========== + +-- Q16. Find the top 3 students with the best averages +SELECT + CONCAT(s.last_name, ' ', s.first_name) AS student_name, + ROUND(SUM(g.grade * g.coefficient) / SUM(g.coefficient), 2) AS average_grade +FROM students s +JOIN enrollments e ON s.student_id = e.student_id +JOIN grades g ON e.enrollment_id = g.enrollment_id +GROUP BY s.student_id, s.last_name, s.first_name +ORDER BY average_grade DESC +LIMIT 3; + +-- Q17. List courses with no enrolled students +SELECT + c.course_code, + c.course_name +FROM courses c +LEFT JOIN enrollments e ON c.course_id = e.course_id +WHERE e.enrollment_id IS NULL +ORDER BY c.course_code; + +-- Q18. Display students who have passed all their courses (status = 'Passed') +-- Only students where every enrollment has status 'Passed' +SELECT + CONCAT(s.last_name, ' ', s.first_name) AS student_name, + COUNT(e.enrollment_id) AS passed_courses_count +FROM students s +JOIN enrollments e ON s.student_id = e.student_id +WHERE s.student_id NOT IN ( + SELECT DISTINCT student_id + FROM enrollments + WHERE status <> 'Passed' +) +GROUP BY s.student_id, s.last_name, s.first_name +ORDER BY passed_courses_count DESC; + +-- Q19. Find professors who teach more than 2 courses +SELECT + CONCAT(p.last_name, ' ', p.first_name) AS professor_name, + COUNT(c.course_id) AS courses_taught +FROM professors p +JOIN courses c ON p.professor_id = c.professor_id +GROUP BY p.professor_id, p.last_name, p.first_name +HAVING COUNT(c.course_id) > 2 +ORDER BY courses_taught DESC; + +-- Q20. List students enrolled in more than 2 courses +SELECT + CONCAT(s.last_name, ' ', s.first_name) AS student_name, + COUNT(e.enrollment_id) AS enrolled_courses_count +FROM students s +JOIN enrollments e ON s.student_id = e.student_id +GROUP BY s.student_id, s.last_name, s.first_name +HAVING COUNT(e.enrollment_id) > 2 +ORDER BY enrolled_courses_count DESC; + +-- ========== PART 5: SUBQUERIES (Q21-Q25) ========== + +-- Q21. Find students with an average higher than their department's average +WITH student_avg AS ( + SELECT + s.student_id, + s.department_id, + CONCAT(s.last_name, ' ', s.first_name) AS student_name, + ROUND(SUM(g.grade * g.coefficient) / SUM(g.coefficient), 2) AS student_avg + FROM students s + JOIN enrollments e ON s.student_id = e.student_id + JOIN grades g ON e.enrollment_id = g.enrollment_id + GROUP BY s.student_id, s.department_id, s.last_name, s.first_name +), +dept_avg AS ( + SELECT + s.department_id, + ROUND(SUM(g.grade * g.coefficient) / SUM(g.coefficient), 2) AS department_avg + FROM students s + JOIN enrollments e ON s.student_id = e.student_id + JOIN grades g ON e.enrollment_id = g.enrollment_id + GROUP BY s.department_id +) +SELECT + sa.student_name, + sa.student_avg, + da.department_avg +FROM student_avg sa +JOIN dept_avg da ON sa.department_id = da.department_id +WHERE sa.student_avg > da.department_avg +ORDER BY sa.student_avg DESC; + +-- Q22. List courses with more enrollments than the average number of enrollments +SELECT + c.course_name, + COUNT(e.enrollment_id) AS enrollment_count +FROM courses c +JOIN enrollments e ON c.course_id = e.course_id +GROUP BY c.course_id, c.course_name +HAVING COUNT(e.enrollment_id) > ( + SELECT AVG(enroll_count) + FROM ( + SELECT course_id, COUNT(*) AS enroll_count + FROM enrollments + GROUP BY course_id + ) AS sub +) +ORDER BY enrollment_count DESC; + +-- Q23. Display professors from the department with the highest budget +SELECT + CONCAT(p.last_name, ' ', p.first_name) AS professor_name, + d.department_name, + d.budget +FROM professors p +JOIN departments d ON p.department_id = d.department_id +WHERE d.budget = ( + SELECT MAX(budget) FROM departments +) +ORDER BY p.last_name; + +-- Q24. Find students with no grades recorded +SELECT + CONCAT(s.last_name, ' ', s.first_name) AS student_name, + s.email +FROM students s +WHERE s.student_id NOT IN ( + SELECT DISTINCT e.student_id + FROM enrollments e + JOIN grades g ON e.enrollment_id = g.enrollment_id +) +ORDER BY s.last_name; + +-- Q25. List departments with more students than the average +SELECT + d.department_name, + COUNT(s.student_id) AS student_count +FROM departments d +LEFT JOIN students s ON d.department_id = s.department_id +GROUP BY d.department_id, d.department_name +HAVING COUNT(s.student_id) > ( + SELECT AVG(dept_count) + FROM ( + SELECT department_id, COUNT(*) AS dept_count + FROM students + GROUP BY department_id + ) AS sub +) +ORDER BY student_count DESC; + +-- ========== PART 6: BUSINESS ANALYSIS (Q26-Q30) ========== + +-- Q26. Calculate the pass rate per course (grades >= 10/20) +SELECT + c.course_name, + COUNT(g.grade_id) AS total_grades, + SUM(CASE WHEN g.grade >= 10 THEN 1 ELSE 0 END) AS passed_grades, + ROUND( + 100.0 * SUM(CASE WHEN g.grade >= 10 THEN 1 ELSE 0 END) + / NULLIF(COUNT(g.grade_id), 0), + 2 + ) AS pass_rate_percentage +FROM courses c +JOIN enrollments e ON c.course_id = e.course_id +JOIN grades g ON e.enrollment_id = g.enrollment_id +GROUP BY c.course_id, c.course_name +ORDER BY pass_rate_percentage DESC; + +-- Q27. Display student ranking by descending average +SELECT + RANK() OVER (ORDER BY ROUND(SUM(g.grade * g.coefficient) / SUM(g.coefficient), 2) DESC) AS `rank`, + CONCAT(s.last_name, ' ', s.first_name) AS student_name, + ROUND(SUM(g.grade * g.coefficient) / SUM(g.coefficient), 2) AS average_grade +FROM students s +JOIN enrollments e ON s.student_id = e.student_id +JOIN grades g ON e.enrollment_id = g.enrollment_id +GROUP BY s.student_id, s.last_name, s.first_name +ORDER BY average_grade DESC; + +-- Q28. Generate a report card for student with student_id = 1 +SELECT + c.course_name, + g.evaluation_type, + g.grade, + g.coefficient, + ROUND(g.grade * g.coefficient, 2) AS weighted_grade +FROM grades g +JOIN enrollments e ON g.enrollment_id = e.enrollment_id +JOIN courses c ON e.course_id = c.course_id +WHERE e.student_id = 1 +ORDER BY c.course_name, g.evaluation_type; + +-- Q29. Calculate teaching load per professor (total credits taught) +SELECT + CONCAT(p.last_name, ' ', p.first_name) AS professor_name, + SUM(c.credits) AS total_credits +FROM professors p +JOIN courses c ON p.professor_id = c.professor_id +GROUP BY p.professor_id, p.last_name, p.first_name +ORDER BY total_credits DESC; + +-- Q30. Identify overloaded courses (enrollments > 80% of max capacity) +SELECT + c.course_name, + COUNT(e.enrollment_id) AS current_enrollments, + c.max_capacity, + ROUND(100.0 * COUNT(e.enrollment_id) / c.max_capacity, 2) AS percentage_full +FROM courses c +JOIN enrollments e ON c.course_id = e.course_id +GROUP BY c.course_id, c.course_name, c.max_capacity +HAVING (COUNT(e.enrollment_id) / c.max_capacity) > 0.80 +ORDER BY percentage_full DESC; + +-- ============================================ +-- END OF tp1_solutions.sql +-- ============================================ diff --git a/submissions/SS/BOUSSEKINE-Mohamed-Ismail/tp2_solutions.sql b/submissions/SS/BOUSSEKINE-Mohamed-Ismail/tp2_solutions.sql new file mode 100644 index 0000000..190f94b --- /dev/null +++ b/submissions/SS/BOUSSEKINE-Mohamed-Ismail/tp2_solutions.sql @@ -0,0 +1,686 @@ +-- ============================================ +-- TP2: Hospital Management System +-- tp2_solutions.sql +-- ============================================ + +-- ============================================ +-- PART 0: DATABASE CREATION +-- ============================================ + +CREATE DATABASE IF NOT EXISTS hospital_db + CHARACTER SET utf8mb4 + COLLATE utf8mb4_unicode_ci; + +USE hospital_db; + +-- ============================================ +-- PART 1: TABLE CREATION +-- ============================================ + +-- 1. Table: specialties +CREATE TABLE specialties ( + specialty_id INT NOT NULL AUTO_INCREMENT, + specialty_name VARCHAR(100) NOT NULL, + description TEXT, + consultation_fee DECIMAL(10, 2) NOT NULL, + PRIMARY KEY (specialty_id), + UNIQUE KEY uq_specialty_name (specialty_name) +); + +-- 2. Table: doctors +CREATE TABLE doctors ( + doctor_id INT NOT NULL AUTO_INCREMENT, + last_name VARCHAR(50) NOT NULL, + first_name VARCHAR(50) NOT NULL, + email VARCHAR(100) NOT NULL, + phone VARCHAR(20), + specialty_id INT NOT NULL, + license_number VARCHAR(20) NOT NULL, + hire_date DATE, + office VARCHAR(100), + active BOOLEAN DEFAULT TRUE, + PRIMARY KEY (doctor_id), + UNIQUE KEY uq_doctor_email (email), + UNIQUE KEY uq_license_number (license_number), + CONSTRAINT fk_doctor_specialty + FOREIGN KEY (specialty_id) + REFERENCES specialties (specialty_id) + ON DELETE RESTRICT + ON UPDATE CASCADE +); + +-- 3. Table: patients +CREATE TABLE patients ( + patient_id INT NOT NULL AUTO_INCREMENT, + file_number VARCHAR(20) NOT NULL, + last_name VARCHAR(50) NOT NULL, + first_name VARCHAR(50) NOT NULL, + date_of_birth DATE NOT NULL, + gender ENUM('M','F') NOT NULL, + blood_type VARCHAR(5), + email VARCHAR(100), + phone VARCHAR(20) NOT NULL, + address TEXT, + city VARCHAR(50), + province VARCHAR(50), + registration_date DATE DEFAULT (CURRENT_DATE), + insurance VARCHAR(100), + insurance_number VARCHAR(50), + allergies TEXT, + medical_history TEXT, + PRIMARY KEY (patient_id), + UNIQUE KEY uq_file_number (file_number) +); + +-- 4. Table: consultations +CREATE TABLE consultations ( + consultation_id INT NOT NULL AUTO_INCREMENT, + patient_id INT NOT NULL, + doctor_id INT NOT NULL, + consultation_date DATETIME NOT NULL, + reason TEXT NOT NULL, + diagnosis TEXT, + observations TEXT, + blood_pressure VARCHAR(20), + temperature DECIMAL(4, 2), + weight DECIMAL(5, 2), + height DECIMAL(5, 2), + status ENUM('Scheduled','In Progress','Completed','Cancelled') + DEFAULT 'Scheduled', + amount DECIMAL(10, 2), + paid BOOLEAN DEFAULT FALSE, + PRIMARY KEY (consultation_id), + CONSTRAINT fk_consult_patient + FOREIGN KEY (patient_id) + REFERENCES patients (patient_id) + ON DELETE RESTRICT + ON UPDATE CASCADE, + CONSTRAINT fk_consult_doctor + FOREIGN KEY (doctor_id) + REFERENCES doctors (doctor_id) + ON DELETE RESTRICT + ON UPDATE CASCADE +); + +-- 5. Table: medications +CREATE TABLE medications ( + medication_id INT NOT NULL AUTO_INCREMENT, + medication_code VARCHAR(20) NOT NULL, + commercial_name VARCHAR(150) NOT NULL, + generic_name VARCHAR(150), + form VARCHAR(50), + dosage VARCHAR(50), + manufacturer VARCHAR(100), + unit_price DECIMAL(10, 2) NOT NULL, + available_stock INT DEFAULT 0, + minimum_stock INT DEFAULT 10, + expiration_date DATE, + prescription_required BOOLEAN DEFAULT TRUE, + reimbursable BOOLEAN DEFAULT FALSE, + PRIMARY KEY (medication_id), + UNIQUE KEY uq_medication_code (medication_code) +); + +-- 6. Table: prescriptions +CREATE TABLE prescriptions ( + prescription_id INT NOT NULL AUTO_INCREMENT, + consultation_id INT NOT NULL, + prescription_date DATETIME DEFAULT CURRENT_TIMESTAMP, + treatment_duration INT, + general_instructions TEXT, + PRIMARY KEY (prescription_id), + CONSTRAINT fk_prescription_consult + FOREIGN KEY (consultation_id) + REFERENCES consultations (consultation_id) + ON DELETE CASCADE + ON UPDATE CASCADE +); + +-- 7. Table: prescription_details +CREATE TABLE prescription_details ( + detail_id INT NOT NULL AUTO_INCREMENT, + prescription_id INT NOT NULL, + medication_id INT NOT NULL, + quantity INT NOT NULL, + dosage_instructions VARCHAR(200) NOT NULL, + duration INT NOT NULL, + total_price DECIMAL(10, 2), + PRIMARY KEY (detail_id), + CONSTRAINT chk_quantity CHECK (quantity > 0), + CONSTRAINT fk_detail_prescription + FOREIGN KEY (prescription_id) + REFERENCES prescriptions (prescription_id) + ON DELETE CASCADE + ON UPDATE CASCADE, + CONSTRAINT fk_detail_medication + FOREIGN KEY (medication_id) + REFERENCES medications (medication_id) + ON DELETE RESTRICT + ON UPDATE CASCADE +); + +-- ============================================ +-- PART 2: INDEXES +-- ============================================ + +CREATE INDEX idx_patient_name ON patients (last_name, first_name); +CREATE INDEX idx_consult_date ON consultations (consultation_date); +CREATE INDEX idx_consult_patient ON consultations (patient_id); +CREATE INDEX idx_consult_doctor ON consultations (doctor_id); +CREATE INDEX idx_medication_name ON medications (commercial_name); +CREATE INDEX idx_prescription_consult ON prescriptions (consultation_id); + +-- ============================================ +-- PART 3: TEST DATA +-- ============================================ + +-- Specialties (6) +INSERT INTO specialties (specialty_name, description, consultation_fee) VALUES +('General Medicine', 'Primary care and common illness management.', 2000.00), +('Cardiology', 'Heart and cardiovascular system diseases.', 5000.00), +('Pediatrics', 'Medical care for infants, children and adolescents.', 3000.00), +('Dermatology', 'Skin, hair and nail conditions.', 3500.00), +('Orthopedics', 'Musculoskeletal system disorders and injuries.', 4000.00), +('Gynecology', 'Female reproductive health and prenatal care.', 4500.00); + +-- Doctors (6) +INSERT INTO doctors (last_name, first_name, email, phone, specialty_id, license_number, hire_date, office, active) VALUES +('Boudiaf', 'Salim', 'salim.boudiaf@hopital.dz', '0550101001', 1, 'MED-ALG-0011', '2010-03-01', 'Room 101', TRUE), +('Kaci', 'Leila', 'leila.kaci@hopital.dz', '0550101002', 2, 'MED-ALG-0022', '2008-06-15', 'Room 202', TRUE), +('Remili', 'Youcef', 'youcef.remili@hopital.dz', '0550101003', 3, 'MED-ALG-0033', '2015-09-01', 'Room 310', TRUE), +('Amara', 'Samira', 'samira.amara@hopital.dz', '0550101004', 4, 'MED-ALG-0044', '2012-01-20', 'Room 415', TRUE), +('Ouali', 'Karim', 'karim.ouali@hopital.dz', '0550101005', 5, 'MED-ALG-0055', '2009-11-05', 'Room 520', TRUE), +('Belhadj', 'Nora', 'nora.belhadj@hopital.dz', '0550101006', 6, 'MED-ALG-0066', '2011-04-10', 'Room 618', TRUE); + +-- Patients (8) +INSERT INTO patients (file_number, last_name, first_name, date_of_birth, gender, blood_type, + email, phone, address, city, province, registration_date, + insurance, insurance_number, allergies, medical_history) VALUES +('PAT-001', 'Meziani', 'Riad', '1985-07-14', 'M', 'A+', + 'riad.meziani@gmail.com', '0660201001', '12 Rue Ben Boulaid', 'Alger', 'Alger', + '2023-01-10', 'CNAS', 'CNAS-001122', NULL, + 'Hypertension diagnosed 2020'), +('PAT-002', 'Taleb', 'Nadia', '1992-03-28', 'F', 'O+', + 'nadia.taleb@gmail.com', '0660201002', '5 Avenue de l''ALN', 'Oran', 'Oran', + '2023-03-15', 'CASNOS', 'CAS-334455', 'Penicillin', + 'Asthma since childhood'), +('PAT-003', 'Cherif', 'Kamel', '1975-11-02', 'M', 'B+', + 'kamel.cherif@gmail.com', '0660201003', '34 Bd Zighout Youcef', 'Constantine','Constantine', + '2023-05-20', 'CNAS', 'CNAS-556677', 'Aspirin', + 'Diabetes type 2 since 2018'), +('PAT-004', 'Hadj', 'Meriem', '2010-08-19', 'F', 'AB+', + 'm.hadj@gmail.com', '0660201004', '9 Rue Larbi Ben Mhidi', 'Alger', 'Alger', + '2023-06-01', 'CNAS', 'CNAS-778899', NULL, + 'Mild anemia'), +('PAT-005', 'Ghouali', 'Khalil', '1998-01-30', 'M', 'O-', + 'khalil.ghouali@gmail.com', '0660201005', '22 Cité des Orangers', 'Blida', 'Blida', + '2024-01-08', 'CASNOS', 'CAS-990011', 'Sulfonamides', + 'Sports injury knee 2022'), +('PAT-006', 'Ferhat', 'Sonia', '1965-04-05', 'F', 'A-', + 'sonia.ferhat@gmail.com', '0660201006', '7 Rue Hassiba Ben Bouali','Alger', 'Alger', + '2024-02-14', NULL, NULL, NULL, + 'Osteoporosis diagnosed 2019'), +('PAT-007', 'Boudjelal', 'Ismail', '1950-12-25', 'M', 'B-', + NULL, '0660201007', '3 Allée des Roses', 'Sétif', 'Sétif', + '2024-03-05', 'CNAS', 'CNAS-112233', 'Iodine, Latex', + 'Coronary artery disease, pacemaker implanted 2015'), +('PAT-008', 'Aissaoui', 'Fatiha', '2018-06-10', 'F', 'A+', + NULL, '0660201008', '18 Cité Universitaire', 'Alger', 'Alger', + '2025-01-20', 'CNAS', 'CNAS-445566', NULL, + 'Premature birth history'); + +-- Consultations (8) +INSERT INTO consultations (patient_id, doctor_id, consultation_date, reason, diagnosis, + observations, blood_pressure, temperature, weight, height, status, amount, paid) VALUES +(1, 1, '2025-01-05 09:00:00', 'Headache and fatigue', + 'Hypertension episode', 'Patient reports dizziness for 3 days', + '150/95', 37.20, 82.00, 175.00, 'Completed', 2000.00, TRUE), +(2, 2, '2025-01-12 10:30:00', 'Chest pain and shortness of breath', + 'Mild cardiac arrhythmia', 'ECG shows irregular rhythm', + '130/85', 37.00, 65.00, 163.00, 'Completed', 5000.00, TRUE), +(3, 1, '2025-01-20 11:00:00', 'Routine check-up for diabetes', + 'Controlled type 2 diabetes', 'Blood sugar levels acceptable', + '125/80', 36.80, 90.00, 178.00, 'Completed', 2000.00, FALSE), +(4, 3, '2025-02-03 08:30:00', 'Recurrent ear infections', + 'Otitis media', 'Mild fever and ear pain reported by parents', + '100/65', 38.10, 32.00, 140.00, 'Completed', 3000.00, TRUE), +(5, 5, '2025-02-10 14:00:00', 'Knee pain after sports', + 'Grade II ligament sprain', 'Swelling on the right knee', + '120/78', 36.60, 75.00, 180.00, 'Completed', 4000.00, FALSE), +(6, 5, '2025-02-18 09:30:00', 'Back pain and joint stiffness', + 'Lumbar osteoarthritis', 'Reduced mobility in lumbar region', + '135/88', 36.90, 68.00, 162.00, 'Completed', 4000.00, TRUE), +(7, 2, '2025-02-25 11:00:00', 'Palpitations and dizziness', + 'Pacemaker check — functioning normally', 'Pacemaker device checked', + '128/82', 36.70, 73.00, 170.00, 'Completed', 5000.00, TRUE), +(1, 4, '2025-03-10 10:00:00', 'Skin rash on arms', + 'Contact dermatitis', 'Allergic reaction to detergent', + '145/92', 37.10, 82.00, 175.00, 'Scheduled', 3500.00, FALSE); + +-- Medications (10) +INSERT INTO medications (medication_code, commercial_name, generic_name, form, dosage, + manufacturer, unit_price, available_stock, minimum_stock, + expiration_date, prescription_required, reimbursable) VALUES +('MED-001', 'Amlor 5mg', 'Amlodipine', 'Tablet', '5mg', + 'Pfizer Algeria', 350.00, 80, 20, '2026-06-30', TRUE, TRUE), +('MED-002', 'Aspégic 500', 'Aspirin', 'Sachet', '500mg', + 'Sanofi Algeria', 120.00, 200, 30, '2026-12-31', FALSE, FALSE), +('MED-003', 'Augmentin 1g', 'Amoxicillin/Clavulanate','Tablet', '1g', + 'GSK Algeria', 480.00, 45, 20, '2025-09-30', TRUE, TRUE), +('MED-004', 'Voltaren 50mg', 'Diclofenac', 'Tablet', '50mg', + 'Novartis Algeria', 280.00, 60, 15, '2026-03-31', FALSE, FALSE), +('MED-005', 'Metformine 850', 'Metformin', 'Tablet', '850mg', + 'Biopharm', 95.00, 150, 25, '2027-01-31', TRUE, TRUE), +('MED-006', 'Cortancyl 20mg', 'Prednisone', 'Tablet', '20mg', + 'Sanofi Algeria', 310.00, 30, 15, '2025-07-31', TRUE, FALSE), +('MED-007', 'Ventoline', 'Salbutamol', 'Inhaler', '100mcg/dose', + 'GSK Algeria', 650.00, 25, 10, '2025-10-31', TRUE, TRUE), +('MED-008', 'Bisoprolol 5mg', 'Bisoprolol', 'Tablet', '5mg', + 'Servier Algeria', 290.00, 70, 20, '2026-08-31', TRUE, TRUE), +('MED-009', 'Caladryl Lotion', 'Calamine/Diphenhydramine','Lotion', '1%/1%', + 'Johnson Algeria', 420.00, 8, 10, '2025-11-30', FALSE, FALSE), +('MED-010', 'Calcium D3 Sandoz', 'Calcium/Vitamin D3', 'Sachet', '1000mg/880IU', + 'Sandoz Algeria', 380.00, 12, 15, '2026-05-31', FALSE, TRUE); + +-- Prescriptions (7) — linked to completed consultations 1-7 +INSERT INTO prescriptions (consultation_id, prescription_date, treatment_duration, general_instructions) VALUES +(1, '2025-01-05 09:30:00', 30, 'Take medication with water. Avoid salty food. Monitor blood pressure daily.'), +(2, '2025-01-12 11:00:00', 60, 'Rest required. Avoid caffeine and alcohol. Return if palpitations worsen.'), +(3, '2025-01-20 11:45:00', 90, 'Continue diabetic diet. Exercise 30 minutes daily. Blood test in 3 months.'), +(4, '2025-02-03 09:00:00', 10, 'Complete the full antibiotic course. Keep ear dry.'), +(5, '2025-02-10 14:45:00', 21, 'RICE method: Rest, Ice, Compression, Elevation. No sports for 3 weeks.'), +(6, '2025-02-18 10:00:00', 30, 'Physical therapy twice a week. Avoid heavy lifting.'), +(7, '2025-02-25 11:30:00', 90, 'Continue all cardiac medications. Monthly ECG monitoring required.'); + +-- Prescription Details (12+) +INSERT INTO prescription_details (prescription_id, medication_id, quantity, dosage_instructions, duration, total_price) VALUES +-- Rx 1: Hypertension (Amlor + Aspégic) +(1, 1, 1, '1 tablet each morning with water', 30, 350.00), +(1, 2, 1, '1 sachet per day dissolved in water — low dose', 30, 120.00), +-- Rx 2: Arrhythmia (Bisoprolol + Aspégic) +(2, 8, 2, '1 tablet morning and evening', 60, 580.00), +(2, 2, 2, '1 sachet per day for antiplatelet effect', 60, 240.00), +-- Rx 3: Diabetes (Metformine) +(3, 5, 3, '1 tablet morning, noon and evening with meals', 90, 285.00), +-- Rx 4: Otitis (Augmentin) +(4, 3, 1, '1 tablet every 8 hours — take with food', 10, 480.00), +-- Rx 5: Knee sprain (Voltaren + Aspégic) +(5, 4, 2, '1 tablet twice daily after meals — max 7 days', 14, 560.00), +(5, 2, 1, '1 sachet per day for inflammation control', 14, 120.00), +-- Rx 6: Osteoarthritis (Voltaren + Calcium D3 + Cortancyl) +(6, 4, 1, '1 tablet twice daily after meals', 30, 280.00), +(6, 10, 1, '1 sachet per day dissolved in water', 30, 380.00), +(6, 6, 1, '1 tablet each morning — do not stop abruptly', 15, 310.00), +-- Rx 7: Cardiac follow-up (Bisoprolol + Amlor + Aspégic) +(7, 8, 3, '1 tablet morning and evening', 90, 870.00), +(7, 1, 3, '1 tablet each morning', 90, 1050.00), +(7, 2, 3, '1 sachet per day', 90, 360.00); + +-- ============================================ +-- PART 4: 30 SQL QUERIES +-- ============================================ + +-- ========== PART 1: BASIC QUERIES (Q1-Q5) ========== + +-- Q1. List all patients with their main information +SELECT + file_number, + CONCAT(last_name, ' ', first_name) AS full_name, + date_of_birth, + phone, + city +FROM patients +ORDER BY last_name, first_name; + +-- Q2. Display all doctors with their specialty +SELECT + CONCAT(d.last_name, ' ', d.first_name) AS doctor_name, + s.specialty_name, + d.office, + d.active +FROM doctors d +JOIN specialties s ON d.specialty_id = s.specialty_id +ORDER BY s.specialty_name, d.last_name; + +-- Q3. Find all medications with price less than 500 DA +SELECT + medication_code, + commercial_name, + unit_price, + available_stock +FROM medications +WHERE unit_price < 500.00 +ORDER BY unit_price; + +-- Q4. List consultations from January 2025 +SELECT + c.consultation_date, + CONCAT(p.last_name, ' ', p.first_name) AS patient_name, + CONCAT(d.last_name, ' ', d.first_name) AS doctor_name, + c.status +FROM consultations c +JOIN patients p ON c.patient_id = p.patient_id +JOIN doctors d ON c.doctor_id = d.doctor_id +WHERE YEAR(c.consultation_date) = 2025 + AND MONTH(c.consultation_date) = 1 +ORDER BY c.consultation_date; + +-- Q5. Display medications where stock is below minimum stock +SELECT + commercial_name, + available_stock, + minimum_stock, + (available_stock - minimum_stock) AS difference +FROM medications +WHERE available_stock < minimum_stock +ORDER BY difference; + +-- ========== PART 2: QUERIES WITH JOINS (Q6-Q10) ========== + +-- Q6. Display all consultations with patient and doctor names +SELECT + c.consultation_date, + CONCAT(p.last_name, ' ', p.first_name) AS patient_name, + CONCAT(d.last_name, ' ', d.first_name) AS doctor_name, + c.diagnosis, + c.amount +FROM consultations c +JOIN patients p ON c.patient_id = p.patient_id +JOIN doctors d ON c.doctor_id = d.doctor_id +ORDER BY c.consultation_date DESC; + +-- Q7. List all prescriptions with medication details +SELECT + pr.prescription_date, + CONCAT(p.last_name, ' ', p.first_name) AS patient_name, + m.commercial_name AS medication_name, + pd.quantity, + pd.dosage_instructions +FROM prescription_details pd +JOIN prescriptions pr ON pd.prescription_id = pr.prescription_id +JOIN consultations c ON pr.consultation_id = c.consultation_id +JOIN patients p ON c.patient_id = p.patient_id +JOIN medications m ON pd.medication_id = m.medication_id +ORDER BY pr.prescription_date, p.last_name; + +-- Q8. Display patients with their last consultation date +SELECT + CONCAT(p.last_name, ' ', p.first_name) AS patient_name, + MAX(c.consultation_date) AS last_consultation_date, + CONCAT(d.last_name, ' ', d.first_name) AS doctor_name +FROM patients p +JOIN consultations c ON p.patient_id = c.patient_id +JOIN doctors d ON c.doctor_id = d.doctor_id +WHERE c.consultation_date = ( + SELECT MAX(c2.consultation_date) + FROM consultations c2 + WHERE c2.patient_id = p.patient_id +) +GROUP BY p.patient_id, p.last_name, p.first_name, d.last_name, d.first_name +ORDER BY last_consultation_date DESC; + +-- Q9. List doctors and the number of consultations performed +SELECT + CONCAT(d.last_name, ' ', d.first_name) AS doctor_name, + COUNT(c.consultation_id) AS consultation_count +FROM doctors d +LEFT JOIN consultations c ON d.doctor_id = c.doctor_id +GROUP BY d.doctor_id, d.last_name, d.first_name +ORDER BY consultation_count DESC; + +-- Q10. Display revenue by medical specialty +SELECT + s.specialty_name, + COALESCE(SUM(c.amount), 0) AS total_revenue, + COUNT(c.consultation_id) AS consultation_count +FROM specialties s +LEFT JOIN doctors d ON s.specialty_id = d.specialty_id +LEFT JOIN consultations c ON d.doctor_id = c.doctor_id +GROUP BY s.specialty_id, s.specialty_name +ORDER BY total_revenue DESC; + +-- ========== PART 3: AGGREGATE FUNCTIONS (Q11-Q15) ========== + +-- Q11. Calculate total prescription amount per patient +SELECT + CONCAT(p.last_name, ' ', p.first_name) AS patient_name, + ROUND(SUM(pd.total_price), 2) AS total_prescription_cost +FROM patients p +JOIN consultations c ON p.patient_id = c.patient_id +JOIN prescriptions pr ON c.consultation_id = pr.consultation_id +JOIN prescription_details pd ON pr.prescription_id = pd.prescription_id +GROUP BY p.patient_id, p.last_name, p.first_name +ORDER BY total_prescription_cost DESC; + +-- Q12. Count the number of consultations per doctor +SELECT + CONCAT(d.last_name, ' ', d.first_name) AS doctor_name, + COUNT(c.consultation_id) AS consultation_count +FROM doctors d +LEFT JOIN consultations c ON d.doctor_id = c.doctor_id +GROUP BY d.doctor_id, d.last_name, d.first_name +ORDER BY consultation_count DESC; + +-- Q13. Calculate total stock value of pharmacy +SELECT + COUNT(*) AS total_medications, + ROUND(SUM(unit_price * available_stock), 2) AS total_stock_value +FROM medications; + +-- Q14. Find average consultation price per specialty +SELECT + s.specialty_name, + ROUND(AVG(c.amount), 2) AS average_price +FROM specialties s +JOIN doctors d ON s.specialty_id = d.specialty_id +JOIN consultations c ON d.doctor_id = c.doctor_id +GROUP BY s.specialty_id, s.specialty_name +ORDER BY average_price DESC; + +-- Q15. Count number of patients by blood type +SELECT + blood_type, + COUNT(*) AS patient_count +FROM patients +WHERE blood_type IS NOT NULL +GROUP BY blood_type +ORDER BY patient_count DESC; + +-- ========== PART 4: ADVANCED QUERIES (Q16-Q20) ========== + +-- Q16. Find the top 5 most prescribed medications +SELECT + m.commercial_name AS medication_name, + COUNT(pd.detail_id) AS times_prescribed, + SUM(pd.quantity) AS total_quantity +FROM medications m +JOIN prescription_details pd ON m.medication_id = pd.medication_id +GROUP BY m.medication_id, m.commercial_name +ORDER BY times_prescribed DESC, total_quantity DESC +LIMIT 5; + +-- Q17. List patients who have never had a consultation +SELECT + CONCAT(p.last_name, ' ', p.first_name) AS patient_name, + p.registration_date +FROM patients p +LEFT JOIN consultations c ON p.patient_id = c.patient_id +WHERE c.consultation_id IS NULL +ORDER BY p.last_name; + +-- Q18. Display doctors who performed more than 2 consultations +SELECT + CONCAT(d.last_name, ' ', d.first_name) AS doctor_name, + s.specialty_name AS specialty, + COUNT(c.consultation_id) AS consultation_count +FROM doctors d +JOIN specialties s ON d.specialty_id = s.specialty_id +JOIN consultations c ON d.doctor_id = c.doctor_id +GROUP BY d.doctor_id, d.last_name, d.first_name, s.specialty_name +HAVING COUNT(c.consultation_id) > 2 +ORDER BY consultation_count DESC; + +-- Q19. Find unpaid consultations with total amount +SELECT + CONCAT(p.last_name, ' ', p.first_name) AS patient_name, + c.consultation_date, + c.amount, + CONCAT(d.last_name, ' ', d.first_name) AS doctor_name +FROM consultations c +JOIN patients p ON c.patient_id = p.patient_id +JOIN doctors d ON c.doctor_id = d.doctor_id +WHERE c.paid = FALSE +ORDER BY c.consultation_date; + +-- Q20. List medications expiring in less than 6 months from today +SELECT + commercial_name AS medication_name, + expiration_date, + DATEDIFF(expiration_date, CURRENT_DATE) AS days_until_expiration +FROM medications +WHERE expiration_date IS NOT NULL + AND expiration_date > CURRENT_DATE + AND expiration_date <= DATE_ADD(CURRENT_DATE, INTERVAL 6 MONTH) +ORDER BY expiration_date; + +-- ========== PART 5: SUBQUERIES (Q21-Q25) ========== + +-- Q21. Find patients who consulted more than the average number of consultations +SELECT + CONCAT(p.last_name, ' ', p.first_name) AS patient_name, + COUNT(c.consultation_id) AS consultation_count, + ROUND((SELECT AVG(cnt) FROM ( + SELECT COUNT(*) AS cnt FROM consultations GROUP BY patient_id + ) AS sub), 2) AS average_count +FROM patients p +JOIN consultations c ON p.patient_id = c.patient_id +GROUP BY p.patient_id, p.last_name, p.first_name +HAVING COUNT(c.consultation_id) > ( + SELECT AVG(cnt) + FROM (SELECT COUNT(*) AS cnt FROM consultations GROUP BY patient_id) AS sub +) +ORDER BY consultation_count DESC; + +-- Q22. List medications more expensive than the average price +SELECT + commercial_name AS medication_name, + unit_price, + ROUND((SELECT AVG(unit_price) FROM medications), 2) AS average_price +FROM medications +WHERE unit_price > (SELECT AVG(unit_price) FROM medications) +ORDER BY unit_price DESC; + +-- Q23. Display doctors from the most requested specialty +-- (specialty with the highest number of consultations) +SELECT + CONCAT(d.last_name, ' ', d.first_name) AS doctor_name, + s.specialty_name, + (SELECT COUNT(*) FROM consultations c2 + JOIN doctors d2 ON c2.doctor_id = d2.doctor_id + WHERE d2.specialty_id = s.specialty_id) AS specialty_consultation_count +FROM doctors d +JOIN specialties s ON d.specialty_id = s.specialty_id +WHERE s.specialty_id = ( + SELECT d3.specialty_id + FROM consultations c3 + JOIN doctors d3 ON c3.doctor_id = d3.doctor_id + GROUP BY d3.specialty_id + ORDER BY COUNT(*) DESC + LIMIT 1 +) +ORDER BY d.last_name; + +-- Q24. Find consultations with amount higher than the average +SELECT + c.consultation_date, + CONCAT(p.last_name, ' ', p.first_name) AS patient_name, + c.amount, + ROUND((SELECT AVG(amount) FROM consultations), 2) AS average_amount +FROM consultations c +JOIN patients p ON c.patient_id = p.patient_id +WHERE c.amount > (SELECT AVG(amount) FROM consultations) +ORDER BY c.amount DESC; + +-- Q25. List allergic patients who received at least one prescription +SELECT + CONCAT(p.last_name, ' ', p.first_name) AS patient_name, + p.allergies, + COUNT(DISTINCT pr.prescription_id) AS prescription_count +FROM patients p +JOIN consultations c ON p.patient_id = c.patient_id +JOIN prescriptions pr ON c.consultation_id = pr.consultation_id +WHERE p.allergies IS NOT NULL +GROUP BY p.patient_id, p.last_name, p.first_name, p.allergies +ORDER BY prescription_count DESC; + +-- ========== PART 6: BUSINESS ANALYSIS (Q26-Q30) ========== + +-- Q26. Calculate total revenue per doctor (paid consultations only) +SELECT + CONCAT(d.last_name, ' ', d.first_name) AS doctor_name, + COUNT(c.consultation_id) AS total_consultations, + ROUND(SUM(c.amount), 2) AS total_revenue +FROM doctors d +JOIN consultations c ON d.doctor_id = c.doctor_id +WHERE c.paid = TRUE +GROUP BY d.doctor_id, d.last_name, d.first_name +ORDER BY total_revenue DESC; + +-- Q27. Display top 3 most profitable specialties +SELECT + RANK() OVER (ORDER BY SUM(c.amount) DESC) AS `rank`, + s.specialty_name, + ROUND(SUM(c.amount), 2) AS total_revenue +FROM specialties s +JOIN doctors d ON s.specialty_id = d.specialty_id +JOIN consultations c ON d.doctor_id = c.doctor_id +GROUP BY s.specialty_id, s.specialty_name +ORDER BY total_revenue DESC +LIMIT 3; + +-- Q28. List medications to restock (stock < minimum) +SELECT + commercial_name AS medication_name, + available_stock AS current_stock, + minimum_stock, + (minimum_stock - available_stock) AS quantity_needed +FROM medications +WHERE available_stock < minimum_stock +ORDER BY quantity_needed DESC; + +-- Q29. Calculate average number of medications per prescription +SELECT + ROUND(AVG(meds_per_rx), 2) AS average_medications_per_prescription +FROM ( + SELECT prescription_id, COUNT(*) AS meds_per_rx + FROM prescription_details + GROUP BY prescription_id +) AS sub; + +-- Q30. Generate patient demographics report by age group (0-18, 19-40, 41-60, 60+) +SELECT + age_group, + COUNT(*) AS patient_count, + ROUND(100.0 * COUNT(*) / SUM(COUNT(*)) OVER (), 2) AS percentage +FROM ( + SELECT + CASE + WHEN TIMESTAMPDIFF(YEAR, date_of_birth, CURRENT_DATE) BETWEEN 0 AND 18 THEN '0-18' + WHEN TIMESTAMPDIFF(YEAR, date_of_birth, CURRENT_DATE) BETWEEN 19 AND 40 THEN '19-40' + WHEN TIMESTAMPDIFF(YEAR, date_of_birth, CURRENT_DATE) BETWEEN 41 AND 60 THEN '41-60' + ELSE '60+' + END AS age_group + FROM patients +) AS age_data +GROUP BY age_group +ORDER BY + CASE age_group + WHEN '0-18' THEN 1 + WHEN '19-40' THEN 2 + WHEN '41-60' THEN 3 + ELSE 4 + END; + +-- ============================================ +-- END OF tp2_solutions.sql +-- ============================================