Вы находитесь на странице: 1из 15

Tema: Facultad : Ingeniería

Procedimientos almacenados Escuela : Computación


y cursores. 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 Descripción
IF..ELSE Define una decisión.
GOTO etiqueta Define un salto incondicional
WAITFOR 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)
WHILE Bucle básico de SQL
BREAK Acompaña al bucle WHILE y le indica
finalizarlo inmediatamente.
CONTINUE Acompaña al bucle WHILE y le indica
continuar con la siguiente iteración.
RETURN [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.
BEGIN..END Utilizado en conjunto con IF..ELSE o
WHILE para agrupar un conjunto de
instrucciones.
CASE Implementada en la instrucción SELECT
y UPDATE y permite realizar consultas y
actualizaciones condicionales.

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 TRIGGER
CREATE PROCEDURE CREATE VIEW
CREATE RULE
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 del producto


„prod0001prov0001‟ (producto 1 distribuido por el proveedor 1)

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 into nombrecito values('1','Carlos','Castro','no')


insert into nombrecito values('2','Jose','Abarca','no')
insert into nombrecito values('3','Lissette','Jimenez','no')
insert into nombrecito 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