Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dev #1

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
a8a1f52
Add E-R Diagram
AbualiYousef Apr 18, 2024
57019d9
Add relational schema
AbualiYousef Apr 18, 2024
f53a6fa
Add tables creation script
AbualiYousef Apr 18, 2024
ab346a6
Add tables seeding scripts
AbualiYousef Apr 18, 2024
44553e9
Add 'List of Borrowed Books' solution script
AbualiYousef Apr 18, 2024
b06713e
Add 'List of Borrowed Books' solution script
AbualiYousef Apr 18, 2024
d960a2c
Add 'Borrowing Frequency using Window Function' solution script
AbualiYousef Apr 18, 2024
747f353
Add 'Popular Genre Analysis using Joins and Window Functions' solutio…
AbualiYousef Apr 18, 2024
83cb750
Add 'Stored Procedure - Add New Borrowers' solution script
AbualiYousef Apr 18, 2024
5f67f03
Add 'Database Function - Calculate Overdue Fees' solution script
AbualiYousef Apr 18, 2024
81ccc98
Add 'Database Function - Book Borrowing Frequency' solution script
AbualiYousef Apr 18, 2024
53dea28
Add 'Overdue Analysis' solution script
AbualiYousef Apr 18, 2024
d71e166
Add 'Author Popularity using Aggregation' solution script
AbualiYousef Apr 18, 2024
72e4a2f
Add 'Genre Preference by Age using Group By and Having' solution script
AbualiYousef Apr 18, 2024
c601fc5
Add 'Stored Procedure - Borrowed Books Report' solution script
AbualiYousef Apr 18, 2024
09cf8e4
Add 'Trigger Implementation' solution script
AbualiYousef Apr 18, 2024
6ee9161
Add 'SQL Stored Procedure with Temp Table' solution script
AbualiYousef Apr 18, 2024
14119ac
Add 'BONUS - Weekly Peaks Days' solution script
AbualiYousef Apr 18, 2024
8dfd03a
Update README.md
AbualiYousef Apr 18, 2024
70f4780
Rename seeding scripts
AbualiYousef Apr 22, 2024
766e8e1
Update multiple procedures using CREATE OR ALTER
AbualiYousef Apr 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added E-R Diagram.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
77 changes: 76 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,76 @@
# Library-Management-System-Database
# Library Management System: Database Project

## Background

A local library is transitioning from traditional book-keeping to a digital system. The aim is to efficiently track books, borrowers, loans, returns, and provide insights into borrowing trends.

## Objective

Design and implement a relational database using MS SQL to support the library's operations and enhance querying capabilities.

## Requirements

### 1. Design an Entity Relationship Model (ERM) Diagram

- **Entities**: Books, Borrowers, and Loans.
- **Attributes**: Specify attributes for each entity.
- **Relationships**: Show connections between entities.
- **Connectivity and Cardinality**: Indicate relationship types.
- **Keys**: Identify primary (PK).
- **Tools**: Use ERDPlus, Lucidchart, or similar.

![Entity Relationship Diagram](E-R%20Diagram.png)

### 2. Design the Relational Schema using MS SQL

- **Books**:
- BookID (PK)
- Title
- Author
- ISBN
- Published Date
- Genre
- Shelf Location
- Current Status ('Available' or 'Borrowed')

- **Borrowers**:
- BorrowerID (PK)
- First Name
- Last Name
- Email
- Date of Birth
- Membership Date

- **Loans**:
- LoanID (PK)
- BookID (FK)
- BorrowerID (FK)
- Date Borrowed
- Due Date
- Date Returned (NULL if not returned yet)

![Database Schema](relationalSchema.png)

### 3. Build and Seed the Database

- Construct the database in MS SQL.
- Seed with 1000 books, 1000 borrowers, and 1000 loans. Include DML scripts for seeding in the repository.

### 4. Complex Queries and Procedures

