Mediante procedimientos almacenados (Stored Procedures)
Es cierto que, cuando pensamos en automatizar un flujo de trabajo, sea cual sea este, lo primero en lo que pensamos es en utilizar lenguajes orientados a objetos como Python, o flujos de trabajo mediante Power Automate, sin embargo, esta no siempre es la mejor opción. En algunos casos, como cuando el proceso implica un gran volumen de datos que ya residen en nuestra BD, la mejor alternativa puede ser el uso de Stored Procedures en SQL. Estos permiten ejecutar la lógica directamente en el motor de la base de datos, reduciendo la latencia, optimizando el rendimiento y garantizando mayor seguridad al limitar el acceso a los datos, al no tener que ser importados directamente a nuestra computadora, pero ¿Qué es un procedimientos almacenado? No es más que un conjunto de instrucciones predefinidas que se almacenan y ejecutan en la base de datos, es decir, prácticamente es el equivalente a generar un pipeline directamente en la base de datos, ya que nos permite encapsular procesos dentro del propio motor de la base de datos.
La lógica para crear un procedimiento almacenado es muy similar a la que utilizaríamos para crear una función en R o Python, por ejemplo, supongamos que queremos insertar de forma periódica datos en una tabla de registro de usuarios como la siguiente:
Pero puede convertirse en algo tedioso el tener que usar constantemente la función INSERT INTO …, por lo que podemos crear un procedimiento almacenado que ya englobe de manera directa esta función, siendo:
registrar_venta(
create procedure in p_cliente_id int,
in p_monto decimal(10,2)
)
beginregistros (usuario_id, transaccion)
insert into values (p_cliente_id, p_monto);
end
Claro que también podemos hacer uso de otras funciones como now()
, para añadir de forma automática la fecha en la que estamos registrando esos valores:
registrar_venta(
create procedure in p_cliente_id int,
in p_monto decimal(10,2)
)
beginregistros (usuario_id, transaccion, fecha)
insert into values (p_cliente_id, p_monto, now());
end
Teniendo este proceso establecido, ahora el insertar datos a la tabla se convierte en una tarea mucho más sencilla, ya que solo tendremos que llamar a la función registrar_venta()
, con los valores a insertar:
registrar_venta(1, 300.99); call
Trabajar con procesos para simplificar ciertas tareas no nos exime de cometer errores al momento de introducir datos, por lo que, tal como lo haríamos con una estructura de control tradicional (IF ELSE
, FOR
, etc), podemos establecer ciertas condiciones para que, en caso de introducir valores incorrectos (o no permitidos), podamos parar la función. Volvamos al caso anterior, supongamos que queremos insertar nuevos registros, pero solamente aquellos que tengan un valor en la transacción positivo. Podemos establecer una condición en la que, cuando el valor esperado no sea mayor a 0, simplemente devuelva un mensaje de error (sin insertar ningún valor), tal que:
create trigger valores_invalidos
before insert on registrosfor each row
beginif new.transaccion < 0 then
'45000'
signal sqlstate = 'el monto de la transacción no puede ser negativo';
set message_text if;
end end
Como puedes darte cuenta, esta condición se encuentra fuera del procedimiento almacenado, por lo que, independientemente de que utilicemos dicha función o simplemente hagamos uso de una sentencia INSERT INTO
, no podremos insertar valores negativos en la tabla.
Obviamente, al no insertar valores negativos debido a la condición anterior, directamente se pierde el registro de esa información. Si quisiéramos guardar esos datos en una tabla separada, podríamos establecer una condición de error en el PA (similar a tryCatch()
en R o Try
en Python). Comencemos por crear una tabla que guarde valores erroneos:
log_errores(
create table varchar(50),
mensaje decimal(10,2),
transaccion
fecha date )
Ahora, ya que tenemos la tabla para guardar los valores erroneos, podemos establecer un PA que, en caso de devolver un error, guarde los datos en esta última:
registrar_transaccion_segura(
create procedure in p_usuario_id int,
in p_transaccion decimal(10,2)
)
beginfor sqlexception
declare exit handler
beginlog_errores (mensaje, transaccion, fecha)
insert into values ('error al registrar transacción', p_transaccion, now());
end;
registros (usuario_id, transaccion, fecha)
insert into values (p_usuario_id, p_transaccion, now());
end
Por lo que, al llamar a la función con un valor incorrecto, por ejemplo:
registrar_transaccion_segura(1,-200) call
La información será guardada en log_errores
.
Finalmente, llegamos al tema que probablemente te interesará más: Programación de eventos para automatizar procesos en intervalos de tiempo. En muchos casos, es necesario ejecutar ciertas tareas de manera recurrente, como la limpieza de registros antiguos, el procesamiento de datos diarios o la actualización de estadísticas. Para ello, en SQL podemos hacer uso de los eventos programados (JOBS).
Para crear un evento programado en MySQL, primero debemos asegurarnos de que el scheduler está habilitado. Esto lo podemos verificar con la siguiente consulta:
'event_scheduler' show variables like
Esto nos devolverá un valor ON u OFF. Si el valor resultante es OFF, entonces tendremos que activar el scheduler mediante la siguiente sentencia:
= ON; set global event_scheduler
Ahora, ya que tenemos activado el scheduler podemos comenzar. Regresemos al ejemplo anterior, con la función registrar_transaccion_segura
. Supongamos que queremos insertar valores cada hora, sencillamente podemos automatizar este proceso mediante:
create event job_registrar_transaccion1 hour
on schedule every
do
beginregistrar_transaccion_segura(1, 100.50);
call end
Donde únicamente englobamos el PA en un JOB que se ejecutará cada hora. En una situación real, no vamos a utilizar constantes que se actualicen cada hora. Generalmente haremos uso de variables basadas en registros nuevos que, usualmente, vendrán de otras tablas. Veamos un ejemplo más detallado, donde el JOB tomará información de, una tabla secundaria llamada transacciones_pendientes
, fila por fila:
create event job_registrar_transaccion1 hour
on schedule every
do
begin
declare done int default false;
declare v_usuario_id int;decimal(10,2);
declare v_transaccion
for
declare cur cursor
select usuario_id, transaccion from transacciones_pendientes;
for not found set done = true;
declare continue handler
open cur;
: loop
read_loop
fetch cur into v_usuario_id, v_transaccion;if done then
leave read_loop;if;
end
registrar_transaccion_segura(v_usuario_id, v_transaccion);
call
end loop;
close cur;
delete from transacciones_pendientes; end
Notese que le agregamos un par de elementos a este JOB: Un cursor declare cur cursor
, ya que este elemento nos permitirá avanzar de fila en fila de forma similar a como trabaja una estructura WHILE
, para la inserción de datos provenientes de otra tabla, así como una pequeña condición declare continue handler for not found set done = true
para que, una vez que se terminen de insertar los registros, cierre el cursor.
[1] Melton, J., & Simon, A. R. (1993). Understanding the new SQL: a complete guide. Morgan Kaufmann.
[2] Celko, J. (2010). Joe Celko’s SQL for smarties: advanced SQL programming. Elsevier.
[3] Eisenberg, A. (1996). New standard for stored procedures in SQL. ACM SIGMOD Record, 25(4), 81-88.
[4] Panwar, V. (2024). Optimizing Big Data Processing in SQL Server through Advanced Utilization of Stored Procedures. Journal Homepage: http://www. ijmra. us, 14(02).