Procedimientos almacenados y cursores. Facultad : Ingeniería

Anuncio
Tema:
Procedimientos almacenados
y cursores.
Facultad : Ingeniería
Escuela : Computación
Asignatura: SQL SERVER
GUÍA 2 Pág.
1
I. OBJETIVOS
Utilizar procedimientos almacenados
Conocer el uso de los cursores
Realizar operaciones utilizando transacciones
II. INTRODUCCIÓN

Programación con Transact-SQL
Transact-SQL no es realmente un lenguaje de programación similar a las herramientas
de tercera y cuarta generación sin embargo permite utilizar SQL para realizar tareas
complejas que requieren saltos, bucles, decisiones. Transact-SQL se utiliza a menudo
en la creación de procedimientos almacenados y triggers de tal forma que las
aplicaciones clientes que se conectan a SQL Server solo se preocupan por la
presentación de los datos para el usuario final, mientras que la lógica de los procesos
se maneja en el servidor.
Variables:
Las variables locales se identifican como aquellos objetos que comienzan con el
carácter arroba '@' una vez; las variables globales se identifican como los objetos que
tienen 2 arrobas al inicio '@@', como ejemplo de variables globales tenemos:
@@rowcount, @@error.
Las variables locales se declaran al inicio de un proceso por lotes o un procedimiento
almacenado, la forma de asignarle valores a una variable es con la instrucción SELECT.
El control de flujo en Transact-SQL
Construcción
IF..ELSE
GOTO etiqueta
WAITFOR
WHILE
BREAK
CONTINUE
RETURN [n]
Descripción
Define una decisión.
Define un salto incondicional
Establece un tiempo para la ejecución
de una instrucción. El tiempo puede ser
un intervalo de retardo o un instante
especificado de ejecución (una hora
concreta del día)
Bucle básico de SQL
Acompaña al bucle WHILE y le indica
finalizarlo inmediatamente.
Acompaña al bucle WHILE y le indica
continuar con la siguiente iteración.
Salida incondicional del procedimiento o
GUÍA 2 Pág.
2
proceso por lotes, se puede definir un
número entero como estado devuelto y
puede asignarse a cualquier variable.
Utilizado en conjunto con IF..ELSE o
WHILE para agrupar un conjunto de
instrucciones.
Implementada en la instrucción SELECT
y UPDATE y permite realizar consultas y
actualizaciones condicionales.
BEGIN..END
CASE
PRINT
Es una instrucción para imprimir un dato en la pantalla, la sintaxis es:
PRINT "cadena" ; cadena puede ser también una variable de tipo varchar.
Por ejemplo: PRINT „Hola a todos‟
RAISERROR
Es similar a PRINT, pero permite especificar un número de error y la severidad del
mensaje. RAISERROR también permite que los errores se registren en el servicio de
sucesos de Windows NT haciendo posible leerlos a través del visor de sucesos de
Windows NT.
La sintaxis es:
RAISERROR({id_mensaje | cadena_mensaje}, severidad, estado [,
argumento1 [,argumento2]]) WITH LOG.
Después de llamar a RAISERROR, la variable global @@ERROR tendrá el valor de
id_mensaje, si no se pasa ningún id_mensaje, asumirá 5000.
Procedimientos almacenados.
Dos de las cuestiones más importantes para el usuario de bases de datos son la
velocidad y la eficiencia. Por ello surge una pregunta: ¿Cómo puedo proporcionar a los
usuarios la velocidad y eficiencia que necesitan y merecen?
Esa herramienta diseñada principalmente para optimizar la obtención de datos, es el
procedimiento almacenado.
Un procedimiento almacenado es una consulta que se almacena en una base de datos
en SQL Server en lugar de almacenarse en el código cliente (normalmente C# o Java)
en el equipo cliente.

Creación de procedimientos almacenados (Store Procedures)
La instrucción general para crear procedimientos almacenados es la siguiente:
CREATE PROC nombre_proc
parametros
GUÍA 2 Pág.
3
AS
INSTRUCCION SQL
Es necesario aclarar, que un procedimiento almacenado puede recibir parámetros de
entrada y devolver parámetros de salida.
Ejemplo 1
Instrucción SQL
USE AdventureWorks
Select name, Color, ListPrice, SellStartDate
FROM Production.Product
WHERE SellStartDate > '1/1/2003'
ORDER BY SellStartDate, Name
Procedimiento con instrucción anterior
CREATE PROCEDURE Production.ShowProduct
AS
Select name, Color, ListPrice, SellStartDate
FROM Production.Product
WHERE SellStartDate > '1/1/2003'
ORDER BY SellStartDate, Name
GO
Para probar el nuevo procedimiento, abra una nueva consulta de SQL Server y
escriba y ejecute el código siguiente.
USE AdventureWorks
EXEC Production.ShowProduct
Nota: los procedimientos almacenados los puede encontrar en la base de datos
donde los trabaja, en la opción programación.
Ejemplo2:
--Obteniendo Ganancia sobre las ventas
CREATE PROC Ganancia_de_Venta
@id_Venta int,
-- parametro de entrada
@id_Prod_bodega int, -- parametro de entrada
@ganancia decimal (8,2) OUTPUT -- parametro de salida
AS
declare @unidades int
--Asignando un valor a la variable unidades
SELECT @unidades = unidades FROM detalleventa
WHERE idventa=@id_Venta AND idprodbod=@id_Prod_bodega
SELECT @ganancia = (preciov-precioc)*@unidades FROM bodega
WHERE idprodbod=@id_Prod_bodega
GO
GUÍA 2 Pág.
4
Reglas de procedimientos almacenados
Entre las reglas para la programación de procedimientos almacenados, cabe citar las
siguientes:
La propia definición CREATE PROCEDURE puede incluir cualquier número y
tipo de instrucciones SQL, excepto las siguientes instrucciones CREATE, que no
pueden ser utilizadas nunca dentro de un procedimiento almacenado:
CREATE DEFAULT
CREATE PROCEDURE
CREATE RULE
CREATE TRIGGER
CREATE VIEW
Se puede crear otros objetos de base de datos dentro de un procedimiento
almacenado. Puede hacer referencia a un objeto creado en el mismo
procedimiento almacenado, siempre que se cree antes de que se haga
referencia al objeto.
Puede hacer referencia a tablas temporales dentro de un procedimiento
almacenado.
Si ejecuta un procedimiento almacenado que llama a otro procedimiento
almacenado, el procedimiento al que se llama puede tener acceso a todos los
objetos creados por el primer procedimiento, incluidas las tablas temporales.
El número máximo de parámetros en un procedimiento almacenado es de 1,024.
El número máximo de variables locales en un procedimiento almacenado está
limitado únicamente por la memoria disponible.
Procedimientos almacenados del sistema
Muchas de las actividades administrativas de SQL Server se realizan mediante un tipo
especial de procedimiento denominado procedimiento almacenado del sistema. Los
procedimientos almacenados del sistema se crean y se almacenan en la base de datos
master, con el prefijo sp_ (stored procedure). Estos procedimientos se pueden ejecutar
desde cualquier base de datos.
Por ejemplo: sp_help, sp_heldb, sp_droplogin, etc.
Cursores
Una base de datos relaciona como SQL Server está orientada a conjuntos de manera
natural, por ejemplo una instrucción SELECT regresa un conjunto de datos; sin
embargo muchas veces es necesario utilizar no el enfoque de conjunto de datos sino de
registros para realizar ciertas operaciones no complejas, es donde se implementan los
cursores.
Un cursor es un conjunto de resultados donde existe la posibilidad de desplazarse
registro por registro en cualquier dirección.
GUÍA 2 Pág.
5
Trabajar con cursores implica algunos pasos básicos como son el declararlos, abrirlos,
capturar filas en el cursor, opcionalmente se puede modificar o eliminar registros, luego
se debe cerrar el cursor y por ultimo retirarlo de la memoria.
Cuando se crean cursores, se debe considerar lo siguiente:
-
-
-
Para declarar un cursor se utiliza la instrucción DECLARE.
Para abrir el cursor se utiliza la instrucción OPEN
Para capturar filas de un cursor se utiliza la instrucción FETCH, es necesario
declarar variables e indicarle a FETCH cuales son las variables., existe una
variable global llamada @@FETCH_STATUS que indica el estado de la última
instrucción FETCH, así si @@ FETCH_STATUS = 0 la captura fue un éxito, si
@@ FETCH_STATUS = -1 no hay mas filas (se ha llegado al inicio o al final) y si
@@FETCH_STATUS = -2 significa que la fila ya no existe en el cursor
(probablemente se eliminó).
Para actualizar o eliminar el registro que esta siendo apuntado por el cursor, se
utiliza la instrucción UPDATE / DELETE tabla WHERE CURRENT OF
nombre_cursor.
Para cerrar el cursor, se utiliza la instrucción: CLOSE.
Para liberar la memoria, se utiliza la instrucción DEALLOCATE.
III. MATERIAL Y EQUIPO A UTILIZAR
Guía de Laboratorio Nº 2 de SQL Server
Computadora con SQL SERVER 2005
Disquete o memoria USB
IV. PROCEDIMIENTO
En esta práctica utilizaremos los procedimientos almacenados, cursores y
transacciones utilizando la base de datos de SQL Server 2005, para ello se plantea la
siguiente base de datos:
Definición.
Esta base de datos se desea para un almacén que tiene varios productos a la venta, los
cuales adquiere de varios proveedores. Un producto puede ser distribuido por varios
proveedores y un proveedor distribuye varios productos, como es una relación de
muchos a muchos se tiene una tercera tabla llamada "catalogo" donde se indica el
detalle del producto.
Lo interesante es que el almacén adquiere productos a cierto precio y los almacena en
bodega, y a medida que las existencias del producto van disminuyendo, vuelve a
adquirir nuevos productos, sin embargo el costo del producto comprado por el almacén
puede variar y el almacén registra el precio de costo y de venta de los productos
GUÍA 2 Pág.
6
adquiridos en una compra y maneja la política de vender primero los productos más
antiguos.
Para poder crear la base de datos anterior, deberá crear una base de datos con el
nombre almacen_carnet, y ejecute los scripts para generar las tablas que se listan a
continuación:
-- creacion de las tablas
create table proveedor
(
idproveedor char(8) not null,
nombre char(60) not null,
constraint pk_idproveedor primary key (idproveedor)
)
create table producto
(
idproducto char(8) not null,
nombre char(60) not null,
constraint pk_idproducto primary key (idproducto)
)
create table catalogo
(
idprodcat char(16) not null,
idproducto char(8) not null,
idproveedor char(8) not null,
precio decimal(8,2) not null,
constraint pk_idprodcat primary key clustered (idprodcat),
GUÍA 2 Pág.
7
constraint fk_idproveedor foreign key (idproveedor)
references proveedor(idproveedor),
constraint fk_idproducto foreign key (idproducto)
references producto(idproducto),
constraint u_prodcat unique (idproducto,idproveedor),
constraint ck_precio_catalogo check (precio>=0),
constraint ck_idprodcat_catalogo
check (idprodcat=idproducto+idproveedor)
)
create table bodega
(
idprodbod integer not null identity,
idprodcat char(16) not null,
fecha datetime not null,
precioc decimal(8,2) not null, -- precio de costo
preciov decimal(8,2) not null, -- precio de venta
unidades integer not null,
-- unidades
constraint pk_idprodbod primary key (idprodbod),
constraint fk_idprodcat foreign key (idprodcat)
references catalogo(idprodcat),
constraint ck_precios_bodega check (precioc >=0 and preciov >=0),
constraint ck_unidades_bodega check (unidades >=0)
)
create table cliente
(
idcliente char(8) not null,
nombre char(60) not null,
constraint pk_idcliente primary key (idcliente)
)
create table venta
(
idventa integer not null identity,
fecha datetime not null,
idcliente char(8) not null,
constraint pk_idventa primary key (idventa),
constraint fk_idcliente foreign key (idcliente)
references cliente(idcliente)
)
create table detalleventa
(
idventa integer not null,
idprodbod integer not null,
unidades integer not null,
constraint fk_idprodbod foreign key (idprodbod)
references bodega(idprodbod),
constraint fk_idventa foreign key (idventa) references venta(idventa),
constraint ck_unidades_detalleventa check (unidades>0)
)
Ejecute las siguientes instrucciones SQL para introducir datos a las tablas de trabajo:
GUÍA 2 Pág.
8
-- productos que ofrecen los proveedores
insert into producto
values ('prod0001', 'producto a')
insert into producto
values ('prod0002', 'producto b')
insert into producto
values ('prod0003', 'producto c')
insert into producto
values ('prod0004', 'producto d')
-- nuestros proveedores
insert into proveedor values ('prov0001','proveedor 1')
insert into proveedor values ('prov0002','proveedor 2')
insert into proveedor values ('prov0003','proveedor 3')
-- insertando los clientes
insert into cliente
values ('clie0001','cliente 1')
insert into cliente
values ('clie0002','cliente 2')
insert into cliente
values ('clie0003','cliente 3')
En la tabla de catálogo, se generará un código para cada producto distribuido por cada
proveedor, dicho código se almacenará en el campo llamado 'idprodcat':
-- insertando que productos distribuye que proveedor y el costo
insert into catalogo VALUES('prod0001'+'prov0001','prod0001','prov0001',5.25)
insert into catalogo VALUES('prod0001'+'prov0002','prod0001','prov0002',6.00)
insert into catalogo VALUES('prod0001'+'prov0003','prod0001','prov0003',5.50)
insert into catalogo VALUES('prod0002'+'prov0001','prod0002','prov0001',3.15)
insert into catalogo VALUES('prod0002'+'prov0002','prod0002','prov0002',3.10)
insert into catalogo VALUES('prod0003'+'prov0002','prod0003','prov0002',15.25)
insert into catalogo VALUES('prod0003'+'prov0003','prod0003','prov0003',14.90)
insert into catalogo VALUES('prod0004'+'prov0001','prod0004','prov0001',3.85)
insert into catalogo VALUES('prod0004'+'prov0003','prod0004','prov0003',2.00)
GUÍA 2 Pág.
9
Para insertar datos en la bodega, se creara un procedimiento almacenado que toma 5
parámetros de entrada los cuales son:
@fecha de tipo datetime, @proveedor,@producto de tipo char, @ganancia de tipo
decimal y @unidades de tipo entero(int).
Ejecute el siguiente query para crear el procedimiento almacenado “producto_bodega”:
-- procedimiento almacenado que inserta productos en bodega
create procedure sp_producto_bodega
@fecha datetime,
@proveedor char(8),
@producto char(8),
@ganancia decimal(4,2),
@unidades int
as
declare @idprodcat char(16)
declare @precio decimal(8,2)
select @idprodcat = idprodcat from catalogo where
idproducto=@producto and idproveedor=@proveedor
if @@rowcount=0 goto error
select @precio = precio from catalogo where idprodcat = @idprodcat
insert into bodega values
(@idprodcat,@fecha,@precio,@precio+(@precio *@ganancia),@unidades)
return(0)
error:
print 'No existe un producto en el catalogo'
return(1)
GO
Algunas características del procedimiento almacenado creado anteriormente son:
a) La línea declare @idprodcat char(16) esta declarando una variable local utilizada
dentro del SP (Store Procedure). No confundir una variable local con un
parámetro de entrada o de salida.
b) Para asignarle un valor a una variable se utiliza la sentencia SELECT seguido de
la consulta o valor que será almacenado en dicha variable. Por ejemplo:
-
SELECT @X = 10 ( aquí se le asigna a la variable X el valor de 10 ).
SELECT @precio = precio from catalogo where idprodcat = @idprodcat
(aquí se le esta asignando a la variable precio el dato que retorna la
consulta hecha a la tabla catalogo. En este caso el query retorna el campo
precio de dicha tabla).
c) La línea if @@rowcount = 0 indica que si el ultimo query realizado no devolvió
ningún registro (@@rowcount=0) entonces se hace un salto incondicional a la
etiqueta error.
GUÍA 2 Pág.
10
d) Si hubo error la sentencia print muestra el mensaje 'No existe un producto en el
catalogo', de lo contrario se insertan los datos.
Utilice el procedimiento almacenado creado anteriormente para insertar los siguientes
datos:
-- haciendo las compras a los proveedores en los meses de enero y febrero
-- del 2000 y ganando un 20% sobre el costo.
exec sp_producto_bodega '01/01/2000','prov0001','prod0001',0.2,5
exec sp_producto_bodega '28/01/2000','prov0002','prod0003',0.2,15
exec sp_producto_bodega '01/01/2000','prov0002','prod0002',0.2,10
exec sp_producto_bodega '01/01/2000','prov0003','prod0004',0.2,20
exec sp_producto_bodega '01/02/2000','prov0003','prod0001',0.2,15
exec sp_producto_bodega '01/02/2000','prov0001','prod0002',0.2,5
exec sp_producto_bodega '07/01/2000','prov0001','prod0001',0.2,15
Ahora procederemos a realizar un procedimiento almacenado que hará referencia a la
tabla detalleventa de tal forma que si insertamos la venta de un producto, primero se
debe verificar que haya existencia de dicho producto en nuestra bodega; si hay
existencia en bodega se permitirá la acción y se actualizarán las existencias en la tabla
bodega para que exista consistencia entre los datos (si vendo productos, disminuye mi
bodega).
El procedimiento almacenado seria el siguiente:
create proc sp_actualizar_bodega
@idprodbod int,
@unidades int
as
declare @existencia int
select @existencia = unidades
from bodega where idprodbod = @idprodbod
begin tran
if (@existencia<@unidades) goto errores
update bodega
set unidades = unidades - @unidades
where idprodbod = @idprodbod
commit tran
goto fin
errores:
rollback tran --se elimina la transaccion
fin:
print 'Bodega actualizada'
Por ultimo crearemos un procedimiento almacenado más sofisticado, que permita
agregar fácilmente las unidades de un producto a nuestra tabla detalleventa, los
parámetros a pasar deben ser: el id de la venta, el idprodcat (es decir el codigo del
GUÍA 2 Pág.
11
producto distribuido por un proveedor determinado) y las unidades vendidas. Además
dicho procedimiento llamara al SP actualizar_bodega.
Nuestro procedimiento almacenado consultará nuestra bodega para revisar las compras
que se han efectuado de ese producto, y como la política es vender el producto más
antiguo, se venderán los productos comprados en las fechas más antiguas.
El procedimiento almacenado necesita crear un cursor, ya que se deben explorar todas
las compras realizadas de ese producto ordenadas por fechas y decrementar las
entradas (compras) más antiguas.
El procedimiento almacenado queda de la siguiente forma:
create procedure sp_producto_detalleventa
@idventa int,
@idprodcat char(16),
@unidades int
as
declare @idprodbod char(8)
declare @existencia int
declare @unidadestmp int
select @existencia=sum(unidades)
from bodega
where idprodcat=@idprodcat
if @existencia < @unidades goto errores
declare ctmp cursor for
select idprodbod,unidades from bodega
where idprodcat=@idprodcat
order by fecha asc
--comienza una transaccion
begin tran
open ctmp
--Ejecutando el primer fetch y almacenando los valores en variables
--Las variables estan en el mismo orden de las columnas de la
--sentencia SELECT del cursor
fetch next from ctmp into @idprodbod,@unidadestmp
while (@unidades>0 and @@fetch_status=0)
begin
if @unidadestmp <= @unidades
begin
select @unidades=@unidades-@unidadestmp
insert into
detalleventa values(@idventa,@idprodbod,@unidadestmp)
--actualizando bodega llamando al SP
EXEC sp_actualizar_bodega @idprodbod,@unidadestmp
end
else
begin
GUÍA 2 Pág.
12
insert into
detalleventa values(@idventa,@idprodbod,@unidades)
--actualizando bodega llamando al SP
EXEC sp_actualizar_bodega @idprodbod,@unidades
select @unidades=0
end
fetch next from ctmp into @idprodbod,@unidadestmp
end
close ctmp
deallocate ctmp
--la transaccion finaliza
commit tran
return 0
errores:
return 1
Para comprobar la ejecución de los procedimientos almacenados anteriores, se harán
unas pruebas registrando una venta para el cliente con código „clie0001‟.
Primero visualicemos cuantas compras se han realizado
„prod0001prov0001‟ (producto 1 distribuido por el proveedor 1)
del
producto
select * from bodega
where idprodcat='prod0001prov0001'
Observe que existen dos compras, una realizada el 1 de enero del 2000 por 5 unidades y otra realizada el
7 de enero del 2000 por 15 por lo que se tienen 20 unidades en total.
Registrando la venta para el cliente 1:
insert into venta
values ('28/03/2001','clie0001')
Ahora se ingresaran datos en la tabla detalleventa utilizando el procedimiento almacenado sp_
producto_detalleventa. Ejecute los siguientes querys como un solo batch (ambos querys de una sola
vez):
-- obteniendo el valor del codigo idventa en una variable
declare @idventa int
select @idventa=idventa from venta
where idcliente='clie0001'
--suponiendo que el cliente 01 compra 4 unidades
EXEC sp_producto_detalleventa @idventa, 'prod0001prov0001', 4
GO
Ahora ejecute este query para ver el estado de la tabla bodega:
-- visualizando como cambiaron nuestros registros en bodega
select * from bodega where idprodcat='prod0001prov0001'
GUÍA 2 Pág.
13
Observe como se disminuyo en 4 el primer lote de compras. Suponga ahora que el
cliente 02 realiza una compra por 7 unidades del mismo producto:
-- se registra primero la venta del cliente 02
insert into venta
values ('28/03/2001','clie0002')
declare @idventa int
select @idventa=idventa from venta
where idcliente='clie0002'
-- el cliente 02 compra 7 unidades
Exec sp_producto_detalleventa @idventa, 'prod0001prov0001', 7
Observe como se han modificado los registros en la tabla bodega :
select * from bodega where idprodcat='prod0001prov0001'
Se puede observar, del primer lote de compras ya no hay existencia, mientras que del
segundo se decremento en 6.
Ejemplo Cursores
A la misma base de datos insertar la siguiente tabla
create table nombrecito(
cod int,
nombre varchar(25),
apellido varchar(25),
nombrecompleto varchar(50))
insert
insert
insert
insert
into
into
into
into
nombrecito
nombrecito
nombrecito
nombrecito
values('1','Carlos','Castro','no')
values('2','Jose','Abarca','no')
values('3','Lissette','Jimenez','no')
values('4','Juan','Elias','no')
Como puede ver se insertaron registros y el campo de nombre completo no se ingreso
correctamente, por lo que tendremos que actualizar registro por registro, para ello
haremos uso de cursores
-- declaramos las variables
declare @cod as int
declare @nombre as varchar(25)
declare @apellido as varchar(25)
-- declaramos un cursor llamado "MICURSOR". El select debe contener sólo los
campos a utilizar.
declare MICURSOR cursor for
select cod, nombre,apellido from nombrecito
GUÍA 2 Pág.
14
open MICURSOR
--Avanzamos un registro y cargamos en las variables los valores encontrados
en el primer registro
fetch next from MICURSOR
into @cod, @nombre, @apellido
while @@fetch_status = 0
begin
update nombrecito set nombrecompleto= @nombre+' '+@apellido where cod=@cod
-- Avanzamos otro registro
fetch next from MICURSOR
into @cod, @nombre, @apellido
end
-- cerramos el cursor
close MICURSOR
deallocate MICURSOR
V. INVESTIGACIÓN Y EJERCICIOS COMPLEMENTARIOS
Investigue que son los desencadenadores
Cual es la diferencia entre un desencadenador DDL y DML
Implemente un trigger en la tabla producto el cual se debe activar cuando halla
una actualizacion de datos en dicha tabla
GUÍA 2 Pág.
15
Descargar