A title is not a lendable object
A catalogue may describe one title while the library owns several physical copies. A member borrows a particular accessioned copy, not the abstract title. Mixing these concepts makes “available” ambiguous and can allow the same copy to be issued twice.
Catalogue
ISBN, title, author and subject describe a work/edition.
Inventory
Accession number and status identify a physical copy.
Circulation
Loan links one member to one copy across time.
Represent valid states explicitly
| Rule | Mechanism | Meaning |
|---|---|---|
| ISBN and accession number are unique | UNIQUE | No duplicate catalogue/copy identity |
| Every copy belongs to a known book | FOREIGN KEY | No orphan inventory |
| Due date is not before issue date | CHECK | Valid loan interval |
| A copy has at most one unreturned loan | Partial UNIQUE index | No double issue |
| Return date is absent until returned | NULL semantics | Open versus closed loan |
Member limits differ for students and faculty and are better represented by a policy table in an extension. “Lost” and “Repair” are copy states independent of title. A real design must define how a lost copy closes or suspends its loan.
Book 1—N copy 1—N loan
Book metadata is stored once. Each copy stores only inventory-specific facts. Loan stores the temporal relationship, so returning a book updates the loan instead of deleting history. This supports borrowing statistics and dispute investigation.
The partial index UNIQUE(copy_id) WHERE returned_on IS NULL is more precise than a general unique copy constraint: one copy may have many historical loans but only one open loan. Partial indexes are SQLite/PostgreSQL-style features; other databases may use a generated column, filtered index or transaction with locking.
Protect invariants under concurrent requests
Issue
- Confirm member is active and within their current policy limit.
- Confirm copy is available.
- Insert an open loan inside a transaction.
- Let uniqueness reject a competing open loan; trigger marks the copy On Loan.
- Commit only if every step succeeds.
Return
- Update the matching open loan with a return date.
- The return trigger marks its copy Available.
- Calculate charges from the policy effective for that loan.
- Commit; if no open row changed, investigate rather than creating a false return.
Choose joins that match the question
Availability begins with book joined to copy because owned copies must exist. Overdue reporting begins with open loan and joins member, copy and book for readable identifiers. Popularity uses LEFT JOIN loan so titles with zero loans are retained.
days overdue = reference date − due date, only when returned_on IS NULL
An illustrative fine of ₹2 per day appears in the demonstration but is not claimed as institutional policy. Production code should store fine rules with effective dates, cap rules and waiver audit. Date calculations and Boolean expressions differ among SQL products, so portability must be planned.
Executable SQLite lifecycle and reports
Loading source…Run with sqlite3 :memory: < library-database.sql. It creates the normalized schema, partial unique index, two synchronization triggers, deterministic records, availability/overdue reports and an atomic return.
Follow loan 101 back to availability
- Observe open loan.
- Open transaction.
- Guard the return.
- Validate chronology.
- Synchronize inventory.
- Publish consistent state.
- Verify postcondition.
Press Next to begin.
Test the lifecycle, not only SELECT output
Multiple physical copies
Double issue
Historical reissue
Invalid chronology
Idempotent return
Zero-loan title
Indexes follow circulation access paths
The unique accession index supports scanning/issue lookup. The partial index both enforces and finds open loans by copy. idx_loan_member_open supports a member’s current/history list. An overdue workload may benefit from an index involving returned_on and due_on; verify selectivity and plans before adding it.
Transactions do not replace durability planning. Use tested backups, point-in-time recovery where required, role-based access, change logs and periodic inventory reconciliation. Barcode input must still be validated; fast incorrect input remains incorrect.
Reason about identity and time
What does loan.copy_id reference?
Why use a partial unique index?
Extensions
- Add reservations with an ordered queue and expiration time.
- Model fine policies by member type and effective date.
- Add acquisitions, suppliers and copy withdrawal history.
- Create monthly circulation and most-borrowed-subject reports.
- Implement safe issue/return procedures in PostgreSQL or MySQL and compare syntax.
Explain why time changes the design
Why separate book and copy?
Shared bibliographic facts describe the title; accession/status describe each lendable unit. Separation prevents duplication and supports correct availability.
Why not delete a loan on return?
Closing it preserves history for analytics, audits and disputes.
What does a partial index do?
It indexes only rows satisfying a predicate; adding UNIQUE enforces uniqueness only inside that subset.
LEFT JOIN purpose in popularity?
It keeps books that have copies but no matching loans, allowing a zero count.
How do transactions prevent double issue?
They make the operation atomic; a unique invariant or appropriate locking resolves concurrent attempts.
Model the physical object and its history separately
A trustworthy library system distinguishes catalogue from inventory, represents open/closed time explicitly and enforces the one-open-loan rule at the database boundary. Transactions coordinate lifecycle changes; carefully chosen joins turn that reliable history into useful reports.
