MySQL - Procedimiento con cursor variable

 
Vista:

Procedimiento con cursor variable

Publicado por Mikel - ANER (3 intervenciones) el 07/05/2018 18:31:10
Buenas tardes:

En procedimiento almacenado de MySql necesito declarar y utilizar un Cursor similar al siguiente:

1
2
3
4
5
6
7
Declare Fin_C_Movimientos Boolean Default False;
Declare C_Movimientos Cursor For
  Select T.Tipo, M.ArticuloId, M.Fecha, M.Hora, M.CantidadAlmacen, M.Precio
  From MovimientoAlmacen As M Left Outer Join TipoMovimiento T On M.TipoMovimientoId = T.TipoMovimientoId
  Where M.Fecha <= in_HastaFecha And M.ArticuloId = in_ArticuloId
  Order By M.ArticuloId, M.Fecha, M.Hora;
Declare Continue Handler For Not Found Set Fin_C_Movimientos = True;


La cuestión es que necesito que la condición sea variable dependiendo de los posibles valores que me hayan pasado al procedimiento.

Gracias de antemano por la ayuda.

Saludos.

Mikel
Valora esta pregunta
Me gusta: Está pregunta es útil y esta claraNo me gusta: Está pregunta no esta clara o no es útil
0
Responder
sin imagen de perfil
Val: 953
Oro
Ha mantenido su posición en MySQL (en relación al último mes)
Gráfica de MySQL

Procedimiento con cursor variable

Publicado por leonardo_josue (414 intervenciones) el 07/05/2018 19:25:52
Hola Mikel - ANER;

Hasta donde sé, no es posible hacer un cursor con una consulta "dinámica", pero por qué mejor no nos dices qué es lo que quieres hacer en tu Procedimiento para ver si hay alguna otra forma de hacerlo sin cursores. También pon un ejemplo de los parámetros que vas a enviarle al SP y dinos cómo quisieras que quedara la consulta, ya que también hay algunos "trucos" para hacer consultas "dinámicas" sin que en realidad lo sean.

Serviría mucho si también nos pones la estructura de tus tablas y nos pones algunos datos de ejemplo, para poder hacer algunas pruebas con las consultas.

Saludos
Leo.
Valora esta respuesta
Me gusta: Está respuesta es útil y esta claraNo me gusta: Está respuesta no esta clara o no es útil
0
Comentar

Procedimiento con cursor variable

Publicado por Mikel - ANER (3 intervenciones) el 08/05/2018 17:54:02
Gracias Leo por tu ayuda ... me explico un poquito más.

Estoy realzando un procedimiento almacenado que me devuelve un Inventario retroactivo a una fecha en base a una consulta sobre la tabla MovimientoAlmacen. Al comentarme que en realidad no se puede hacer un cursor con una consulta dinámica he cambiado un poquito la idea que tenía y la consulta en la que se basa el cursor es siempre la misma, es decir, ya no es dinámica.

Ahora me ha surgido un nuevo problema. En la consulta en la que se basa el cursor he puesto un par de condiciones para tratar los articulos y almacenes que recibe el parametro en 2 listas de IDs que le paso por parametro: Mo.ArticuloId IN (in_ListaArticulos) And Mo.AlmacenId IN (in_ListaAlmacenes).

Llamando al procedimiento de esta manera: call prueba('2017-12-31', '3,4,5,6,7,8', '1,2,3', True) el cursor unicamente recibe las filas de la primera posición de la lista de IDs, es decir como si in_ListaArticulos recibiera unicamente 3 en lugar de 3,4,5,6,7,8 y como si in_ListaAlmacenes recibiera unicamente 1 en lugar de 1,2,3.

Gracias por vuestra colaboración, saludos.

Mikel


Código completo del procedimiento actualmente:

CREATE DEFINER=`root`@`%` PROCEDURE `prueba`(IN in_HastaFecha Date, IN in_ListaArticulos LongText, IN in_ListaAlmacenes LongText, IN in_ExcluirSinExistencia Boolean)
BEGIN
Declare v_ArticuloId_Anterior BigInt(20) Default Null;
Declare v_CodigoArticulo_Anterior VarChar(50) Default Null;
Declare v_NombreArticulo_Anterior VarChar(200) Default Null;
Declare v_AlmacenId_Anterior BigInt(20) Default Null;
Declare v_CodigoAlmacen_Anterior VarChar(50) Default Null;
Declare v_NombreAlmacen_Anterior VarChar(100) Default Null;
Declare v_FamiliaId_Anterior BigInt(20) Default Null;
Declare v_CodigoFamilia_Anterior VarChar(50) Default Null;
Declare v_NombreFamilia_Anterior VarChar(100) Default Null;
Declare v_TipoArticuloId_Anterior BigInt(20) Default Null;
Declare v_CodigoTipoArticulo_Anterior VarChar(20) Default Null;
Declare v_NombreTipoArticulo_Anterior VarChar(300) Default Null;
Declare v_Existencia Decimal(38,6) Default 0;
Declare v_PrecioUltimaEntrada Decimal(38,6) Default 0;
Declare v_PMP Decimal(38,6) Default 0;

