10

同じルーチンで 2 つのカーソルを使用するにはどうすればよいですか? 2 番目のカーソル宣言とフェッチ ループを削除すると、すべて正常に動作します。このルーチンは、私の 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

同じルーチンで 2 つのカーソルを使用する方法の簡単な例を次に示します。

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; が必要だということだと思います。そうしないと、2 番目のカーソルは、前のハンドラーからの Done=1 により、ループを終了する前に 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);

2 番目のクエリに関する Paul のコメントを読んだ後に変更を加え、論理を逆にして、挿入によって重複が追加されないようにしました。理想的には、これは複合キー (2 つ以上の列を含む) である主キーとして処理する必要があります。これにより、コードをチェックインする必要がなくなります。

于 2010-01-03T18:13:05.957 に答える
0

うわー、私は何を言うべきかわかりません.SQLについて読んで少し学んでください.不快ではありませんが、これは私が今まで見た中で最悪のSQLの1つです.

SQL は集合ベースの言語であり、一般的にカーソルは良くありません。カーソルが役立つ場合もありますが、かなりまれです。ここでのカーソルの使用はまったく不適切です。

必要な友情だけでなく、友人を含むレコードを選択するため、2 番目のカーソルのロジックにも欠陥があります。

修正したい場合は、2 番目のカーソルに別の名前を付けてみることもできますが、最初からやり直すことをお勧めします。

users_friendsに複合PKまたは一意の制約を設定すると、関係のチェックについて心配する必要がなくなり、次のようなことを試してください。

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