Documente Academic
Documente Profesional
Documente Cultură
9.- CURSORES.
Normalmente los procedimientos implican la manipulacin de BD pudindose utilizar cualquier
instruccin SQL, es decir, pertenecientes al DDL, DML o DCL.
Ejemplo:
DELIMITER $$
DROP PROCEDURE IF EXISTS simple_sql$$
CREATE PROCEDURE simple_sql( )
BEGIN
DECLARE i INT;
SET i = 1;
DROP TABLE IF EXISTS testtable;
CREATE TABLE testtable ( id INT PRIMARY KEY, dato VARCHAR(30) );
WHILE i <= 10 DO
INSERT INTO testtable VALUES (i, CONCAT (registro , i) ) ;
SET i = i + 1;
END WHILE;
SET i = 5;
UPDATE testtable SET dato = CONCAT (actualizado , i) WHERE id = i;
DELETE FROM testtable WHERE id > i;
END$$
>CALL simple_sql( )$$
SELECT * FROM testtable$$
Podemos usar la sentencia SELECT para enviar valores a variables (usando INTO).
Ejemplo:
DELIMITER $$
DROP PROCEDURE IF EXISTS simple_sql1$$
CREATE PROCEDURE simple_sql1(job VARCHAR(10))
BEGIN
DECLARE emple INT(4);
DECLARE apell VARCHAR(30);
SELECT emp_no, apellido INTO emple, apell FROM empleados
WHERE oficio = job;
/* procesamos los datos obtenidos, por ejemplo mostrndolos */
SELECT emple, apell, job;
END$$
>CALL simple_sql1(PRESIDENTE)$$
Si queremos recuperar ms de una fila para manipular sus datos, la sentencia anterior no sirve y
necesitamos usar los cursores. Conceptualmente los cursores se asocian a un conjunto de filas o una consulta
sobre la BD. Nos permite recuperar los datos de una consulta select en un procedimiento almacenado de forma
similar a como se recuperan los datos de un archivo con un lenguaje de programacin procedural.
2 ASIR - Administracin de Sistemas Gestores de Bases de Datos
1/20
>CALL simple_sql1(VENDEDOR)$$
da ERROR 1172 ..
Los cursores se utilizan para procesar individualmente cada una de las filas que hemos obtenido como
resultado de una consulta SELECT. En las versiones de MySQL superior a 5.x los cursores tienen las siguientes
propiedades:
- Solo lectura: Los valores en el cursor no se pueden actualizar
- No scrollable: Solo se puede recorrer en una direccin y no se pueden dar saltos ni mover hacia
delante y hacia atrs en el conjunto de filas.
- Insensible: Se debe evitar hacer actualizaciones en la tabla asociada mientras el cursor est abierto ya
que se pueden obtener resultados inesperados.
MySQL facilita las siguientes sentencias para trabajar con cursores:
Lo primero que hay que hacer es declarar el cursor usando la sentencia DECLARE.
DECLARE cursor_name CURSOR FOR SELECT_statement;
Despus, se abrir el cursor con la sentencia OPEN. Hay que abrir el cursor para poder trabajar con las
filas almacenadas en el cursor.
OPEN cursor_name;
A continuacin, para ir recuperando filas guardadas en el cursor e ir avanzando a travs de las diferentes
filas se usa la sentencia FETCH.
FETCH cursor_name INTO variable list;
Por ltimo, hay que cerrar el cursor para desactivarlo y liberar la memoria asignada al cursor. Para cerrar
el cursor se usa la sentencia CLOSE:
CLOSE cursor_name;
Algunas funciones tiles para manejar los cursores (tambin disparadores y eventos) son:
FOUND_ROWS( )
limit.
Ejemplo:
SELECT * FROM departamentos;
SELECT FOUND_ROWS( );
ROW_COUNT( )
devuelve el nmero de filas afectadas por la ltima sentencia insert, update o
delete, es decir, devuelve el nmero de filas insertadas, actualizadas o borradas de una tabla. Si devuelve el
valor -1, indica que la ltima sentencia fue una select.
Ejemplo:
SELECT ROW_COUNT( );
INSERT INTO departamentos VALUES (70, MARKETING, SEVILLA);
SELECT ROW_COUNT( );
Veremos un ejemplo de un cursor que muestre todos los cdigos y su descripcin de la tabla productos
(usamos la funcin FOUND_ROWS( ) que nos dice el nmero de filas seleccionadas por la ltima instruccin
select):
2 ASIR - Administracin de Sistemas Gestores de Bases de Datos
2/20
DELIMITER $$
DROP PROCEDURE IF EXISTS mis_productos$$
CREATE PROCEDURE mis_productos( )
BEGIN
DECLARE codigo INT(2);
DECLARE descri VARCHAR(50);
DECLARE num_filas INT;
DECLARE cur_prod CURSOR FOR
SELECT producto_no, descripcion FROM productos;
/* Abrimos el cursor y guardamos su nmero de filas */
OPEN cur_prod;
SELECT FOUND_ROWS( ) INTO num_filas;
SELECT num_filas;
IF num_filas = 0 THEN
SELECT La tabla de productos est vaca ;
END IF;
WHILE (num_filas <> 0) DO
FETCH cur_prod INTO codigo, descri;
SELECT codigo, descri;
/* Mostramos los datos de cada fila */
END WHILE;
END$$
>CALL mis_productos( )$$
..
ERROR 1329 (02000): No data zero rows fetched, selected, or processed
El procedimiento funciona pero al final da un mensaje de error ya que el bucle intenta seguir leyendo
ms all del final del cursor. Lo elegante es evitar que se produzca o usar un manejador de error:
Solucin 1: /* Solo va a leer tantas veces como le indica el nmero de filas */
3/20
DELIMITER $$
DROP PROCEDURE IF EXISTS mis_productos1$$
CREATE PROCEDURE mis_productos1( )
BEGIN
DECLARE codigo INT(2);
DECLARE descri VARCHAR(50);
DECLARE num_filas INT;
DECLARE cur_prod CURSOR FOR
SELECT producto_no, descripcion FROM productos;
/* Abrimos el cursor y guardamos su nmero de filas */
OPEN cur_prod;
SELECT FOUND_ROWS( ) INTO num_filas;
IF num_filas = 0 THEN
SELECT La tabla de productos est vaca ;
END IF;
WHILE (num_filas <> 0) DO
FETCH cur_prod INTO codigo, descri;
SELECT codigo, descri;
/* Mostramos los datos de cada fila */
SET num_filas = num_filas -1;
END WHILE;
END$$
>CALL mis_productos1( )$$
Solucin 2: /* Utilizamos un manejador de tipo no encuentro ms */
DELIMITER $$
DROP PROCEDURE IF EXISTS mis_productos2$$
CREATE PROCEDURE mis_productos2( )
BEGIN
DECLARE no_mas INT DEFAULT 0;
DECLARE codigo INT(2);
DECLARE descri VARCHAR(50);
DECLARE cur_prod CURSOR FOR
SELECT producto_no, descripcion FROM productos;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET no_mas = 1;
/* Abrimos el cursor y guardamos su nmero de filas */
OPEN cur_prod;
A1: LOOP
FETCH cur_prod INTO codigo, descri;
IF no_mas = 1 THEN LEAVE A1;
END IF;
SELECT codigo, descri;
/* Mostramos los datos de cada fila */
END LOOP A1;
END$$
4/20
DELIMITER $$
DROP PROCEDURE IF EXISTS mis_productos2$$
CREATE PROCEDURE mis_productos2( )
BEGIN
DECLARE no_mas INT DEFAULT 0;
DECLARE codigo INT(2);
DECLARE descri VARCHAR(50);
DECLARE cur_prod CURSOR FOR
SELECT producto_no, descripcion FROM productos;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET no_mas = 1;
/* Abrimos el cursor y guardamos su nmero de filas */
OPEN cur_prod;
WHILE no_mas = 0 DO
FETCH cur_prod INTO codigo, descri;
SELECT codigo, descri;
/* Mostramos los datos de cada fila */
END WHILE;
END$$
>CALL mis_productos2( )$$
9.1.- CONDICIONES Y MANEJADORES DE ERROR (HANDLERS)
-
Se llama condicin o excepcin, al error que se produce en tiempo de ejecucin. Cada excepcin debe
tener su manejador correspondiente, que se encargar de procesar el error. Su declaracin es la siguiente:
DECLARE tipo_manejador HANDLER FOR valor_condicin [, ] instruccin;
Tipo_manejador: CONTINUE | EXIT | UNDO
Valor_condicin: SQLSTATE valor | SQLWARNING | NOT FOUND |
Instruccin: puede ser nica o varias en un bloque BEGIN END
CONTINUE, contina la rutina actual que provoc el error despus de ejecutar la instruccin de
manejador.
EXIT, termina la ejecucin.
UNDO, deshace lo ya hecho, aunque de momento no est implementado.
SQLSTATE valor, cdigo de error de SQL.
SQLWARNING, incluye todos los cdigos SQLSTATE que empiezan por 01 (warnings)
NOT FOUND, los que empiezan por 02 (no encontrados)
Cuando se trabaja con cursores lo ms habitual es usar un manejador (handler) para evitar que se
produzca un error no data to fetch cuando se llega al final del cursor y ya no quedan ms filas que leer, es
decir:
DECLARE CONTINUE HANDLER FOR NOT FOUND instruccin;
5/20
Ejemplo: Cursor con manejador que recorre el resultado de una SELECT y muestra el total de registros
encontrados:
DELIMITER $$
DROP PROCEDURE IF EXISTS simple_sql2$$
CREATE PROCEDURE simple_sql2(job VARCHAR(10))
BEGIN
DECLARE no_mas INT DEFAULT 0;
DECLARE total INT DEFAULT 0;
DECLARE regi VARCHAR(255);
DECLARE cur_of CURSOR FOR SELECT
apellido FROM empleados WHERE oficio = job;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET no_mas = 1;
OPEN cur_of;
B1: LOOP
FETCH cur_of INTO regi;
IF no_mas = 1 THEN LEAVE B1;
END IF;
SET total = total +1;
SELECT regi;
END LOOP B1;
CLOSE cur_of;
SELECT total;
END$$
>CALL simple_sql2(VENDEDOR)$$
Ejemplo: En este procedimiento se define un cursor sobre la tabla PRODUCTOS y se realiza un bucle
que comprueba si la cantidad en stock de cada uno de los productos es menor que 25. En caso de que el stock
sea menor que 25, se guarda informacin de dicho producto en una tabla temporal. Una vez procesados todos
los registros de la tabla PRODUCTOS se visualiza en pantalla la tabla temporal y se elimina.
Inicialmente se han declarado tanto el cursor como el manejador del error NOT FOUND para que no se
produzca un error cuando se llegue al final
DELIMITER $$
DROP PROCEDURE IF EXISTS CursorProc$$
CREATE PROCEDURE CursorProc( )
BEGIN
DECLARE no_mas, cant_stock INT DEFAULT 0;
DECLARE prd_code VARCHAR(255);
DECLARE cur_prod CURSOR FOR SELECT producto_no FROM productos;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET no_mas = 1;
/* para guardar informacin de log en infologs */
CREATE TABLE infologs (
id INT(11) NOT NULL AUTO_INCREMENT,
msg VARCHAR(255) NOT NULL,
PRIMARY KEY (Id)
);
OPEN cur_prod;
FETCH cur_prod INTO prd_code;
REPEAT
2 ASIR - Administracin de Sistemas Gestores de Bases de Datos
6/20
7/20
delimiter //
create procedure novendedor7()
begin
declare no_mas int default 0;
declare emp_no_c int;
declare apellido_c varchar(30);
declare oficio_c varchar(30);
declare dep_no_c int;
declare dnombre_c varchar(30);
declare
cursor40
cursor
for
select
empleados.emp_no,empleados.apellido, empleados.oficio, empleados.dep_no,
departamentos.dnombre from empleados, departamentos
where
empleados.dep_no=departamentos.dep_no
and
oficio!
="vendedor";
declare continue handler for not found set no_mas=1;
open cursor40;
fetch cursor40 into emp_no_c, apellido_c, oficio_c, dep_no_c,
dnombre_c;
while no_mas=0 do
select emp_no_c, apellido_c, oficio_c, dep_no_c, dnombre_c;
fetch cursor40 into emp_no_c, apellido_c, oficio_c, dep_no_c,
dnombre_c;
end while;
close cursor40;
end //
delimiter ;
call novendedor7();
2. Al ejercicio anterior, aadir que para los empleados cuyo departamento sea MADRID, le subamos el sueldo
un 10%.
Delimiter //
drop procedure if exists novendedorsal//
create procedure novendedorsal()
begin
declare no_mas int default 0;
declare apellido_c varchar(30);
declare oficio_c varchar(10);
declare fecha_alta_c date;
declare dep_no_c int(2);
declare dpto varchar(30);
declare salario_c decimal(6,2);
declare cursor_noven cursor for select apellido, oficio,
fecha_alta, dep_no from empleados where oficio<>"vendedor";
Declare continue handler for not found set no_mas=1;
open cursor_noven;
salario,
fetch
cursor_noven
into
apellido_c,oficio_c,salario_c,
fecha_alta_c,dep_no_c;
while no_mas=0 do
if dep_no_c=30 then
2 ASIR - Administracin de Sistemas Gestores de Bases de Datos
8/20
update
empleados
where apellido=apellido_C;
select dnombre from
into dpto;
select
salario_c,fecha_alta_c,dpto;
select "Salario del
set
salario=salario+(salario*0.1)
oficio_c,
Empleado Actualizado.";
end if;
select dnombre from departamentos where dep_no=dep_no_c into
dpto;
select apellido_c, oficio_c, salario_c,fecha_alta_c,dpto;
fetch
cursor_noven
into
apellido_c,oficio_c,
salario_c,fecha_alta_c,dep_no_c;
end while;
close cursor_noven;
end//
delimiter ;
OTRA FORMA:
Delimiter //
drop procedure if exists novendedorsal//
create procedure novendedorsal()
begin
declare no_mas int default 0;
declare apellido_c varchar(30);
declare oficio_c varchar(10);
declare fecha_alta_c date;
declare dep_no_c int(2);
declare dpto varchar(30);
declare salario_c decimal(6,2);
declare cursor_noven cursor for select apellido, oficio, salario,
fecha_alta, dep_no from empleados where oficio<>"vendedor" and dep_no
in(select dep_no from departamentos where localidad="madrid");
Declare continue handler for not found set no_mas=1;
open cursor_noven;
fetch
cursor_noven
into
apellido_c,oficio_c,salario_c,
fecha_alta_c,dep_no_c;
while no_mas=0 do
update
empleados
set
salario=salario+(salario*0.1)
where apellido=apellido_C;
select dnombre from departamentos where dep_no=dep_no_c
into dpto;
select
apellido_c,
oficio_c,
salario_c,fecha_alta_c,dpto;
fetch
cursor_noven
into
apellido_c,oficio_c,
salario_c,fecha_alta_c,dep_no_c;
end while;
close cursor_noven;
end//
delimiter ;
2 ASIR - Administracin de Sistemas Gestores de Bases de Datos
9/20
10/20
11/20
Condicin
Accin
12/20
SELECT @sum;
Las instrucciones para gestionar los disparadores son: CREATE TRIGGER, SHOW TRIGGER y
DROP TRIGGER.
La sintaxis ms utilizada y sencilla es:
CREATE TRIGGER nombre_disp momento_disp evento_disp
ON nombre_tabla FOR EACH ROW sentencia_disp;
- momento_disp, puede ser BEFORE (antes) o AFTER (despus) para indicar que se ejecute antes o
despus de la sentencia que lo activa.
- evento_disp, indica la clase de sentencias que activa el disparador (INSERT, UPDATE o DELETE).
No puede haber dos disparadores en una misma tabla que correspondan al mismo momento y sentencia.
- FOR EACH ROW, hace referencia a las acciones a llevar a cabo sobre cada fila de la tabla indicada.
- sentencia_disp, es la sentencia que se ejecuta cuando se activa el disparador. Si hay ms de una hay
que colocarlas entre BEGIN . END.
Las columnas de la tabla asociada pueden referenciarse con:
- OLD.nom_columna (valor de la columna antes de ser actualizada o borrada).
- NEW.nom_columna (despus de ser actualizada o insertada).
En un disparador para INSERT solo se puede usar NEW, ya que no hay valor previo. Para DELETE,
solo OLD. Si es para UPDATE podemos usar ambos.
Para obtener informacin de los disparadores creados:
SHOW TRIGGERS [ { FROM | IN } bd_nombre]
[ LIKE patrn | WHERE expresin];
Al crear los disparadores se crea un nuevo registro en la tabla INFORMATION_SCHEMA llamada
INFORMATION_SCHEMA.TRIGGERS que se puede ver de la siguiente manera:
SELECT trigger_name, action_statement FROM information_schema.triggers;
Para eliminar disparadores:
DROP TRIGGER [ IF EXISTS ] [esquema_nombre.]nombre_disp;
10.1.- USO DE TRIGGERS o DISPARADORES
10.1.1. Control de sesiones:
Muchas veces queremos almacenar ciertos valores en variables de sesin creadas por el usuario para ver
un resumen de lo realizado en la sesin. El caso del ejemplo anterior.
SET @sum = 0;
INSERT INTO movimiento VALUES ('11111111', '2011-11-20', 1000, 58, 1),
('11111111', '2011-11-20', -500, 59, 1), ('11111111', '2011-11-20', 750, 60, 1);
SELECT @sum AS Total insertado;
10.1.2. Control de valores de entrada:
Para controlar valores insertados o actualizados en tablas
2 ASIR - Administracin de Sistemas Gestores de Bases de Datos
13/20
DELIMITER $$
CREATE TRIGGER comprobar_saldo BEFORE UPDATE ON movimiento
FOR EACH ROW
BEGIN
IF NEW.cantidad < 0 THEN SET NEW.cantidad = 0;
ELSEIF NEW.cantidad > 100 THEN SET NEW.cantidad = 100;
END IF;
END$$
UPDATE movimiento SET cantidad = 500 where idmov = 59$$
UPDATE movimiento SET cantidad = -50 where idmov = 60$$
SELECT * FROM movimiento$$
10.1.3. Mantenimiento de campos derivados:
Para actualizar campos que pueden calcularse a partir de otros, por ejemplo el campo saldo en la tabla
cuenta cada vez que se hace un ingreso:
DELIMITER ;
CREATE TRIGGER actualizar_cta BEFORE INSERT ON movimiento
FOR EACH ROW
UPDATE cuenta SET saldo = saldo + NEW.cantidad
WHERE cod_cuenta = NEW.cod_cuenta;
SELECT * FROM cuenta where cod_cuenta = 1;
INSERT INTO movimiento VALUES ('11111111', '2011-11-20', 1000, 61, 1);
SELECT * FROM cuenta where cod_cuenta = 1;
10.1.4. Estadsticas:
Podemos registrar estadsticas de operaciones o valores de nuestras BD en tiempo real. Por ejemplo
podemos registrar los ingresos que se hacen cada mes en una tabla aparte:
/* Creamos la tabla donde almacenar la cantidad total para cada mes */
CREATE TABLE testadistica (tcantidad double, tmes char(2));
/* Creamos una funcin existe que devuelve 1 o 0 si existe o no el registro para cada mes */
DELIMITER $$
DROP FUNCTION IF EXISTS existe$$
CREATE FUNCTION existe (mes char(2))
RETURNS int
BEGIN
DECLARE cont INT;
SELECT count(*) INTO cont FROM testadistica WHERE tmes = mes;
RETURN cont;
END$$
CREATE TRIGGER ingresos_dia AFTER INSERT ON movimiento
FOR EACH ROW
BEGIN
IF existe(MONTH(NEW.fechahora)) = 0 THEN
INSERT
INTO
testadistica(tcantidad, tmes)
MONTH(NEW.fechahora) );
2 ASIR - Administracin de Sistemas Gestores de Bases de Datos
VALUES
(NEW.cantidad,
14/20
15/20
if new.precio < 200 then delete from productos where precio < 200;
end if;
end//
delimiter ;
2. En la base ebanca, haz un disparador que cree un registro en la tabla nrojos con los campos cliente, cuenta,
fecha y saldo cada vez que algn cliente se quede en nmeros rojos en alguna de sus cuentas.
drop trigger if exists registro;
delimiter //
create trigger registro after update on cuenta for each row
begin
if
new.saldo
<
0
then
insert
into
nrojos
values
new.fecha_creacion,
new.cod_cliente, new.cod_cuenta, new.saldo);
end if;
end //
delimiter ;
update cuenta set saldo=-500 where cod_cliente=2;
select * from nrojos;
(null,
3. Haz lo necesario para que cada vez que un cliente de ebanca ingrese ms de 1000 se le bonifique con 100,
solo para clientes con cuentas que superen tres aos de antigedad y para movimientos realizados entre el 1 de
enero de 2011 y el 31 de marzo de 2011.
drop trigger if exists bono;
delimiter //
create trigger bono after insert on movimiento for each row
if new.cantidad > 1000 then update cuenta set saldo=saldo+new.cantidad+100
where fecha_creacion<"2011-02-19" and cod_cuenta in(select cod_cuenta
from movimiento where cuenta.cod_cuenta=movimiento.cod_cuenta and
fechahora between "2011-01-01" and "2011-03-31");
end if;
end //
delimiter ;
11.- EVENTOS.
Los eventos son tareas que se ejecutan de acuerdo a un horario. Por eso, tambin se les llama eventos
programados.
Es un concepto similar al Programador de Tareas (comando AT) de Windows.
Un evento se identifica por su nombre y el esquema o la BD al que se le asigna. Lleva a cabo una accin
especfica (una o varias instrucciones SQL dentro de un bloque BEGIN/END) de acuerdo a un horario.
Pueden distinguirse 2 tipos de eventos: los que se programan para una nica ocasin y los que ocurren
peridicamente cada cierto tiempo.
2 ASIR - Administracin de Sistemas Gestores de Bases de Datos
16/20
17/20
2. Programa un anlisis (ANALYZE TABLE) de las tablas de la base ebanca para el 1 de febrero de 2012.
DROP EVENT IF EXISTS ANALISIS;
CREATE EVENT ANALISIS ON SCHEDULE AT current_timestamp
DO ANALYZE TABLE CLIENTE, CUENTA, MOVIMIENTO, NROJOS;
3. Crea un evento que registre diariamente los movimientos superiores a 1000 en una tabla temponal tmp_mov.
Cralo deshabilitado.
2 ASIR - Administracin de Sistemas Gestores de Bases de Datos
18/20
9. En la BD Ventas, crea un procedimiento almacenado (cursor) que devuelva el vendedor con mayor nmero
de clientes.
10. En la BD Ventas, crea una tabla Cantidad_Pedir. Esta tabla contendr informacin de las cantidades de
diferentes productos que debemos pedir a nuestros proveedores para alcanzar un nivel de stock en nuestro
almacen:
cantidad_Pedir
(id_producto,
cantidad)
Adems
id_producto
es
clave
ajena
de
PRODUCTOS
20/20