Declare v_Tipo VarChar(5) Default Null;
Declare v_ArticuloId BigInt(20) Default Null;
Declare v_CodigoArticulo VarChar(50) Default Null;
Declare v_NombreArticulo VarChar(200) Default Null;
Declare v_AlmacenId BigInt(20) Default Null;
Declare v_CodigoAlmacen VarChar(50) Default Null;
Declare v_NombreAlmacen VarChar(100) Default Null;
Declare v_FamiliaId BigInt(20) Default Null;
Declare v_CodigoFamilia VarChar(50) Default Null;
Declare v_NombreFamilia VarChar(100) Default Null;
Declare v_TipoArticuloId BigInt(20) Default Null;
Declare v_CodigoTipoArticulo VarChar(20) Default Null;
Declare v_NombreTipoArticulo VarChar(300) Default Null;
Declare v_FechaHora DateTime Default Null;
Declare v_CantidadAlmacen Decimal(38,6) Default 0;
Declare v_Precio Decimal(38,6) Default 0;
Declare v_ParaCalculoPrecioUltimaEntrada Boolean Default Null;
Declare v_ParaCalculoPMP Boolean Default Null;

Declare Fin_C_Movimientos Boolean Default False;
Declare C_Movimientos Cursor For
Select TM.Tipo, Mo.ArticuloId, Ar.Codigo As CodigoArticulo, Ar.Nombre As NombreArticulo, Mo.AlmacenId, Al.Codigo As CodigoAlmacen, Al.Nombre As NombreAlmacen,
Ar.FamiliaId, Fa.Codigo As CodigoFamilia, Fa.Nombre As NombreFamilia, Ar.TipoArticuloId, TA.Abreviatura As CodigoTipoArticulo, TA.Descripcion As NombreTipoArticulo,
Mo.FechaHora, Mo.CantidadAlmacen, Mo.Precio, TM.ParaCalculoPrecioUltimaEntrada, TM.ParaCalculoPMP
From MovimientoAlmacen As Mo
Left Outer Join TipoMovimiento As TM On Mo.TipoMovimientoId = TM.TipoMovimientoId
Left Outer Join Articulo As Ar On Mo.ArticuloId = Ar.ArticuloId
Left Outer Join Almacen As Al On Mo.AlmacenId = Al.AlmacenId
Left Outer Join Familia As Fa On Ar.FamiliaId = Fa.FamiliaId
Left Outer Join TablaGeneral As TA On Ar.TipoArticuloId = TA.TablaGeneralId
Where Mo.ArticuloId Is Not Null And Date(Mo.FechaHora) <= in_HastaFecha And Mo.ArticuloId IN (in_ListaArticulos) And Mo.AlmacenId IN (in_ListaAlmacenes)
Order By Mo.ArticuloId, Mo.FechaHora, Mo.AlmacenId, Mo.UbicacionId;
Declare Continue Handler For Not Found Set Fin_C_Movimientos = True;

Drop Temporary Table If exists zzzInventarioTemporal;
Create Temporary Table zzzInventarioTemporal (ArticuloId BigInt(20), CodigoArticulo VarChar(50), NombreArticulo VarChar(200),
AlmacenId BigInt(20), CodigoAlmacen VarChar(50), NombreAlmacen VarChar(100),
FamiliaId BigInt(20), CodigoFamilia VarChar(50), NombreFamilia VarChar(100),
TipoArticuloId BigInt(20), CodigoTipoArticulo VarChar(20), NombreTipoArticulo VarChar(300),
Existencia Decimal(38,6), PrecioUltimaEntrada Decimal(38,6), PMP Decimal(38,6));

