Aller au contenu

Les curseurs⚓︎

définition⚓︎

Nous avons vu qu'il était possible d'exploiter le résultat d'un SELECT dans un bloc d'instructions en utilisant la commande SELECT colonne(s) INTO variable(s), qui assigne les valeurs sélectionnées à des variables.
Cependant, SELECT ... INTO ne peut être utilisé que pour des requêtes qui ne ramènent qu'une seule ligne de résultats.
Les curseurs permettent de parcourir un jeu de résultats d'une requête SELECT, quel que soit le nombre de lignes récupérées, et d'en exploiter les valeurs.

Quatre étapes sont nécessaires pour utiliser un curseur.
- Déclaration du curseur : avec une instruction DECLARE.
- Ouverture du curseur : on exécute la requête SELECT du curseur et on stocke le résultat dans celui-ci.
- Parcours du curseur : on parcourt une à une les lignes.
- Fermeture du curseur.

Syntaxe⚓︎

Déclaration du curseur⚓︎

Comme toutes les instructions DECLARE, la déclaration d'un curseur doit se faire au début du bloc d'instructions pour lequel celui-ci est défini. Plus précisément, on déclare les curseurs après les variables locales et les conditions, mais avant les gestionnaires d'erreurs.

🐬 SQL
DECLARE nom_curseur CURSOR FOR requete_select;
Un curseur est donc composé d'un nom, et d'une requête SELECT.
Exemple :
DECLARE curseur_client CURSOR 
    FOR SELECT * 
    FROM Client;

Ouverture et fermeture du curseur⚓︎

En déclarant le curseur, on a donc associé un nom et une requête SELECT. L'ouverture du curseur va provoquer l'exécution de la requête SELECT, ce qui va produire un jeu de résultats. Une fois que l'on aura parcouru les résultats, il n'y aura plus qu'à fermer le curseur. Si l'on ne le fait pas explicitement, le curseur sera fermé à la fin du bloc d'un OPEN nom_curseur;

🐬 SQL
  -- Parcours du curseur et instructions diverses
CLOSE nom_curseur;.

Parcours du curseur⚓︎

Une fois que le curseur a été ouvert et le jeu de résultats récupéré, le curseur place un pointeur sur la première ligne de résultats. Avec la commande FETCH, on récupère la ligne sur laquelle pointe le curseur, et on fait avancer le pointeur vers la ligne de résultats suivante. On peut comparer ce fonctionnement avec la lecture de requête en PHP.

🐬 SQL
FETCH nom_curseur INTO variable(s);

Bien entendu, comme pour SELECT ... INTO, il faut donner autant de variables dans la clause INTO que l'on a récupéré de colonnes dans la clause SELECT du curseur.

Exemple : la procédure suivante parcourt les deux premières lignes d’une table Client avec un curseur.

🐬 SQL
DELIMITER |
CREATE PROCEDURE parcours_deux_clients()
BEGIN
    DECLARE v_nom, v_prenom VARCHAR(100);

    DECLARE curs_clients CURSOR
        FOR SELECT nom, prenom  -- Le SELECT récupère deux colonnes
        FROM Client
        ORDER BY nom, prenom;   
        -- On trie les clients par ordre alphabétique

    OPEN curs_clients;  -- Ouverture du curseur

    FETCH curs_clients INTO v_nom, v_prenom;    
    -- On récupère la première ligne et on assigne les valeurs récupérées à nos variables locales
    SELECT CONCAT(v_prenom, ' ', v_nom) AS 'Premier client';

    FETCH curs_clients INTO v_nom, v_prenom;
    -- On récupère la seconde ligne et on assigne les valeurs récupérées à nos variables locales
    SELECT CONCAT(v_prenom, ' ', v_nom) AS 'Second client';

    CLOSE curs_clients;     -- Fermeture du curseur
END|
DELIMITER ;

CALL parcours_deux_clients();

Restrictions⚓︎

FETCH est la seule commande permettant de récupérer une partie d'un jeu de résultats d'un curseur, et elle ne permet qu'une chose : récupérer la ligne de résultats suivante.
Il n'est pas possible de sauter une ou plusieurs lignes ni d'aller rechercher une ligne précédente. On ne peut que parcourir les lignes une à une, de la première à la dernière. Ensuite, il n'est pas possible de modifier une ligne directement à partir d'un curseur. Il s'agit d'une restriction particulière à MySQL. D'autres SGBD vous permettent des requêtes d'UPDATE directement sur les curseurs.

Parcourir intelligemment tous les résultats d'un curseur⚓︎

Pour récupérer une ligne de résultats, on utilise donc FETCH. Dans la procédure parcours_deux_clients(), on voulait récupérer les deux premières lignes, on a donc utilisé deux FETCH. Cependant, la plupart du temps, on ne veut pas seulement utiliser les deux premières lignes, mais toutes ! Or, sauf exception, on ne sait pas combien de lignes seront sélectionnées.

On veut donc parcourir une à une les lignes de résultats et leur appliquer un traitement, sans savoir à l'avance combien de fois ce traitement devra être répété. Pour cela, on utilise une boucle ! WHILE, REPEAT ou LOOP. Il n'y a plus qu'à trouver une condition pour arrêter la boucle une fois tous les résultats parcourus.

Condition d'arrêt⚓︎

Voyons ce qui se passe lorsque l'on fait un FETCH alors qu'il n'y a plus, ou pas, de résultats.
Voici une procédure qui sélectionne les clients selon une ville donnée en paramètre. Les lignes sont récupérées et affichées grâce au FETCH, placé dans une boucle LOOP. Je rappelle que cette boucle ne définit pas de condition d'arrêt : il est nécessaire d'ajouter une instruction LEAVE pour l'arrêter. Ici, pour tester, on ne mettra pas d'instruction LEAVE.

🐬 SQL
DELIMITER |
CREATE PROCEDURE test_condition(IN p_ville VARCHAR(100))
BEGIN
    DECLARE v_nom, v_prenom VARCHAR(100);

    DECLARE curs_clients CURSOR
        FOR SELECT nom, prenom
        FROM Client
        WHERE ville = p_ville;

    OPEN curs_clients;                                    

    LOOP                                                  
        FETCH curs_clients INTO v_nom, v_prenom;                   
        SELECT CONCAT(v_prenom, ' ', v_nom) AS 'Client';
    END LOOP;

    CLOSE curs_clients; 
END|
DELIMITER ;

Exemple : Pour afficher la liste des articles sous la forme : L'article Numéro ........ portant la désignation ………coûte …. …..>

▶️ Ecrire le curseur correspondant

Correction
🐬 SQL
Declare @a int, @b Varchar(10), @c real
Declare Cur_ListeArt Cursor for Select NumArt, DesArt,puart from article
Open Cur_ListeArt
Fetch Next from Cur_ListeArt into @a,@b,@c
While @@fetch_status=0
Begin
Print 'L''article numéro ' + convert(varchar,@a) + ' portant la désignation ' + @b+ ' coûte    ' +     convert(varchar,@c)
Fetch Next from Cur_ListeArt into @a,@b,@c
End
Close Cur_ListeArt
Deallocate Cur_ListeArt