SQL Project: Library Management System
A city library tracks its books, members and loans in MySQL. In this project you answer the questions the librarians ask every day: which copies are on the shelf, which loans are overdue, how much each member owes in fines, which books are popular, and you build a view and a stored procedure that issues books safely. The reference date for “today” is 30 June 2025.
- Compute availability from loans that are still open
- Find overdue loans and calculate fines with date functions
- Rank popular books and active members
- Package logic in a view and a transaction-safe stored procedure
The database
| Table | One row per | Notes |
|---|---|---|
books | title in the catalogue | copies = how many physical copies the library owns |
members | library member | member_type: Student, Regular, Senior |
loans | one book lent to one member | return_date is NULL while the book is still out |
| book_id | title | author | genre | published_year | copies |
|---|---|---|---|---|---|
| 1 | The Pragmatic Programmer | Hunt & Thomas | Technology | 1999 | 3 |
| 2 | Clean Code | Robert Martin | Technology | 2008 | 2 |
| 3 | Atomic Habits | James Clear | Self-help | 2018 | 4 |
| 4 | Sapiens | Yuval Noah Harari | History | 2011 | 2 |
| 5 | The Alchemist | Paulo Coelho | Fiction | 1988 | 3 |
| 6 | Wings of Fire | A. P. J. Abdul Kalam | Biography | 1999 | 2 |
| 7 | The Guide | R. K. Narayan | Fiction | 1958 | 1 |
| 8 | Thinking, Fast and Slow | Daniel Kahneman | Psychology | 2011 | 2 |
| 9 | Deep Work | Cal Newport | Self-help | 2016 | 2 |
| 10 | A Brief History of Time | Stephen Hawking | Science | 1988 | 1 |
| 11 | The God of Small Things | Arundhati Roy | Fiction | 1997 | 2 |
| 12 | Lessons in Testing | Cem Kaner | Technology | 2001 | 1 |
| member_id | full_name | member_type | joined_on |
|---|---|---|---|
| 1 | Rhea Kulkarni | Senior | 2024-04-26 |
| 2 | Om Prakash | Senior | 2023-12-27 |
| 3 | Tara Sen | Student | 2023-09-09 |
| 4 | Neil D'Souza | Senior | 2024-10-09 |
| 5 | Ira Malhotra | Student | 2024-01-06 |
| 6 | Dev Chauhan | Senior | 2024-08-09 |
| 7 | Noor Fatima | Senior | 2023-06-19 |
| 8 | Kunal Bhatt | Regular | 2023-09-12 |
| 9 | Asha Pillai | Student | 2024-02-09 |
| 10 | Yash Rawat | Student | 2024-08-18 |
| loan_id | book_id | member_id | loan_date | due_date | return_date |
|---|---|---|---|---|---|
| 1 | 8 | 1 | 2025-01-05 | 2025-01-19 | 2025-01-17 |
| 2 | 3 | 7 | 2025-01-05 | 2025-01-19 | 2025-01-10 |
| 3 | 8 | 1 | 2025-01-13 | 2025-01-27 | 2025-01-29 |
| 4 | 1 | 7 | 2025-01-17 | 2025-01-31 | 2025-02-02 |
| 5 | 10 | 3 | 2025-01-20 | 2025-02-03 | 2025-02-03 |
| 6 | 3 | 10 | 2025-01-21 | 2025-02-04 | 2025-02-10 |
| 7 | 1 | 8 | 2025-01-27 | 2025-02-10 | 2025-02-10 |
| 8 | 5 | 1 | 2025-02-16 | 2025-03-02 | 2025-02-25 |
| 9 | 1 | 6 | 2025-02-20 | 2025-03-06 | 2025-03-01 |
| 10 | 6 | 2 | 2025-02-21 | 2025-03-07 | 2025-03-09 |
Rules: a loan lasts 14 days (due_date); a late return costs ₹5 per day; a member may hold at most 3 books at a time.
-- sqatools.in SQL project 2: library management system (generated by gen_projects.py)CREATE TABLE books ( book_id INT PRIMARY KEY, title VARCHAR(60) NOT NULL, author VARCHAR(40) NOT NULL, genre VARCHAR(20) NOT NULL, published_year INT, copies INT NOT NULL);CREATE TABLE members ( member_id INT PRIMARY KEY, full_name VARCHAR(40) NOT NULL, member_type VARCHAR(10) NOT NULL, joined_on DATE NOT NULL);CREATE TABLE loans ( loan_id INT PRIMARY KEY, book_id INT NOT NULL, member_id INT NOT NULL, loan_date DATE NOT NULL, due_date DATE NOT NULL, return_date DATE, FOREIGN KEY (book_id) REFERENCES books (book_id), FOREIGN KEY (member_id) REFERENCES members (member_id));INSERT INTO books VALUES(1, 'The Pragmatic Programmer', 'Hunt & Thomas', 'Technology', 1999, 3),(2, 'Clean Code', 'Robert Martin', 'Technology', 2008, 2),(3, 'Atomic Habits', 'James Clear', 'Self-help', 2018, 4),(4, 'Sapiens', 'Yuval Noah Harari', 'History', 2011, 2),(5, 'The Alchemist', 'Paulo Coelho', 'Fiction', 1988, 3),(6, 'Wings of Fire', 'A. P. J. Abdul Kalam', 'Biography', 1999, 2),(7, 'The Guide', 'R. K. Narayan', 'Fiction', 1958, 1),(8, 'Thinking, Fast and Slow', 'Daniel Kahneman', 'Psychology', 2011, 2),(9, 'Deep Work', 'Cal Newport', 'Self-help', 2016, 2),(10, 'A Brief History of Time', 'Stephen Hawking', 'Science', 1988, 1),(11, 'The God of Small Things', 'Arundhati Roy', 'Fiction', 1997, 2),(12, 'Lessons in Testing', 'Cem Kaner', 'Technology', 2001, 1);INSERT INTO members VALUES(1, 'Rhea Kulkarni', 'Senior', '2024-04-26'),(2, 'Om Prakash', 'Senior', '2023-12-27'),(3, 'Tara Sen', 'Student', '2023-09-09'),(4, 'Neil D''Souza', 'Senior', '2024-10-09'),(5, 'Ira Malhotra', 'Student', '2024-01-06'),(6, 'Dev Chauhan', 'Senior', '2024-08-09'),(7, 'Noor Fatima', 'Senior', '2023-06-19'),(8, 'Kunal Bhatt', 'Regular', '2023-09-12'),(9, 'Asha Pillai', 'Student', '2024-02-09'),(10, 'Yash Rawat', 'Student', '2024-08-18');INSERT INTO loans VALUES(1, 8, 1, '2025-01-05', '2025-01-19', '2025-01-17'),(2, 3, 7, '2025-01-05', '2025-01-19', '2025-01-10'),(3, 8, 1, '2025-01-13', '2025-01-27', '2025-01-29'),(4, 1, 7, '2025-01-17', '2025-01-31', '2025-02-02'),(5, 10, 3, '2025-01-20', '2025-02-03', '2025-02-03'),(6, 3, 10, '2025-01-21', '2025-02-04', '2025-02-10'),(7, 1, 8, '2025-01-27', '2025-02-10', '2025-02-10'),(8, 5, 1, '2025-02-16', '2025-03-02', '2025-02-25'),(9, 1, 6, '2025-02-20', '2025-03-06', '2025-03-01'),(10, 6, 2, '2025-02-21', '2025-03-07', '2025-03-09'),(11, 4, 5, '2025-02-21', '2025-03-07', '2025-03-05'),(12, 3, 4, '2025-02-22', '2025-03-08', '2025-03-03'),(13, 5, 6, '2025-02-22', '2025-03-08', '2025-03-06'),(14, 2, 9, '2025-02-24', '2025-03-10', '2025-03-01'),(15, 10, 10, '2025-02-26', '2025-03-12', '2025-03-12'),(16, 6, 3, '2025-02-27', '2025-03-13', '2025-03-13'),(17, 10, 5, '2025-02-27', '2025-03-13', '2025-03-15'),(18, 7, 3, '2025-03-04', '2025-03-18', '2025-03-20'),(19, 5, 3, '2025-03-06', '2025-03-20', '2025-03-20'),(20, 5, 3, '2025-03-07', '2025-03-21', '2025-04-03'),(21, 1, 7, '2025-03-09', '2025-03-23', '2025-03-14'),(22, 11, 3, '2025-03-15', '2025-03-29', '2025-03-28'),(23, 4, 5, '2025-03-17', '2025-03-31', '2025-04-02'),(24, 7, 4, '2025-03-21', '2025-04-04', '2025-04-06'),(25, 7, 10, '2025-03-24', '2025-04-07', '2025-04-02'),(26, 9, 7, '2025-03-26', '2025-04-09', '2025-04-07'),(27, 8, 6, '2025-03-30', '2025-04-13', '2025-04-13'),(28, 9, 10, '2025-03-31', '2025-04-14', '2025-04-16'),(29, 8, 4, '2025-04-01', '2025-04-15', '2025-04-06'),(30, 10, 4, '2025-04-10', '2025-04-24', '2025-04-30'),(31, 1, 5, '2025-04-15', '2025-04-29', '2025-05-01'),(32, 9, 5, '2025-04-21', '2025-05-05', '2025-05-18'),(33, 1, 10, '2025-04-27', '2025-05-11', '2025-05-13'),(34, 11, 9, '2025-05-01', '2025-05-15', '2025-05-13'),(35, 6, 8, '2025-05-01', '2025-05-15', '2025-05-13'),(36, 4, 7, '2025-05-01', '2025-05-15', '2025-05-06'),(37, 5, 8, '2025-05-02', '2025-05-16', '2025-05-29'),(38, 7, 6, '2025-05-03', '2025-05-17', '2025-05-15'),(39, 7, 10, '2025-05-06', '2025-05-20', '2025-05-22'),(40, 11, 2, '2025-05-09', '2025-05-23', '2025-05-29'),(41, 9, 10, '2025-05-12', '2025-05-26', '2025-06-01'),(42, 8, 7, '2025-05-19', '2025-06-02', '2025-05-28'),(43, 11, 8, '2025-05-22', '2025-06-05', '2025-05-31'),(44, 2, 10, '2025-05-23', '2025-06-06', '2025-06-19'),(45, 5, 3, '2025-05-25', '2025-06-08', '2025-06-06'),(46, 3, 1, '2025-06-02', '2025-06-16', '2025-06-16'),(47, 8, 8, '2025-06-09', '2025-06-23', '2025-06-23'),(48, 10, 7, '2025-06-09', '2025-06-23', NULL),(49, 5, 4, '2025-06-13', '2025-06-27', NULL),(50, 10, 3, '2025-06-14', '2025-06-28', '2025-06-19'),(51, 7, 8, '2025-06-19', '2025-07-03', NULL),(52, 1, 2, '2025-06-21', '2025-07-05', NULL),(53, 4, 4, '2025-06-21', '2025-07-05', NULL),(54, 4, 5, '2025-06-22', '2025-07-06', NULL),(55, 2, 6, '2025-06-25', '2025-07-09', NULL);Task 1: Catalogue overview
SELECT genre, COUNT(*) AS titles, SUM(copies) AS copiesFROM booksGROUP BY genreORDER BY copies DESC, genre;| genre | titles | copies |
|---|---|---|
| Fiction | 3 | 6 |
| Self-help | 2 | 6 |
| Technology | 3 | 6 |
| Biography | 1 | 2 |
| History | 1 | 2 |
| Psychology | 1 | 2 |
| Science | 1 | 1 |
Task 2: Copies available right now
Available = copies owned − loans that are still open:
SELECT b.book_id, b.title, b.copies, COUNT(l.loan_id) AS on_loan, b.copies - COUNT(l.loan_id) AS availableFROM books bLEFT JOIN loans l ON l.book_id = b.book_id AND l.return_date IS NULLGROUP BY b.book_id, b.title, b.copiesORDER BY available, b.title;| book_id | title | copies | on_loan | available |
|---|---|---|---|---|
| 10 | A Brief History of Time | 1 | 1 | 0 |
| 4 | Sapiens | 2 | 2 | 0 |
| 7 | The Guide | 1 | 1 | 0 |
| 2 | Clean Code | 2 | 1 | 1 |
| 12 | Lessons in Testing | 1 | 0 | 1 |
| 9 | Deep Work | 2 | 0 | 2 |
| 5 | The Alchemist | 3 | 1 | 2 |
| 11 | The God of Small Things | 2 | 0 | 2 |
| 1 | The Pragmatic Programmer | 3 | 1 | 2 |
| 8 | Thinking, Fast and Slow | 2 | 0 | 2 |
| 6 | Wings of Fire | 2 | 0 | 2 |
| 3 | Atomic Habits | 4 | 0 | 4 |
- The
LEFT JOINattaches only open loans (return_date IS NULL); the condition is inONso books with no open loan are kept. COUNT(l.loan_id)counts the open loans of each book (0 if none).- Subtracting from
copiesgives the copies on the shelf. Books with 0 available come first.
Task 3: Overdue loans today
SELECT l.loan_id, m.full_name, b.title, l.due_date, DATEDIFF('2025-06-30', l.due_date) AS days_overdueFROM loans lJOIN members m ON m.member_id = l.member_idJOIN books b ON b.book_id = l.book_idWHERE l.return_date IS NULL AND l.due_date < '2025-06-30'ORDER BY days_overdue DESC;| loan_id | full_name | title | due_date | days_overdue |
|---|---|---|---|---|
| 48 | Noor Fatima | A Brief History of Time | 2025-06-23 | 7 |
| 49 | Neil D'Souza | The Alchemist | 2025-06-27 | 3 |
- A loan is overdue when it is still open and its due date has passed.
DATEDIFF(today, due_date)gives the number of days late.- In a live system replace
'2025-06-30'withCURDATE().
Task 4: Fines per member
Late days are counted up to the return date for returned books, and up to today for books still out:
WITH late AS ( SELECT l.member_id, GREATEST(DATEDIFF(COALESCE(l.return_date, '2025-06-30'), l.due_date), 0) AS days_late FROM loans l)SELECT m.member_id, m.full_name, SUM(late.days_late > 0) AS late_loans, SUM(late.days_late) * 5 AS fine_rupeesFROM lateJOIN members m ON m.member_id = late.member_idGROUP BY m.member_id, m.full_nameHAVING fine_rupees > 0ORDER BY fine_rupees DESC;| member_id | full_name | late_loans | fine_rupees |
|---|---|---|---|
| 10 | Yash Rawat | 6 | 155 |
| 5 | Ira Malhotra | 4 | 95 |
| 3 | Tara Sen | 2 | 75 |
| 8 | Kunal Bhatt | 1 | 65 |
| 4 | Neil D'Souza | 3 | 55 |
| 7 | Noor Fatima | 2 | 45 |
| 2 | Om Prakash | 2 | 40 |
| 1 | Rhea Kulkarni | 1 | 10 |
COALESCE(return_date, '2025-06-30')uses today for loans that are still open.DATEDIFF(that date, due_date)counts the days after the due date; a negative number means the book came back early.GREATEST(..., 0)turns early returns into 0 days late.- The fine is the total late days × ₹5;
HAVINGkeeps only members who owe something.
Task 5: Most borrowed books
SELECT b.title, b.genre, COUNT(*) AS loans, DENSE_RANK() OVER (ORDER BY COUNT(*) DESC) AS popularity_rankFROM loans lJOIN books b ON b.book_id = l.book_idGROUP BY b.book_id, b.title, b.genreORDER BY loans DESC, b.titleLIMIT 5;| title | genre | loans | popularity_rank |
|---|---|---|---|
| The Alchemist | Fiction | 7 | 1 |
| The Pragmatic Programmer | Technology | 7 | 1 |
| A Brief History of Time | Science | 6 | 2 |
| The Guide | Fiction | 6 | 2 |
| Thinking, Fast and Slow | Psychology | 6 | 2 |
A window function can rank the grouped rows: DENSE_RANK() OVER (ORDER BY COUNT(*) DESC) runs after GROUP BY.
Task 6: Books nobody has borrowed
SELECT b.book_id, b.title, b.authorFROM books bWHERE NOT EXISTS (SELECT 1 FROM loans l WHERE l.book_id = b.book_id);| book_id | title | author |
|---|---|---|
| 12 | Lessons in Testing | Cem Kaner |
Task 7: Member activity
SELECT m.member_type, COUNT(DISTINCT m.member_id) AS members, COUNT(l.loan_id) AS loans, ROUND(COUNT(l.loan_id) / COUNT(DISTINCT m.member_id), 1) AS loans_per_memberFROM members mLEFT JOIN loans l ON l.member_id = m.member_idGROUP BY m.member_typeORDER BY loans_per_member DESC;| member_type | members | loans | loans_per_member |
|---|---|---|---|
| Regular | 1 | 6 | 6.0 |
| Student | 4 | 24 | 6.0 |
| Senior | 5 | 25 | 5.0 |
Task 8: Average loan length by genre
SELECT b.genre, COUNT(*) AS returned_loans, ROUND(AVG(DATEDIFF(l.return_date, l.loan_date)), 1) AS avg_days_kept, SUM(l.return_date > l.due_date) AS returned_lateFROM loans lJOIN books b ON b.book_id = l.book_idWHERE l.return_date IS NOT NULLGROUP BY b.genreORDER BY avg_days_kept DESC;| genre | returned_loans | avg_days_kept | returned_late |
|---|---|---|---|
| Self-help | 8 | 15.4 | 4 |
| Fiction | 15 | 14.9 | 6 |
| Biography | 3 | 14.0 | 1 |
| Science | 5 | 13.8 | 2 |
| Technology | 8 | 13.5 | 4 |
| Psychology | 6 | 11.7 | 1 |
| History | 3 | 11.0 | 1 |
Task 9: Loans per month
SELECT DATE_FORMAT(loan_date, '%Y-%m') AS month, COUNT(*) AS loans, COUNT(DISTINCT member_id) AS active_membersFROM loansGROUP BY monthORDER BY month;| month | loans | active_members |
|---|---|---|
| 2025-01 | 7 | 5 |
| 2025-02 | 10 | 8 |
| 2025-03 | 11 | 6 |
| 2025-04 | 5 | 3 |
| 2025-05 | 12 | 7 |
| 2025-06 | 10 | 8 |
Task 10: Members at their borrowing limit
SELECT m.member_id, m.full_name, COUNT(*) AS books_out, GROUP_CONCAT(b.title ORDER BY l.loan_date SEPARATOR '; ') AS titlesFROM loans lJOIN members m ON m.member_id = l.member_idJOIN books b ON b.book_id = l.book_idWHERE l.return_date IS NULLGROUP BY m.member_id, m.full_nameHAVING COUNT(*) >= 2ORDER BY books_out DESC;| member_id | full_name | books_out | titles |
|---|---|---|---|
| 4 | Neil D'Souza | 2 | The Alchemist; Sapiens |
Task 11: A view for the front desk
Save the availability query as a view, so the desk application only has to SELECT from it:
CREATE VIEW available_books ASSELECT b.book_id, b.title, b.author, b.copies - COUNT(l.loan_id) AS availableFROM books bLEFT JOIN loans l ON l.book_id = b.book_id AND l.return_date IS NULLGROUP BY b.book_id, b.title, b.author, b.copies; SELECT * FROM available_books WHERE available > 0 AND title LIKE '%Habit%';| book_id | title | author | available |
|---|---|---|---|
| 3 | Atomic Habits | James Clear | 4 |
Task 12: Issue a book safely (stored procedure)
Issuing a book must check two rules and then insert the loan, as one unit of work:
DELIMITER //CREATE PROCEDURE issue_book(IN p_book_id INT, IN p_member_id INT, IN p_date DATE)BEGIN DECLARE v_available INT; DECLARE v_out INT; DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN ROLLBACK; RESIGNAL; END; START TRANSACTION; SELECT b.copies - (SELECT COUNT(*) FROM loans WHERE book_id = p_book_id AND return_date IS NULL) INTO v_available FROM books b WHERE b.book_id = p_book_id FOR UPDATE; SELECT COUNT(*) INTO v_out FROM loans WHERE member_id = p_member_id AND return_date IS NULL; IF v_available IS NULL OR v_available < 1 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'No copy available'; END IF; IF v_out >= 3 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Member already has 3 books'; END IF; INSERT INTO loans (loan_id, book_id, member_id, loan_date, due_date, return_date) SELECT MAX(loan_id) + 1, p_book_id, p_member_id, p_date, p_date + INTERVAL 14 DAY, NULL FROM loans; COMMIT;END //DELIMITER ; CALL issue_book(12, 1, '2025-06-30');SELECT * FROM loans ORDER BY loan_id DESC LIMIT 1;| loan_id | book_id | member_id | loan_date | due_date | return_date |
|---|---|---|---|---|---|
| 56 | 12 | 1 | 2025-06-30 | 2025-07-14 | NULL |
FOR UPDATElocks the book row, so two librarians cannot hand out the last copy at the same moment.- Two
SIGNALs enforce the rules: a copy must be free, and the member must hold fewer than 3 books. - If anything fails, the handler rolls back and passes the error on.
- Book 12 had never been borrowed, so member 1 can take it: a new open loan due in 14 days.
Asking for a book with no copy left fails, and nothing is inserted:
CALL issue_book(12, 2, '2025-06-30');ERROR 1644 (45000): No copy available
Your turn
Exercise: Members with nothing borrowed right now
List members who currently have no book out (no open loan).
| member_id | full_name |
|---|---|
| 1 | Rhea Kulkarni |
| 3 | Tara Sen |
| 9 | Asha Pillai |
| 10 | Yash Rawat |
Hint 1
NOT EXISTS with a subquery on open loans (return_date IS NULL).
SELECT m.member_id, m.full_nameFROM members mWHERE NOT EXISTS ( SELECT 1 FROM loans l WHERE l.member_id = m.member_id AND l.return_date IS NULL)ORDER BY m.member_id;| member_id | full_name |
|---|---|
| 1 | Rhea Kulkarni |
| 3 | Tara Sen |
| 9 | Asha Pillai |
| 10 | Yash Rawat |
Exercise: Late returns per book
For each book, count how many loans were returned after the due date.
| title | late_returns |
|---|---|
| Deep Work | 3 |
| The Guide | 3 |
| The Pragmatic Programmer | 3 |
| A Brief History of Time | 2 |
| The Alchemist | 2 |
| Atomic Habits | 1 |
| Clean Code | 1 |
| Sapiens | 1 |
| The God of Small Things | 1 |
| Thinking, Fast and Slow | 1 |
| Wings of Fire | 1 |
Hint 1
Filter returned loans with return_date > due_date, group by title.
SELECT b.title, COUNT(*) AS late_returnsFROM loans lJOIN books b ON b.book_id = l.book_idWHERE l.return_date > l.due_dateGROUP BY b.book_id, b.titleORDER BY late_returns DESC, b.title;| title | late_returns |
|---|---|
| Deep Work | 3 |
| The Guide | 3 |
| The Pragmatic Programmer | 3 |
| A Brief History of Time | 2 |
| The Alchemist | 2 |
| Atomic Habits | 1 |
| Clean Code | 1 |
| Sapiens | 1 |
| The God of Small Things | 1 |
| Thinking, Fast and Slow | 1 |
| Wings of Fire | 1 |
What you practised
| Task | SQL skills |
|---|---|
| Availability, front-desk view | LEFT JOIN with a condition in ON, COUNT, views |
| Overdue loans and fines | DATEDIFF, COALESCE, GREATEST, CTE, HAVING |
| Popular books, members | GROUP BY, DENSE_RANK() OVER, GROUP_CONCAT |
| Never borrowed | NOT EXISTS, anti-join |
| Issue book | stored procedure, SIGNAL, transaction, FOR UPDATE |
Project FAQ
What tables does a library management system database need?
At least books (the catalogue), members, and loans that link a book to a member with loan, due and return dates. Larger systems add copies, reservations, fines and staff.
How do you calculate overdue fines in SQL?
Compute the days between the due date and the return date (or today, for open loans) with DATEDIFF, clamp negatives to zero with GREATEST, and multiply by the daily rate.
How do you check book availability in SQL?
Subtract the number of open loans (return_date IS NULL) from the number of copies, using a LEFT JOIN so books with no loans are included.