Los cursores nos permiten almacenar una conjunto de filas de una tabla en una estructura de datos que podemos ir recorriendo de forma secuencial.
Los cursores tienen las siguientes propiedades:
Asensitive: The server may or may not make a copy of its result table.
Read only: son de sólo lectura. No permiten actualizar los datos.
Nonscrollable: sólo pueden ser recorridos en una dirección y no podemos saltarnos filas.
Cuando declaramos un cursor dentro de un procedimiento almacenado debe aparecer antes de las declaraciones de los manejadores de errores (HANDLER) y después de la declaración de variables locales.
Las operaciones que podemos hacer con los cursores son las siguientes:
DECLARE
El primer paso que tenemos que hacer para trabajar con cursores es declararlo. La sintaxis para declarar un cursor es:
DECLARE cursor_name CURSOR FOR select_statement
OPEN
Una vez que hemos declarado un cursor tenemos que abrirlo con OPEN.
OPEN cursor_name
FETCH
Una vez que el cursor está abierto podemos ir obteniendo cada una de las filas con FETCH. La sintaxis es la siguiente:
FETCH [[NEXT] FROM] cursor_name INTO var_name [, var_name] …
Cuando se está recorriendo un cursor y no quedan filas por recorrer se lanza el error NOT FOUND, que se corresponde con el valor SQLSTATE ‘02000’. Por eso cuando estemos trabajando con cursores será necesario declarar un handler para manejar este error.
DECLARE CONTINUE HANDLER FOR NOT FOUND …
CLOSE
Cuando hemos terminado de trabajar con un cursor tenemos que cerrarlo.
CLOSE cursor_name
-- Paso 1
DROP DATABASE IF EXISTS test;
CREATE DATABASE test;
USE test;
-- Paso 2
CREATE TABLE t1 (
id INT UNSIGNED PRIMARY KEY,
data VARCHAR(16)
);
CREATE TABLE t2 (
i INT UNSIGNED
);
CREATE TABLE t3 (
data VARCHAR(16),
i INT UNSIGNED
);
INSERT INTO t1 VALUES (1, 'A');
INSERT INTO t1 VALUES (2, 'B');
INSERT INTO t2 VALUES (10);
INSERT INTO t2 VALUES (20);
-- Paso 3
DELIMITER $$
DROP PROCEDURE IF EXISTS curdemo$$
CREATE PROCEDURE curdemo()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE a CHAR(16);
DECLARE b, c INT;
DECLARE cur1 CURSOR FOR SELECT id,data FROM test.t1;
DECLARE cur2 CURSOR FOR SELECT i FROM test.t2;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur1;
OPEN cur2;
read_loop: LOOP
FETCH cur1 INTO b, a;
FETCH cur2 INTO c;
IF done THEN
LEAVE read_loop;
END IF;
IF b < c THEN
INSERT INTO test.t3 VALUES (a,b);
ELSE
INSERT INTO test.t3 VALUES (a,c);
END IF;
END LOOP;
CLOSE cur1;
CLOSE cur2;
END
-- Paso 4
DELIMITER ;
CALL curdemo();
SELECT * FROM t3;
Solución utilizando un bucle WHILE:
DELIMITER $$
DROP PROCEDURE IF EXISTS curdemo$$
CREATE PROCEDURE curdemo()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE a CHAR(16);
DECLARE b, c INT;
DECLARE cur1 CURSOR FOR SELECT id,data FROM test.t1;
DECLARE cur2 CURSOR FOR SELECT i FROM test.t2;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur1;
OPEN cur2;
WHILE done = FALSE DO
FETCH cur1 INTO b, a;
FETCH cur2 INTO c;
IF done = FALSE THEN
IF b < c THEN
INSERT INTO test.t3 VALUES (a,b);
ELSE
INSERT INTO test.t3 VALUES (a,c);
END IF;
END IF;
END WHILE;
CLOSE cur1;
CLOSE cur2;
END;
CREATE DEFINER=`3eva`@`%` PROCEDURE `miprimercursor`()
LANGUAGE SQL
DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT ''
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE valu VARCHAR(50);
DECLARE vmodu VARCHAR(50);
DECLARE vnota INT;
DECLARE micursor CURSOR FOR
SELECT a.nombre as alu, m.nombre AS modu, n.nota
FROM notas n JOIN alumnos a
ON n.fk_alumno = a.id JOIN modulos m
ON n.fk_modulo = m.id
WHERE n.nota>=5;
DECLARE CONTINUE handler FOR NOT found
SET done = TRUE;
OPEN micursor;
fetch micursor INTO valu, vmodu, vnota;
while done = FALSE do
INSERT INTO logs VALUES
(NULL,NOW(),CONCAT_WS(" ",USER(),valu,vmodu,vnota));
fetch micursor INTO valu, vmodu, vnota;
END while;
close micursor;
END