同じルーチンで2つのカーソルを使用するにはどうすればよいですか? 2番目のカーソル宣言を削除してフェッチループを実行すると、すべて正常に機能します。このルーチンは、私のWebアプリに友達を追加するために使用されます。現在のユーザーの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 ;
私はついに同じことをする別の関数を書きました:
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 ;
これがより良い解決策であることを願っています、とにかくうまくいきます。不要なときはカーソルを使わないように言ってくれてありがとう。
同じルーチンで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
より良い解決策を見つけたと思いますが、元の質問に対する答えは、SET Done = 0にする必要があるということだと思います。 2つのカーソルの間にある場合、前のハンドラーからのDone = 1のため、2番目のカーソルはループを終了する前に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つ以上の列を含む)である主キーとして処理する必要があります。これにより、コードをチェックインする必要がなくなります。
うわー、私は何を言うべきかわかりません、SQLについて少し読んで学んでください、不快ではありませんが、これは私が今まで見た中で最悪のSQLの1つです。
SQLはセットベースの言語であり、カーソルは一般的に悪いものであり、便利な場合もありますが、かなりまれです。ここでのカーソルの使用は完全に不適切です。
2番目のカーソルのロジックにも欠陥があります。これは、必要な友情だけでなく、友人を含むすべてのレコードを選択するためです。
修正したい場合は、2番目のカーソルに別の名前を付けてみてください。ただし、最初からやり直すことをお勧めします。
Users_friendsに複合PKまたは一意の制約を設定すると、関係の確認について心配する必要がなくなり、次のようなことを試してください。
INSERT INTO users_friends
SELECT
@inUserId,
users.user_id
FROM
users
WHERE
email = @inFriendEmail