MySQL存储过程,处理多个游标和查询结果
Posted
技术标签:
【中文标题】MySQL存储过程,处理多个游标和查询结果【英文标题】:MySQL stored procedure, handling multiple cursors and query results 【发布时间】:2010-01-03 14:55:50 【问题描述】:如何在同一个例程中使用两个游标?如果我删除第二个游标声明并获取循环一切正常。该例程用于在我的 webapp 中添加朋友。它获取当前用户的 id 和我们想要添加为朋友的朋友的电子邮件,然后检查电子邮件是否具有相应的用户 id,如果不存在朋友关系,它将创建一个。除此以外的任何其他常规解决方案也都很棒。
DROP PROCEDURE IF EXISTS addNewFriend;
DELIMITER //
CREATE PROCEDURE addNewFriend(IN inUserId INT UNSIGNED, IN inFriendEmail VARCHAR(80))
BEGIN
DECLARE tempFriendId INT UNSIGNED DEFAULT 0;
DECLARE tempId INT UNSIGNED DEFAULT 0;
DECLARE done INT DEFAULT 0;
DECLARE cur CURSOR FOR
SELECT id FROM users WHERE email = inFriendEmail;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN cur;
REPEAT
FETCH cur INTO tempFriendId;
UNTIL done = 1 END REPEAT;
CLOSE cur;
DECLARE cur CURSOR FOR
SELECT user_id FROM users_friends WHERE user_id = tempFriendId OR friend_id = tempFriendId;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN cur;
REPEAT
FETCH cur INTO tempId;
UNTIL done = 1 END REPEAT;
CLOSE cur;
IF tempFriendId != 0 AND tempId != 0 THEN
INSERT INTO users_friends (user_id, friend_id) VALUES(inUserId, tempFriendId);
END IF;
SELECT tempFriendId as friendId;
END //
DELIMITER ;
【问题讨论】:
【参考方案1】:下面是一个简单的例子,说明如何在同一个例程中使用两个游标:
DELIMITER $$
CREATE PROCEDURE `books_routine`()
BEGIN
DECLARE rowCountDescription INT DEFAULT 0;
DECLARE rowCountTitle INT DEFAULT 0;
DECLARE updateDescription CURSOR FOR
SELECT id FROM books WHERE description IS NULL OR CHAR_LENGTH(description) < 10;
DECLARE updateTitle CURSOR FOR
SELECT id FROM books WHERE title IS NULL OR CHAR_LENGTH(title) <= 10;
OPEN updateDescription;
BEGIN
DECLARE exit_flag INT DEFAULT 0;
DECLARE book_id INT(10);
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET exit_flag = 1;
updateDescriptionLoop: LOOP
FETCH updateDescription INTO book_id;
IF exit_flag THEN LEAVE updateDescriptionLoop;
END IF;
UPDATE books SET description = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.' WHERE books.id = book_id;
SET rowCountDescription = rowCountDescription + 1;
END LOOP;
END;
CLOSE updateDescription;
OPEN updateTitle;
BEGIN
DECLARE exit_flag INT DEFAULT 0;
DECLARE book_id INT(10);
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET exit_flag = 1;
updateTitleLoop: LOOP
FETCH updateTitle INTO book_id;
IF exit_flag THEN LEAVE updateTitleLoop;
END IF;
UPDATE books SET title = 'Lorem ipsum dolor sit amet' WHERE books.id = book_id;
SET rowCountTitle = rowCountTitle + 1;
END LOOP;
END;
CLOSE updateTitle;
SELECT 'number of titles updated =', rowCountTitle, 'number of descriptions updated =', rowCountDescription;
END
【讨论】:
【参考方案2】:我知道您找到了更好的解决方案,但我相信您最初问题的答案是您需要 SET Done=0;在两个游标之间,否则第二个游标在退出循环之前只会获取一条记录,因为前一个处理程序的 Done=1。
【讨论】:
是的,我现在可以看到了,你是对的。我认为我最终以更好的方式解决了这个问题,没有游标和循环。【参考方案3】:我终于写了一个不同的函数来做同样的事情:
DROP PROCEDURE IF EXISTS addNewFriend;
DELIMITER //
CREATE PROCEDURE addNewFriend(IN inUserId INT UNSIGNED, IN inFriendEmail VARCHAR(80))
BEGIN
SET @tempFriendId = (SELECT id FROM users WHERE email = inFriendEmail);
SET @tempUsersFriendsUserId = (SELECT user_id FROM users_friends WHERE user_id = inUserId AND friend_id = @tempFriendId);
IF @tempFriendId IS NOT NULL AND @tempUsersFriendsUserId IS NULL THEN
INSERT INTO users_friends (user_id, friend_id) VALUES(inUserId, @tempFriendId);
END IF;
SELECT @tempFriendId as friendId;
END //
DELIMITER ;
我希望这是一个更好的解决方案,无论如何它都可以正常工作。感谢您告诉我在不需要时不要使用游标。
【讨论】:
【参考方案4】:你可以在 WHERE 子句中使用 EXISTS 子句,而不是使用游标来检查记录是否存在:
INSERT INTO users_friends
(user_id, friend_id)
VALUES
(inUserId, tempFriendId)
WHERE EXISTS(SELECT NULL
FROM users
WHERE email = inFriendEmail)
AND NOT EXISTS(SELECT NULL
FROM users_friends
WHERE user_id = tempFriendId
AND friend_id = tempFriendId);
在阅读 Paul 的关于第二个查询的 cmets 后,我进行了更改,并颠倒了逻辑,因此插入不会添加重复项。理想情况下,这应该作为复合键(包括两列或更多列)的主键来处理,这样就不需要签入代码。
【讨论】:
【参考方案5】:哇,我不知道该说什么,请去阅读并学习一下 sql,无意冒犯,但这是我见过的最糟糕的 SQL 之一。
SQL 是一种基于集合的语言,游标通常很糟糕,在某些情况下它们很有用,但它们相当少见。您在这里使用游标是完全不合适的。
您在第二个光标中的逻辑也有缺陷,因为它会选择包含朋友的任何记录,而不仅仅是所需的友谊。
如果您想修复它,可以尝试为第二个光标指定一个不同的名称,但最好重新开始。
在 users_friends 上设置复合 PK 或唯一约束,然后您不必担心检查关系,然后尝试这样的操作。
INSERT INTO users_friends
SELECT
@inUserId,
users.user_id
FROM
users
WHERE
email = @inFriendEmail
【讨论】:
感谢您的指导,我是存储过程的新手,并尝试使用以前获得帮助的常规 (***.com/questions/1903189/…),我将尝试找到不同的方法来编写此过程. 是的,你的逻辑是对的,应该是: SELECT user_id FROM users_friends WHERE (user_id = tempFriendId ANDfriend_id = inUserId) OR (friend_id = tempFriendId AND user_id = inUserId);但正如我所说,我会尝试另一种方法。以上是关于MySQL存储过程,处理多个游标和查询结果的主要内容,如果未能解决你的问题,请参考以下文章