Mini Challenge 11 — Capstone: Complete Business Intelligence Dashboard
title: “Mini Challenge 11 — Capstone Project” part: 3 topic_number: 0 slug: “mini-challenge-11” difficulty: “Advanced” prerequisites: “row-number, rank, transactions” —
Mini Challenge 11 — Capstone: Complete Business Intelligence Dashboard
Overview
Congratulations on reaching the final challenge! This capstone project combines ALL skills you’ve learned across Parts 1-3. You’ll build a comprehensive business intelligence system with window functions, ranking, and transaction safety.
Capstone Project: Executive Dashboard System
Build a complete analytics system with views, window functions, and safe data operations.
Challenge 1: Employee Performance Scorecard
Create a view employee_performance_scorecard that ranks employees using multiple metrics:
Metrics:
- salary_rank (DENSE_RANK by salary DESC within department)
- tenure_rank (ROW_NUMBER by hire_date ASC within department)
- project_load_rank (RANK by project count DESC within department)
- composite_score (average of the three ranks, lower is better)
- performance_tier: ‘Top Performer’ if composite_score <= 3, ‘High Performer’ if <= 5, ‘Good Performer’ if <= 7, ‘Standard’ otherwise
- compared_to_dept_avg: ‘Above’ if salary > dept avg, ‘Below’ otherwise
Show: employee_name, department_name, all ranks, composite_score, performance_tier, compared_to_dept_avg.
Sort by performance_tier, then composite_score.
Hint
Join employees, departments, count projects. Use multiple window functions (DENSE_RANK, ROW_NUMBER, RANK) with PARTITION BY department. Calculate averages and composite scores. Use CASE for tiering.Challenge 2: Payroll Adjustment Transaction
Create a safe transaction that performs a company-wide salary adjustment with these rules:
- Give 10% raise to employees with tenure > 5 years
- Give 7% raise to employees with tenure 3-5 years
- Give 5% raise to employees with tenure < 3 years
- ROLLBACK if total new payroll exceeds $2,000,000
- ROLLBACK if any individual salary exceeds $150,000
- Create a log table
salary_adjustmentsto track changes (employee_id, old_salary, new_salary, adjustment_date, adjustment_reason)
Use savepoints to allow partial rollback if needed. Provide clear success/failure messages.
Hint
START TRANSACTION, calculate tenure categories, UPDATE with CASE WHEN, check constraints, INSERT INTO log table, conditional COMMIT/ROLLBACK with savepoints.Challenge 3: Dynamic Top Performers by Multiple Criteria
Create a view top_performers_multi_criteria that shows:
- The top 3 employees per department by salary (include ties with DENSE_RANK)
- The top 2 most senior employees per department (by hire_date)
- The top 2 most productive employees per department (by project count)
Combine all three lists (UNION), showing:
- employee_name
- department_name
- criterion (‘Top Salary’, ‘Most Senior’, ‘Most Productive’)
- rank_within_criterion
- metric_value (salary, years, or project count depending on criterion)
Sort by department_name, criterion, rank_within_criterion.
Hint
Three separate CTEs with window functions (one per criterion), UNION ALL them, format output. Use DENSE_RANK to include ties.Challenge 4: Advanced Ranking with Percentiles
Create a comprehensive ranking report:
- employee_name, department_name, salary
- salary_rank_overall (RANK across company)
- salary_rank_dept (RANK within department)
- salary_percentile (PERCENT_RANK, show as percentage 0-100)
- salary_quintile (NTILE(5) - divide into 5 groups)
- department_quartile (NTILE(4) within each department)
- high_earner_flag: ‘Top 10%’ if percentile >= 90, ‘Top 25%’ if >= 75, ‘Middle 50%’ if >= 25, ‘Lower 25%’ otherwise
Sort by salary_percentile DESC.
Hint
Multiple window functions: RANK (global and per dept), PERCENT_RANK, NTILE(5), NTILE(4) with PARTITION BY. Use CASE for flags.Challenge 5: Real-World Simulation - Department Restructuring
Implement a complex transaction simulating a department merger:
Scenario: Merge “Legal” department into “Human Resources”
Requirements:
- Move all Legal employees to HR
- Update their manager_id if their current manager was also in Legal
- Increase their salaries by 5% (relocation bonus)
- Archive the Legal department (UPDATE status to ‘Inactive’ instead of DELETE)
- Create audit trail in
restructuring_logtable (department_id, action, timestamp, affected_employee_count) - ROLLBACK if:
- Any employee’s new salary > $100,000
- Total HR department size > 10 employees
- Any orphaned manager_id references
Use savepoints for each major step. Provide detailed success/failure report.
Hint
START TRANSACTION. Multiple savepoints (sp_move, sp_managers, sp_salaries, sp_archive). Check constraints after each step. Comprehensive validation query before COMMIT. INSERT audit records.Answer Key
Challenge 1 Answer
```sql CREATE OR REPLACE VIEW employee_performance_scorecard AS WITH employee_metrics AS ( SELECT e.employee_id, CONCAT(e.first_name, ' ', e.last_name) AS employee_name, d.department_name, e.salary, e.hire_date, TIMESTAMPDIFF(YEAR, e.hire_date, CURDATE()) AS tenure_years, COUNT(ep.project_id) AS project_count, DENSE_RANK() OVER (PARTITION BY e.department_id ORDER BY e.salary DESC) AS salary_rank, ROW_NUMBER() OVER (PARTITION BY e.department_id ORDER BY e.hire_date ASC) AS tenure_rank, RANK() OVER (PARTITION BY e.department_id ORDER BY COUNT(ep.project_id) DESC) AS project_load_rank, AVG(e.salary) OVER (PARTITION BY e.department_id) AS dept_avg_salary FROM employees e JOIN departments d ON e.department_id = d.department_id LEFT JOIN employee_projects ep ON e.employee_id = ep.employee_id GROUP BY e.employee_id, e.first_name, e.last_name, d.department_name, e.salary, e.hire_date, e.department_id ) SELECT employee_name, department_name, salary, salary_rank, tenure_rank, project_load_rank, ROUND((salary_rank + tenure_rank + project_load_rank) / 3.0, 2) AS composite_score, CASE WHEN (salary_rank + tenure_rank + project_load_rank) / 3.0 <= 3 THEN 'Top Performer' WHEN (salary_rank + tenure_rank + project_load_rank) / 3.0 <= 5 THEN 'High Performer' WHEN (salary_rank + tenure_rank + project_load_rank) / 3.0 <= 7 THEN 'Good Performer' ELSE 'Standard' END AS performance_tier, CASE WHEN salary > dept_avg_salary THEN 'Above' ELSE 'Below' END AS compared_to_dept_avg FROM employee_metrics ORDER BY CASE performance_tier WHEN 'Top Performer' THEN 1 WHEN 'High Performer' THEN 2 WHEN 'Good Performer' THEN 3 ELSE 4 END, composite_score; -- Use the view SELECT * FROM employee_performance_scorecard LIMIT 10; ``` Comprehensive performance metrics combining multiple factors.Challenge 2 Answer
```sql -- Create log table CREATE TABLE IF NOT EXISTS salary_adjustments ( adjustment_id INT AUTO_INCREMENT PRIMARY KEY, employee_id INT, old_salary DECIMAL(10,2), new_salary DECIMAL(10,2), adjustment_percentage DECIMAL(5,2), adjustment_date DATETIME DEFAULT CURRENT_TIMESTAMP, adjustment_reason VARCHAR(200) ); -- Transaction START TRANSACTION; -- Store old salaries for logging CREATE TEMPORARY TABLE salary_backup AS SELECT employee_id, salary, TIMESTAMPDIFF(YEAR, hire_date, CURDATE()) AS tenure_years FROM employees; SAVEPOINT sp_before_adjustments; -- Apply raises based on tenure UPDATE employees e JOIN salary_backup sb ON e.employee_id = sb.employee_id SET e.salary = CASE WHEN sb.tenure_years > 5 THEN e.salary * 1.10 WHEN sb.tenure_years >= 3 THEN e.salary * 1.07 ELSE e.salary * 1.05 END; -- Check constraint 1: Total payroll SELECT @total_payroll := SUM(salary) FROM employees; IF @total_payroll > 2000000 THEN ROLLBACK TO sp_before_adjustments; SELECT CONCAT('ROLLBACK: Total payroll (', @total_payroll, ') exceeds limit') AS message; ELSE -- Check constraint 2: Individual salaries SELECT @max_individual := MAX(salary) FROM employees; IF @max_individual > 150000 THEN ROLLBACK TO sp_before_adjustments; SELECT CONCAT('ROLLBACK: Individual salary (', @max_individual, ') exceeds limit') AS message; ELSE -- Log all changes INSERT INTO salary_adjustments (employee_id, old_salary, new_salary, adjustment_percentage, adjustment_reason) SELECT e.employee_id, sb.salary, e.salary, ROUND(((e.salary - sb.salary) / sb.salary) * 100, 2), CONCAT('Annual adjustment - ', CASE WHEN sb.tenure_years > 5 THEN '10% (5+ years)' WHEN sb.tenure_years >= 3 THEN '7% (3-5 years)' ELSE '5% (<3 years)' END ) FROM employees e JOIN salary_backup sb ON e.employee_id = sb.employee_id WHERE e.salary != sb.salary; COMMIT; SELECT CONCAT('SUCCESS: Adjusted salaries for ', ROW_COUNT(), ' employees. New total payroll: $', @total_payroll) AS message; END IF; END IF; -- Cleanup DROP TEMPORARY TABLE salary_backup; ``` Safe, audited salary adjustment transaction.Challenge 3 Answer
```sql CREATE OR REPLACE VIEW top_performers_multi_criteria AS WITH top_salaries AS ( SELECT CONCAT(e.first_name, ' ', e.last_name) AS employee_name, d.department_name, 'Top Salary' AS criterion, e.salary AS metric_value, DENSE_RANK() OVER (PARTITION BY e.department_id ORDER BY e.salary DESC) AS rank_within_criterion FROM employees e JOIN departments d ON e.department_id = d.department_id ), most_senior AS ( SELECT CONCAT(e.first_name, ' ', e.last_name) AS employee_name, d.department_name, 'Most Senior' AS criterion, TIMESTAMPDIFF(YEAR, e.hire_date, CURDATE()) AS metric_value, ROW_NUMBER() OVER (PARTITION BY e.department_id ORDER BY e.hire_date ASC) AS rank_within_criterion FROM employees e JOIN departments d ON e.department_id = d.department_id ), most_productive AS ( SELECT CONCAT(e.first_name, ' ', e.last_name) AS employee_name, d.department_name, 'Most Productive' AS criterion, COUNT(ep.project_id) AS metric_value, RANK() OVER (PARTITION BY e.department_id ORDER BY COUNT(ep.project_id) DESC) AS rank_within_criterion FROM employees e JOIN departments d ON e.department_id = d.department_id LEFT JOIN employee_projects ep ON e.employee_id = ep.employee_id GROUP BY e.employee_id, e.first_name, e.last_name, d.department_name, e.department_id ) SELECT * FROM top_salaries WHERE rank_within_criterion <= 3 UNION ALL SELECT * FROM most_senior WHERE rank_within_criterion <= 2 UNION ALL SELECT * FROM most_productive WHERE rank_within_criterion <= 2 ORDER BY department_name, criterion, rank_within_criterion; -- Use the view SELECT * FROM top_performers_multi_criteria; ``` Multi-dimensional top performers list.Challenge 4 Answer
```sql SELECT CONCAT(e.first_name, ' ', e.last_name) AS employee_name, d.department_name, e.salary, RANK() OVER (ORDER BY e.salary DESC) AS salary_rank_overall, RANK() OVER (PARTITION BY e.department_id ORDER BY e.salary DESC) AS salary_rank_dept, ROUND(PERCENT_RANK() OVER (ORDER BY e.salary DESC) * 100, 1) AS salary_percentile, NTILE(5) OVER (ORDER BY e.salary DESC) AS salary_quintile, NTILE(4) OVER (PARTITION BY e.department_id ORDER BY e.salary DESC) AS department_quartile, CASE WHEN PERCENT_RANK() OVER (ORDER BY e.salary DESC) >= 0.9 THEN 'Top 10%' WHEN PERCENT_RANK() OVER (ORDER BY e.salary DESC) >= 0.75 THEN 'Top 25%' WHEN PERCENT_RANK() OVER (ORDER BY e.salary DESC) >= 0.25 THEN 'Middle 50%' ELSE 'Lower 25%' END AS high_earner_flag FROM employees e JOIN departments d ON e.department_id = d.department_id ORDER BY salary_percentile DESC; ``` **Expected output:** | employee_name | department_name | salary | salary_rank_overall | salary_rank_dept | salary_percentile | salary_quintile | department_quartile | high_earner_flag | |---------------|-----------------|---------|---------------------|------------------|-------------------|-----------------|---------------------|------------------| | Sam Clark | Engineering | 95000.00 | 1 | 1 | 0.0 | 1 | 1 | Top 10% | | Frank Miller | Marketing | 93000.00 | 2 | 1 | 5.3 | 1 | 1 | Top 10% | | Henry Moore | Finance | 92000.00 | 3 | 1 | 10.5 | 1 | 1 | Top 25% | | ... | ... | ... | ... | ... | ... | ... | ... | ... | Advanced multi-dimensional ranking system.Challenge 5 Answer
```sql -- Create audit table CREATE TABLE IF NOT EXISTS restructuring_log ( log_id INT AUTO_INCREMENT PRIMARY KEY, department_id INT, action VARCHAR(100), timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, affected_employee_count INT, notes TEXT ); -- Add status column to departments if doesn't exist ALTER TABLE departments ADD COLUMN IF NOT EXISTS status VARCHAR(20) DEFAULT 'Active'; -- Transaction START TRANSACTION; -- Get Legal and HR department IDs SELECT @legal_dept_id := department_id FROM departments WHERE department_name = 'Legal'; SELECT @hr_dept_id := department_id FROM departments WHERE department_name = 'Human Resources'; -- Count affected employees SELECT @legal_emp_count := COUNT(*) FROM employees WHERE department_id = @legal_dept_id; SAVEPOINT sp_move_employees; -- Step 1: Move employees to HR UPDATE employees SET department_id = @hr_dept_id WHERE department_id = @legal_dept_id; SAVEPOINT sp_fix_managers; -- Step 2: Fix manager_id for moved employees whose managers were also in Legal UPDATE employees e1 SET manager_id = NULL -- Or reassign to HR manager WHERE department_id = @hr_dept_id AND manager_id IN ( SELECT employee_id FROM (SELECT employee_id FROM employees WHERE department_id = @hr_dept_id) AS moved_emps ); SAVEPOINT sp_salary_adjustment; -- Step 3: Give 5% relocation bonus UPDATE employees SET salary = salary * 1.05 WHERE department_id = @hr_dept_id; -- Constraint checks SELECT @max_salary := MAX(salary) FROM employees WHERE department_id = @hr_dept_id; SELECT @hr_size := COUNT(*) FROM employees WHERE department_id = @hr_dept_id; IF @max_salary > 100000 THEN ROLLBACK TO sp_salary_adjustment; SELECT CONCAT('ROLLBACK: Salary limit exceeded (', @max_salary, ')') AS message; ROLLBACK; ELSEIF @hr_size > 10 THEN ROLLBACK TO sp_move_employees; SELECT CONCAT('ROLLBACK: HR department too large (', @hr_size, ' employees)') AS message; ROLLBACK; ELSE SAVEPOINT sp_archive_dept; -- Step 4: Archive Legal department UPDATE departments SET status = 'Inactive' WHERE department_id = @legal_dept_id; -- Step 5: Log the restructuring INSERT INTO restructuring_log (department_id, action, affected_employee_count, notes) VALUES (@legal_dept_id, 'Department Merged', @legal_emp_count, 'Legal merged into HR'), (@hr_dept_id, 'Received Employees', @legal_emp_count, CONCAT('Received ', @legal_emp_count, ' employees from Legal')); COMMIT; SELECT CONCAT('SUCCESS: Merged Legal into HR. Moved ', @legal_emp_count, ' employees. New HR size: ', @hr_size) AS message; -- Show final state SELECT d.department_name, d.status, COUNT(e.employee_id) AS employee_count, AVG(e.salary) AS avg_salary FROM departments d LEFT JOIN employees e ON d.department_id = e.department_id WHERE d.department_id IN (@legal_dept_id, @hr_dept_id) GROUP BY d.department_id, d.department_name, d.status; END IF; ``` Complex department restructuring with full audit trail and safety checks.COURSE COMPLETE!
Congratulations! You’ve mastered MySQL 8+ from beginner to advanced:
Part 1 — Foundations
INSERT, UPDATE, DELETE, table management, SELECT with filtering, JOINs, aggregates, NULL handling, string functions
Part 2 — Intermediate
SUBSTRING, CASE WHEN, self joins, multiple joins, COALESCE, nested/correlated subqueries
Part 3 — Advanced
Recursive CTEs, date/time functions, views, ROW_NUMBER/RANK/DENSE_RANK, transactions
What’s Next?
You’re now equipped to:
- Build complex database applications
- Perform sophisticated data analysis
- Design efficient queries for real-world scenarios
- Ensure data integrity with transactions
- Create maintainable, professional SQL code
Keep learning:
- Practice with real datasets
- Optimize query performance (indexes, execution plans)
- Explore MySQL 8+ features (JSON, full-text search, geospatial)
- Build complete applications integrating SQL with your favorite programming language
**Thank you for completing this journey! Keep coding! **