Set v_ArticuloId_Anterior = 0;
Set v_CodigoArticulo_Anterior = '';
Set v_NombreArticulo_Anterior = '';
Set v_AlmacenId_Anterior = 0;
Set v_CodigoAlmacen_Anterior = '';
Set v_NombreAlmacen_Anterior = '';
Set v_FamiliaId_Anterior = 0;
Set v_CodigoFamilia_Anterior = '';
Set v_NombreFamilia_Anterior = '';
Set v_TipoArticuloId_Anterior = 0;
Set v_CodigoTipoArticulo_Anterior = '';
Set v_NombreTipoArticulo_Anterior = '';
Set v_Existencia = 0;
Set v_PrecioUltimaEntrada = 0;
Set v_PMP = 0;

Open C_Movimientos;
Loop_C_Movimientos: LOOP
Fetch C_Movimientos Into
v_Tipo, v_ArticuloId, v_CodigoArticulo, v_NombreArticulo, v_AlmacenId, v_CodigoAlmacen, v_NombreAlmacen,
v_FamiliaId, v_CodigoFamilia, v_NombreFamilia, v_TipoArticuloId, v_CodigoTipoArticulo, v_NombreTipoArticulo,
v_FechaHora, v_CantidadAlmacen, v_Precio, v_ParaCalculoPrecioUltimaEntrada, v_ParaCalculoPMP;

If Fin_C_Movimientos = True Then
If v_ArticuloId_Anterior <> 0 And (v_Existencia <> 0 Or in_ExcluirSinExistencia = False) Then
Insert Into zzzInventarioTemporal Values
(v_ArticuloId_Anterior, v_CodigoArticulo_Anterior, v_NombreArticulo_Anterior, v_AlmacenId_Anterior, v_CodigoAlmacen_Anterior, v_NombreAlmacen_Anterior,
v_FamiliaId_Anterior, v_CodigoFamilia_Anterior, v_NombreFamilia_Anterior,
v_TipoArticuloId_Anterior, v_CodigoTipoArticulo_Anterior, v_NombreTipoArticulo_Anterior,
v_Existencia, v_PrecioUltimaEntrada, v_PMP);
End If;

Leave Loop_C_Movimientos;
End If;

IF v_ArticuloId <> v_ArticuloId_Anterior Or v_AlmacenId <> v_AlmacenId_Anterior Then
If v_ArticuloId_Anterior <> 0 And (v_Existencia <> 0 Or in_ExcluirSinExistencia = False) Then
Insert Into zzzInventarioTemporal Values
(v_ArticuloId_Anterior, v_CodigoArticulo_Anterior, v_NombreArticulo_Anterior, v_AlmacenId_Anterior, v_CodigoAlmacen_Anterior, v_NombreAlmacen_Anterior,
v_FamiliaId_Anterior, v_CodigoFamilia_Anterior, v_NombreFamilia_Anterior,
v_TipoArticuloId_Anterior, v_CodigoTipoArticulo_Anterior, v_NombreTipoArticulo_Anterior,
v_Existencia, v_PrecioUltimaEntrada, v_PMP);
End If;

Set v_ArticuloId_Anterior = v_ArticuloId;
Set v_CodigoArticulo_Anterior = v_CodigoArticulo;
Set v_NombreArticulo_Anterior = v_NombreArticulo;
Set v_AlmacenId_Anterior = v_AlmacenId;
Set v_CodigoAlmacen_Anterior = v_CodigoAlmacen;
Set v_NombreAlmacen_Anterior = v_NombreAlmacen;
Set v_FamiliaId_Anterior = v_FamiliaId;
Set v_CodigoFamilia_Anterior = v_CodigoFamilia;
Set v_NombreFamilia_Anterior = v_NombreFamilia;
Set v_TipoArticuloId_Anterior = v_TipoArticuloId;
Set v_CodigoTipoArticulo_Anterior = v_CodigoTipoArticulo;
Set v_NombreTipoArticulo_Anterior = v_NombreTipoArticulo;
Set v_Existencia = 0;
Set v_PrecioUltimaEntrada = 0;
Set v_PMP = 0;
End If;

Case
When v_Tipo = 'PA' Then
Set v_Existencia = v_CantidadAlmacen;
When Left(v_Tipo,1) = 'E' Then
Set v_Existencia = v_Existencia + v_CantidadAlmacen;
When Left(v_Tipo,1) = 'S' Then
Set v_Existencia = v_Existencia - v_CantidadAlmacen;
Else
Set v_Existencia = v_Existencia;
End Case;

If v_ParaCalculoPrecioUltimaEntrada = True Then
Set v_PrecioUltimaEntrada = v_Precio;
End If;

End Loop Loop_C_Movimientos;
Close C_Movimientos;