- **List of Borrowed Books**: Query to retrieve all borrowed books for a specific borrower.
- **Active Borrowers with CTEs**: Identify borrowers with 2+ borrowed books not yet returned.
- **Borrowing Frequency using Window Functions**: Rank borrowers based on frequency.
- **Popular Genre Analysis**: Determine the most popular genre per month.
- **Stored Procedure - `sp_AddNewBorrower`**: Add a new borrower; handle existing emails.
- **Database Function - `fn_CalculateOverdueFees`**: Compute overdue fees for loans.
- **Database Function - `fn_BookBorrowingFrequency`**: Count how many times a book has been borrowed.
- **Overdue Analysis**: List overdue books with associated borrowers.
- **Author Popularity using Aggregation**: Rank authors by borrowing frequency.
- **Genre Preference by Age**: Determine genre preference across age groups.
- **Stored Procedure - `sp_BorrowedBooksReport`**: Generate a report of books borrowed within a date range.
- **Trigger Implementation**: Log changes in book status to an `AuditLog`.

### BONUS

- **Weekly Peak Days**: Identify the three busiest days of the week for loans and express as a percentage of total loans.
7 changes: 7 additions & 0 deletions complexQueriesAndProcedures/1.ListOfBorrowedBooks.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
--1. List of Borrowed Books: Retrieve all books borrowed by a specific borrower, including those currently unreturned.

declare @BorrowerId int = 7;
SELECT Books.title, Books.author, Loans.dateBorrowed, Loans.dueDate
FROM Loans
JOIN Books ON Loans.bookId = Books.bookId
WHERE Loans.borrowerId = @BorrowerId;
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
--10.Genre Preference by Age using Group By and Having:
WITH BorrowerAges AS (
SELECT
borrowerId,
(DATEDIFF(YEAR, dateOfBirth, GETDATE()) / 10) * 10 AS AgeBracket
FROM
Borrowers
),
LoanGenres AS (
SELECT
ba.AgeBracket,
b.genre,
COUNT(*) AS GenreCount
FROM
BorrowerAges ba
JOIN Loans l ON ba.borrowerId = l.borrowerId
JOIN Books b ON l.bookId = b.bookId
GROUP BY
ba.AgeBracket,
b.genre
),
RankedGenres AS (
SELECT
AgeBracket,
genre,
GenreCount,
ROW_NUMBER() OVER (
PARTITION BY AgeBracket
ORDER BY GenreCount DESC, genre ASC
) AS RowNum
FROM
LoanGenres
)
SELECT
CASE
WHEN AgeBracket = 0 THEN '0-10'
WHEN AgeBracket = 10 THEN '11-20'
WHEN AgeBracket = 20 THEN '21-30'
WHEN AgeBracket = 30 THEN '31-40'
WHEN AgeBracket = 40 THEN '41-50'
WHEN AgeBracket = 50 THEN '51-60'
WHEN AgeBracket >= 60 THEN '61+'
ELSE 'Unknown'
END AS AgeGroup,
genre,
GenreCount
FROM
RankedGenres
WHERE
RowNum = 1
ORDER BY
AgeBracket;
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
--11.Stored Procedure - Borrowed Books Report:
CREATE PROCEDURE sp_BorrowedBooksReport
@StartDate DATE,
@EndDate DATE
AS
BEGIN
-- Query to retrieve borrowed book details within the specified date range
SELECT
b.title AS BookTitle,
b.author AS Author,
br.firstName + ' ' + br.lastName AS BorrowerName,
l.dateBorrowed AS BorrowingDate
FROM
Loans l
INNER JOIN Books b ON l.bookId = b.bookId
INNER JOIN Borrowers br ON l.borrowerId = br.borrowerId
WHERE
l.dateBorrowed BETWEEN @StartDate AND @EndDate
ORDER BY
BorrowingDate ASC;
END;

--Test the sp
EXEC sp_BorrowedBooksReport
@StartDate = '2024-01-01',
@EndDate = '2024-01-17';
38 changes: 38 additions & 0 deletions complexQueriesAndProcedures/12.TriggerImplementation.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
--12.Trigger Implementation
DROP TRIGGER IF EXISTS trg_BookStatusChange;
GO
CREATE TRIGGER trg_BookStatusChange ON Books
AFTER UPDATE
AS
BEGIN
DECLARE @BookID INT;
DECLARE @OldStatus VARCHAR(50);
DECLARE @NewStatus VARCHAR(50);
DECLARE @StatusChange VARCHAR(100);
IF UPDATE(currentStatus)
BEGIN
SELECT @BookID = i.bookId, @OldStatus = d.currentStatus, @NewStatus = i.currentStatus
FROM inserted i
JOIN deleted d ON i.bookId = d.bookId;

