2010-01-03 4 views
6

Come posso utilizzare due cursori nella stessa routine? Se rimuovo la seconda dichiarazione del cursore e il ciclo di recupero tutto funziona correttamente. La routine viene utilizzata per aggiungere un amico nella mia webapp. Prende l'id dell'utente corrente e l'e-mail dell'amico che vogliamo aggiungere come amico, quindi controlla se l'e-mail ha un id utente corrispondente e se non esiste alcuna relazione amico ne creerà uno. Qualsiasi altra soluzione di routine rispetto a questa sarebbe ottima.Procedura memorizzata MySQL, gestione di più cursori e risultati delle query

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 ; 

risposta

2

ho finalmente scritto una funzione diversa che fa la stessa 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 ; 

Spero che questa sia una soluzione migliore, funziona benissimo comunque. Grazie per avermi detto di non usare i cursori quando non è necessario.

0

Wow, non so cosa dire, si prega di andare a leggere e imparare un po 'di SQL, senza offesa ma questo è tra i peggiori che abbia mai SQL sembra.

SQL è un linguaggio basato su set, i cursori, in generale, sono negativi, ci sono situazioni in cui sono utili, ma sono abbastanza rari. Il tuo uso dei cursori qui è totalmente inappropriato.

La logica nel secondo cursore è anch'essa difettosa in quanto selezionerà qualsiasi record che includa l'amico, non solo l'amicizia richiesta.

Se si desidera risolvere il problema, provare a dare al secondo cursore un nome diverso, ma preferibilmente ricominciare.

Imposta un PK composto o un vincolo univoco su users_friends, quindi non devi preoccuparti di controllare una relazione, quindi provare qualcosa di simile.

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

Grazie per i vostri sensi, Sono nuovo di stored procedure e ha cercato di utilizzare una rutine precedente ho ottenuto aiuto con (http://stackoverflow.com/questions/1903189/using-select-resultset-to-run-update-query-with-mysql-stored-procedures), cercherò di trovare un diverso approccio per scrivere questa procedura. – Tirithen

+0

E sì, hai ragione riguardo la logica, dovrebbe essere: SELECT user_id FROM users_friends WHERE (user_id = tempFriendId AND friend_id = inUserId) OR (friend_id = tempFriendId AND user_id = inUserId); Ma come ho detto, proverò un modo diverso di farlo. – Tirithen

1

Invece di utilizzare i cursori per verificare l'esistenza di record, è possibile utilizzare la clausola EXISTS nella clausola 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); 

Ho fatto un'alterazione dopo aver letto i commenti di Paolo circa la seconda query, e invertita la logica in modo che l'inserto non aggiunga duplicati. Idealmente questo dovrebbe essere gestito come una chiave primaria essendo una chiave composta (includendo due o più colonne), che fermerebbe la necessità di verificare il codice.

4

So che avete trovato una soluzione migliore, ma credo che la risposta alla vostra domanda iniziale sia che è necessario SET Done = 0; tra i due cursori, altrimenti il ​​secondo cursore recupererà un solo record prima di uscire dal ciclo a causa di Done = 1 dal gestore precedente.

+0

Sì, ora posso vederlo, hai ragione. Penso di aver risolto questo problema in un modo migliore senza cursori e loop. – Tirithen

12

Ecco un semplice esempio di come utilizzare due cursori nella stessa routine:

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 
Problemi correlati