10

如何在同一个例程中使用两个游标?如果我删除第二个游标声明并获取循环一切正常。该例程用于在我的 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 ;
4

5 回答 5

18

下面是一个简单示例,说明如何在同一例程中使用两个游标:

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
于 2012-04-07T17:10:21.827 回答
4

我知道您找到了更好的解决方案,但我相信您最初问题的答案是您需要 SET Done=0; 在两个游标之间,否则第二个游标将在退出循环之前仅获取一条记录,因为前一个处理程序的 Done=1。

于 2010-06-03T14:12:13.613 回答
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 ;

我希望这是一个更好的解决方案,无论如何它都可以正常工作。感谢您告诉我在不需要时不要使用游标。

于 2010-01-05T20:33:22.850 回答
1

您可以在 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 关于第二个查询的评论后,我进行了更改,并颠倒了逻辑,因此插入不会添加重复项。理想情况下,这应该作为复合键(包括两列或更多列)的主键来处理,这将不再需要签入代码。

于 2010-01-03T18:13:05.957 回答
0

哇,我不知道该说什么,请去阅读并学习一下sql,无意冒犯,但这是我见过的最糟糕的SQL之一。

SQL 是一种基于集合的语言,游标通常很糟糕,在某些情况下它们很有用,但很少见。您在这里使用游标是完全不合适的。

您在第二个光标中的逻辑也有缺陷,因为它会选择包含朋友的任何记录,而不仅仅是所需的友谊。

如果你想修复它,你可以尝试给第二个光标一个不同的名称,但最好重新开始。

在 users_friends 上设置复合 PK 或唯一约束,然后您不必担心检查关系,然后尝试这样的事情。

INSERT INTO users_friends 
SELECT 
    @inUserId, 
    users.user_id
FROM 
    users
WHERE
    email = @inFriendEmail
于 2010-01-03T15:07:47.823 回答