first commit
This commit is contained in:
@@ -0,0 +1,469 @@
|
||||
-- 假设当前操作的管理员ID
|
||||
-- 在实际应用中,这个ID会从登录会话中获取
|
||||
-- SELECT admin_id INTO v_admin_id FROM admins WHERE emp_no = 'A001'; -- 示例:获取张管理的ID
|
||||
-- 为了简化示例,我们直接使用数字,假设 admin_id=1 是一个有效管理员
|
||||
-- 请根据你的 admins 表实际数据替换
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- (1). 图书信息管理(增删改查、批量导入)
|
||||
----------------------------------------------------------------------
|
||||
|
||||
-- A. 新增图书 (Create)
|
||||
INSERT INTO books (isbn, title, authors, publisher, publish_date, price, classification_no, location, total_copies, available_copies, description, cover_url)
|
||||
VALUES
|
||||
('9787121408881', 'Effective Java 中文版 (原书第3版)', ARRAY['Joshua Bloch'], '机械工业出版社', '2019-01-01', 119.00, 'TP312JA', 'A区3架5层', 5, 5, 'Java程序员必读的经典著作,包含90个实用条目。', 'http://example.com/cover_effective_java.jpg');
|
||||
|
||||
-- B. 查询图书 (Read)
|
||||
-- B1. 按ISBN精确查询
|
||||
SELECT * FROM books WHERE isbn = '9787111624927';
|
||||
|
||||
-- B2. 按书名模糊查询 (使用 trigram 索引 idx_books_title_trgm)
|
||||
SELECT * FROM books WHERE title % '计算机'; -- 查询包含“计算机”的
|
||||
SELECT * FROM books WHERE title ILIKE '%系统%'; -- 不区分大小写查询包含“系统”的
|
||||
|
||||
-- B3. 按作者查询 (使用 GIN 索引 idx_books_authors_gin)
|
||||
SELECT * FROM books WHERE authors @> ARRAY['吴军']; -- 查询作者包含“吴军”的
|
||||
|
||||
-- B4. 按分类号查询
|
||||
SELECT * FROM books WHERE classification_no = 'TP301';
|
||||
|
||||
-- B5. 查看所有“正常”状态的图书
|
||||
SELECT * FROM books WHERE status = 'normal';
|
||||
|
||||
-- C. 修改图书信息 (Update)
|
||||
-- 假设要修改 book_id 为 1 的图书信息
|
||||
UPDATE books
|
||||
SET
|
||||
price = 145.00,
|
||||
location = 'A区2架3层 (更新)',
|
||||
description = '计算机系统的经典之作,最新修订版。',
|
||||
updated_at = now()
|
||||
WHERE book_id = 1; -- 假设 book_id 为 1 的是《深入理解计算机系统》
|
||||
|
||||
-- 修改图书状态 (例如,将一本损坏的书修复后改为正常)
|
||||
UPDATE books
|
||||
SET status = 'normal', available_copies = available_copies + 1 -- 如果之前因损坏不可借
|
||||
WHERE isbn = '9787208159659' AND status = 'damaged'; -- 假设《三体全集》某本修复
|
||||
|
||||
-- D. 删除图书 (Delete) - 谨慎操作,通常建议逻辑删除或下架
|
||||
-- 注意:如果该书有关联的借阅记录、预约记录、评价记录,且外键设置了 ON DELETE CASCADE,则这些关联记录也会被删除。
|
||||
-- 如果设置了 ON DELETE RESTRICT (或默认),且存在关联记录,则删除会失败。
|
||||
-- 你的表结构中,borrow_records, reservations, reviews 都对 book_id 设置了 ON DELETE CASCADE。
|
||||
-- DELETE FROM books WHERE isbn = '9780132350884'; -- 删除已下架的《Clean Code》
|
||||
|
||||
-- 更好的方式是“下架”
|
||||
UPDATE books
|
||||
SET status = 'removed', available_copies = 0
|
||||
WHERE isbn = '9780132350884';
|
||||
|
||||
-- E. 批量导入图书 (Batch Import)
|
||||
INSERT INTO books (isbn, title, authors, publisher, total_copies, available_copies) VALUES
|
||||
('ISBN001', '批量导入书1', ARRAY['作者X'], '出版社A', 2, 2),
|
||||
('ISBN002', '批量导入书2', ARRAY['作者Y', '作者Z'], '出版社B', 3, 3);
|
||||
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- (2). 学生账户管理
|
||||
----------------------------------------------------------------------
|
||||
-- A. 新增学生账户
|
||||
INSERT INTO students (stu_no, name, gender, department, major, grade, class, phone, email, max_borrow)
|
||||
VALUES
|
||||
('S2024001', '刘新', 'M', '物理学院', '应用物理', '2024', '01班', '13600001111', 'liuxin@example.com', (SELECT setting_value::int FROM system_settings WHERE setting_key='max_borrow_default'));
|
||||
|
||||
-- B. 查询学生账户
|
||||
-- B1. 按学号查询
|
||||
SELECT * FROM students WHERE stu_no = 'S2023001';
|
||||
|
||||
-- B2. 按姓名模糊查询
|
||||
SELECT * FROM students WHERE name LIKE '张%';
|
||||
|
||||
-- B3. 查询某院系所有学生
|
||||
SELECT * FROM students WHERE department = '计算机学院';
|
||||
|
||||
-- C. 修改学生账户信息
|
||||
-- C1. 修改学生联系方式
|
||||
UPDATE students
|
||||
SET phone = '13511112222', email = 'new_zhangsan@example.com'
|
||||
WHERE stu_no = 'S2023001';
|
||||
|
||||
-- C2. 修改学生账户状态 (例如:解挂、冻结/解冻 - 冻结通常由触发器完成,但管理员也可手动操作)
|
||||
-- 解挂
|
||||
UPDATE students SET account_status = 'active' WHERE stu_no = 'S2023005' AND account_status = 'reported';
|
||||
-- 手动冻结 (如果业务需要)
|
||||
UPDATE students SET account_status = 'frozen' WHERE stu_no = 'S2023001';
|
||||
-- 手动解冻 (例如,学生缴清罚款后,触发器未及时更新或有特殊情况)
|
||||
-- 首先确保罚款已清或问题已解决
|
||||
-- UPDATE fines SET status = 'paid' WHERE student_id = (SELECT student_id FROM students WHERE stu_no = 'S2023005') AND status = 'unpaid';
|
||||
-- 然后(如果触发器没有将所有欠款清零后的账户自动激活)
|
||||
-- UPDATE students SET account_status = 'active' WHERE stu_no = 'S2023005' AND account_status = 'frozen';
|
||||
|
||||
-- C3. 修改学生最大借阅量
|
||||
UPDATE students SET max_borrow = 12 WHERE stu_no = 'S2023001';
|
||||
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- (3). 处理借阅、归还、续借请求
|
||||
----------------------------------------------------------------------
|
||||
-- A. 处理借阅请求 (学生在前台操作,管理员后台处理或确认)
|
||||
-- 假设学生 (stu_no='S2023002', 李四) 要借阅图书 (isbn='9787111624927', 深入理解计算机系统)
|
||||
-- 前提检查 (通常在应用层完成,但也可在存储过程中封装):
|
||||
-- 1. 图书可借: SELECT available_copies, status FROM books WHERE isbn = '9787111624927'; (available_copies > 0 and status = 'normal')
|
||||
-- 2. 学生账户正常: SELECT account_status FROM students WHERE stu_no = 'S2023002'; (account_status = 'active')
|
||||
-- 3. 学生未超借阅上限: SELECT current_borrow, max_borrow FROM students WHERE stu_no = 'S2023002'; (current_borrow < max_borrow)
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
v_student_id bigint;
|
||||
v_book_id bigint;
|
||||
v_available_copies int;
|
||||
v_book_status book_status_enum;
|
||||
v_account_status acct_status_enum;
|
||||
v_current_borrow int;
|
||||
v_max_borrow int;
|
||||
v_borrow_duration int;
|
||||
BEGIN
|
||||
SELECT student_id, account_status, current_borrow, max_borrow INTO v_student_id, v_account_status, v_current_borrow, v_max_borrow
|
||||
FROM students WHERE stu_no = 'S2023002';
|
||||
|
||||
SELECT book_id, available_copies, status INTO v_book_id, v_available_copies, v_book_status
|
||||
FROM books WHERE isbn = '9787111624927';
|
||||
|
||||
IF NOT FOUND THEN RAISE EXCEPTION '学生或图书不存在'; END IF;
|
||||
|
||||
IF v_book_status != 'normal' THEN RAISE EXCEPTION '图书状态异常,不可借阅: %', v_book_status; END IF;
|
||||
IF v_available_copies <= 0 THEN RAISE EXCEPTION '图书已无馆藏可借'; END IF;
|
||||
IF v_account_status != 'active' THEN RAISE EXCEPTION '学生账户状态异常,不可借阅: %', v_account_status; END IF;
|
||||
IF v_current_borrow >= v_max_borrow THEN RAISE EXCEPTION '已达到最大借阅量'; END IF;
|
||||
|
||||
SELECT setting_value::int INTO v_borrow_duration FROM system_settings WHERE setting_key = 'borrow_duration_days';
|
||||
|
||||
INSERT INTO borrow_records (book_id, student_id, borrow_date, due_date, status)
|
||||
VALUES (v_book_id, v_student_id, current_date, current_date + (v_borrow_duration || ' days')::interval, 'borrowed');
|
||||
|
||||
RAISE NOTICE '借阅成功! Book ID: %, Student ID: %', v_book_id, v_student_id;
|
||||
-- 触发器 trg_sync_book_student 会自动更新 books.available_copies 和 students.current_borrow
|
||||
END $$;
|
||||
|
||||
-- B. 处理归还请求
|
||||
-- 假设要归还 borrow_id 为 (假设是1,根据实际情况查询得到) 的借阅记录
|
||||
-- SELECT borrow_id FROM borrow_records br JOIN students s ON br.student_id = s.student_id JOIN books b ON br.book_id = b.book_id
|
||||
-- WHERE s.stu_no='S2023001' AND b.isbn='9787111624927' AND br.status IN ('borrowed', 'overdue'); (假设这是张三借的《深入理解计算机系统》)
|
||||
|
||||
-- 获取特定借阅记录ID (例如,张三当前借阅的《深入理解计算机系统》)
|
||||
DO $$
|
||||
DECLARE
|
||||
v_borrow_id bigint;
|
||||
BEGIN
|
||||
SELECT br.borrow_id INTO v_borrow_id
|
||||
FROM borrow_records br
|
||||
JOIN students s ON br.student_id = s.student_id
|
||||
JOIN books b ON br.book_id = b.book_id
|
||||
WHERE s.stu_no = 'S2023001' AND b.isbn = '9787111624927' AND br.status IN ('borrowed', 'overdue')
|
||||
ORDER BY br.borrow_date DESC LIMIT 1; -- 获取最近一次未还的
|
||||
|
||||
IF NOT FOUND THEN
|
||||
RAISE NOTICE '未找到该学生对应的此书的当前借阅记录。';
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
RAISE NOTICE '正在归还 borrow_id: %', v_borrow_id;
|
||||
|
||||
UPDATE borrow_records
|
||||
SET
|
||||
status = 'returned',
|
||||
return_date = current_date
|
||||
WHERE borrow_id = v_borrow_id;
|
||||
-- 触发器 trg_calc_fine 会在逾期归还时自动记录罚款
|
||||
-- 触发器 trg_sync_book_student 会自动更新 books.available_copies 和 students.current_borrow
|
||||
RAISE NOTICE '归还操作完成。';
|
||||
END $$;
|
||||
|
||||
|
||||
-- C. 处理续借请求
|
||||
-- 假设要续借 borrow_id 为 (假设是2,李四借的《数学之美》) 的借阅记录
|
||||
-- 续借条件检查 (应用层或存储过程):
|
||||
-- 1. 图书未被其他人预约: SELECT 1 FROM reservations WHERE book_id = _book_id_ AND status = 'waiting'; (应为空)
|
||||
-- 2. 未超最大续借次数: SELECT renew_times FROM borrow_records WHERE borrow_id = _borrow_id_;
|
||||
-- (renew_times < (SELECT setting_value::int FROM system_settings WHERE setting_key = 'max_renew_times'))
|
||||
-- 3. 学生账户正常,图书未逾期等。
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
v_borrow_id bigint;
|
||||
v_book_id bigint;
|
||||
v_renew_times int;
|
||||
v_max_renew_times int;
|
||||
v_is_reserved int;
|
||||
v_borrow_duration int;
|
||||
v_current_due_date date;
|
||||
v_current_status borrow_status_enum;
|
||||
BEGIN
|
||||
-- 假设李四 (S2023002) 续借《数学之美》 (isbn='9787030598007')
|
||||
SELECT br.borrow_id, br.book_id, br.renew_times, br.due_date, br.status
|
||||
INTO v_borrow_id, v_book_id, v_renew_times, v_current_due_date, v_current_status
|
||||
FROM borrow_records br
|
||||
JOIN students s ON br.student_id = s.student_id
|
||||
JOIN books b ON br.book_id = b.book_id
|
||||
WHERE s.stu_no = 'S2023002' AND b.isbn = '9787030598007' AND br.status = 'borrowed' -- 通常只能续借未逾期的
|
||||
ORDER BY br.borrow_date DESC LIMIT 1;
|
||||
|
||||
IF NOT FOUND THEN RAISE EXCEPTION '未找到该借阅记录或记录状态不符。'; END IF;
|
||||
IF v_current_status = 'overdue' THEN RAISE EXCEPTION '图书已逾期,不可续借,请先处理逾期。'; END IF;
|
||||
|
||||
|
||||
SELECT setting_value::int INTO v_max_renew_times FROM system_settings WHERE setting_key = 'max_renew_times';
|
||||
SELECT setting_value::int INTO v_borrow_duration FROM system_settings WHERE setting_key = 'borrow_duration_days';
|
||||
|
||||
IF v_renew_times >= v_max_renew_times THEN
|
||||
RAISE EXCEPTION '已达到最大续借次数 (%)', v_max_renew_times;
|
||||
END IF;
|
||||
|
||||
SELECT count(*) INTO v_is_reserved FROM reservations
|
||||
WHERE book_id = v_book_id AND status = 'waiting'
|
||||
AND student_id != (SELECT student_id FROM students WHERE stu_no = 'S2023002'); -- 排除自己的预约
|
||||
|
||||
IF v_is_reserved > 0 THEN
|
||||
RAISE EXCEPTION '该图书已被他人预约,不可续借。';
|
||||
END IF;
|
||||
|
||||
UPDATE borrow_records
|
||||
SET
|
||||
due_date = v_current_due_date + (v_borrow_duration || ' days')::interval, -- 从当前应还日期开始延长
|
||||
renew_times = v_renew_times + 1
|
||||
WHERE borrow_id = v_borrow_id;
|
||||
|
||||
RAISE NOTICE '续借成功! Borrow ID: %,新应还日期: %', v_borrow_id, (SELECT due_date FROM borrow_records WHERE borrow_id=v_borrow_id);
|
||||
END $$;
|
||||
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- (4). 管理预约队列
|
||||
----------------------------------------------------------------------
|
||||
-- A. 查看某本图书的预约队列 (例如 book_id = 7, Java核心技术)
|
||||
SELECT r.reservation_id, r.reserve_date, r.status, s.stu_no, s.name as student_name, s.email
|
||||
FROM reservations r
|
||||
JOIN students s ON r.student_id = s.student_id
|
||||
WHERE r.book_id = 7 AND r.status = 'waiting' -- 假设book_id 7是《Java核心技术 卷I》
|
||||
ORDER BY r.reserve_date ASC; -- 按预约日期升序,先预约的在前
|
||||
|
||||
-- B. 处理到书通知 (当一本被预约的书归还后)
|
||||
-- 假设 book_id = 7 的书有副本归还,管理员通知队列中的第一个学生
|
||||
-- B1. 找到第一个等待的学生
|
||||
DO $$
|
||||
DECLARE
|
||||
v_reservation_id bigint;
|
||||
v_student_id bigint;
|
||||
v_book_id int := 7; -- 假设是 Java 核心技术
|
||||
v_reservation_expiry_days int;
|
||||
BEGIN
|
||||
SELECT reservation_id, student_id INTO v_reservation_id, v_student_id
|
||||
FROM reservations
|
||||
WHERE book_id = v_book_id AND status = 'waiting'
|
||||
ORDER BY reserve_date ASC
|
||||
LIMIT 1;
|
||||
|
||||
IF FOUND THEN
|
||||
SELECT setting_value::int INTO v_reservation_expiry_days
|
||||
FROM system_settings WHERE setting_key = 'reservation_expiry_days';
|
||||
|
||||
UPDATE reservations
|
||||
SET status = 'available' -- (可以增加一个 notified_at timestamp 和 available_until timestamp 字段)
|
||||
-- available_until = current_timestamp + (v_reservation_expiry_days || ' days')::interval -- (如果表有此字段)
|
||||
WHERE reservation_id = v_reservation_id;
|
||||
RAISE NOTICE '已通知学生ID % (预约ID %) 图书 % 可取。', v_student_id, v_reservation_id, v_book_id;
|
||||
-- 实际应用中会发送邮件/短信
|
||||
ELSE
|
||||
RAISE NOTICE '图书 % 没有等待中的预约。', v_book_id;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- C. 学生未在规定时间内取书,预约自动或手动过期
|
||||
UPDATE reservations
|
||||
SET status = 'expired'
|
||||
WHERE status = 'available' AND reservation_id = _reservation_id_ ;
|
||||
-- AND available_until < current_timestamp; -- (如果表有 available_until 字段)
|
||||
|
||||
-- D. 管理员取消某个预约 (例如,学生请求取消)
|
||||
UPDATE reservations
|
||||
SET status = 'cancelled'
|
||||
WHERE reservation_id = _reservation_id_ ; -- 替换为实际预约ID
|
||||
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- (5). 处理图书遗失、损坏等异常情况
|
||||
----------------------------------------------------------------------
|
||||
-- A. 处理图书遗失
|
||||
-- 假设学生 (stu_no='S2022006', 周八) 遗失了图书 (isbn='9787559620187', 明朝那些事儿)
|
||||
-- A1. 更新借阅记录状态 (如果该书是被借阅后遗失的)
|
||||
-- 你已经在测试数据中插入了一条 borrow_records 状态为 'lost' 的记录,这里假设是管理员新发现的遗失
|
||||
DO $$
|
||||
DECLARE
|
||||
v_student_id bigint;
|
||||
v_book_id bigint;
|
||||
v_borrow_id bigint;
|
||||
v_book_price numeric;
|
||||
v_admin_id bigint := (SELECT admin_id FROM admins WHERE emp_no = 'A002'); -- 操作管理员ID
|
||||
BEGIN
|
||||
SELECT student_id INTO v_student_id FROM students WHERE stu_no = 'S2022006';
|
||||
SELECT book_id, price INTO v_book_id, v_book_price FROM books WHERE isbn = '9787559620187';
|
||||
|
||||
-- 查找该学生对这本书的未还借阅记录
|
||||
SELECT borrow_id INTO v_borrow_id
|
||||
FROM borrow_records
|
||||
WHERE student_id = v_student_id AND book_id = v_book_id AND status IN ('borrowed', 'overdue')
|
||||
LIMIT 1;
|
||||
|
||||
IF FOUND THEN
|
||||
UPDATE borrow_records SET status = 'lost' WHERE borrow_id = v_borrow_id;
|
||||
RAISE NOTICE '借阅记录 % 已更新为遗失。', v_borrow_id;
|
||||
ELSE
|
||||
RAISE NOTICE '未找到该学生对此书的当前借阅记录,可能是在馆藏中发现遗失。';
|
||||
END IF;
|
||||
|
||||
-- A2. 更新图书信息状态和可借数量 (如果这本书之前是可借的)
|
||||
-- total_copies 一般不变,除非彻底报废且不再补充
|
||||
UPDATE books
|
||||
SET
|
||||
status = 'lost', -- 如果整本书遗失
|
||||
available_copies = GREATEST(0, available_copies - 1) -- 确保可借不为负
|
||||
WHERE book_id = v_book_id AND status != 'lost'; -- 避免重复操作,如果已是lost就不再减available_copies
|
||||
|
||||
-- A3. 生成罚款记录 (按书价赔偿)
|
||||
INSERT INTO fines (student_id, amount, reason, status, issue_date, admin_id)
|
||||
VALUES (v_student_id, v_book_price, '遗失图书《明朝那些事儿》(ISBN:9787559620187)', 'unpaid', current_date, v_admin_id);
|
||||
RAISE NOTICE '遗失罚款已记录。';
|
||||
-- 触发器 trg_freeze_account 会检查是否需要冻结账户
|
||||
-- 触发器 trg_sync_book_student 会因为 borrow_records 更新而调整 current_borrow
|
||||
END $$;
|
||||
|
||||
-- B. 处理图书损坏
|
||||
-- 假设发现馆藏图书 (isbn='9787208159659', 三体全集) 有一本损坏,需要记录并可能罚款最后借阅人
|
||||
DO $$
|
||||
DECLARE
|
||||
v_book_id bigint;
|
||||
v_last_borrower_student_id bigint;
|
||||
v_damage_fine_amount numeric := 20.00; -- 损坏赔偿金额
|
||||
v_admin_id bigint := (SELECT admin_id FROM admins WHERE emp_no = 'A002');
|
||||
BEGIN
|
||||
SELECT book_id INTO v_book_id FROM books WHERE isbn = '9787208159659';
|
||||
|
||||
-- B1. 更新图书状态,如果损坏到不可借阅,减少可借数量
|
||||
UPDATE books
|
||||
SET
|
||||
status = 'damaged',
|
||||
available_copies = GREATEST(0, available_copies - 1) -- 如果损坏导致不可借
|
||||
WHERE book_id = v_book_id;
|
||||
RAISE NOTICE '图书 % 状态已更新为损坏。', v_book_id;
|
||||
|
||||
-- B2. (可选) 查找最后借阅人并生成罚款
|
||||
SELECT student_id INTO v_last_borrower_student_id
|
||||
FROM borrow_records
|
||||
WHERE book_id = v_book_id AND return_date IS NOT NULL
|
||||
ORDER BY return_date DESC
|
||||
LIMIT 1;
|
||||
|
||||
IF FOUND THEN
|
||||
INSERT INTO fines (student_id, amount, reason, status, issue_date, admin_id)
|
||||
VALUES (v_last_borrower_student_id, v_damage_fine_amount, '损坏图书《三体全集》(ISBN:9787208159659)', 'unpaid', current_date, v_admin_id);
|
||||
RAISE NOTICE '已向最后借阅人 (学生ID: %) 记录损坏罚款。', v_last_borrower_student_id;
|
||||
-- 触发器 trg_freeze_account
|
||||
ELSE
|
||||
RAISE NOTICE '未找到该书的最后借阅人,或为馆藏期间损坏。';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- C. 图书下架 (例如,图书过于陈旧或残破不再流通)
|
||||
UPDATE books
|
||||
SET status = 'removed', available_copies = 0
|
||||
WHERE isbn = 'ISBN_TO_BE_REMOVED';
|
||||
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- (6). 设置和管理罚款规则 (主要通过 system_settings 表)
|
||||
----------------------------------------------------------------------
|
||||
-- A. 查看当前罚款规则
|
||||
SELECT * FROM system_settings WHERE setting_key LIKE 'fine%';
|
||||
|
||||
-- B. 修改每日逾期罚款金额
|
||||
UPDATE system_settings
|
||||
SET setting_value = '0.30' -- 修改为每天0.3元
|
||||
WHERE setting_key = 'fine_per_day';
|
||||
|
||||
-- C. 修改欠款冻结账户的阈值
|
||||
UPDATE system_settings
|
||||
SET setting_value = '15.00' -- 修改为欠款15元冻结
|
||||
WHERE setting_key = 'freeze_threshold';
|
||||
|
||||
-- D. 添加新的罚款规则 (如果系统支持更复杂的规则,可能需要修改表结构或增加新表)
|
||||
-- 当前设计下,罚款规则比较简单,主要就是每日费率。
|
||||
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- (7). 生成各类统计报表 (主要使用视图和聚合查询)
|
||||
----------------------------------------------------------------------
|
||||
-- A. 查看热门图书 (借阅量前20,使用视图)
|
||||
SELECT * FROM v_hot_books;
|
||||
|
||||
-- B. 查看各院系借阅统计 (使用视图)
|
||||
SELECT * FROM v_dept_borrow_stats;
|
||||
|
||||
-- C. 查看图书逾期情况 (使用视图)
|
||||
SELECT vd.*, b.title AS book_title, s.name AS student_name, s.phone AS student_phone
|
||||
FROM v_overdue_details vd
|
||||
JOIN books b ON vd.book_id = b.book_id
|
||||
JOIN students s ON vd.student_id = s.student_id;
|
||||
|
||||
-- D. 自定义报表:某一时段内各类图书的借阅次数
|
||||
SELECT
|
||||
b.classification_no,
|
||||
COUNT(br.borrow_id) AS borrow_count
|
||||
FROM borrow_records br
|
||||
JOIN books b ON br.book_id = b.book_id
|
||||
WHERE br.borrow_date BETWEEN '2025-01-01' AND '2025-12-31' -- 示例时间段
|
||||
GROUP BY b.classification_no
|
||||
ORDER BY borrow_count DESC;
|
||||
|
||||
-- E. 自定义报表:每月借阅总量趋势
|
||||
SELECT
|
||||
to_char(borrow_date, 'YYYY-MM') AS borrow_month,
|
||||
COUNT(borrow_id) AS total_borrows
|
||||
FROM borrow_records
|
||||
GROUP BY borrow_month
|
||||
ORDER BY borrow_month;
|
||||
|
||||
-- F. 调用存储过程生成定期报表 (如果 sp_generate_circulation_stats 是设计为手动触发的)
|
||||
-- SELECT sp_generate_circulation_stats();
|
||||
-- 然后查询报表数据
|
||||
-- SELECT * FROM circulation_stats ORDER BY stat_date DESC;
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- (8). 系统参数设置 (主要通过 system_settings 表)
|
||||
----------------------------------------------------------------------
|
||||
-- A. 查看所有系统参数
|
||||
SELECT * FROM system_settings;
|
||||
|
||||
-- B. 修改默认最大借阅量
|
||||
UPDATE system_settings
|
||||
SET setting_value = '8'
|
||||
WHERE setting_key = 'max_borrow_default';
|
||||
-- 注意:这只影响未来新建学生账户的默认值,或在 sp_init_students 中使用此值批量更新。
|
||||
-- 已有学生的 max_borrow 需要单独修改,或在初始化脚本中覆盖。
|
||||
|
||||
-- C. 修改默认借阅期限(天数)
|
||||
UPDATE system_settings
|
||||
SET setting_value = '25'
|
||||
WHERE setting_key = 'borrow_duration_days';
|
||||
|
||||
-- D. 修改最大续借次数
|
||||
UPDATE system_settings
|
||||
SET setting_value = '1'
|
||||
WHERE setting_key = 'max_renew_times';
|
||||
|
||||
-- E. 修改预约保留天数
|
||||
UPDATE system_settings
|
||||
SET setting_value = '2'
|
||||
WHERE setting_key = 'reservation_expiry_days';
|
||||
|
||||
-- F. 添加新的系统参数 (如果应用需要)
|
||||
-- INSERT INTO system_settings (setting_key, setting_value)
|
||||
-- VALUES ('new_feature_xyz_enabled', 'true');
|
||||
@@ -0,0 +1,30 @@
|
||||
-- 查询验证触发器效果
|
||||
---------- 查询验证 ----------
|
||||
|
||||
-- 1. 书籍可借数量和学生当前借阅量 (应由 trg_sync_book_student 更新):
|
||||
SELECT book_id, title, total_copies, available_copies FROM books WHERE book_id <= 7;
|
||||
SELECT student_id, name, max_borrow, current_borrow, account_status FROM students WHERE student_id <= 5;
|
||||
|
||||
-- 2. 孙七 (S2023005) 的罚款记录 (应有 trg_calc_fine 生成的逾期罚款 和 手动添加的罚款):
|
||||
SELECT f.*, s.name as student_name
|
||||
FROM fines f JOIN students s ON f.student_id = s.student_id
|
||||
WHERE s.stu_no = 'S2023005';
|
||||
|
||||
-- 3. 孙七 (S2023005) 的账户状态 (应由 trg_freeze_account 根据总欠款更新):
|
||||
SELECT stu_no, name, account_status, current_borrow
|
||||
FROM students
|
||||
WHERE stu_no = 'S2023005';
|
||||
|
||||
-- 4. 查看热门图书视图:
|
||||
SELECT * FROM v_hot_books;
|
||||
|
||||
-- 5. 查看院系借阅统计视图:
|
||||
SELECT * FROM v_dept_borrow_stats;
|
||||
|
||||
-- 6. 查看逾期详情视图:
|
||||
SELECT od.*, b.title, s.name as student_name
|
||||
FROM v_overdue_details od
|
||||
JOIN books b ON od.book_id = b.book_id
|
||||
JOIN students s ON od.student_id = s.student_id;
|
||||
|
||||
-- 测试数据插入完毕。
|
||||
@@ -0,0 +1,346 @@
|
||||
/* ---------- 基础设置 ---------- */
|
||||
CREATE SCHEMA IF NOT EXISTS library;
|
||||
SET search_path TO library,public;
|
||||
|
||||
/* ---------- ENUM 类型 ---------- */
|
||||
CREATE TYPE gender_enum AS ENUM ('M','F','O');
|
||||
CREATE TYPE book_status_enum AS ENUM ('normal','lost','damaged','removed');
|
||||
CREATE TYPE borrow_status_enum AS ENUM ('borrowed','returned','overdue','lost');
|
||||
CREATE TYPE reserve_status_enum AS ENUM ('waiting','available','cancelled','expired');
|
||||
CREATE TYPE acct_status_enum AS ENUM ('active','reported','frozen');
|
||||
|
||||
/* ---------- 系统参数表 ---------- */
|
||||
CREATE TABLE system_settings (
|
||||
setting_key text PRIMARY KEY,
|
||||
setting_value text NOT NULL
|
||||
);
|
||||
/* 罚款费率、冻结阈值等默认值 */
|
||||
INSERT INTO system_settings(setting_key,setting_value) VALUES
|
||||
('fine_per_day', '0.50'), -- 每本书每日罚款金额
|
||||
('freeze_threshold', '20.00'), -- 欠款 ≥ 此值自动冻结
|
||||
('max_borrow_default', '10'); -- 默认最大借阅量
|
||||
|
||||
/* ---------- 图书信息 ---------- */
|
||||
CREATE TABLE books (
|
||||
book_id bigserial PRIMARY KEY,
|
||||
isbn varchar(13) UNIQUE NOT NULL,
|
||||
title text NOT NULL,
|
||||
authors text[] NOT NULL,
|
||||
publisher text,
|
||||
publish_date date,
|
||||
price numeric(10,2),
|
||||
classification_no varchar(64),
|
||||
location varchar(255),
|
||||
total_copies int NOT NULL DEFAULT 1 CHECK(total_copies>0),
|
||||
available_copies int NOT NULL DEFAULT 1 CHECK(available_copies>=0),
|
||||
status book_status_enum NOT NULL DEFAULT 'normal',
|
||||
description text,
|
||||
cover_url text,
|
||||
created_at timestamptz DEFAULT now(),
|
||||
updated_at timestamptz DEFAULT now()
|
||||
);
|
||||
|
||||
/* ---------- 帮助函数:按键名返回 int 型系统参数 ---------- */
|
||||
CREATE OR REPLACE FUNCTION library.get_setting_int(p_key text)
|
||||
RETURNS int
|
||||
LANGUAGE sql
|
||||
STABLE -- ← 允许做 DEFAULT
|
||||
AS $$
|
||||
SELECT setting_value::int
|
||||
FROM library.system_settings
|
||||
WHERE setting_key = p_key;
|
||||
$$;
|
||||
|
||||
/* ---------- 学生信息 ---------- */
|
||||
CREATE TABLE library.students (
|
||||
student_id bigserial PRIMARY KEY,
|
||||
stu_no varchar(20) UNIQUE NOT NULL, -- 学号
|
||||
name varchar(64) NOT NULL,
|
||||
gender gender_enum,
|
||||
department varchar(128),
|
||||
major varchar(128),
|
||||
grade varchar(10),
|
||||
class varchar(50),
|
||||
phone varchar(20),
|
||||
email varchar(255),
|
||||
account_status acct_status_enum NOT NULL DEFAULT 'active',
|
||||
max_borrow int NOT NULL
|
||||
DEFAULT library.get_setting_int('max_borrow_default'),
|
||||
current_borrow int NOT NULL DEFAULT 0,
|
||||
created_at timestamptz DEFAULT now()
|
||||
);
|
||||
|
||||
/* ---------- 管理员信息 ---------- */
|
||||
CREATE TABLE admins (
|
||||
admin_id bigserial PRIMARY KEY,
|
||||
emp_no varchar(20) UNIQUE NOT NULL,
|
||||
name varchar(64) NOT NULL,
|
||||
position varchar(64),
|
||||
phone varchar(20),
|
||||
privilege_lv int NOT NULL DEFAULT 1,
|
||||
created_at timestamptz DEFAULT now()
|
||||
);
|
||||
|
||||
/* ---------- 借阅记录 ---------- */
|
||||
CREATE TABLE borrow_records (
|
||||
borrow_id bigserial PRIMARY KEY,
|
||||
book_id bigint REFERENCES books(book_id) ON DELETE CASCADE,
|
||||
student_id bigint REFERENCES students(student_id) ON DELETE CASCADE,
|
||||
borrow_date date NOT NULL DEFAULT current_date,
|
||||
due_date date NOT NULL,
|
||||
return_date date,
|
||||
renew_times int NOT NULL DEFAULT 0,
|
||||
status borrow_status_enum NOT NULL DEFAULT 'borrowed',
|
||||
fine_amount numeric(10,2) NOT NULL DEFAULT 0
|
||||
);
|
||||
|
||||
/* ---------- 图书预约 ---------- */
|
||||
CREATE TABLE reservations (
|
||||
reservation_id bigserial PRIMARY KEY,
|
||||
book_id bigint REFERENCES books(book_id) ON DELETE CASCADE,
|
||||
student_id bigint REFERENCES students(student_id) ON DELETE CASCADE,
|
||||
reserve_date date NOT NULL DEFAULT current_date,
|
||||
status reserve_status_enum NOT NULL DEFAULT 'waiting'
|
||||
);
|
||||
|
||||
/* ---------- 图书评价 ---------- */
|
||||
CREATE TABLE reviews (
|
||||
review_id bigserial PRIMARY KEY,
|
||||
book_id bigint REFERENCES books(book_id) ON DELETE CASCADE,
|
||||
student_id bigint REFERENCES students(student_id) ON DELETE CASCADE,
|
||||
rating int NOT NULL CHECK(rating BETWEEN 1 AND 5),
|
||||
content text,
|
||||
review_time timestamptz DEFAULT now(),
|
||||
UNIQUE(book_id,student_id) -- 一人一书仅一次评价
|
||||
);
|
||||
|
||||
/* ---------- 罚款记录 ---------- */
|
||||
CREATE TABLE fines (
|
||||
fine_id bigserial PRIMARY KEY,
|
||||
student_id bigint REFERENCES students(student_id) ON DELETE CASCADE,
|
||||
amount numeric(10,2) NOT NULL CHECK(amount>0),
|
||||
reason text,
|
||||
status varchar(10) NOT NULL CHECK(status IN ('unpaid','paid')),
|
||||
issue_date date NOT NULL DEFAULT current_date,
|
||||
admin_id bigint REFERENCES admins(admin_id)
|
||||
);
|
||||
|
||||
/* ---------- 拓展 ---------- */
|
||||
CREATE EXTENSION IF NOT EXISTS pg_trgm WITH SCHEMA public;
|
||||
|
||||
/* ---------- 索引 ---------- */
|
||||
CREATE INDEX idx_books_title_trgm
|
||||
ON library.books USING gin (title gin_trgm_ops);
|
||||
CREATE INDEX idx_books_authors_gin
|
||||
ON library.books USING gin (authors);
|
||||
CREATE INDEX idx_br_student_status ON borrow_records(student_id,status);
|
||||
CREATE INDEX idx_reserve_book_wait ON reservations(status,book_id);
|
||||
CREATE INDEX idx_fines_student_unpaid ON fines(student_id) WHERE status='unpaid';
|
||||
|
||||
/* 要使用 trigram GIN 索引需启用扩展 */
|
||||
CREATE EXTENSION IF NOT EXISTS pg_trgm;
|
||||
|
||||
/* ---------- 触发器函数 ---------- */
|
||||
|
||||
/* 1. 更新可借数量 & 学生当前借阅量 */
|
||||
CREATE OR REPLACE FUNCTION trg_sync_book_student() RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
-- 更新图书可借册数
|
||||
UPDATE books SET available_copies = total_copies -
|
||||
(SELECT count(*) FROM borrow_records
|
||||
WHERE book_id = COALESCE(NEW.book_id,OLD.book_id)
|
||||
AND status IN ('borrowed','overdue'))
|
||||
WHERE book_id = COALESCE(NEW.book_id,OLD.book_id);
|
||||
|
||||
-- 更新学生当前借阅量
|
||||
UPDATE students SET current_borrow =
|
||||
(SELECT count(*) FROM borrow_records
|
||||
WHERE student_id = COALESCE(NEW.student_id,OLD.student_id)
|
||||
AND status IN ('borrowed','overdue'))
|
||||
WHERE student_id = COALESCE(NEW.student_id,OLD.student_id);
|
||||
|
||||
RETURN NULL;
|
||||
END; $$ LANGUAGE plpgsql;
|
||||
|
||||
/* 2. 归还时自动计算并记账罚款 */
|
||||
CREATE OR REPLACE FUNCTION trg_calc_fine() RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
days_overdue int;
|
||||
rate numeric(10,2) := (SELECT setting_value::numeric FROM system_settings WHERE setting_key='fine_per_day');
|
||||
BEGIN
|
||||
IF TG_OP='UPDATE' AND NEW.status='returned' AND OLD.status IN ('borrowed','overdue') THEN
|
||||
days_overdue := GREATEST((NEW.return_date - NEW.due_date),0);
|
||||
IF days_overdue > 0 THEN
|
||||
INSERT INTO fines(student_id,amount,reason,status,admin_id)
|
||||
VALUES (NEW.student_id, days_overdue*rate,
|
||||
format('Overdue %s days for borrow_id=%s',days_overdue,NEW.borrow_id),
|
||||
'unpaid', NULL);
|
||||
END IF;
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END; $$ LANGUAGE plpgsql;
|
||||
|
||||
/* 3. 欠款超阈值自动冻结账户 */
|
||||
CREATE OR REPLACE FUNCTION library.trg_freeze_account() RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
total_unpaid numeric(10,2);
|
||||
threshold numeric(10,2);
|
||||
v_relevant_student_id bigint;
|
||||
v_current_status library.acct_status_enum;
|
||||
v_new_status library.acct_status_enum;
|
||||
BEGIN
|
||||
-- 1. 获取最新的冻结阈值
|
||||
SELECT setting_value::numeric INTO threshold
|
||||
FROM library.system_settings
|
||||
WHERE setting_key = 'freeze_threshold';
|
||||
|
||||
IF NOT FOUND THEN
|
||||
RAISE WARNING 'System setting "freeze_threshold" not found. Account status will not be updated by trigger.';
|
||||
RETURN NULL; -- 或者 RAISE EXCEPTION;
|
||||
END IF;
|
||||
|
||||
-- 2. 确定相关的学生ID
|
||||
IF TG_OP = 'DELETE' THEN
|
||||
v_relevant_student_id := OLD.student_id;
|
||||
ELSE -- INSERT or UPDATE
|
||||
v_relevant_student_id := NEW.student_id;
|
||||
END IF;
|
||||
|
||||
-- 如果没有相关的学生ID(理论上不应发生,因为student_id是FK且NOT NULL),则退出
|
||||
IF v_relevant_student_id IS NULL THEN
|
||||
RAISE WARNING 'trg_freeze_account: No relevant student_id found. TG_OP: %', TG_OP;
|
||||
RETURN NULL;
|
||||
END IF;
|
||||
|
||||
-- 3. 计算该学生未缴罚款总额
|
||||
SELECT COALESCE(sum(amount),0) INTO total_unpaid
|
||||
FROM library.fines
|
||||
WHERE student_id = v_relevant_student_id AND status = 'unpaid';
|
||||
|
||||
-- 4. 获取学生当前账户状态
|
||||
SELECT account_status INTO v_current_status
|
||||
FROM library.students
|
||||
WHERE student_id = v_relevant_student_id;
|
||||
|
||||
-- 5. 根据罚款确定新的目标状态
|
||||
-- 注意:原始逻辑是,如果欠款低于阈值,则账户变为 'active'。
|
||||
-- 这意味着如果账户之前是 'reported'(挂失),且欠款低于阈值,它也会被此触发器改为 'active'。
|
||||
-- 如果希望 'reported' 状态不受此罚款逻辑影响(除非因欠款被冻结),则需要更复杂的判断。
|
||||
-- 此处保持与你原触发器相似的逻辑,仅修复类型问题并优化。
|
||||
IF total_unpaid >= threshold THEN
|
||||
v_new_status := 'frozen'::library.acct_status_enum;
|
||||
ELSE
|
||||
-- 如果当前已经是 'frozen',则解冻为 'active'
|
||||
-- 如果当前不是 'frozen' (比如是 'active' 或 'reported'),且欠款未超限,则应保持其原状态,
|
||||
-- 而不是都强制变为 'active'。
|
||||
-- 为了更安全地处理 'reported' 状态,我们修改这里的逻辑:
|
||||
IF v_current_status = 'frozen'::library.acct_status_enum THEN
|
||||
v_new_status := 'active'::library.acct_status_enum;
|
||||
ELSE
|
||||
v_new_status := v_current_status; -- 保持现有状态 (active 或 reported)
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
-- 6. 如果计算出的新状态与当前状态不同,则更新学生账户状态
|
||||
IF v_current_status IS DISTINCT FROM v_new_status THEN
|
||||
UPDATE library.students
|
||||
SET account_status = v_new_status
|
||||
WHERE student_id = v_relevant_student_id;
|
||||
END IF;
|
||||
|
||||
RETURN NULL; -- AFTER 触发器通常返回 NULL
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
/* ---------- 触发器绑定 ---------- */
|
||||
CREATE TRIGGER trg_borrow_sync_aiud
|
||||
AFTER INSERT OR UPDATE OR DELETE ON borrow_records
|
||||
FOR EACH ROW EXECUTE FUNCTION trg_sync_book_student();
|
||||
|
||||
CREATE TRIGGER trg_borrow_calc_fine_upd
|
||||
AFTER UPDATE ON borrow_records
|
||||
FOR EACH ROW EXECUTE FUNCTION trg_calc_fine();
|
||||
|
||||
CREATE TRIGGER trg_fine_freeze_aiud
|
||||
AFTER INSERT OR UPDATE OR DELETE ON fines
|
||||
FOR EACH ROW EXECUTE FUNCTION trg_freeze_account();
|
||||
|
||||
/* ---------- 视图 ---------- */
|
||||
|
||||
/* (1) 当前热门图书:借阅量前 20 */
|
||||
CREATE OR REPLACE VIEW v_hot_books AS
|
||||
SELECT b.book_id, b.isbn, b.title,
|
||||
COUNT(br.borrow_id) AS borrow_cnt
|
||||
FROM books b
|
||||
JOIN borrow_records br ON br.book_id = b.book_id
|
||||
GROUP BY b.book_id
|
||||
ORDER BY borrow_cnt DESC
|
||||
LIMIT 20;
|
||||
|
||||
/* (2) 各院系借阅统计 */
|
||||
CREATE OR REPLACE VIEW v_dept_borrow_stats AS
|
||||
SELECT s.department,
|
||||
COUNT(br.borrow_id) AS total_borrows,
|
||||
COUNT(DISTINCT br.student_id) AS unique_readers
|
||||
FROM borrow_records br
|
||||
JOIN students s ON s.student_id = br.student_id
|
||||
GROUP BY s.department
|
||||
ORDER BY total_borrows DESC;
|
||||
|
||||
/* (3) 图书逾期情况 */
|
||||
CREATE OR REPLACE VIEW v_overdue_details AS
|
||||
SELECT br.borrow_id, br.book_id, br.student_id,
|
||||
br.due_date, CURRENT_DATE - br.due_date AS days_overdue
|
||||
FROM borrow_records br
|
||||
WHERE br.status='overdue';
|
||||
|
||||
/* ---------- 存储过程(plpgsql 函数) ---------- */
|
||||
|
||||
/* 1. 学期初批量初始化学生账户
|
||||
- 示例:EXECUTE library.sp_init_students('2025-Fall');
|
||||
*/
|
||||
CREATE OR REPLACE FUNCTION sp_init_students(term_code text)
|
||||
RETURNS void LANGUAGE plpgsql AS $$
|
||||
BEGIN
|
||||
-- 示例逻辑:所有学生 current_borrow 清零、状态激活
|
||||
UPDATE students SET current_borrow=0, account_status='active';
|
||||
RAISE NOTICE 'Students initialized for %', term_code;
|
||||
END; $$;
|
||||
|
||||
/* 2. 定期生成图书流通统计(存入自定义表) */
|
||||
CREATE TABLE IF NOT EXISTS circulation_stats (
|
||||
stat_date date PRIMARY KEY,
|
||||
total_borrows bigint,
|
||||
unique_readers bigint,
|
||||
created_at timestamptz DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE OR REPLACE FUNCTION sp_generate_circulation_stats()
|
||||
RETURNS void LANGUAGE plpgsql AS $$
|
||||
BEGIN
|
||||
INSERT INTO circulation_stats(stat_date,total_borrows,unique_readers)
|
||||
SELECT CURRENT_DATE,
|
||||
(SELECT COUNT(*) FROM borrow_records WHERE borrow_date=CURRENT_DATE),
|
||||
(SELECT COUNT(DISTINCT student_id) FROM borrow_records WHERE borrow_date=CURRENT_DATE);
|
||||
END; $$;
|
||||
|
||||
/* 3. 自动发送逾期提醒(示例写入提醒表) */
|
||||
CREATE TABLE IF NOT EXISTS overdue_notices (
|
||||
notice_id bigserial PRIMARY KEY,
|
||||
borrow_id bigint,
|
||||
student_id bigint,
|
||||
notice_time timestamptz DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE OR REPLACE FUNCTION sp_send_overdue_notices()
|
||||
RETURNS void LANGUAGE plpgsql AS $$
|
||||
BEGIN
|
||||
INSERT INTO overdue_notices(borrow_id,student_id)
|
||||
SELECT br.borrow_id, br.student_id
|
||||
FROM borrow_records br
|
||||
WHERE br.status='overdue'
|
||||
AND NOT EXISTS (SELECT 1 FROM overdue_notices onot WHERE onot.borrow_id=br.borrow_id);
|
||||
END; $$;
|
||||
|
||||
/* ---------- 完成 ---------- */
|
||||
COMMENT ON SCHEMA library IS '智能图书管理系统数据库架构(2025-06-21)';
|
||||
@@ -0,0 +1,208 @@
|
||||
SET search_path TO library, public;
|
||||
|
||||
-- 清理可能存在的旧数据(可选,测试时方便重跑)
|
||||
-- 请注意,CASCADE 会删除关联数据,请谨慎在生产环境使用
|
||||
DELETE FROM fines;
|
||||
DELETE FROM reviews;
|
||||
DELETE FROM reservations;
|
||||
DELETE FROM borrow_records;
|
||||
DELETE FROM admins;
|
||||
DELETE FROM students;
|
||||
DELETE FROM books;
|
||||
DELETE FROM system_settings WHERE setting_key NOT IN ('fine_per_day', 'freeze_threshold', 'max_borrow_default'); -- 保留核心设置
|
||||
|
||||
-- 0. 系统参数 (你已插入,这里可以补充更多,如果需要)
|
||||
INSERT INTO system_settings(setting_key, setting_value) VALUES
|
||||
('max_renew_times', '2'), -- 最大续借次数
|
||||
('borrow_duration_days', '30'), -- 默认借阅时长(天)
|
||||
('reservation_expiry_days', '3'), -- 预约保留天数
|
||||
('admin_default_password_hash', 'xxxx'); -- 示例:管理员默认密码哈希
|
||||
-- ON CONFLICT (setting_key) DO UPDATE SET setting_value = EXCLUDED.setting_value; -- 如果要允许更新
|
||||
|
||||
-- 1. 管理员信息 (Admins)
|
||||
INSERT INTO admins (emp_no, name, position, phone, privilege_lv) VALUES
|
||||
('A001', '张管理', '馆长', '13800138000', 0), -- 最高权限
|
||||
('A002', '李协助', '图书管理员', '13900139000', 1),
|
||||
('A003', '王登记', '流通部职员', '13700137000', 2);
|
||||
|
||||
-- 2. 图书信息 (Books)
|
||||
-- 注意: available_copies 初始设为 total_copies,后续借阅会通过触发器减少
|
||||
INSERT INTO books (isbn, title, authors, publisher, publish_date, price, classification_no, location, total_copies, available_copies, status, description, cover_url) VALUES
|
||||
('9787111624927', '深入理解计算机系统', ARRAY['Randal E. Bryant', 'David R. O''Hallaron'], '机械工业出版社', '2019-05-01', 139.00, 'TP301', 'A区2架3层', 10, 10, 'normal', '计算机系统的经典之作。', 'http://example.com/cover1.jpg'),
|
||||
('9787030598007', '数学之美', ARRAY['吴军'], '科学出版社', '2020-01-01', 68.00, 'O1', 'B区1架1层', 15, 15, 'normal', '通俗易懂的数学科普读物。', 'http://example.com/cover2.jpg'),
|
||||
('9787544270878', '百年孤独', ARRAY['加西亚·马尔克斯'], '南海出版公司', '2011-06-01', 39.50, 'I775.45', 'C区5架2层', 8, 8, 'normal', '魔幻现实主义文学的代表作。', 'http://example.com/cover3.jpg'),
|
||||
('9787115478850', 'Python编程:从入门到实践', ARRAY['Eric Matthes'], '人民邮电出版社', '2018-03-01', 89.00, 'TP312PY', 'A区3架1层', 12, 12, 'normal', 'Python入门畅销书。', 'http://example.com/cover4.jpg'),
|
||||
('9787508647009', '人类简史:从动物到上帝', ARRAY['尤瓦尔·赫拉利'], '中信出版社', '2014-11-01', 68.00, 'K02', 'D区1架1层', 20, 20, 'normal', '一部宏大的人类发展史。', 'http://example.com/cover5.jpg'),
|
||||
('9787532767406', '追风筝的人', ARRAY['卡勒德·胡赛尼'], '上海人民出版社', '2006-05-01', 29.00, 'I712.45', 'C区4架3层', 7, 7, 'normal', '关于爱、友谊、背叛和救赎的故事。', 'http://example.com/cover6.jpg'),
|
||||
('9787121362308', 'Java核心技术 卷I', ARRAY['Cay S. Horstmann'], '电子工业出版社', '2019-06-01', 128.00, 'TP312JA', 'A区3架2层', 5, 0, 'normal', 'Java经典教材,目前全部被借出,用于测试预约。', 'http://example.com/cover7.jpg'), -- 特意设置 available_copies 为 0
|
||||
('9787208159659', '三体全集', ARRAY['刘慈欣'], '重庆出版社', '2019-01-01', 168.00, 'I247.5', 'C区6架1层', 3, 3, 'damaged', '科幻巨作,其中一本有损坏。', 'http://example.com/cover8.jpg'),
|
||||
('9787559620187', '明朝那些事儿 (套装全7册)', ARRAY['当年明月'], '北京联合出版公司', '2017-08-01', 258.00, 'K248', 'D区2架1层', 6, 6, 'normal', '通俗易懂的明史。', 'http://example.com/cover9.jpg'),
|
||||
('9787111600815', '算法导论 (原书第3版)', ARRAY['Thomas H. Cormen'], '机械工业出版社', '2012-12-01', 128.00, 'TP301.6', 'A区2架4层', 9, 9, 'normal', '算法领域的圣经。', 'http://example.com/cover10.jpg'),
|
||||
('9780132350884', 'Clean Code', ARRAY['Robert C. Martin'], 'Prentice Hall', '2008-08-01', 50.00, 'TP311.1', 'A区1架5层', 4, 4, 'removed', '一本已下架的书,测试状态。', 'http://example.com/cover11.jpg');
|
||||
|
||||
|
||||
-- 3. 学生信息 (Students)
|
||||
-- max_borrow 使用默认值(通过 get_setting_int('max_borrow_default') 获取,即10)
|
||||
-- current_borrow 初始为0,会由触发器更新
|
||||
INSERT INTO students (stu_no, name, gender, department, major, grade, class, phone, email, account_status) VALUES
|
||||
('S2023001', '张三', 'M', '计算机学院', '软件工程', '2023', '01班', '13512345671', 'zhangsan@example.com', 'active'),
|
||||
('S2023002', '李四', 'F', '计算机学院', '人工智能', '2023', '02班', '13512345672', 'lisi@example.com', 'active'),
|
||||
('S2022003', '王五', 'M', '外国语学院', '英语', '2022', '01班', '13512345673', 'wangwu@example.com', 'active'),
|
||||
('S2021004', '赵六', 'F', '经济管理学院', '工商管理', '2021', '03班', '13512345674', 'zhaoliu@example.com', 'active'),
|
||||
('S2023005', '孙七', 'M', '计算机学院', '软件工程', '2023', '01班', '13512345675', 'sunqi@example.com', 'reported'), -- 测试挂失状态
|
||||
('S2022006', '周八', 'F', '人文学院', '历史学', '2022', '02班', '13512345676', 'zhouba@example.com', 'active'),
|
||||
('S2021007', '吴九', 'M', '理学院', '数学与应用数学', '2021', '01班', '13512345677', 'wujiu@example.com', 'active'),
|
||||
('S2023008', '郑十', 'F', '计算机学院', '网络工程', '2023', '03班', '13512345678', 'zhengshi@example.com', 'active');
|
||||
|
||||
-- 修改一个学生的 max_borrow,测试非默认值
|
||||
UPDATE students SET max_borrow = 15 WHERE stu_no = 'S2023001';
|
||||
UPDATE students SET max_borrow = 5 WHERE stu_no = 'S2021004'; -- 用于测试借阅上限
|
||||
|
||||
-- 4. 借阅记录 (Borrow Records)
|
||||
-- book_id 和 student_id 需要引用已存在的 ID
|
||||
-- due_date = borrow_date + (SELECT setting_value::int FROM system_settings WHERE setting_key = 'borrow_duration_days')
|
||||
-- fine_amount 初始为0, 逾期归还将由触发器在 fines 表中记录罚款
|
||||
-- 触发器 trg_sync_book_student 会在每次插入/更新/删除后执行
|
||||
|
||||
-- 获取 book_id 和 student_id 的示例 (实际使用时,你可能需要根据书名/学号查询得到)
|
||||
-- SELECT book_id FROM books WHERE isbn = '9787111624927'; -- 1
|
||||
-- SELECT student_id FROM students WHERE stu_no = 'S2023001'; -- 1
|
||||
|
||||
-- 借阅场景 1: 正常借出,未到期
|
||||
INSERT INTO borrow_records (book_id, student_id, borrow_date, due_date, status) VALUES
|
||||
(1, 1, current_date - interval '10 days', current_date - interval '10 days' + interval '30 days', 'borrowed'), -- 张三借《深入理解计算机系统》
|
||||
(2, 2, current_date - interval '5 days', current_date - interval '5 days' + interval '30 days', 'borrowed'); -- 李四借《数学之美》
|
||||
|
||||
-- 借阅场景 2: 正常借出,今天到期
|
||||
INSERT INTO borrow_records (book_id, student_id, borrow_date, due_date, status) VALUES
|
||||
(3, 1, current_date - interval '30 days', current_date, 'borrowed'); -- 张三借《百年孤独》,今天到期
|
||||
|
||||
-- 借阅场景 3: 已逾期,未归还
|
||||
INSERT INTO borrow_records (book_id, student_id, borrow_date, due_date, status) VALUES
|
||||
(4, 3, current_date - interval '40 days', current_date - interval '10 days', 'overdue'); -- 王五借《Python编程》,已逾期10天
|
||||
|
||||
-- 借阅场景 4: 正常归还 (触发器 trg_calc_fine 不会产生罚款)
|
||||
INSERT INTO borrow_records (book_id, student_id, borrow_date, due_date, return_date, status) VALUES
|
||||
(5, 4, current_date - interval '20 days', current_date + interval '10 days', current_date - interval '2 days', 'returned'); -- 赵六借《人类简史》,已按时归还
|
||||
|
||||
-- 借阅场景 5: 逾期归还 (为了测试 trg_calc_fine,先插入为逾期,再UPDATE为归还)
|
||||
-- 步骤A: 先插入一条记录,状态为 'overdue' (或者 'borrowed' 但实际已过 due_date)
|
||||
INSERT INTO borrow_records (book_id, student_id, borrow_date, due_date, status)
|
||||
VALUES (6, 5, current_date - interval '35 days', current_date - interval '5 days', 'overdue'); -- 孙七借《追风筝的人》
|
||||
-- 记录下这条 borrow_id (假设是 6,根据实际情况调整)
|
||||
-- 步骤B: 更新这条记录为 'returned',这将触发 trg_calc_fine
|
||||
-- UPDATE borrow_records
|
||||
-- SET status = 'returned', return_date = current_date
|
||||
-- WHERE borrow_id = (SELECT borrow_id FROM borrow_records WHERE student_id=5 AND book_id=6 AND status='overdue');
|
||||
-- (该update放到下面演示触发器部分)
|
||||
|
||||
-- 借阅场景 6: 书籍遗失
|
||||
INSERT INTO borrow_records (book_id, student_id, borrow_date, due_date, status) VALUES
|
||||
(9, 6, current_date - interval '15 days', current_date + interval '15 days', 'lost'); -- 周八借《明朝那些事儿》,遗失
|
||||
|
||||
|
||||
-- 5. 图书预约 (Reservations)
|
||||
-- 只能预约 available_copies = 0 的书
|
||||
-- (book_id=7, 'Java核心技术 卷I', available_copies 初始为0)
|
||||
INSERT INTO reservations (book_id, student_id, reserve_date, status) VALUES
|
||||
(7, 1, current_date - interval '2 days', 'waiting'), -- 张三预约《Java核心技术》
|
||||
(7, 2, current_date - interval '1 day', 'waiting'); -- 李四预约《Java核心技术》
|
||||
|
||||
-- 假设一本被预约的书有归还了,管理员将其状态改为可取
|
||||
-- UPDATE reservations SET status = 'available' WHERE book_id = 7 AND student_id = 1;
|
||||
|
||||
-- 6. 图书评价 (Reviews)
|
||||
-- 学生通常评价已借阅过的书
|
||||
INSERT INTO reviews (book_id, student_id, rating, content, review_time) VALUES
|
||||
(5, 4, 5, '《人类简史》这本书太棒了,视角独特,值得一读!', now() - interval '1 day'), -- 赵六评价《人类简史》
|
||||
(1, 1, 4, '《深入理解计算机系统》有点难,但很有收获。', now()), -- 张三评价
|
||||
(2, 2, 5, '《数学之美》让我对数学有了新的认识。', now()); -- 李四评价
|
||||
|
||||
-- 尝试插入重复评价(应失败,因为有UNIQUE约束)
|
||||
-- INSERT INTO reviews (book_id, student_id, rating, content, review_time) VALUES
|
||||
-- (5, 4, 3, '第二次评价,内容一般。', now());
|
||||
|
||||
-- 7. 罚款记录 (Fines)
|
||||
-- 部分罚款会由 trg_calc_fine 自动生成 (当逾期借阅记录更新为 'returned' 时)
|
||||
-- 手动添加一些罚款记录:
|
||||
|
||||
-- 罚款场景1: 图书损坏 (假设管理员A002处理)
|
||||
INSERT INTO fines (student_id, amount, reason, status, issue_date, admin_id) VALUES
|
||||
( (SELECT student_id FROM students WHERE stu_no = 'S2022003'), -- 王五
|
||||
20.00,
|
||||
'损坏图书《Python编程》(ISBN:9787115478850)',
|
||||
'unpaid',
|
||||
current_date - interval '1 day',
|
||||
(SELECT admin_id FROM admins WHERE emp_no = 'A002')
|
||||
);
|
||||
|
||||
-- 罚款场景2: 图书遗失 (假设管理员A002处理)
|
||||
-- 对应 borrow_records 中周八遗失的《明朝那些事儿》
|
||||
INSERT INTO fines (student_id, amount, reason, status, issue_date, admin_id) VALUES
|
||||
( (SELECT student_id FROM students WHERE stu_no = 'S2022006'), -- 周八
|
||||
(SELECT price FROM books WHERE isbn = '9787559620187'), -- 按书价赔偿
|
||||
'遗失图书《明朝那些事儿》(ISBN:9787559620187)',
|
||||
'unpaid',
|
||||
current_date,
|
||||
(SELECT admin_id FROM admins WHERE emp_no = 'A002')
|
||||
);
|
||||
|
||||
-- 罚款场景3: 之前的逾期罚款,已缴纳
|
||||
INSERT INTO fines (student_id, amount, reason, status, issue_date, admin_id) VALUES
|
||||
( (SELECT student_id FROM students WHERE stu_no = 'S2021007'), -- 吴九
|
||||
5.50,
|
||||
'图书逾期11天 (旧记录)',
|
||||
'paid',
|
||||
current_date - interval '60 days',
|
||||
(SELECT admin_id FROM admins WHERE emp_no = 'A003')
|
||||
);
|
||||
|
||||
|
||||
----------------------------------------------------
|
||||
-- 测试触发器 trg_calc_fine 和 trg_freeze_account
|
||||
----------------------------------------------------
|
||||
DO $$
|
||||
DECLARE
|
||||
v_student_id_sunqi bigint;
|
||||
v_borrow_id_sunqi bigint;
|
||||
v_fine_per_day numeric;
|
||||
v_days_overdue int;
|
||||
v_expected_fine numeric;
|
||||
BEGIN
|
||||
-- 获取孙七的 student_id
|
||||
SELECT student_id INTO v_student_id_sunqi FROM students WHERE stu_no = 'S2023005';
|
||||
-- 获取孙七之前插入的逾期借阅记录 (book_id=6, 追风筝的人)
|
||||
SELECT borrow_id INTO v_borrow_id_sunqi
|
||||
FROM borrow_records
|
||||
WHERE student_id = v_student_id_sunqi AND book_id = (SELECT book_id FROM books WHERE isbn='9787532767406') AND status = 'overdue';
|
||||
|
||||
RAISE NOTICE '孙七(ID:%)的借阅记录ID: % 将被更新为已归还。', v_student_id_sunqi, v_borrow_id_sunqi;
|
||||
|
||||
-- 模拟孙七归还之前逾期的书 (book_id=6, 《追风筝的人》)
|
||||
-- 这将触发 trg_calc_fine
|
||||
UPDATE borrow_records
|
||||
SET status = 'returned', return_date = current_date
|
||||
WHERE borrow_id = v_borrow_id_sunqi;
|
||||
|
||||
RAISE NOTICE '已更新孙七的借阅记录。现在检查 fines 表是否自动产生罚款记录...';
|
||||
-- trg_calc_fine 会根据 borrow_records 中的 due_date 和 return_date 计算罚款并插入 fines 表
|
||||
-- 假设罚款汇率是 0.5/天,逾期5天,罚款应为 2.50
|
||||
-- (current_date - (current_date - interval '5 days')) * 0.50
|
||||
SELECT setting_value::numeric INTO v_fine_per_day FROM system_settings WHERE setting_key = 'fine_per_day';
|
||||
SELECT (current_date - (SELECT due_date FROM borrow_records WHERE borrow_id = v_borrow_id_sunqi)) INTO v_days_overdue;
|
||||
v_expected_fine := v_days_overdue * v_fine_per_day;
|
||||
RAISE NOTICE '预期罚款金额: % * % = %', v_days_overdue, v_fine_per_day, v_expected_fine;
|
||||
|
||||
-- 检查孙七的账户状态是否因为罚款超过阈值 (默认20) 而被冻结
|
||||
-- trg_freeze_account 会在 fines 表 INSERT/UPDATE/DELETE 后触发
|
||||
RAISE NOTICE '检查孙七账户是否因罚款自动冻结...';
|
||||
-- 如果孙七的罚款 (例如 2.50) 没有超过 freeze_threshold (默认20.00),账户不会冻结。
|
||||
-- 我们再为孙七手动添加一笔大额罚款,使其总欠款超过阈值
|
||||
IF (SELECT sum(amount) FROM fines WHERE student_id = v_student_id_sunqi AND status = 'unpaid') < (SELECT setting_value::numeric FROM system_settings WHERE setting_key = 'freeze_threshold') THEN
|
||||
RAISE NOTICE '孙七当前未付罚款未达冻结阈值,为其添加一笔大额罚款...';
|
||||
INSERT INTO fines (student_id, amount, reason, status, issue_date, admin_id) VALUES
|
||||
(v_student_id_sunqi, 25.00, '严重损坏图书赔偿', 'unpaid', current_date, (SELECT admin_id FROM admins WHERE emp_no='A001'));
|
||||
RAISE NOTICE '已为孙七添加大额罚款,再次检查账户状态。';
|
||||
END IF;
|
||||
|
||||
END $$;
|
||||
@@ -0,0 +1,42 @@
|
||||
1. 数据库设计
|
||||
设计一个关系型数据库,可包含以下实体及属性:
|
||||
(1). 图书信息:ISBN、书名、作者、出版社、出版日期、价格、分类号、馆藏位置、总册数、可借册数、图书状态(正常/遗失/损坏/下架等)、简介、封面图片URL
|
||||
(2). 学生信息:学号、姓名、性别、院系、专业、年级、班级、联系方式、邮箱、账户状态(正常/挂失/冻结)、最大借阅量、当前借阅量
|
||||
(3). 借阅记录:借阅ID、图书ID、学号、借出日期、应还日期、实际归还日期、续借次数、借阅状态(借出/已还/逾期/遗失等)、罚款金额
|
||||
(4). 管理员信息:工号、姓名、职位、联系方式、权限等级
|
||||
(5). 图书预约:预约ID、图书ID、学号、预约日期、预约状态(等待/可取/取消/过期)
|
||||
(6). 图书评价:评价ID、图书ID、学号、评分、评论内容、评论时间
|
||||
(7). 罚款记录:记录ID、学号、金额、产生原因、缴纳状态、产生日期、处理管理员
|
||||
2. 功能需求
|
||||
学生功能:
|
||||
(1). 多条件组合查询图书(按书名、作者、分类、ISBN等)
|
||||
(2). 查看图书详情及当前可借状态
|
||||
(3). 在线预约热门图书(当所有副本都被借出时)
|
||||
(4). 查看个人借阅历史、当前借阅情况和预约状态
|
||||
(5). 在线续借图书(有限制条件)
|
||||
(6). 查看和缴纳罚款
|
||||
(7). 对已借阅图书进行评分和评论
|
||||
(8). 查看图书推荐(如基于热门被借阅图书)
|
||||
管理员功能:
|
||||
(1). 图书信息管理(增删改查、批量导入)
|
||||
(2). 学生账户管理
|
||||
(3). 处理借阅、归还、续借请求
|
||||
(4). 管理预约队列
|
||||
(5). 处理图书遗失、损坏等异常情况
|
||||
(6). 设置和管理罚款规则
|
||||
(7). 生成各类统计报表(借阅量统计、热门图书、逾期分析等)
|
||||
(8). 系统参数设置(如最大借阅量、借阅期限等)
|
||||
3. 高级功能要求
|
||||
(1). 设计触发器实现以下功能:
|
||||
o 自动更新图书的可借数量
|
||||
o 自动计算并记录逾期罚款
|
||||
o 学生账户状态自动更新(如欠款超过阈值自动冻结)
|
||||
(2). 设计存储过程实现:
|
||||
o 批量处理学期初的学生账户初始化
|
||||
o 定期生成图书流通统计报表
|
||||
o 自动发送逾期提醒
|
||||
(3). 设计视图:
|
||||
o 当前热门图书视图(借阅量前20)
|
||||
o 各院系借阅统计视图
|
||||
o 图书逾期情况视图
|
||||
(4). 设计索引优化查询性能(选做)
|
||||
@@ -0,0 +1,340 @@
|
||||
-- 假设当前操作的学生是 张三 (学号 S2023001),我们先获取其 student_id
|
||||
-- 在实际应用中,这个 ID 通常来自登录会话
|
||||
DO $$
|
||||
DECLARE
|
||||
v_student_id bigint;
|
||||
BEGIN
|
||||
SELECT student_id INTO v_student_id FROM students WHERE stu_no = 'S2023001';
|
||||
-- 将 v_student_id 设为会话级变量,方便后续查询使用
|
||||
-- 注意: 这是一种在 psql 或特定SQL工具中设置变量的方式,
|
||||
-- 在应用代码中,你会直接使用从会话中获取的 student_id 变量。
|
||||
EXECUTE 'SET app.current_student_id = ' || v_student_id;
|
||||
EXCEPTION
|
||||
WHEN NO_DATA_FOUND THEN
|
||||
RAISE NOTICE '学生 S2023001 未找到。请确保测试数据已插入。';
|
||||
-- 或者你可以在这里直接硬编码一个存在的 student_id 用于测试
|
||||
-- EXECUTE 'SET app.current_student_id = 1';
|
||||
END $$;
|
||||
|
||||
-- 如果上面的 DO $$ 块由于环境限制无法执行 SET app.current_student_id,
|
||||
-- 后续查询中请手动替换 current_setting('app.current_student_id')::bigint 为实际的 student_id (例如 1)
|
||||
|
||||
-- (1). 多条件组合查询图书
|
||||
RAISE NOTICE '---------- (1) 多条件组合查询图书 ----------';
|
||||
|
||||
-- 按书名模糊查询 (使用 pg_trgm GIN 索引)
|
||||
RAISE NOTICE '-- 按书名模糊查询 "计算机":';
|
||||
SELECT book_id, isbn, title, authors, available_copies
|
||||
FROM books
|
||||
WHERE title LIKE '%计算机%' AND status = 'normal';
|
||||
|
||||
RAISE NOTICE '-- 按书名精确查询 (如果知道全名):';
|
||||
SELECT book_id, isbn, title, authors, available_copies
|
||||
FROM books
|
||||
WHERE title = '数学之美' AND status = 'normal';
|
||||
|
||||
-- 按作者查询 (使用 GIN 索引)
|
||||
RAISE NOTICE '-- 按作者查询包含 "吴军":';
|
||||
SELECT book_id, isbn, title, authors, available_copies
|
||||
FROM books
|
||||
WHERE authors @> ARRAY['吴军'] AND status = 'normal'; -- '@>' 表示数组包含
|
||||
|
||||
-- 按分类号查询
|
||||
RAISE NOTICE '-- 按分类号查询 "TP301":';
|
||||
SELECT book_id, isbn, title, authors, classification_no, available_copies
|
||||
FROM books
|
||||
WHERE classification_no = 'TP301' AND status = 'normal';
|
||||
|
||||
-- 按ISBN精确查询
|
||||
RAISE NOTICE '-- 按ISBN查询 "9787111624927":';
|
||||
SELECT book_id, isbn, title, authors, available_copies
|
||||
FROM books
|
||||
WHERE isbn = '9787111624927' AND status = 'normal';
|
||||
|
||||
-- 组合查询:书名包含 "编程" 且 作者包含 "Eric Matthes"
|
||||
RAISE NOTICE '-- 组合查询:书名包含 "编程" 且 作者包含 "Eric Matthes":';
|
||||
SELECT book_id, isbn, title, authors, available_copies
|
||||
FROM books
|
||||
WHERE title LIKE '%编程%' AND authors @> ARRAY['Eric Matthes'] AND status = 'normal';
|
||||
|
||||
-- (2). 查看图书详情及当前可借状态
|
||||
RAISE NOTICE '---------- (2) 查看图书详情及当前可借状态 ----------';
|
||||
-- 假设查看 ISBN 为 '9787111624927' (深入理解计算机系统) 的图书详情
|
||||
-- 首先获取 book_id
|
||||
DO $$
|
||||
DECLARE
|
||||
v_book_id bigint;
|
||||
BEGIN
|
||||
SELECT book_id INTO v_book_id FROM books WHERE isbn = '9787111624927';
|
||||
EXECUTE 'SET app.current_book_id = ' || v_book_id;
|
||||
END $$;
|
||||
|
||||
RAISE NOTICE '-- 查看图书 (ISBN: 9787111624927) 的详情:';
|
||||
SELECT
|
||||
book_id,
|
||||
isbn,
|
||||
title,
|
||||
authors,
|
||||
publisher,
|
||||
publish_date,
|
||||
price,
|
||||
classification_no,
|
||||
location,
|
||||
total_copies,
|
||||
available_copies,
|
||||
status,
|
||||
description,
|
||||
cover_url,
|
||||
CASE
|
||||
WHEN status = 'normal' AND available_copies > 0 THEN '可借阅'
|
||||
WHEN status = 'normal' AND available_copies = 0 THEN '已全部借出'
|
||||
ELSE '不可借阅 (' || status::text || ')'
|
||||
END as borrow_availability
|
||||
FROM books
|
||||
WHERE book_id = current_setting('app.current_book_id')::bigint;
|
||||
|
||||
-- (3). 在线预约热门图书(当所有副本都被借出时)
|
||||
RAISE NOTICE '---------- (3) 在线预约热门图书 ----------';
|
||||
-- 假设要预约的书是 'Java核心技术 卷I' (book_id=7),其 available_copies 在测试数据中为0
|
||||
-- 首先确认该书确实没有可借副本
|
||||
DO $$
|
||||
DECLARE
|
||||
v_target_book_id bigint := 7; -- Java核心技术 卷I
|
||||
v_available int;
|
||||
BEGIN
|
||||
SELECT available_copies INTO v_available FROM books WHERE book_id = v_target_book_id AND status = 'normal';
|
||||
IF v_available = 0 THEN
|
||||
RAISE NOTICE '-- 图书ID % (Java核心技术 卷I) 当前无可用副本,可以进行预约。';
|
||||
-- 学生张三 (student_id=1) 进行预约
|
||||
-- 检查是否已预约过
|
||||
IF NOT EXISTS (SELECT 1 FROM reservations
|
||||
WHERE book_id = v_target_book_id
|
||||
AND student_id = current_setting('app.current_student_id')::bigint
|
||||
AND status IN ('waiting', 'available'))
|
||||
THEN
|
||||
INSERT INTO reservations (book_id, student_id, reserve_date, status)
|
||||
VALUES (v_target_book_id, current_setting('app.current_student_id')::bigint, current_date, 'waiting');
|
||||
RAISE NOTICE '-- 学生ID % 已成功预约图书ID %。', current_setting('app.current_student_id')::bigint, v_target_book_id;
|
||||
ELSE
|
||||
RAISE NOTICE '-- 学生ID % 已预约过图书ID % 或预约已可取。', current_setting('app.current_student_id')::bigint, v_target_book_id;
|
||||
END IF;
|
||||
ELSE
|
||||
RAISE NOTICE '-- 图书ID % 当前有 % 本可借,无需预约。', v_target_book_id, v_available;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- 查看张三的预约记录来确认
|
||||
RAISE NOTICE '-- 查看学生张三的当前预约:';
|
||||
SELECT r.reservation_id, b.title, r.reserve_date, r.status
|
||||
FROM reservations r
|
||||
JOIN books b ON r.book_id = b.book_id
|
||||
WHERE r.student_id = current_setting('app.current_student_id')::bigint AND r.status IN ('waiting', 'available');
|
||||
|
||||
|
||||
-- (4). 查看个人借阅历史、当前借阅情况和预约状态
|
||||
RAISE NOTICE '---------- (4) 查看个人借阅历史、当前借阅情况和预约状态 ----------';
|
||||
|
||||
RAISE NOTICE '-- 学生张三 (ID: %) 的当前借阅情况:';
|
||||
SELECT br.borrow_id, b.title, b.isbn, br.borrow_date, br.due_date, br.renew_times, br.status
|
||||
FROM borrow_records br
|
||||
JOIN books b ON br.book_id = b.book_id
|
||||
WHERE br.student_id = current_setting('app.current_student_id')::bigint
|
||||
AND br.status IN ('borrowed', 'overdue');
|
||||
|
||||
RAISE NOTICE '-- 学生张三 (ID: %) 的借阅历史 (已还):';
|
||||
SELECT br.borrow_id, b.title, b.isbn, br.borrow_date, br.return_date, br.status
|
||||
FROM borrow_records br
|
||||
JOIN books b ON br.book_id = b.book_id
|
||||
WHERE br.student_id = current_setting('app.current_student_id')::bigint
|
||||
AND br.status = 'returned';
|
||||
|
||||
RAISE NOTICE '-- 学生张三 (ID: %) 的当前预约状态:'; -- 重复上面的预约查询,为了完整性
|
||||
SELECT r.reservation_id, b.title, r.reserve_date, r.status
|
||||
FROM reservations r
|
||||
JOIN books b ON r.book_id = b.book_id
|
||||
WHERE r.student_id = current_setting('app.current_student_id')::bigint AND r.status IN ('waiting', 'available');
|
||||
|
||||
-- (5). 在线续借图书(有限制条件)
|
||||
RAISE NOTICE '---------- (5) 在线续借图书 ----------';
|
||||
-- 假设张三要续借他借阅的《深入理解计算机系统》(book_id=1)
|
||||
-- borrow_id 需要从他的当前借阅中找到,假设其 borrow_id 为 1 (根据你的测试数据)
|
||||
DO $$
|
||||
DECLARE
|
||||
v_borrow_id_to_renew bigint;
|
||||
v_student_id bigint := current_setting('app.current_student_id')::bigint;
|
||||
v_book_id_of_borrow bigint;
|
||||
v_max_renew_times int;
|
||||
v_borrow_duration_days int;
|
||||
v_current_renew_times int;
|
||||
v_current_due_date date;
|
||||
v_current_status borrow_status_enum;
|
||||
v_is_reserved boolean;
|
||||
BEGIN
|
||||
-- 找到张三正在借阅的《深入理解计算机系统》的 borrow_id
|
||||
SELECT br.borrow_id, br.book_id, br.renew_times, br.due_date, br.status
|
||||
INTO v_borrow_id_to_renew, v_book_id_of_borrow, v_current_renew_times, v_current_due_date, v_current_status
|
||||
FROM borrow_records br
|
||||
JOIN books b ON br.book_id = b.book_id
|
||||
WHERE br.student_id = v_student_id
|
||||
AND b.isbn = '9787111624927' -- 《深入理解计算机系统》
|
||||
AND br.status = 'borrowed'
|
||||
LIMIT 1;
|
||||
|
||||
IF NOT FOUND THEN
|
||||
RAISE NOTICE '-- 未找到学生ID % 对图书ISBN 9787111624927 的可续借记录。';
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
RAISE NOTICE '-- 尝试为学生ID % 续借 borrow_id % (Book ID: %)', v_student_id, v_borrow_id_to_renew, v_book_id_of_borrow;
|
||||
|
||||
-- 获取系统参数
|
||||
SELECT setting_value::int INTO v_max_renew_times FROM system_settings WHERE setting_key = 'max_renew_times';
|
||||
SELECT setting_value::int INTO v_borrow_duration_days FROM system_settings WHERE setting_key = 'borrow_duration_days';
|
||||
|
||||
-- 检查条件:
|
||||
-- 1. 状态为 'borrowed' (不能是 'overdue' 或 'lost')
|
||||
IF v_current_status != 'borrowed' THEN
|
||||
RAISE NOTICE '-- 续借失败: 图书状态为 %,不是 "borrowed"。', v_current_status;
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
-- 2. 续借次数未达上限
|
||||
IF v_current_renew_times >= v_max_renew_times THEN
|
||||
RAISE NOTICE '-- 续借失败: 已达到最大续借次数 (%)。', v_max_renew_times;
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
-- 3. 该书没有其他人预约
|
||||
SELECT EXISTS (SELECT 1 FROM reservations WHERE book_id = v_book_id_of_borrow AND status = 'waiting')
|
||||
INTO v_is_reserved;
|
||||
IF v_is_reserved THEN
|
||||
RAISE NOTICE '-- 续借失败: 该图书已被其他用户预约。';
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
-- 执行续借
|
||||
UPDATE borrow_records
|
||||
SET due_date = v_current_due_date + (v_borrow_duration_days || ' days')::interval, -- 从原应还日期开始计算
|
||||
renew_times = renew_times + 1
|
||||
WHERE borrow_id = v_borrow_id_to_renew;
|
||||
|
||||
RAISE NOTICE '-- 续借成功! borrow_id: %, 新的应还日期: %, 当前续借次数: %',
|
||||
v_borrow_id_to_renew,
|
||||
(SELECT due_date FROM borrow_records WHERE borrow_id = v_borrow_id_to_renew),
|
||||
(SELECT renew_times FROM borrow_records WHERE borrow_id = v_borrow_id_to_renew);
|
||||
|
||||
EXCEPTION
|
||||
WHEN NO_DATA_FOUND THEN
|
||||
RAISE NOTICE '-- 未找到相关借阅记录或系统参数。';
|
||||
WHEN OTHERS THEN
|
||||
RAISE NOTICE '续借操作发生错误: %', SQLERRM;
|
||||
END $$;
|
||||
|
||||
-- (6). 查看和缴纳罚款
|
||||
RAISE NOTICE '---------- (6) 查看和缴纳罚款 ----------';
|
||||
-- 假设学生王五 (S2022003, student_id=3) 有未缴罚款
|
||||
DO $$
|
||||
DECLARE
|
||||
v_student_id_wangwu bigint;
|
||||
v_fine_id_to_pay bigint;
|
||||
v_amount_to_pay numeric;
|
||||
BEGIN
|
||||
SELECT student_id INTO v_student_id_wangwu FROM students WHERE stu_no = 'S2022003';
|
||||
IF NOT FOUND THEN
|
||||
RAISE NOTICE '学生 S2022003 未找到。';
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
RAISE NOTICE '-- 查看学生王五 (ID: %) 的未缴罚款:';
|
||||
SELECT fine_id, amount, reason, status, issue_date
|
||||
FROM fines
|
||||
WHERE student_id = v_student_id_wangwu AND status = 'unpaid';
|
||||
|
||||
-- 假设王五要缴纳 fine_id 为 (从上面查询结果中选取一个,例如第一个未缴罚款)
|
||||
SELECT fine_id, amount INTO v_fine_id_to_pay, v_amount_to_pay
|
||||
FROM fines
|
||||
WHERE student_id = v_student_id_wangwu AND status = 'unpaid'
|
||||
ORDER BY issue_date
|
||||
LIMIT 1;
|
||||
|
||||
IF v_fine_id_to_pay IS NOT NULL THEN
|
||||
RAISE NOTICE '-- 模拟缴纳学生王五 (ID: %) 的罚款 fine_id: %, 金额: %', v_student_id_wangwu, v_fine_id_to_pay, v_amount_to_pay;
|
||||
UPDATE fines
|
||||
SET status = 'paid'
|
||||
WHERE fine_id = v_fine_id_to_pay;
|
||||
-- 触发器 trg_freeze_account 会在此 UPDATE 后执行,可能解冻账户
|
||||
|
||||
RAISE NOTICE '-- 缴纳后,再次查看学生王五 (ID: %) 的未缴罚款:';
|
||||
SELECT fine_id, amount, reason, status, issue_date
|
||||
FROM fines
|
||||
WHERE student_id = v_student_id_wangwu AND status = 'unpaid';
|
||||
|
||||
RAISE NOTICE '-- 查看学生王五 (ID: %) 的账户状态 (可能已因罚款缴纳而解冻):';
|
||||
SELECT stu_no, name, account_status FROM students WHERE student_id = v_student_id_wangwu;
|
||||
ELSE
|
||||
RAISE NOTICE '-- 学生王五 (ID: %) 没有未缴罚款可供缴纳。';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- (7). 对已借阅图书进行评分和评论
|
||||
RAISE NOTICE '---------- (7) 对已借阅图书进行评分和评论 ----------';
|
||||
-- 假设张三 (student_id=1) 要对他借阅过的《深入理解计算机系统》(book_id=1)进行评价
|
||||
-- (假设他已经借过或者正在借阅)
|
||||
DO $$
|
||||
DECLARE
|
||||
v_student_id bigint := current_setting('app.current_student_id')::bigint;
|
||||
v_book_id_to_review bigint;
|
||||
BEGIN
|
||||
SELECT book_id INTO v_book_id_to_review FROM books WHERE isbn = '9787111624927'; -- 深入理解计算机系统
|
||||
|
||||
-- 检查是否已经评论过 (因为有 UNIQUE(book_id, student_id) 约束)
|
||||
IF EXISTS (SELECT 1 FROM reviews WHERE student_id = v_student_id AND book_id = v_book_id_to_review) THEN
|
||||
RAISE NOTICE '-- 学生ID % 已评价过图书ID %。';
|
||||
UPDATE reviews
|
||||
SET rating = 5, content = '更新评价:太经典了,常看常新!', review_time = now()
|
||||
WHERE student_id = v_student_id AND book_id = v_book_id_to_review;
|
||||
RAISE NOTICE '-- 已更新学生ID % 对图书ID % 的评价。';
|
||||
ELSE
|
||||
-- 应用层面通常会检查该学生是否实际借阅过这本书,这里我们直接插入
|
||||
INSERT INTO reviews (book_id, student_id, rating, content, review_time)
|
||||
VALUES (v_book_id_to_review, v_student_id, 5, '这本书写得太好了,深入浅出,对我帮助很大!', now());
|
||||
RAISE NOTICE '-- 学生ID % 已成功评价图书ID %。';
|
||||
END IF;
|
||||
|
||||
RAISE NOTICE '-- 查看图书ID % 的所有评价:';
|
||||
SELECT s.name as student_name, r.rating, r.content, r.review_time
|
||||
FROM reviews r
|
||||
JOIN students s ON r.student_id = s.student_id
|
||||
WHERE r.book_id = v_book_id_to_review
|
||||
ORDER BY r.review_time DESC;
|
||||
END $$;
|
||||
|
||||
-- (8). 查看图书推荐(如基于热门被借阅图书)
|
||||
RAISE NOTICE '---------- (8) 查看图书推荐(热门图书) ----------';
|
||||
RAISE NOTICE '-- 查看当前热门图书 (基于 v_hot_books 视图):';
|
||||
SELECT b.isbn, b.title, b.authors, vhb.borrow_cnt
|
||||
FROM v_hot_books vhb
|
||||
JOIN books b ON vhb.book_id = b.book_id
|
||||
ORDER BY vhb.borrow_cnt DESC;
|
||||
|
||||
-- 另一种推荐思路:基于用户所在院系的热门图书
|
||||
RAISE NOTICE '-- 查看学生张三所在院系的热门图书 (示例):';
|
||||
WITH student_department AS (
|
||||
SELECT department FROM students WHERE student_id = current_setting('app.current_student_id')::bigint
|
||||
),
|
||||
department_borrows AS (
|
||||
SELECT
|
||||
br.book_id,
|
||||
COUNT(*) as dept_borrow_count
|
||||
FROM borrow_records br
|
||||
JOIN students s ON br.student_id = s.student_id
|
||||
WHERE s.department = (SELECT department FROM student_department)
|
||||
GROUP BY br.book_id
|
||||
)
|
||||
SELECT b.title, b.authors, db.dept_borrow_count
|
||||
FROM department_borrows db
|
||||
JOIN books b ON db.book_id = b.book_id
|
||||
ORDER BY db.dept_borrow_count DESC
|
||||
LIMIT 10;
|
||||
|
||||
RAISE NOTICE '---------- 学生功能SQL示例演示完毕 ----------';
|
||||
Reference in New Issue
Block a user