Select * From zzzInventarioTemporal;
Drop Temporary Table If exists zzzInventarioTemporal;
END

Valora esta respuesta
Me gusta: Está respuesta es útil y esta claraNo me gusta: Está respuesta no esta clara o no es útil
0
Comentar
sin imagen de perfil
Val: 953
Oro
Ha mantenido su posición en MySQL (en relación al último mes)
Gráfica de MySQL

Procedimiento con cursor variable

Publicado por leonardo_josue (414 intervenciones) el 08/05/2018 19:32:37
Hola de nuevo Mikel:

Este comportamiento es perfectamente correcto, y es debido a cómo queda conformada la consulta, checa este ejemplo. Imagina que tienes esta tabla:

1
2
3
4
5
6
7
8
9
10
11
mysql> SELECT * FROM tabla;
+------+-------------+
| id   | descripcion |
+------+-------------+
|    1 | uno         |
|    2 | dos         |
|    3 | tres        |
|    4 | cuatro      |
|    5 | cinco       |
+------+-------------+
5 rows in set (0.00 sec)
}
Entonces, si quisieras los id's 2 y 3, como lo estás haciendo envías el parámetro, con un in '2,3' entonces la consulta quedaría así:

1
2
3
4
5
6
7
mysql> SELECT * FROM tabla WHERE id IN ('2, 3');
+------+-------------+
| id   | descripcion |
+------+-------------+
|    2 | dos         |
+------+-------------+
1 row in set, 1 warning (0.00 sec)

Esto sucede porque TODO EL ARGUMENTO FORMA UN SÓLO elemento, no dos... en otras palabras, no es lo mismo '2,3', a poner '2','3' (¿se entiende?) en el segundo caso, SI TIENES DOS ARGUMENTOS, POR LO TANTO LA BÚSQUEDA SE HACE CORRECTA:

1
2
3
4
5
6
7
8
mysql> SELECT * FROM tabla WHERE id IN ('2', '3');
+------+-------------+
| id   | descripcion |
+------+-------------+
|    2 | dos         |
|    3 | tres        |
+------+-------------+
2 rows in set (0.00 sec)

Sin embargo, no hay una manera de que puedas enviar una cadena como la requieres para que se forme correctamente la sentencia IN. Una manera simple de resolver esto es cambiar la condición IN por una condición FIND_IN_SET(), de tal suerte que tu SP podría quedar más o menos así:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
mysql> DELIMITER $$
mysql> CREATE
    ->     PROCEDURE `pruebas`.`select_in`(condicion VARCHAR(20))
    ->     BEGIN
    ->       SELECT * FROM tabla WHERE FIND_IN_SET (id, condicion) > 0;
    ->     END$$
Query OK, 0 rows affected (0.00 sec)
mysql> DELIMITER ;
mysql> SELECT * FROM tabla;
+------+-------------+
| id   | descripcion |
+------+-------------+
|    1 | uno         |
|    2 | dos         |
|    3 | tres        |
|    4 | cuatro      |
|    5 | cinco       |
+------+-------------+
5 rows in set (0.00 sec)
 
mysql> CALL select_in('2,4');
+------+-------------+
| id   | descripcion |
+------+-------------+
|    2 | dos         |
|    4 | cuatro      |
+------+-------------+
2 rows in set (0.00 sec)
 
Query OK, 0 rows affected (0.00 sec)
 
mysql> CALL select_in('3');
+------+-------------+
| id   | descripcion |
+------+-------------+
|    3 | tres        |
+------+-------------+
1 row in set (0.00 sec)
 
Query OK, 0 rows affected (0.02 sec)

Para entender cómo trabaja FIND_IN_SET, observa el valor que le asigna:

1
2
3
4
5
6
7
8
9
10
11
mysql> SELECT id, FIND_IN_SET(id, '2,3') FROM tabla;
+------+------------------------+
| id   | FIND_IN_SET(id, '2,3') |
+------+------------------------+
|    1 |                      0 |
|    2 |                      1 |
|    3 |                      2 |
|    4 |                      0 |
|    5 |                      0 |
+------+------------------------+
5 rows in set (0.00 sec)

En otras palabras, la función FIND_IN_SET devuelve LA POSICIÓN del primer argumento DENTRO del segundo argumento, como si fuera una especie de arreglo.

Haz la prueba y nos comentas.

Saludos
Leo.
Valora esta respuesta
Me gusta: Está respuesta es útil y esta claraNo me gusta: Está respuesta no esta clara o no es útil
1
Comentar