英文:
a library db , the goal is to prevent more than one user trying to check out the same book at the same time Mysql workbench 8.0
问题
我是一个初学者,正在使用MySQL Workbench 8.0,似乎无法使其正常工作。我有三个表格。
这是我的表格:
- 书籍 (id=pk)
id | title | author |
---|---|---|
1 | 我的第一本书 | 莎拉 |
2 | 我的第二本书 | 马曼 |
3 | 我的第三本书 | 巴巴 |
- 用户 (id=pk)
id | full_name | last_login |
---|---|---|
1 | 阿拉 | 1973-07-03 |
2 | 巴迪 | 1972-07-03 |
3 | 萨莉 | 2015-10-18 |
- 借阅记录 (id=pk, user_id=fk, book_id=fk)
id | user_id | book_id | checkout_date | return_date |
---|---|---|---|---|
1 | 1 | 1 | 2000-10-18 | 2017-10-18 |
2 | 1 | 2 | 2015-11-18 | 空 |
3 | 2 | 3 | 2015-10-28 | 2025-10-18 |
4 | 3 | 2 | 2016-10-18 | 应该报错 |
-
user_id=fk 是
users
表格的子表。 -
book_id=fk 是
books
表格的子表。
我的解决方案是:当book_id
列有重复时,return_date
列不应该有重复。
所以在插入第4行时,它不应该允许我;因为用户(3)不能借阅没有return_date
的书籍。
我尝试过在这两列 (book_id
, return_date
) 上建立唯一索引,以便拥有唯一组合,但由于MySQL允许NULL
值重复,我没有取得任何进展。
而且我也不懂PHP或Python。
还有哪些其他解决方案可以尝试?
你的帮助对我来说意义重大
对于我的英文,我很抱歉。
英文:
im a beginner and using mysql workbench8.0 ,and it seems like i cant get this to work. ive got three tables.
these are my tables:
1.books(id=pk)
id | title | author |
---|---|---|
1 | my first book | sara |
2 | my second book | maman |
3 | my third book | baba |
2.users (id=pk)
id | full_name | last_login |
---|---|---|
1 | ala | 1973-07-03 |
2 | badi | 1972-07-03 |
3 | saly | 2015-10-18 |
3.checkouts (id=pk, user_id=fk , book_id=fk)
id | user_id | book_id | checkout_date | return_date |
---|---|---|---|---|
1 | 1 | 1 | 2000-10-18 | 2017-10-18 |
2 | 1 | 2 | 2015-11-18 | null |
3 | 2 | 3 | 2015-10-28 | 2025-10-18 |
4 | 3 | 2 | 2016-10-18 | it should give me an error |
-
user_id=fk is the child table of
users
table. -
book_id=fk is the child table of the
books
table.
My solution is : when there is a duplicate for the book_id
column, there cant be duplicates for the return_date
column.
so when inserting row number 4, its not supposed to let me ; because user (3) cant checkout sth that doesnt have a return_date.
ive tried unique index for those two columns(book_id
,return_date
) so that i can have unique combos , but since MySQL allows duplicates for NULL
values, i didnt get anywhere with that.
and i also still dont know php or python.
what are the other solutions that ill be able to try?
Your help would mean a lot
and sorry for my english
答案1
得分: 0
你可以使用触发器来阻止这种情况。
对于触发器,最终需要添加DELIMITER,依赖于GUI和方法。
在批量插入中,1个错误将阻止会话中所有行的插入。
CREATE TABLE books
(`id` int, `title` varchar(14), `author` varchar(5))
;
INSERT INTO books
(`id`, `title`, `author`)
VALUES
(1, 'my first book', 'sara'),
(2, 'my second book', 'maman'),
(3, 'my third book', 'baba')
;
CREATE TABLE users
(`id` int, `full_name` varchar(4), `last_login` varchar(10))
;
INSERT INTO users
(`id`, `full_name`, `last_login`)
VALUES
(1, 'ala', '1973-07-03'),
(2, 'badi', '1972-07-03'),
(3, 'saly', '2015-10-18')
;
CREATE TABLE checkouts
(`id` int, `user_id` int, `book_id` int, `checkout_date` Date, `return_date` date
)
;
CREATE TRIGGER before_wcheckouts_insert
BEFORE INSERT
ON checkouts FOR EACH ROW
BEGIN
DECLARE msg VARCHAR(100);
IF EXISTS( SELECT 1 FROM checkouts
WHERE `book_id` = NEW.`book_id` AND (`return_date` > NEW.`checkout_date` OR `return_date` IS NULL)) THEN
set msg = "INSERTTriggerError: Trying to checkout book, that isn't returned.";
signal sqlstate '45000' set message_text = msg;
END IF;
END
INSERT INTO checkouts
(`id`, `user_id`, `book_id`, `checkout_date`, `return_date`)
VALUES
(1, 1, 1, '2000-10-18', '2017-10-18'),
(2, 1, 2, '2015-11-18', NULL),
(3, 2, 3, '2015-10-28', '2025-10-18')
;
INSERT INTO checkouts
(`id`, `user_id`, `book_id`, `checkout_date`, `return_date`)
VALUES
(4, 3 , 2 , '2016-10-18', NULL)
error INSERTTriggerError: Trying to checkout book, that isn't returned.
SELECT * FROM checkouts
id | user_id | book_id | checkout_date | return_date |
---|---|---|---|---|
1 | 1 | 1 | 2000-10-18 | 2017-10-18 |
2 | 1 | 2 | 2015-11-18 | null |
3 | 2 | 3 | 2015-10-28 | 2025-10-18 |
英文:
you can use a trigger to prevent that
for the trigger you need eventually to add DELIMITER, depend on gui and method
In bulk insert will 1 error prevent the insertion of all rows ind that session
CREATE TABLE books
(`id` int, `title` varchar(14), `author` varchar(5))
;
INSERT INTO books
(`id`, `title`, `author`)
VALUES
(1, 'my first book', 'sara'),
(2, 'my second book', 'maman'),
(3, 'my third book', 'baba')
;
CREATE TABLE users
(`id` int, `full_name` varchar(4), `last_login` varchar(10))
;
INSERT INTO users
(`id`, `full_name`, `last_login`)
VALUES
(1, 'ala', '1973-07-03'),
(2, 'badi', '1972-07-03'),
(3, 'saly', '2015-10-18')
;
CREATE TABLE checkouts
(`id` int, `user_id` int, `book_id` int, `checkout_date` Date, `return_date` date
)
;
CREATE TRIGGER before_wcheckouts_insert
BEFORE INSERT
ON checkouts FOR EACH ROW
BEGIN
DECLARE msg VARCHAR(100);
IF EXISTS( SELECT 1 FROM checkouts
WHERE `book_id` = NEW.`book_id` AND (`return_date` > NEW.`checkout_date` OR `return_date` IS NULL)) THEN
set msg = "INSERTTriggerError: Trying to checkout book, that isn't returned.";
signal sqlstate '45000' set message_text = msg;
END IF;
END
INSERT INTO checkouts
(`id`, `user_id`, `book_id`, `checkout_date`, `return_date`)
VALUES
(1, 1, 1, '2000-10-18', '2017-10-18'),
(2, 1, 2, '2015-11-18', NULL),
(3, 2, 3, '2015-10-28', '2025-10-18')
;
INSERT INTO checkouts
(`id`, `user_id`, `book_id`, `checkout_date`, `return_date`)
VALUES
(4, 3 , 2 , '2016-10-18', NULL)
> error
> INSERTTriggerError: Trying to checkout book, that isn't returned.
>
SELECT * FROM checkouts
id | user_id | book_id | checkout_date | return_date |
---|---|---|---|---|
1 | 1 | 1 | 2000-10-18 | 2017-10-18 |
2 | 1 | 2 | 2015-11-18 | null |
3 | 2 | 3 | 2015-10-28 | 2025-10-18 |
答案2
得分: 0
你可以创建一个存储过程来执行结帐逻辑。它会锁定表,以便检查书是否已被借阅,然后紧接着的插入将被视为一个"关键部分",只允许一次调用该存储过程运行。除非借书率非常高,否则这不应对性能产生不利影响,并确保两个人不能借阅同一本书。
DROP PROCEDURE IF EXISTS checkout;
DELIMITER $$
CREATE PROCEDURE checkout(IN checkout_user_id, IN checkout_book_id INT, OUT return_code INT)
NOT DETERMINISTIC
SQL SECURITY INVOKER
BEGIN
DECLARE row_count INT;
START TRANSACTION;
/* 书是否已经被借出? */
SELECT COUNT(*) INTO row_count FROM checkouts
WHERE book_id = checkout_book_id AND return_date IS NULL
FOR UPDATE; /* 这实际上锁定了表 */
IF row_count = 0 THEN
INSERT INTO checkouts(user_id, book_id, checkout_date, return_date)
VALUES(checkout_user_id, checkout_book_id, CURRENT_DATE(), NULL);
COMMIT;
SET return_code = 0;
ELSE
/* 书已经被借出。 */
ROLLBACK;
SET return_code = 1;
END IF;
END$$
DELIMITER ;
创建了存储过程后,你可以这样做:
SET @return_code = 0;
/* 用户ID 21想借阅书ID 37: */
CALL checkout(21, 37, @return_code);
/* 0表示成功,否则表示失败: */
SELECT @return_code;
英文:
You could create a stored procedure that does the checkout logic for you. It locks the table so that the check for whether the book has been checked out or not followed by the subsequent insert will be treated as a "critical section" only allowing a single call of the procedure to run at a time. Unless the checkout rate is very high, this should not adversely affect performance and ensures that two individuals cannot check out the same book.
DROP PROCEDURE IF EXISTS checkout;
DELIMITER $$
CREATE PROCEDURE checkout(IN checkout_user_id, IN checkout_book_id INT, OUT return_code INT)
NOT DETERMINISTIC
SQL SECURITY INVOKER
BEGIN
DECLARE row_count INT;
START TRANSACTION;
/* Was the book already checked out? */
SELECT COUNT(*) INTO row_count FROM checkouts
WHERE book_id = checkout_book_id AND return_date IS NULL
FOR UPDATE; /* This essentially locks the table */
IF row_count = 0 THEN
INSERT INTO checkouts(user_id, book_id, checkout_date, return_date)
VALUES(checkout_user_id, checkout_book_id, CURRENT_DATE(), NULL);
COMMIT;
SET return_code = 0;
ELSE
/* Book was already checked out. */
ROLLBACK;
set return_code = 1;
END IF;
END$$
DELIMITER ;
Having created the stored procedure, you could do:
SET @return_code = 0;
/* user_id 21 wants to checkout book_id 37: */
CALL checkout(21, 37, @return_code);
/* 0 for success otherwise failure: */
SELECT @return_code;
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论