IF (@OldStatus <> @NewStatus)
BEGIN
SET @StatusChange = 'Status changed from ' + @OldStatus + ' to ' + @NewStatus;
INSERT INTO AuditLog
VALUES (@BookID, @StatusChange, GETDATE());
END
END
END;

-- Example to update a book's status to 'Borrowed'
UPDATE Books
SET currentStatus = 'Borrowed'
WHERE bookId = 1 AND currentStatus = 'Available';

-- Example to update a book's status to 'Available'
UPDATE Books
SET currentStatus = 'Available'
WHERE bookId = 1 AND currentStatus = 'Borrowed';

-- Check the AuditLog:
SELECT * FROM AuditLog;
36 changes: 36 additions & 0 deletions complexQueriesAndProcedures/14.SQLStoredProcedureWithTempTable.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
-- 14.SQL Stored Procedure with Temp Table:
DROP PROCEDURE IF EXISTS sp_GetOverdueBooksByBorrower;
GO
CREATE PROCEDURE sp_GetOverdueBooksByBorrower
AS
BEGIN
-- Create a temporary table
CREATE TABLE #OverdueBorrowers (
borrowerId INT
);

-- Insert borrowers who have overdue books into the temporary table:
INSERT INTO #OverdueBorrowers (borrowerId)
SELECT DISTINCT l.borrowerId
FROM Loans l
WHERE l.dateReturned IS NULL AND l.dueDate < GETDATE();

-- Select the overdue books for each borrower by joining the temp table with the Loans table
SELECT
br.borrowerId,
br.firstName + ' ' + br.lastName AS BorrowerName,
b.bookId,
b.title AS BookTitle,
l.dueDate AS DueDate
FROM #OverdueBorrowers obr
JOIN Loans l ON obr.borrowerId = l.borrowerId
JOIN Borrowers br ON obr.borrowerId = br.borrowerId -- Make sure to join Borrowers here
JOIN Books b ON l.bookId = b.bookId
WHERE l.dateReturned IS NULL AND l.dueDate < GETDATE();

-- Drop the temporary table
DROP TABLE #OverdueBorrowers;
END;

-- Test the sp:
EXEC sp_GetOverdueBooksByBorrower;
11 changes: 11 additions & 0 deletions complexQueriesAndProcedures/2.ActiveBorrowersWithCTEs.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
--2.Active Borrowers with CTEs:
WITH ActiveBorrowers AS (
SELECT borrowerId, COUNT(*) AS TotalLoans
FROM Loans
WHERE dateReturned IS NULL
GROUP BY borrowerId
HAVING COUNT(*) >= 2
)
SELECT Borrowers.*, ActiveBorrowers.TotalLoans
FROM ActiveBorrowers
JOIN Borrowers ON ActiveBorrowers.borrowerId = Borrowers.borrowerId;
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
--3.Borrowing Frequency using Window Functions:
SELECT Borrowers.borrowerId, Borrowers.firstName, Borrowers.lastName, COUNT(Loans.borrowerId) AS TotalLoans,
RANK() OVER (ORDER BY COUNT(Loans.borrowerId) DESC) AS BorrowerRank
FROM Borrowers
LEFT JOIN Borrowers ON Loans.borrowerId = Borrowers.borrowerId
GROUP BY Borrowers.borrowerId, Borrowers.firstName, Borrowers.lastName;
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
--4.Popular Genre Analysis using Joins and Window Functions:
DECLARE @Month INT=4;
DECLARE @Year INT=2022;
SELECT genre, COUNT(*) AS TotalLoans,
RANK() OVER (ORDER BY COUNT(*) DESC) AS GenreRank
FROM Loans
JOIN Books ON Loans.bookId = Books.bookId
WHERE MONTH(dateBorrowed) = @Month AND YEAR(dateBorrowed) = @Year
GROUP BY genre;
28 changes: 28 additions & 0 deletions complexQueriesAndProcedures/5.StoredProcedure-AddNewBorrowers.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
--5.Stored Procedure - Add New Borrowers:
CREATE PROCEDURE sp_AddNewBorrower
AbualiYousef marked this conversation as resolved.
Show resolved Hide resolved
@FirstName VARCHAR(255),
@LastName VARCHAR(255),
@Email VARCHAR(255),
@DateOfBirth DATE,
@MembershipDate DATE
AS
BEGIN
IF EXISTS (SELECT Borrowers.borrowerId FROM Borrowers WHERE email = @Email)
BEGIN
SELECT 'Error: A borrower with this email already exists.' AS ErrorMessage;
END
ELSE
BEGIN
INSERT INTO Borrowers
VALUES (@FirstName, @LastName, @Email, @DateOfBirth, @MembershipDate);
SELECT SCOPE_IDENTITY() AS NewBorrowerID;
END
END;

