Para los que les haya gustado mi anterior artículo en el cual substituiamos el uso de cursores por un bucle While, aquí está la continuación DEFINITIVA. Supongo que muchos pensareis que no ganamos mucho substituyendo una iteración por la otra, y en parte tenéis razón. No obstante la no utilización de cursores tiene beneficios intrínsecos que van más allá del propio rendimiento de la consulta, como podría ser la seguridad ante fallos de que no tendremos objetos instanciados en memoria.
De todas formas viendo el código anterior uno se queda con la sensación de que es un poco rudimentario. Hay otras alternativasparecidas, como puede ser incluir en la tabla temporal una columna IDENTITY que nos permita iterar la tabla en el While a través de una variable puntero que iremos incrementando en el propio While: más de lo mismo. Ahora bien, que os parecería si os digo que se puede substituir todo el bucle, el recorrido de filas, la comparación e incluso la tabla temporal por una única instrucción SELECT "corriente". Lo de corriente tiene un poco de trampa, pero nada de complicación: si utilizamos una instrucción select de asignación, la asignación se repetirá para cada fila de la consulta. Si además introducimos una expresión de tipo "case ... then .... else ..... end" podemos en cada fila comparar el valor máximo con el que llevamos agregado y asignar al máximo el mayor de ellos.
-- Creamos la tabla del ejemplo. Para simplificar introducimos solo los
--datos que utilizaremos, pero esto no afecta al ejemplo
declare @movimientos as table (Producto nvarchar(10), Cantidad int, Fecha date)
insert into @movimientos values ('tornillos',500,'20090101')
insert into @movimientos values ('tornillos',-200,'20090104')
insert into @movimientos values ('tornillos',-50,'20090102')
insert into @movimientos values ('tornillos',-220,'20090110')
insert into @movimientos values ('tornillos',500,'20090111')
insert into @movimientos values ('tornillos',-100,'20090120')
--Variables auxiliares que utilizaremos
declare @Acc int,@Max
int set @Acc=0 --Valor acumulado hasta la fila actual
set @Max=0 --Valor máximo hasta la fila actual
-- Consulta "importante"
select
@Acc=@Acc+Cantidad, --Valor acumulado hasta la fila actual
@Max=case sign(@Acc-@Max) when 1 then @Acc else @Max end --Asigna el nuevo máximo o se queda con el que había
From @movimientos
Where Producto= 'tornillos'
orderby fecha asc
print 'Máximo alcanzado:'
print @Max
-- Héchale una pensada, puede ser muy útil !!!!
Tenemos una tabla en nuestra base de datos llamada “Movimientos”, que registra entradas y salidas de material de nuestro almacén. La siguiente consulta es el resultado de consultar los movimientos de “tornillos” en el mes de enero. Cómo se puede ver la tabla carece de un autonumérico, los registros no tienen porqué estar en orden cronológico y hay más productos mezclados.
|
tornillos
|
500
|
01/01/2009
|
|
tornillos
|
-50
|
02/01/2009
|
|
tornillos
|
-200
|
04/01/2009
|
|
tornillos
|
-220
|
10/01/2009
|
|
tornillos
|
500
|
11/01/2009
|
|
tornillos
|
-100
|
20/01/2009
|
Si en este escenario nos pidiesen obtener la cantidad máxima de tornillos que llegó a haber en el almacén podrían ponernos en un pequeño apuro. La idea sería conseguir una tercera columna con la cantidad en stock y poder seleccionar el máximo. El problema de eso es que necesitamos de alguna manera recorrer la tabla fila a fila, pero claro, dicen por ahí que eso de los cursores es cosa mala….
|
tornillos
|
500
|
01/01/2009
|
500
|
|
tornillos
|
-50
|
02/01/2009
|
450
|
|
tornillos
|
-200
|
04/01/2009
|
250
|
|
tornillos
|
-220
|
10/01/2009
|
30
|
|
tornillos
|
500
|
11/01/2009
|
530
|
|
tornillos
|
-100
|
20/01/2009
|
430
|
He aquí un método alternativo a la utilización de los cursores para obtener la columna “agregado” y poder seleccionar el máximo stock, que tiene lugar el 11 de enero con un valor de 530 tornillos.
1º) Creamos una tabla temporal con los registros que nos interesan, ordenándolos por orden cronológico
2º) Recorremos un bucle que analice la primera fila y la eliminamos, de esta forma podemos recorrer la tabla completa.
Veámoslo en el ejemplo:
--Creamos la tabla del ejemplo. Para simplificar introducimos solo los
--datos que utilizaremos, pero esto no afecta al ejemplo
declare @movimientos as table
(Producto nvarchar(10), Cantidad int, Fecha date)
insert into @movimientos values ('tornillos',500,'20090101')
insert into @movimientos values ('tornillos',-200,'20090104')
insert into @movimientos values ('tornillos',-50,'20090102')
insert into @movimientos values ('tornillos',-220,'20090110')
insert into @movimientos values ('tornillos',500,'20090111')
insert into @movimientos values ('tornillos',-100,'20090120')
--Definimos una tabla temporal, con la misma estructura que la original
declare @tmp as table
(Producto nvarchar(10), Cantidad int, Fecha date)
--Introducimos aquí los datos que nos interesen, ordenados convenientemente
insert into @tmp
select * from @movimientos
where Producto='tornillos'
order by fecha asc
--Variables auxiliares que utilizaremos
declare @Acc int,@Max int
set @Acc=0 --Valor acumulado hasta la fila actual
set @Max=0 --Valor máximo hasta la fila actual
--Esta instrucción elimina de la ventana de mensajes el recuento de filas afectadas
set nocount on
--Mientras exista alguna fila en @tmp recorro el bucle
While(Exists(select * from @tmp))
BEGIN
--Sumamos la 1ªfila al acumulado
select top(1) @Acc=@Acc+Cantidad from @tmp;
--Eliminamos la 1ª fila; ya está computada
delete top(1) from @tmp;
--Comprobamos si el acumulado es máximo
if(@Acc>@Max)
set @Max=@Acc;
print @Acc;
END
print ''
print 'Máximo alcanzado:'
print @Max
Lo que nos devuelve por pantalla la columna de acumulados y el máximo stock que llegó a haber en el almacén:
500
450
250
30
530
430
Máximo alcanzado:
530
La pega de este método son la cantidad de deletes que utiliza, así que en el siguiente post tocará un método alternativo (sin cursores, of course) más eficaz.