Логички и физички дизајн - Креирање база податоци (со SQL DDL): DDL-Script-Book-Tracker.sql

File DDL-Script-Book-Tracker.sql, 10.9 KB (added by 222039, 4 months ago)
Line 
1-- Drop procedures if they exist
2DROP PROCEDURE IF EXISTS register_user;
3DROP PROCEDURE IF EXISTS delete_book;
4DROP PROCEDURE IF EXISTS add_book;
5DROP PROCEDURE IF EXISTS update_book;
6
7-- Drop tables if they exist
8DROP TABLE IF EXISTS Users CASCADE;
9DROP TABLE IF EXISTS Member CASCADE;
10DROP TABLE IF EXISTS Book CASCADE;
11DROP TABLE IF EXISTS Book_Details CASCADE;
12DROP TABLE IF EXISTS Book_Copies CASCADE;
13DROP TABLE IF EXISTS Book_Author CASCADE;
14DROP TABLE IF EXISTS Author CASCADE;
15DROP TABLE IF EXISTS Loan CASCADE;
16DROP TABLE IF EXISTS Fine CASCADE;
17DROP TABLE IF EXISTS FinePayment CASCADE;
18DROP TABLE IF EXISTS Cart CASCADE;
19
20-- Create procedures first
21CREATE OR REPLACE PROCEDURE register_user(
22 p_username TEXT,
23 p_email TEXT,
24 p_password TEXT,
25 p_role TEXT
26)
27LANGUAGE plpgsql
28AS $$
29BEGIN
30 IF EXISTS (SELECT 1 FROM users WHERE username = p_username OR email = p_email) THEN
31 RAISE EXCEPTION 'Username or Email already exists';
32 END IF;
33
34 -- Insert new user
35 INSERT INTO users (username, email, password, role)
36 VALUES (p_username, p_email, p_password, p_role);
37
38END;
39$$;
40
41CREATE OR REPLACE PROCEDURE delete_book(
42 p_book_id INT
43)
44LANGUAGE plpgsql
45AS $$
46BEGIN
47 IF EXISTS (SELECT 1 FROM Book WHERE bookid = p_book_id) THEN
48 DELETE FROM Book WHERE bookid = p_book_id;
49 END IF;
50END;
51$$;
52
53CREATE OR REPLACE PROCEDURE add_book(
54 p_book_isbn TEXT,
55 p_book_title TEXT,
56 p_book_genre TEXT,
57 p_book_published_year INT,
58 p_book_description TEXT,
59 p_book_total_copies INT,
60 p_book_format TEXT,
61 p_book_language TEXT,
62 p_book_publisher TEXT,
63 p_book_pages INT,
64 p_book_author_id INT,
65 p_book_image TEXT
66)
67LANGUAGE plpgsql
68AS $$
69DECLARE
70 new_book_id INT;
71BEGIN
72 INSERT INTO BOOK (ISBN, Title, Genre, PublishedYear, Description, CoverImage, TotalCopies) VALUES (
73 p_book_isbn, p_book_title, p_book_genre, p_book_published_year, p_book_description, p_book_image, p_book_total_copies
74 ) RETURNING BookID INTO new_book_id;
75
76 INSERT INTO BOOK_DETAILS (BookID, Format, Language, Publisher, Pages) VALUES (
77 new_book_id, p_book_format, p_book_language, p_book_publisher, p_book_pages
78 );
79
80 INSERT INTO Book_Author (BookID, AuthorID) VALUES (
81 new_book_id, p_book_author_id
82 );
83END;
84$$;
85
86CREATE OR REPLACE PROCEDURE update_book(
87 p_book_id INT,
88 p_book_title TEXT,
89 p_book_isbn TEXT,
90 p_book_genre TEXT,
91 p_book_published_year INT,
92 p_book_description TEXT,
93 p_book_total_copies INT,
94 p_book_image TEXT,
95 p_book_language TEXT,
96 p_book_publisher TEXT,
97 p_book_pages INT,
98 p_book_format TEXT
99)
100LANGUAGE plpgsql
101AS $$
102BEGIN
103 UPDATE BOOK SET isbn = p_book_isbn, title = p_book_title, genre = p_book_genre, publishedyear = p_book_published_year,
104 description = p_book_description, totalcopies = p_book_total_copies, coverimage = p_book_image
105 WHERE bookid = p_book_id;
106
107 UPDATE BOOK_DETAILS SET format = p_book_format, language = p_book_language, publisher = p_book_publisher, pages = p_book_pages
108 WHERE bookid = p_book_id;
109END;
110$$;
111
112-- Now create tables
113
114CREATE TABLE Users (
115 UserID SERIAL PRIMARY KEY,
116 Username TEXT NOT NULL,
117 FirstName TEXT,
118 LastName TEXT,
119 Address TEXT,
120 Phone NUMERIC,
121 Password TEXT NOT NULL,
122 Email TEXT NOT NULL,
123 Role TEXT NOT NULL
124);
125
126CREATE TABLE Member (
127 MemberID SERIAL PRIMARY KEY,
128 Expired_Date DATE NOT NULL,
129 Membership_Status TEXT NOT NULL CHECK (Membership_Status IN ('Active', 'Inactive', 'Suspended')),
130 UserID INT REFERENCES Users(UserID) ON DELETE CASCADE
131);
132
133CREATE TABLE Book (
134 BookID SERIAL PRIMARY KEY,
135 ISBN TEXT NOT NULL,
136 Title TEXT NOT NULL,
137 Genre TEXT NOT NULL,
138 PublishedYear NUMERIC NOT NULL,
139 Description TEXT,
140 CoverImage TEXT,
141 TotalCopies NUMERIC DEFAULT 0
142);
143
144CREATE TABLE Book_Details (
145 DetailsID SERIAL PRIMARY KEY,
146 BookID INT REFERENCES Book(BookID) ON DELETE CASCADE,
147 Format TEXT NOT NULL CHECK (Format IN ('Hardcover', 'Paperback')),
148 Language TEXT NOT NULL,
149 Publisher TEXT NOT NULL,
150 Pages TEXT NOT NULL
151);
152
153CREATE TABLE Book_Copies (
154 CopyID SERIAL PRIMARY KEY,
155 BookID INT REFERENCES Book(BookID) ON DELETE CASCADE,
156 Condition TEXT NOT NULL CHECK (Condition IN ('New', 'Good', 'Damaged'))
157);
158
159CREATE TABLE Author (
160 AuthorID SERIAL PRIMARY KEY,
161 FirstName TEXT NOT NULL,
162 LastName TEXT NOT NULL,
163 Nationality TEXT NOT NULL,
164 DateOfBirth DATE NOT NULL,
165 Author_description TEXT,
166 Author_image TEXT
167);
168
169CREATE TABLE Book_Author (
170 BookID INT REFERENCES Book(BookID) ON DELETE CASCADE,
171 AuthorID INT REFERENCES Author(AuthorID) ON DELETE CASCADE,
172 PRIMARY KEY (BookID, AuthorID)
173);
174
175CREATE TABLE Loan (
176 LoanID SERIAL PRIMARY KEY,
177 LoanDate DATE NOT NULL,
178 ReturnDate DATE,
179 Status TEXT NOT NULL,
180 BookCopyID INT REFERENCES Book_Copies(CopyID) ON DELETE CASCADE,
181 MemberID INT REFERENCES Member(MemberID) ON DELETE CASCADE
182);
183
184CREATE TABLE Fine (
185 FineID SERIAL PRIMARY KEY,
186 FineAmount NUMERIC NOT NULL,
187 FineDate DATE NOT NULL,
188 Status TEXT NOT NULL,
189 LoanID INT REFERENCES Loan(LoanID) ON DELETE CASCADE
190);
191
192CREATE TABLE FinePayment (
193 FinePaymentID SERIAL PRIMARY KEY,
194 PaymentDate DATE NOT NULL,
195 PaymentAmount NUMERIC NOT NULL,
196 FineID INT REFERENCES Fine(FineID) ON DELETE CASCADE
197);
198
199CREATE TABLE Cart (
200 CartID SERIAL PRIMARY KEY,
201 BookID INT REFERENCES Book(BookID) ON DELETE CASCADE,
202 MemberID INT REFERENCES Member(MemberID) ON DELETE CASCADE
203);
204
205-- Create the function and trigger for total copies
206CREATE OR REPLACE FUNCTION update_total_copies()
207RETURNS TRIGGER AS $$
208BEGIN
209 -- If a new copy is inserted, increment the total copies count
210 IF TG_OP = 'INSERT' THEN
211 UPDATE Book
212 SET TotalCopies = TotalCopies + 1
213 WHERE BookID = NEW.BookID;
214 -- If a copy is deleted, decrement the total copies count
215 ELSIF TG_OP = 'DELETE' THEN
216 UPDATE Book
217 SET TotalCopies = TotalCopies - 1
218 WHERE BookID = OLD.BookID;
219 END IF;
220 RETURN NEW;
221END;
222$$ LANGUAGE plpgsql;
223
224CREATE TRIGGER trigger_update_total_copies
225AFTER INSERT OR DELETE ON Book_Copies
226FOR EACH ROW EXECUTE FUNCTION update_total_copies();
227
228CREATE OR REPLACE FUNCTION validate_login(
229 p_username TEXT
230) RETURNS users -- Returns a full row from the users table
231LANGUAGE plpgsql
232AS $$
233DECLARE
234 user_record users; -- Variable to store the row
235BEGIN
236 -- Check if the user exists
237 IF NOT EXISTS (SELECT 1 FROM users WHERE username = p_username) THEN
238 RAISE EXCEPTION 'Username does not exist!';
239 END IF;
240
241 -- Fetch the user row
242 SELECT * INTO user_record FROM users WHERE username = p_username;
243
244 RETURN user_record;
245END;
246$$;
247
248CREATE INDEX idx_author_firstname ON author(LOWER(firstname));
249CREATE INDEX idx_author_lastname ON author(LOWER(lastname));
250CREATE INDEX idx_member_userid ON member(userid);
251
252CREATE OR REPLACE FUNCTION get_genre_loan_stats()
253RETURNS TABLE (
254 Genre text,
255 Period date,
256 LoanCount integer,
257 MovingAverage numeric
258)
259LANGUAGE SQL
260STABLE
261AS $$
262WITH TimePeriodLoans AS (
263 SELECT
264 b.Genre,
265 l.LoanDate,
266 DATE_TRUNC('quarter', l.LoanDate) as QuarterPeriod
267 FROM
268 Book b
269 JOIN Book_Copies bc ON b.BookID = bc.BookID
270 JOIN Loan l ON bc.CopyID = l.BookCopyID
271 WHERE
272 l.LoanDate >= DATE_TRUNC('quarter', CURRENT_DATE) - INTERVAL '6 months'
273)
274SELECT
275 Genre::text,
276 Period::date,
277 LoanCount::integer,
278 ROUND(MovingAverage, 2)::numeric
279FROM (
280 SELECT
281 Genre,
282 QuarterPeriod as Period,
283 COUNT(*) as LoanCount,
284 AVG(COUNT(*)) OVER (
285 PARTITION BY Genre
286 ORDER BY QuarterPeriod
287 ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
288 ) as MovingAverage
289 FROM TimePeriodLoans
290 GROUP BY Genre, QuarterPeriod
291 HAVING COUNT(*) > 3 -- Filter for genres with more than 3 loans per period
292) subquery
293ORDER BY
294 Period DESC, LoanCount DESC;
295$$;
296
297CREATE OR REPLACE FUNCTION get_book_loan_stats()
298RETURNS TABLE (
299 Title text,
300 AvgDaysToReturn numeric(10,2),
301 MinDaysToReturn integer,
302 MaxDaysToReturn integer,
303 TotalLoans integer,
304 LoansWithFines integer
305)
306LANGUAGE SQL
307STABLE
308AS $$
309SELECT
310 b.Title::text,
311 AVG(l.ReturnDate - l.LoanDate)::numeric(10,2) AS AvgDaysToReturn,
312 MIN(l.ReturnDate - l.LoanDate)::integer AS MinDaysToReturn,
313 MAX(l.ReturnDate - l.LoanDate)::integer AS MaxDaysToReturn,
314 COUNT(*)::integer AS TotalLoans,
315 COUNT(CASE WHEN f.FineID IS NOT NULL THEN 1 END)::integer AS LoansWithFines
316FROM
317 Loan l
318 JOIN Book_Copies bc ON l.BookCopyID = bc.CopyID
319 JOIN Book b ON bc.BookID = b.BookID
320 LEFT JOIN Fine f ON l.LoanID = f.LoanID
321WHERE
322 l.ReturnDate IS NOT NULL
323 AND l.ReturnDate >= l.LoanDate -- Prevent negative days
324GROUP BY
325 b.BookID, b.Title
326HAVING
327 COUNT(*) >= 5
328ORDER BY
329 AvgDaysToReturn DESC;
330$$;
331
332CREATE OR REPLACE FUNCTION get_member_history()
333RETURNS TABLE (
334 MemberName text,
335 MembershipStatus text,
336 StartDate date,
337 EndDate date,
338 TotalLoans integer,
339 CurrentLoans integer,
340 TotalFines integer,
341 TotalFineAmount numeric,
342 TotalPayments numeric
343)
344LANGUAGE plpgsql
345STABLE
346AS $$
347BEGIN
348 RETURN QUERY
349 WITH DateRanges AS (
350 SELECT
351 generate_series(
352 date_trunc('year', CURRENT_DATE),
353 date_trunc('month', CURRENT_DATE),
354 interval '1 month'
355 ) as start_date,
356 generate_series(
357 date_trunc('year', CURRENT_DATE) + interval '1 month',
358 date_trunc('month', CURRENT_DATE) + interval '1 month',
359 interval '1 month'
360 ) as end_date
361 )
362 SELECT
363 u.FirstName || ' ' || u.LastName::text AS MemberName,
364 m.Membership_Status::text AS MembershipStatus,
365 d.start_date::date AS StartDate,
366 d.end_date::date AS EndDate,
367 COUNT(DISTINCT l.LoanID)::integer AS TotalLoans,
368 COUNT(DISTINCT CASE WHEN l.ReturnDate IS NULL THEN l.LoanID END)::integer AS CurrentLoans,
369 COUNT(DISTINCT f.FineID)::integer AS TotalFines,
370 COALESCE(SUM(f.FineAmount), 0)::numeric AS TotalFineAmount,
371 COALESCE(SUM(fp.PaymentAmount), 0)::numeric AS TotalPayments
372 FROM
373 DateRanges d
374 CROSS JOIN Member m
375 JOIN Users u ON m.UserID = u.UserID
376 LEFT JOIN Loan l ON m.MemberID = l.MemberID
377 AND l.LoanDate >= d.start_date
378 AND l.LoanDate < d.end_date
379 LEFT JOIN Fine f ON l.LoanID = f.LoanID
380 LEFT JOIN FinePayment fp ON f.FineID = fp.FineID
381 GROUP BY
382 u.FirstName, u.LastName, m.Membership_Status, d.start_date, d.end_date
383 ORDER BY
384 d.start_date DESC, TotalLoans DESC;
385END;
386$$;