2010-01-03 20 views
6

¿Cómo puedo usar dos cursores en la misma rutina? Si elimino la segunda declaración del cursor y el ciclo de búsqueda todo funciona bien. La rutina se usa para agregar un amigo en mi aplicación web. Toma la identificación del usuario actual y el correo electrónico del amigo que queremos agregar como amigo, luego verifica si el correo electrónico tiene una identificación de usuario correspondiente y si no existe una relación de amistad, creará una. Cualquier otra solución de rutina que esta también sería genial.Procedimiento almacenado de MySQL, manejo de múltiples cursores y resultados de consultas

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 ; 

Respuesta

2

fin he escrito una función diferente que hace la misma cosa:

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 ; 

espero que esta es una solución mejor, que trabaja muy bien de todos modos. Gracias por decirme que no use los cursores cuando no sea necesario.

0

Vaya, no sé qué decir, por favor ir a leer y aprender sobre sql un poco, sin ánimo de ofender, pero este es uno de los peores que he SQL parece.

SQL es un lenguaje basado en conjunto, los cursores, en general, son malos, hay situaciones en las que son útiles, pero son bastante raros. Tu uso de cursores aquí es totalmente inapropiado.

Su lógica en el segundo cursor también es defectuosa, ya que seleccionará cualquier registro que incluya al amigo, no solo la amistad requerida.

Si desea solucionarlo, intente darle al segundo cursor un nombre diferente, pero preferiblemente vuelva a comenzar.

Establezca un PK compuesto o una restricción única en users_friends, entonces no tiene que preocuparse de buscar una relación, intente algo como esto.

INSERT INTO users_friends 
SELECT 
    @inUserId, 
    users.user_id 
FROM 
    users 
WHERE 
    email = @inFriendEmail 
+0

Gracias por sus direcciones, soy nuevo en procedimientos almacenados y trató de utilizar una rutina anterior llegué con ayuda (http://stackoverflow.com/questions/1903189/using-select-resultset-to-run-update-query-with-mysql-stored-procedures), intentaré encontrar un enfoque diferente para escribir este procedimiento. – Tirithen

+0

Y sí, tiene razón acerca de la lógica, debería ser: SELECCIONAR user_id DE users_friends WHERE (user_id = tempFriendId AND friend_id = inUserId) O (friend_id = tempFriendId AND user_id = inUserId); Pero como dije, intentaré una forma diferente de hacerlo. – Tirithen

1

En lugar de utilizar los cursores para comprobar la existencia de registros, puede utilizar la cláusula EXISTS en la cláusula WHERE:

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); 

hice una alteración después de leer los comentarios de Paul sobre la segunda consulta, e invierte la lógica para que el inserto no agregue duplicados. Idealmente, esto debería manejarse como una clave primaria que es una clave compuesta (incluidas dos o más columnas), lo que detendría la necesidad de verificar el código.

4

Sé que ha encontrado una mejor solución, pero creo que la respuesta a su pregunta original es que debe SET Done = 0; entre los dos cursores, de lo contrario, el segundo cursor solo obtendrá un registro antes de salir del ciclo debido a Done = 1 desde el controlador anterior.

+0

Sí, puedo ver esto ahora, tienes razón. Creo que terminé resolviendo esto de una mejor manera sin cursores y bucles. – Tirithen

12

Aquí es un ejemplo sencillo de cómo utilizar dos cursores en la misma rutina:

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 
Cuestiones relacionadas