Comprehensive SQL Database Training Guide: HR Management System with Attendance and Payroll
Database Overview
I'll create a detailed HR database system for attendance and payroll management with 12 tables, sample data, and comprehensive examples for SQL practice.
-- Database Creation
CREATE DATABASE HR_Management;
USE HR_Management;
Table Structure and Sample Data
1. Departments Table
CREATE TABLE Departments (
DepartmentID INT IDENTITY(1,1) PRIMARY KEY,
DepartmentName VARCHAR(100) NOT NULL UNIQUE,
DepartmentHeadID INT NULL,
Location VARCHAR(100) NULL,
Budget DECIMAL(15,2) NULL,
EstablishedDate DATE NULL,
Description VARCHAR(MAX) NULL, -- TEXT is deprecated in SQL Server
IsActive BIT DEFAULT 1, -- BOOLEAN replaced with BIT
CreatedAt DATETIME DEFAULT GETDATE(),
UpdatedAt DATETIME DEFAULT GETDATE()
);
INSERT INTO Departments (DepartmentName, DepartmentHeadID, Location, Budget, EstablishedDate, Description) VALUES
('Human Resources', 101, 'Floor 3, Building A', 500000.00, '2020-01-15', 'Handles recruitment, employee relations, and benefits administration'),
('Information Technology', 102, 'Floor 5, Building B', 1200000.00, '2020-02-10', 'Manages technology infrastructure and software development'),
('Finance', 103, 'Floor 2, Building A', 800000.00, '2020-01-20', 'Handles accounting, budgeting, and financial reporting'),
('Marketing', 104, 'Floor 4, Building C', 750000.00, '2020-03-05', 'Responsible for brand management and marketing campaigns'),
('Operations', 105, 'Floor 1, Building B', 950000.00, '2020-02-15', 'Manages daily business activities and processes');
2. Employees Table
CREATE TABLE Employees (
EmployeeID INT IDENTITY(1,1) PRIMARY KEY,
FirstName VARCHAR(50) NOT NULL,
LastName VARCHAR(50) NOT NULL,
Email VARCHAR(100) NOT NULL UNIQUE,
Phone VARCHAR(20) NULL,
DateOfBirth DATE NULL,
HireDate DATE NOT NULL,
JobTitle VARCHAR(100) NULL,
DepartmentID INT NULL,
ManagerID INT NULL,
Salary DECIMAL(10,2) NULL,
Status VARCHAR(20) DEFAULT 'Active',
BankAccountNumber VARCHAR(50) NULL,
NationalID VARCHAR(50) UNIQUE,
CreatedAt DATETIME DEFAULT GETDATE(),
UpdatedAt DATETIME DEFAULT GETDATE(),
CONSTRAINT FK_Employees_Departments FOREIGN KEY (DepartmentID)
REFERENCES Departments(DepartmentID)
ON DELETE SET NULL
ON UPDATE CASCADE,
CONSTRAINT FK_Employees_Manager FOREIGN KEY (ManagerID)
REFERENCES Employees(EmployeeID)
ON DELETE SET NULL
ON UPDATE CASCADE
);
INSERT INTO Employees (FirstName, LastName, Email, Phone, DateOfBirth, HireDate, JobTitle, DepartmentID, ManagerID, Salary, BankAccountNumber, NationalID) VALUES
('John', 'Smith', 'john.smith@company.com', '555-0101', '1985-03-15', '2020-05-10', 'HR Manager', 1, NULL, 85000.00, 'ACC001234567', 'NID001'),
('Sarah', 'Johnson', 'sarah.johnson@company.com', '555-0102', '1990-07-22', '2020-06-15', 'IT Manager', 2, NULL, 95000.00, 'ACC001234568', 'NID002'),
('Michael', 'Brown', 'michael.brown@company.com', '555-0103', '1982-11-05', '2020-05-20', 'Finance Manager', 3, NULL, 90000.00, 'ACC001234569', 'NID003'),
('Emily', 'Davis', 'emily.davis@company.com', '555-0104', '1988-09-12', '2020-07-01', 'Marketing Manager', 4, NULL, 88000.00, 'ACC001234570', 'NID004'),
('Robert', 'Wilson', 'robert.wilson@company.com', '555-0105', '1979-12-30', '2020-06-01', 'Operations Manager', 5, NULL, 92000.00, 'ACC001234571', 'NID005'),
('Jennifer', 'Miller', 'jennifer.miller@company.com', '555-0106', '1992-04-18', '2020-08-15', 'HR Specialist', 1, 1, 60000.00, 'ACC001234572', 'NID006'),
('David', 'Taylor', 'david.taylor@company.com', '555-0107', '1991-06-25', '2020-09-10', 'Software Developer', 2, 2, 75000.00, 'ACC001234573', 'NID007');
-- Additional 15 employees would be added here
3. AttendanceRecords Table
CREATE TABLE AttendanceRecords (
AttendanceID INT IDENTITY(1,1) PRIMARY KEY, -- AUTO_INCREMENT equivalent
EmployeeID INT NOT NULL,
[Date] DATE NOT NULL, -- reserved keyword, so use []
ClockIn TIME NULL,
ClockOut TIME NULL,
HoursWorked DECIMAL(4,2) NULL,
Status VARCHAR(20) DEFAULT 'Present', -- ENUM replaced with VARCHAR
Notes VARCHAR(MAX) NULL, -- TEXT replaced with VARCHAR(MAX)
CreatedAt DATETIME DEFAULT GETDATE(),
UpdatedAt DATETIME DEFAULT GETDATE(),
CONSTRAINT FK_Attendance_Employees FOREIGN KEY (EmployeeID)
REFERENCES Employees(EmployeeID)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT UQ_Attendance_EmployeeDate UNIQUE (EmployeeID, [Date])
);
INSERT INTO AttendanceRecords (EmployeeID, Date, ClockIn, ClockOut, HoursWorked, Status, Notes) VALUES
(1, '2023-10-01', '08:58:00', '17:02:00', 8.07, 'Present', NULL),
(1, '2023-10-02', '09:05:00', '17:10:00', 8.08, 'Late', 'Traffic delay'),
(2, '2023-10-01', '08:45:00', '17:15:00', 8.50, 'Present', NULL),
(2, '2023-10-02', '08:50:00', '17:05:00', 8.25, 'Present', NULL),
(3, '2023-10-01', '09:15:00', '16:45:00', 7.50, 'Late', 'Doctor appointment'),
(3, '2023-10-02', '08:55:00', '17:10:00', 8.25, 'Present', NULL);
-- Additional records for all employees for multiple dates would be added
4. LeaveTypes Table
CREATE TABLE LeaveTypes (
LeaveTypeID INT IDENTITY(1,1) PRIMARY KEY, -- AUTO_INCREMENT equivalent
LeaveName VARCHAR(50) NOT NULL UNIQUE,
Description VARCHAR(MAX) NULL, -- TEXT replaced with VARCHAR(MAX)
MaxDays INT NOT NULL,
IsPaid BIT DEFAULT 1, -- BOOLEAN replaced with BIT (1 = TRUE, 0 = FALSE)
RequiresApproval BIT DEFAULT 1,
CreatedAt DATETIME DEFAULT GETDATE()
);
INSERT INTO LeaveTypes (LeaveName, Description, MaxDays, IsPaid, RequiresApproval) VALUES
('Annual Leave', 'Paid time off work', 20, TRUE, TRUE),
('Sick Leave', 'Leave for health reasons', 10, TRUE, TRUE),
('Maternity Leave', 'Leave for childbirth', 90, TRUE, TRUE),
('Paternity Leave', 'Leave for new fathers', 10, TRUE, TRUE),
('Bereavement Leave', 'Leave for family loss', 5, TRUE, TRUE),
('Unpaid Leave', 'Leave without pay', 30, FALSE, TRUE);
5. LeaveApplications Table
CREATE TABLE LeaveApplications (
LeaveID INT IDENTITY(1,1) PRIMARY KEY, -- AUTO_INCREMENT equivalent
EmployeeID INT NOT NULL,
LeaveTypeID INT NOT NULL,
StartDate DATE NOT NULL,
EndDate DATE NOT NULL,
DaysRequested INT NULL,
Reason VARCHAR(MAX) NULL, -- TEXT replaced with VARCHAR(MAX)
Status VARCHAR(20) DEFAULT 'Pending', -- ENUM replaced with VARCHAR
ApprovedBy INT NULL,
ApprovedDate DATE NULL,
CreatedAt DATETIME DEFAULT GETDATE(),
UpdatedAt DATETIME DEFAULT GETDATE(),
CONSTRAINT FK_LeaveApplications_Employee FOREIGN KEY (EmployeeID)
REFERENCES Employees(EmployeeID)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT FK_LeaveApplications_LeaveType FOREIGN KEY (LeaveTypeID)
REFERENCES LeaveTypes(LeaveTypeID)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT FK_LeaveApplications_ApprovedBy FOREIGN KEY (ApprovedBy)
REFERENCES Employees(EmployeeID)
ON DELETE SET NULL
ON UPDATE CASCADE
);
INSERT INTO LeaveApplications (EmployeeID, LeaveTypeID, StartDate, EndDate, DaysRequested, Reason, Status, ApprovedBy, ApprovedDate) VALUES
(1, 1, '2023-10-10', '2023-10-12', 3, 'Family vacation', 'Approved', 1, '2023-10-05'),
(2, 2, '2023-10-05', '2023-10-06', 2, 'Flu', 'Approved', 1, '2023-10-04'),
(3, 1, '2023-10-15', '2023-10-20', 5, 'Personal time off', 'Pending', NULL, NULL),
(4, 3, '2023-11-01', '2024-01-29', 90, 'Childbirth', 'Approved', 1, '2023-09-20');
6. PayrollPeriods Table
CREATE TABLE PayrollPeriods (
PeriodID INT IDENTITY(1,1) PRIMARY KEY, -- AUTO_INCREMENT equivalent
PeriodName VARCHAR(50) NOT NULL,
StartDate DATE NOT NULL,
EndDate DATE NOT NULL,
PayDate DATE NOT NULL,
Status VARCHAR(20) DEFAULT 'Open', -- ENUM replaced with VARCHAR
CreatedAt DATETIME DEFAULT GETDATE(),
UpdatedAt DATETIME DEFAULT GETDATE()
);
INSERT INTO PayrollPeriods (PeriodName, StartDate, EndDate, PayDate, Status) VALUES
('October 2023', '2023-10-01', '2023-10-31', '2023-11-05', 'Processed'),
('November 2023', '2023-11-01', '2023-11-30', '2023-12-05', 'Open'),
('December 2023', '2023-12-01', '2023-12-31', '2024-01-05', 'Open');
7. SalaryComponents Table
CREATE TABLE SalaryComponents (
ComponentID INT IDENTITY(1,1) PRIMARY KEY, -- AUTO_INCREMENT equivalent
ComponentName VARCHAR(100) NOT NULL UNIQUE,
ComponentType VARCHAR(20) NOT NULL, -- ENUM replaced with VARCHAR
Description VARCHAR(MAX) NULL, -- TEXT replaced with VARCHAR(MAX)
IsActive BIT DEFAULT 1, -- BOOLEAN replaced with BIT
IsTaxable BIT DEFAULT 1,
CreatedAt DATETIME DEFAULT GETDATE(),
CONSTRAINT CK_SalaryComponents_ComponentType CHECK (ComponentType IN ('Earning', 'Deduction'))
);
INSERT INTO SalaryComponents (ComponentName, ComponentType, Description, IsTaxable) VALUES
('Basic Salary', 'Earning', 'Base monthly salary', TRUE),
('House Rent Allowance', 'Earning', 'Allowance for housing', TRUE),
('Transport Allowance', 'Earning', 'Allowance for transportation', TRUE),
('Overtime Pay', 'Earning', 'Payment for extra hours worked', TRUE),
('Bonus', 'Earning', 'Performance bonus', TRUE),
('Provident Fund', 'Deduction', 'Retirement savings deduction', FALSE),
('Income Tax', 'Deduction', 'Tax deduction', TRUE),
('Health Insurance', 'Deduction', 'Medical insurance premium', FALSE);
8. EmployeeSalary Table
CREATE TABLE EmployeeSalary (
SalaryID INT IDENTITY(1,1) PRIMARY KEY, -- AUTO_INCREMENT equivalent
EmployeeID INT NOT NULL,
ComponentID INT NOT NULL,
Amount DECIMAL(10,2) NOT NULL,
EffectiveDate DATE NOT NULL,
EndDate DATE NULL,
CreatedAt DATETIME DEFAULT GETDATE(),
UpdatedAt DATETIME DEFAULT GETDATE(),
CONSTRAINT FK_EmployeeSalary_Employee FOREIGN KEY (EmployeeID)
REFERENCES Employees(EmployeeID)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT FK_EmployeeSalary_Component FOREIGN KEY (ComponentID)
REFERENCES SalaryComponents(ComponentID)
ON DELETE CASCADE
ON UPDATE CASCADE
);
INSERT INTO EmployeeSalary (EmployeeID, ComponentID, Amount, EffectiveDate) VALUES
(1, 1, 60000.00, '2023-01-01'),
(1, 2, 15000.00, '2023-01-01'),
(1, 3, 10000.00, '2023-01-01'),
(1, 6, 6000.00, '2023-01-01'),
(1, 7, 12000.00, '2023-01-01'),
(2, 1, 65000.00, '2023-01-01'),
(2, 2, 16000.00, '2023-01-01'),
(2, 3, 10000.00, '2023-01-01'),
(2, 6, 6500.00, '2023-01-01'),
(2, 7, 13000.00, '2023-01-01');
9. PayrollTransactions Table
CREATE TABLE PayrollTransactions (
TransactionID BIGINT IDENTITY(1,1) PRIMARY KEY, -- AUTO_INCREMENT equivalent
EmployeeID INT NOT NULL,
PeriodID INT NOT NULL,
ComponentID INT NOT NULL,
Amount DECIMAL(10,2) NOT NULL,
Description VARCHAR(MAX) NULL, -- TEXT replaced with VARCHAR(MAX)
CreatedAt DATETIME DEFAULT GETDATE(),
CONSTRAINT FK_PayrollTransactions_Employee FOREIGN KEY (EmployeeID)
REFERENCES Employees(EmployeeID)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT FK_PayrollTransactions_Period FOREIGN KEY (PeriodID)
REFERENCES PayrollPeriods(PeriodID)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT FK_PayrollTransactions_Component FOREIGN KEY (ComponentID)
REFERENCES SalaryComponents(ComponentID)
ON DELETE CASCADE
ON UPDATE CASCADE
);
INSERT INTO PayrollTransactions (EmployeeID, PeriodID, ComponentID, Amount, Description) VALUES
(1, 1, 1, 60000.00, 'Basic Salary for October 2023'),
(1, 1, 2, 15000.00, 'HRA for October 2023'),
(1, 1, 3, 10000.00, 'Transport Allowance for October 2023'),
(1, 1, 4, 5000.00, 'Overtime for 10 hours'),
(1, 1, 6, 6000.00, 'PF deduction'),
(1, 1, 7, 12000.00, 'Income Tax deduction'),
(2, 1, 1, 65000.00, 'Basic Salary for October 2023'),
(2, 1, 2, 16000.00, 'HRA for October 2023'),
(2, 1, 3, 10000.00, 'Transport Allowance for October 2023'),
(2, 1, 6, 6500.00, 'PF deduction'),
(2, 1, 7, 13000.00, 'Income Tax deduction');
10. OvertimeRequests Table
CREATE TABLE OvertimeRequests (
OvertimeID INT IDENTITY(1,1) PRIMARY KEY, -- AUTO_INCREMENT equivalent
EmployeeID INT NOT NULL,
RequestDate DATE NOT NULL,
Hours DECIMAL(4,2) NOT NULL,
Reason VARCHAR(MAX) NULL, -- TEXT replaced with VARCHAR(MAX)
Status VARCHAR(20) DEFAULT 'Pending', -- ENUM replaced with VARCHAR
ApprovedBy INT NULL,
ApprovedDate DATE NULL,
CreatedAt DATETIME DEFAULT GETDATE(),
UpdatedAt DATETIME DEFAULT GETDATE(),
CONSTRAINT FK_OvertimeRequests_Employee FOREIGN KEY (EmployeeID)
REFERENCES Employees(EmployeeID)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT FK_OvertimeRequests_ApprovedBy FOREIGN KEY (ApprovedBy)
REFERENCES Employees(EmployeeID)
ON DELETE SET NULL
ON UPDATE CASCADE
);
INSERT INTO OvertimeRequests (EmployeeID, RequestDate, Hours, Reason, Status, ApprovedBy, ApprovedDate) VALUES
(1, '2023-10-15', 3.5, 'Project deadline', 'Approved', 1, '2023-10-14'),
(2, '2023-10-20', 4.0, 'System migration', 'Approved', 2, '2023-10-19'),
(3, '2023-10-25', 5.0, 'Month-end closing', 'Pending', NULL, NULL);
11. TaxConfigurations Table
CREATE TABLE TaxConfigurations (
TaxConfigID INT IDENTITY(1,1) PRIMARY KEY, -- AUTO_INCREMENT equivalent
TaxYear INT NOT NULL, -- YEAR type replaced with INT
LowerLimit DECIMAL(10,2) NOT NULL,
UpperLimit DECIMAL(10,2) NULL,
TaxRate DECIMAL(5,2) NOT NULL,
DeductionAmount DECIMAL(10,2) NOT NULL DEFAULT 0,
Description VARCHAR(MAX) NULL, -- TEXT replaced with VARCHAR(MAX)
CreatedAt DATETIME DEFAULT GETDATE()
);
INSERT INTO TaxConfigurations (TaxYear, LowerLimit, UpperLimit, TaxRate, DeductionAmount, Description) VALUES
(2023, 0, 250000, 0, 0, 'No tax for income up to 2.5L'),
(2023, 250000, 500000, 5, 0, '5% tax for income between 2.5L-5L'),
(2023, 500000, 750000, 10, 12500, '10% tax for income between 5L-7.5L'),
(2023, 750000, 1000000, 15, 37500, '15% tax for income between 7.5L-10L'),
(2023, 1000000, 1250000, 20, 75000, '20% tax for income between 10L-12.5L'),
(2023, 1250000, 1500000, 25, 125000, '25% tax for income between 12.5L-15L'),
(2023, 1500000, NULL, 30, 187500, '30% tax for income above 15L');
12. EmployeeDocuments Table
CREATE TABLE EmployeeDocuments (
DocumentID INT IDENTITY(1,1) PRIMARY KEY, -- AUTO_INCREMENT equivalent
EmployeeID INT NOT NULL,
DocumentType VARCHAR(50) NOT NULL, -- ENUM replaced with VARCHAR
DocumentName VARCHAR(255) NOT NULL,
FilePath VARCHAR(500) NOT NULL,
IssueDate DATE NULL,
ExpiryDate DATE NULL,
Description VARCHAR(MAX) NULL, -- TEXT replaced with VARCHAR(MAX)
UploadedAt DATETIME DEFAULT GETDATE(),
CONSTRAINT FK_EmployeeDocuments_Employee FOREIGN KEY (EmployeeID)
REFERENCES Employees(EmployeeID)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT CK_EmployeeDocuments_DocumentType CHECK (DocumentType IN ('Contract', 'ID Proof', 'Degree', 'Certificate', 'Other'))
);
INSERT INTO EmployeeDocuments (EmployeeID, DocumentType, DocumentName, FilePath, IssueDate, ExpiryDate, Description) VALUES
(1, 'Contract', 'Employment Contract', '/documents/1/contract.pdf', '2020-05-10', '2023-05-10', 'Initial employment contract'),
(1, 'ID Proof', 'Passport', '/documents/1/passport.pdf', '2018-03-15', '2028-03-15', 'Passport copy'),
(2, 'Contract', 'Employment Contract', '/documents/2/contract.pdf', '2020-06-15', '2023-06-15', 'Initial employment contract'),
(2, 'Degree', 'Master Degree', '/documents/2/degree.pdf', '2015-05-20', NULL, 'Masters in Computer Science');
SQL Practice Exercises
Table Operations
-- 1. Create a new table for training records
CREATE TABLE TrainingRecords (
TrainingID INT IDENTITY(1,1) PRIMARY KEY, -- AUTO_INCREMENT equivalent
EmployeeID INT NOT NULL,
TrainingName VARCHAR(255) NOT NULL,
TrainingDate DATE NOT NULL,
DurationHours DECIMAL(4,2) NULL,
Provider VARCHAR(100) NULL,
Cost DECIMAL(10,2) NULL,
Status VARCHAR(20) DEFAULT 'Planned', -- ENUM replaced with VARCHAR
CreatedAt DATETIME DEFAULT GETDATE(),
CONSTRAINT FK_TrainingRecords_Employee FOREIGN KEY (EmployeeID)
REFERENCES Employees(EmployeeID)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT CK_TrainingRecords_Status CHECK (Status IN ('Planned', 'Completed', 'Cancelled'))
);
-- 2. Alter table to add a new column
ALTER TABLE TrainingRecords
ADD CertificateIssued BIT DEFAULT 0; -- BOOLEAN replaced with BIT
-- 3. Drop a column
ALTER TABLE TrainingRecords
DROP COLUMN CertificateIssued;
-- 4. Rename a column
EXEC sp_rename 'TrainingRecords.DurationHours', 'Duration', 'COLUMN';
-- 5. Add a foreign key constraint (already added during create, but syntax for adding separately)
ALTER TABLE TrainingRecords
ADD CONSTRAINT FK_TrainingRecords_Employee2
FOREIGN KEY (EmployeeID) REFERENCES Employees(EmployeeID)
ON DELETE CASCADE
ON UPDATE CASCADE;
-- 6. Create an index on frequently queried columns
CREATE INDEX idx_employee_department ON Employees(DepartmentID);
CREATE INDEX idx_attendance_date ON AttendanceRecords([Date]); -- Date is a reserved word
CREATE INDEX idx_payroll_period ON PayrollTransactions(PeriodID);
-- 7. Drop a table if exists
IF OBJECT_ID('TrainingRecords', 'U') IS NOT NULL
DROP TABLE TrainingRecords;
Data Manipulation Language (DML)
-- 1. Insert new employee
INSERT INTO Employees (FirstName, LastName, Email, Phone, DateOfBirth, HireDate, JobTitle, DepartmentID, Salary, BankAccountNumber, NationalID)
VALUES ('James', 'Anderson', 'james.anderson@company.com', '555-0123', '1993-08-14', '2023-10-01', 'Marketing Specialist', 4, 55000.00, 'ACC001234599', 'NID023');
-- 2. Update employee salary
UPDATE Employees
SET Salary = Salary * 1.05
WHERE DepartmentID = 2 AND Status = 'Active';
-- 3. Delete an employee (set to Terminated instead of actual delete)
UPDATE Employees
SET Status = 'Terminated', UpdatedAt = GETDATE()
WHERE EmployeeID = 10;
-- 4. Insert multiple attendance records
INSERT INTO AttendanceRecords (EmployeeID, [Date], ClockIn, ClockOut, HoursWorked, Status) VALUES
(5, '2023-10-03', '08:55:00', '17:05:00', 8.17, 'Present'),
(6, '2023-10-03', '09:00:00', '17:00:00', 8.00, 'Present'),
(7, '2023-10-03', '08:50:00', '17:10:00', 8.33, 'Present');
-- 5. Update leave application status
UPDATE LeaveApplications
SET Status = 'Approved', ApprovedBy = 1, ApprovedDate = CAST(GETDATE() AS DATE)
WHERE LeaveID = 3;
-- 6. Delete specific attendance record
DELETE FROM AttendanceRecords
WHERE EmployeeID = 5 AND [Date] = '2023-10-03';
-- 7. Truncate table (removes all data)
TRUNCATE TABLE OvertimeRequests;
Data Query Language (DQL) - SELECT Queries
-- 1. Basic select with filtering
SELECT *
FROM Employees
WHERE DepartmentID = 1 AND Status = 'Active';
-- 2. Select specific columns with aliases
SELECT
EmployeeID AS ID,
FirstName + ' ' + LastName AS FullName, -- CONCAT in SQL Server
Email,
Salary
FROM Employees
WHERE Salary > 70000;
-- 3. Using aggregate functions
SELECT
DepartmentID,
COUNT(*) AS EmployeeCount,
AVG(Salary) AS AverageSalary,
MAX(Salary) AS MaxSalary,
MIN(Salary) AS MinSalary
FROM Employees
WHERE Status = 'Active'
GROUP BY DepartmentID;
-- 4. Date functions
SELECT
EmployeeID,
[Date],
ClockIn,
ClockOut,
DATEDIFF(SECOND, ClockIn, ClockOut)/3600.0 AS TimeWorked, -- TIMEDIFF equivalent in hours
DATENAME(WEEKDAY, [Date]) AS DayOfWeek -- DAYNAME equivalent
FROM AttendanceRecords
WHERE [Date] BETWEEN '2023-10-01' AND '2023-10-07';
-- 5. Case statements
SELECT
EmployeeID,
[Date],
Status,
CASE
WHEN Status = 'Present' THEN 'Worked'
WHEN Status = 'Absent' THEN 'Missing'
WHEN Status = 'Late' THEN 'Delayed'
ELSE 'Other'
END AS StatusDescription
FROM AttendanceRecords;
-- 6. Subqueries
SELECT
FirstName + ' ' + LastName AS EmployeeName,
Salary
FROM Employees
WHERE Salary > (SELECT AVG(Salary) FROM Employees);
-- 7. Window functions
SELECT
DepartmentID,
FirstName + ' ' + LastName AS EmployeeName,
Salary,
RANK() OVER (PARTITION BY DepartmentID ORDER BY Salary DESC) AS SalaryRank
FROM Employees
WHERE Status = 'Active';
JOIN Operations with Business Cases
-- 1. INNER JOIN: Get employees with their department details
SELECT
e.EmployeeID,
e.FirstName + ' ' + e.LastName AS EmployeeName, -- CONCAT replaced
e.JobTitle,
d.DepartmentName,
d.Location
FROM Employees e
INNER JOIN Departments d ON e.DepartmentID = d.DepartmentID
WHERE e.Status = 'Active';
-- 2. LEFT JOIN: Get all employees and their attendance for a specific date
SELECT
e.EmployeeID,
e.FirstName + ' ' + e.LastName AS EmployeeName,
a.[Date],
a.ClockIn,
a.ClockOut,
a.Status
FROM Employees e
LEFT JOIN AttendanceRecords a ON e.EmployeeID = a.EmployeeID AND a.[Date] = '2023-10-01'
WHERE e.Status = 'Active';
-- 3. RIGHT JOIN: Get all departments and their employees (even if no employees)
SELECT
d.DepartmentName,
d.Location,
COUNT(e.EmployeeID) AS EmployeeCount
FROM Employees e
RIGHT JOIN Departments d ON e.DepartmentID = d.DepartmentID AND e.Status = 'Active'
GROUP BY d.DepartmentID, d.DepartmentName, d.Location;
-- 4. FULL OUTER JOIN simulation (using UNION of LEFT and RIGHT joins)
SELECT
e.EmployeeID,
e.FirstName + ' ' + e.LastName AS EmployeeName,
d.DepartmentName
FROM Employees e
LEFT JOIN Departments d ON e.DepartmentID = d.DepartmentID
UNION
SELECT
e.EmployeeID,
e.FirstName + ' ' + e.LastName AS EmployeeName,
d.DepartmentName
FROM Employees e
RIGHT JOIN Departments d ON e.DepartmentID = d.DepartmentID;
-- 5. SELF JOIN: Get employees and their managers
SELECT
e.EmployeeID,
e.FirstName + ' ' + e.LastName AS EmployeeName,
e.JobTitle,
m.EmployeeID AS ManagerID,
m.FirstName + ' ' + m.LastName AS ManagerName,
m.JobTitle AS ManagerJobTitle
FROM Employees e
LEFT JOIN Employees m ON e.ManagerID = m.EmployeeID;
-- 6. Multiple JOINs: Get payroll details for employees
SELECT
e.EmployeeID,
e.FirstName + ' ' + e.LastName AS EmployeeName,
p.PeriodName,
sc.ComponentName,
sc.ComponentType,
pt.Amount
FROM Employees e
INNER JOIN PayrollTransactions pt ON e.EmployeeID = pt.EmployeeID
INNER JOIN PayrollPeriods p ON pt.PeriodID = p.PeriodID
INNER JOIN SalaryComponents sc ON pt.ComponentID = sc.ComponentID
WHERE p.PeriodName = 'October 2023'
ORDER BY e.EmployeeID, sc.ComponentType DESC, sc.ComponentName;
-- 7. Complex JOIN with aggregation: Calculate total earnings and deductions for each employee
SELECT
e.EmployeeID,
e.FirstName + ' ' + e.LastName AS EmployeeName,
p.PeriodName,
SUM(CASE WHEN sc.ComponentType = 'Earning' THEN pt.Amount ELSE 0 END) AS TotalEarnings,
SUM(CASE WHEN sc.ComponentType = 'Deduction' THEN pt.Amount ELSE 0 END) AS TotalDeductions,
SUM(CASE WHEN sc.ComponentType = 'Earning' THEN pt.Amount ELSE 0 END) -
SUM(CASE WHEN sc.ComponentType = 'Deduction' THEN pt.Amount ELSE 0 END) AS NetPay
FROM Employees e
INNER JOIN PayrollTransactions pt ON e.EmployeeID = pt.EmployeeID
INNER JOIN PayrollPeriods p ON pt.PeriodID = p.PeriodID
INNER JOIN SalaryComponents sc ON pt.ComponentID = sc.ComponentID
WHERE p.PeriodName = 'October 2023'
GROUP BY e.EmployeeID, e.FirstName, e.LastName, p.PeriodName
ORDER BY NetPay DESC;
Advanced Queries and Business Cases
-- 1. Calculate employee attendance summary for a month
SELECT
e.EmployeeID,
e.FirstName + ' ' + e.LastName AS EmployeeName,
COUNT(CASE WHEN a.Status = 'Present' THEN 1 END) AS PresentDays,
COUNT(CASE WHEN a.Status = 'Absent' THEN 1 END) AS AbsentDays,
COUNT(CASE WHEN a.Status = 'Late' THEN 1 END) AS LateDays,
COUNT(CASE WHEN a.Status = 'HalfDay' THEN 1 END) AS HalfDays,
SUM(a.HoursWorked) AS TotalHours
FROM Employees e
LEFT JOIN AttendanceRecords a
ON e.EmployeeID = a.EmployeeID
AND a.[Date] BETWEEN '2023-10-01' AND '2023-10-31'
WHERE e.Status = 'Active'
GROUP BY e.EmployeeID, e.FirstName, e.LastName;
-- 2. Find employees with excessive absenteeism
SELECT
e.EmployeeID,
e.FirstName + ' ' + e.LastName AS EmployeeName,
d.DepartmentName,
COUNT(CASE WHEN a.Status = 'Absent' THEN 1 END) AS AbsentDays,
COUNT(a.AttendanceID) AS TotalWorkingDays
FROM Employees e
INNER JOIN Departments d ON e.DepartmentID = d.DepartmentID
LEFT JOIN AttendanceRecords a
ON e.EmployeeID = a.EmployeeID
AND a.[Date] BETWEEN '2023-10-01' AND '2023-10-31'
WHERE e.Status = 'Active'
GROUP BY e.EmployeeID, e.FirstName, e.LastName, d.DepartmentName
HAVING COUNT(CASE WHEN a.Status = 'Absent' THEN 1 END) > 3;
-- 3. Calculate payroll for a specific period with tax calculation
WITH EmployeeEarnings AS (
SELECT
pt.EmployeeID,
SUM(CASE WHEN sc.ComponentType = 'Earning' THEN pt.Amount ELSE 0 END) AS GrossEarnings,
SUM(CASE WHEN sc.ComponentType = 'Deduction' AND sc.ComponentName != 'Income Tax' THEN pt.Amount ELSE 0 END) AS OtherDeductions
FROM PayrollTransactions pt
INNER JOIN SalaryComponents sc ON pt.ComponentID = sc.ComponentID
INNER JOIN PayrollPeriods pp ON pt.PeriodID = pp.PeriodID
WHERE pp.PeriodName = 'October 2023'
GROUP BY pt.EmployeeID
),
TaxCalculation AS (
SELECT
ee.EmployeeID,
ee.GrossEarnings,
ee.OtherDeductions,
(ee.GrossEarnings - ee.OtherDeductions) AS TaxableIncome,
tc.TaxRate,
tc.DeductionAmount,
((ee.GrossEarnings - ee.OtherDeductions - tc.LowerLimit) * tc.TaxRate/100) + tc.DeductionAmount AS CalculatedTax
FROM EmployeeEarnings ee
CROSS JOIN TaxConfigurations tc
WHERE (ee.GrossEarnings - ee.OtherDeductions) >= tc.LowerLimit
AND (ee.GrossEarnings - ee.OtherDeductions) < ISNULL(tc.UpperLimit, 999999999)
AND tc.TaxYear = 2023
)
SELECT
e.EmployeeID,
e.FirstName + ' ' + e.LastName AS EmployeeName,
tc.GrossEarnings,
tc.OtherDeductions,
tc.TaxableIncome,
tc.CalculatedTax AS IncomeTax,
(tc.GrossEarnings - tc.OtherDeductions - tc.CalculatedTax) AS NetPay
FROM TaxCalculation tc
INNER JOIN Employees e ON tc.EmployeeID = e.EmployeeID
ORDER BY e.EmployeeID;
-- 4. Find employees who worked overtime in a specific period
SELECT
e.EmployeeID,
e.FirstName + ' ' + e.LastName AS EmployeeName,
d.DepartmentName,
SUM(CASE WHEN a.HoursWorked > 8 THEN a.HoursWorked - 8 ELSE 0 END) AS OvertimeHours,
ROUND(SUM(CASE WHEN a.HoursWorked > 8 THEN a.HoursWorked - 8 ELSE 0 END) * es.Amount, 2) AS OvertimePay
FROM Employees e
INNER JOIN Departments d ON e.DepartmentID = d.DepartmentID
INNER JOIN AttendanceRecords a ON e.EmployeeID = a.EmployeeID
AND a.[Date] BETWEEN '2023-10-01' AND '2023-10-31'
INNER JOIN EmployeeSalary es ON e.EmployeeID = es.EmployeeID
INNER JOIN SalaryComponents sc ON es.ComponentID = sc.ComponentID
WHERE e.Status = 'Active'
AND sc.ComponentName = 'Overtime Pay'
AND a.HoursWorked > 8
GROUP BY e.EmployeeID, e.FirstName, e.LastName, d.DepartmentName, es.Amount
HAVING SUM(CASE WHEN a.HoursWorked > 8 THEN a.HoursWorked - 8 ELSE 0 END) > 0;
-- 5. Employee leave balance report
SELECT
e.EmployeeID,
e.FirstName + ' ' + e.LastName AS EmployeeName,
d.DepartmentName,
lt.LeaveName,
lt.MaxDays AS AnnualEntitlement,
COUNT(la.LeaveID) AS LeavesTaken,
(lt.MaxDays - COUNT(la.LeaveID)) AS RemainingBalance
FROM Employees e
INNER JOIN Departments d ON e.DepartmentID = d.DepartmentID
CROSS JOIN LeaveTypes lt
LEFT JOIN LeaveApplications la
ON e.EmployeeID = la.EmployeeID
AND lt.LeaveTypeID = la.LeaveTypeID
AND la.Status = 'Approved'
AND YEAR(la.StartDate) = 2023
WHERE e.Status = 'Active'
AND lt.RequiresApproval = 1 -- BOOLEAN in SQL Server is BIT
GROUP BY e.EmployeeID, e.FirstName, e.LastName, d.DepartmentName, lt.LeaveTypeID, lt.LeaveName, lt.MaxDays
ORDER BY e.EmployeeID, lt.LeaveName;
Assignment Questions
Create a report showing all employees who were late more than 3 times in October 2023.
Calculate the total payroll cost for each department for the last processed period.
Find employees who have taken more than their entitled leave days for any leave type.
Create a query to show employee attendance patterns by day of the week.
Develop a query to identify employees who have not taken any leave in the current year.
Calculate the average time between clock-in and clock-out for each employee.
Create a report showing employees with their managers and their manager's manager (up to 2 levels).
Find departments where the average salary is higher than the company's average salary.
Create a query to show employee turnover rate by department.
Develop a function to calculate income tax based on the tax configuration table.
Create a report showing employees with expired documents or documents expiring in the next 30 days.
Calculate the overtime cost for each department for the last month.
Find employees who have changed departments in the last year.
Create a query to show employee salary history and percentage changes.
Develop a query to identify employees who are eligible for promotion based on tenure and performance.
Calculate the cost of absenteeism for each department.
Create a report showing leave utilization by leave type and department.
Find employees with the longest tenure in each department.
Develop a query to forecast payroll expenses for the next quarter.
Create a comprehensive employee profile report with all related information.
Employee & HR SQL Practical Assignments
1. List all active employees.
2. Show full names and email of all employees in the Marketing department.
3. Count the number of employees in each department.
4. Find the highest paid employee in each department.
5. List employees whose salary is above the average salary.
6. Calculate total hours worked for each employee in October 2023.
7. Show employees with more than 3 absent days in October 2023.
8. List employees and their managers.
9. Get employees with their department details using INNER JOIN.
10. Show all employees even if they have no attendance record using LEFT JOIN.
11. Calculate total earnings and deductions per employee for October 2023.
12. Show employees who worked overtime (>8 hours) in October 2023.
13. Find employees who never took any leave in 2023.
14. List leave balance for each leave type per employee.
15. Show top 3 highest paid employees.
16. Display employees with their total number of trainings attended.
17. Find employees with duplicate email addresses (data quality check).
18. Calculate net pay including tax for October 2023.
19. Rank employees by salary within their department.
20. Show employees whose overtime pay is greater than 1000.
21. List employees with their total leave days taken in 2023.
22. Find the department with maximum number of employees.
23. Show employees who have not completed any training.
24. Find employees whose salary increased by more than 5% recently.
25. List employees and the number of documents uploaded.
26. Show employees with tax configurations applied (tax > 0).
27. Find employees with multiple managers (data check).
28. Show payroll transactions for a specific component (Bonus).
29. Show employees who joined after 1st Jan 2023.
30. Find employees who have more than 2 types of leave in 2023.
Tricky & Confusing SQL Assignments with Answers
1. Find employees whose salary is above the department average.
Tricky point: Correlated subquery per department.
2. Get the second highest salary in the company.
Alternative: Use ROW_NUMBER()
.
3. Find employees who have the same manager as EmployeeID=5.
Tricky point: Exclude the reference employee.
4. Count employees in each department including departments with 0 employees.
Tricky point: Use LEFT JOIN for zero-count rows.
5. Find employees whose salary is neither highest nor lowest in their department.
Tricky point: Exclude both extremes per department.
6. Identify employees with duplicate emails (data integrity check).
Tricky point: Grouping and HAVING.
7. List employees who never took leave.
Tricky point: LEFT JOIN + IS NULL.
8. Find employees whose salary is above average in all departments.
Tricky point: Use of ALL
with aggregation.
9. Find department with maximum total salary.
Tricky point: Aggregation + TOP 1 + ORDER BY.
10. Find employees with more than one manager (invalid data scenario).
Tricky point: Detect inconsistent foreign key relationships.
11. Calculate running total salary by department.
Tricky point: Window function with partitioning.
12. Rank employees by salary but skip ties in numbering.
Tricky point: DENSE_RANK
vs RANK
vs ROW_NUMBER
.
13. Find departments where every employee earns more than 50,000.
Tricky point: Use MIN in HAVING instead of multiple joins.
14. Identify employees with attendance gap more than 3 days in a month.
Tricky point: LEAD/LAG for gap analysis.
15. List employees with same salary as someone in another department.
Tricky point: Self-comparison with EXISTS.
16. Delete duplicate attendance records, keeping only earliest.
Tricky point: ROW_NUMBER with CTE for deletion.
17. Find top 2 earners in each department.
Tricky point: Partitioned ranking.
18. Pivot attendance status count per employee.
Tricky point: Conditional aggregation.
19. Find employees who never worked on Fridays.
Tricky point: WEEKDAY function + HAVING.
20. Show employee name with maximum total overtime pay in a month.
Tricky point: Multi-table join + calculation + TOP 1.
1. Find the employees who earn more than their manager.
Tricky point: Self join to compare hierarchical data.
2. Find the longest streak of consecutive attendance for each employee.
Tricky point: Using gap-and-island problem with row numbers.
3. Find employees whose salary is above 90% percentile in their department.
Tricky point: Percentile calculation with window function.
4. Identify employees who took leave immediately after a holiday.
Tricky point: Date calculation + join with holiday table.
5. Find departments where at least one employee is the top 3 earners globally.
Tricky point: Window function + global ranking.
6. Recursive query: Display management hierarchy for a given employee.
Tricky point: Recursive CTE for hierarchy traversal.
7. Detect overlapping leave requests for the same employee.
Tricky point: Self join + overlap condition.
8. Get top 2 departments by total payroll including deductions.
Tricky point: Aggregation with conditional sum.
9. Find employees who worked overtime on weekends only.
Tricky point: Conditional aggregation + date functions.
10. Find employees with no salary components assigned.
Tricky point: LEFT JOIN + NULL check.
11. Calculate cumulative net pay month-wise for each employee.
Tricky point: Window function with running total.
12. Identify employees who have taken more than 50% of their allowed leave in current year.
Tricky point: Aggregate with division for percentage.
13. Find departments where average salary increased more than 10% compared to last month.
Tricky point: Self-join with lag calculation (monthly trend).
14. Detect employees with inconsistent attendance records (ClockOut < ClockIn).
Tricky point: Data validation logic.
15. Find employees who received the same net pay as another employee in the same period.
Tricky point: Self-join with conditional equality.
16. Recursive query to find all subordinates under a manager.
Tricky point: Hierarchy recursion.
17. Find employees who always come late in the last 10 working days.
Tricky point: Row filtering + HAVING condition.
18. Identify “orphan” payroll transactions (EmployeeID not in Employees table).
Tricky point: Data integrity check with LEFT JOIN + NULL.
19. Generate sequence of dates (calendar table) to join with attendance.
Tricky point: Recursive CTE + cross join to generate full attendance matrix.
20. Find employees whose salary has never decreased.
Tricky point: Detect decreasing sequence using self-existence check.
0 Comments
thanks for your comments!