-- Test the procedure by executing it
EXEC sp_AddNewBorrower
@FirstName = 'John',
@LastName = 'Doe',
@Email = 'john.doe@example.com',
@DateOfBirth = '1990-05-15',
@MembershipDate = '2023-04-15';
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
--6.Database Function - Calculate Overdue Fees
drop function if exists fn_CalculateOverdueFees;
go
CREATE FUNCTION fn_CalculateOverdueFees (@LoanID INT)
RETURNS MONEY
AS
BEGIN
DECLARE @DateBorrowed DATE;
DECLARE @DueDate DATE;
DECLARE @DateReturned DATE;
DECLARE @OverdueDays INT;
DECLARE @OverdueFee MONEY = 0;

-- Retrieve loan dates
SELECT @DateBorrowed = dateBorrowed, @DueDate = dueDate, @DateReturned = dateReturned
FROM Loans
WHERE loanId = @LoanID;

-- Calculate overdue days
IF @DateReturned IS NULL
SET @DateReturned = GETDATE();

SET @OverdueDays = DATEDIFF(DAY, @DueDate, @DateReturned);

-- Calculate fees based on the overdue days
IF @OverdueDays > 0
BEGIN
IF @OverdueDays <= 30
SET @OverdueFee = @OverdueDays * 1.00;
ELSE
SET @OverdueFee = (30 * 1.00) + ((@OverdueDays - 30) * 2.00);
END

RETURN @OverdueFee;
END;



--Test the function:
SELECT dbo.fn_CalculateOverdueFees(1) AS OverdueFee;
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
--7.Database Function - Book Borrowing Frequency:

CREATE FUNCTION fn_BookBorrowingFrequency (@BookID INT)
RETURNS INT
AS
BEGIN
RETURN(
SELECT COUNT(*)
FROM Loans
WHERE bookId = @BookID
);
END;


SELECT dbo.fn_BookBorrowingFrequency(1) AS BorrowingFrequency;
11 changes: 11 additions & 0 deletions complexQueriesAndProcedures/8.OverdueAnalysis.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
--8.Overdue Analysis: List all books overdue by more than 30 days with their associated borrowers.

SELECT b.*,bo.*,
DATEDIFF(DAY, l.dueDate, GETDATE()) AS DaysOverdue
FROM
Loans l
JOIN Books b ON l.bookId = b.bookId
JOIN Borrowers bo ON l.borrowerId = bo.borrowerId
WHERE
l.dateReturned IS NULL AND
DATEDIFF(DAY, l.dueDate, GETDATE()) > 30;
11 changes: 11 additions & 0 deletions complexQueriesAndProcedures/9.AuthorPopularityUsingAggregation.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
--9.Author Popularity using Aggregation:

SELECT
b.author,
COUNT(l.loanId) AS BorrowingCount,
RANK() OVER (ORDER BY COUNT(l.loanId) DESC) AS AuthorRank
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just care that you are aware RANK() OVER is not aggregation its window func

FROM
Books b
LEFT JOIN Loans l ON b.bookId = l.bookId
GROUP BY
b.author
Loading