Calculando el Seguro Social con un procedimiento almacenado de mySQL 5
Sunday, September 16th, 2007Hace algún tiempo atrás habia realizado esta y otras funciones para mySQL 5 en un proyecto de nómina (no sé como les terminó de ir) y quise publicarlo ahora ya que algunas personas me habian pedido esta función.
La función almacenada de mySQL consiste en un simple calculo de la cuota del Seguro Social Obligatorio Venezolano; hacerlo en procedimiento almacenado?, claro!, todos los datos necesarios para realizar el cálculo se encuentran del lado del servidor, por qué llevarlo al lado de la aplicación (Lenguaje de programación) para realizar un calculo que enteramente puede realizar la DB?.
Habia hecho esta función junto con otras 15 de conceptos de nómina para unos amigos que estaban desarrollando un sistema.
Primero; vamos a comentar el código; despues aparecerá integramente.
Concepto de Funciones en mySQL 5.
Una función es un pequeño “script” de código SQL (si fuera postgresql; pudiera ser en python o hasta en php) que puede ser convocado en la línea de una instrucción SQL; ejemplo:
SELECT md5(password), CURDATE() as fecha_actual, CURTIME() as hora_actual FROM usuarios
En el ejemplo anterior, las resaltadas son funciones internas de mySQL (una calcula el checksum md5 de una cadena; las otras devuelven la fecha y la hora actual).
Se pueden crear funciones propias, la diferencia entre una función y un procedimiento almacenado en mySQL es que una función SIEMPRE debe devolver un valor y este valor debe ser un escalar (un número, una cadena, el valor de un campo de una fila, etc); un procedimiento almacenado puede devolver escalares, filas, resultsets completos o simplemente no devolver nada.
SSO (Seguro Social Obligatorio) for Dummies.
El SSO Venezolano se calcula en base a los lunes contabilizados del mes, la formula suele ser:
((sueldo*12 meses) / Semanas laborales(default: 52)) * (retención SSO (defecto: 0.04)*lunes del mes)) <-redondeado a 2 cifras
Bien; ahora describamos el asunto:
La tabla parámetros:
Es un verdadero fastidio tener que modificar una costante (lease IVA, SSO, retención ISLR, algún gravamen) cada vez que el gobierno decide modificarla; por ende, es mejor guardar este valor en una tabla de parámetros; en dicha tabla, podremos guardar los porcentajes y valores de los distintos conceptos; de esta manera no tendremos que editar la función si de repente estos valores cambian:
La forma de la tabla es como sigue:
CREATE TABLE `nomina`.`parametros` ( `parametro` varchar(10) NOT NULL default '', `valor` decimal(3,3) NOT NULL default '0.000', `patronal` decimal(3,2) NOT NULL default '0.00', PRIMARY KEY (`parametro`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1
e Insertamos un registro como el siguiente:
INSERT INTO parametros VALUES ('sso', 0.04, -0.87);
A los patronos se les retiene el doble de lo que se le retiene al empleado.
La función Almacenada SSO:
Oficialmente el delimitador de sentencias de mySQL es el punto y coma (;), por ende, debemos cambiarlo antes de poder escribir una función almacenada (que termina todas sus sentencias en punto y coma) para ello escribimos:
DELIMITER $$
Y ahora cuando terminemos de escribir la función almacenada escribimos:
END$$ (en vez de END;)
Ahora viene el cuerpo de declaración de la función:
DELIMITER $$
DROP FUNCTION IF EXISTS `nomina`.`sso`$$
CREATE DEFINER=`root`@`localhost` FUNCTION `sso`(sueldo DECIMAL(8,2), tipo INT) RETURNS decimal(8,2)
Inicialmente ejecuto un DROP IF EXISTS que me permite verificar si la función existe y borrarla (en caso que estuviera editandola; no existe ALTER FUNCTION o ALTER PROCEDURE, por lo que hay que borrar primero un procedimiento si deseamos cambiarlo).
La directiva SQL SECURITY DEFINER nos indica que la función se ejecutará en un ámbito de permisos igual a quien creó la función (forma por defecto); la otra forma INVOKER hace que la función se ejecute en el ámbito de permisos de quien la ejecuta; para un ejemplo:
Tengo permisos sobre la tabla A y creo una función con DEFINER que lee esa tabla -> resultado -> La función puede leer la tabla A
Creo una función que intenta leer la tabla B y no tengo permisos para leer esa tabla -> resultado -> La función no puede leer la tabla B
Creo una función con INVOKER que lee la tabla B y usuario que invoca la función puede leer B -> resultado -> la función puede leer B
Creo una función con INVOKER que lea la tabla A y usuario que invoca NO puede leer la tabla A -> resultado -> La función no puede leer la tabla A.
DEFINER pide el usuario que deseamos colocar como DEFINER.
FUNCTION sso() es la declaración de función y pide algunos parámetros (dentro del paréntesis); estos son sueldo (de tipo DECIMAL(8,2), longitud integer 8 con 2 decimales de precisión) y un tipo (que es un entero 1 o 0); la situación es que en Venezuela, hay 2 cotizaciones de SSO; la nacional (0.04) y la estadal (0.02, es menor porque por lo general en las zonas rurales no hay Seguro Social, asi que para que te voy a retener algo que no existe?); la función retorna un valor escalar (el SSO calculado) de tipo DECIMAL(8,2).
Las funciones de mySQL repito, tienen 3 limitantes:
- No pueden conectarse (por defecto) a datos en otras tablas.
- No pueden recibir parámetros de salida (todos los parámetros son de entrada)
- Solo pueden devolver escalares
Para obviar la primera observación (para que nuestra función pueda obtener datos desde otras tablas) entonces agregamos a la declaración:
READS SQL DATA
Y eso permite a nuestra función ejecutar sentencias SQL dentro del código.
El cuerpo de la función:
BEGIN
DECLARE porcentaje DECIMAL(2,2);
DECLARE lunes INT;
DECLARE mes INT;
DECLARE fecha DATE;
DECLARE primero DATE;
DECLARE quince DATE;
SELECT curdate() INTO fecha;
SELECT DAYOFMONTH(fecha) INTO mes;
SELECT STR_TO_DATE(CONCAT(YEAR(fecha), ‘-’, MONTH(fecha), ‘-’, 1), ‘%Y-%m-%d’) INTO primero;
SELECT STR_TO_DATE(CONCAT(YEAR(fecha), ‘-’, MONTH(fecha), ‘-’, 15), ‘%Y-%m-%d’) INTO quince;
IF tipo=0 THEN
SELECT valor INTO porcentaje FROM parametros WHERE parametro = ’sso’;
ELSE
SELECT valor INTO porcentaje FROM parametros WHERE parametro = ’sso-estado’;
END IF;
IF mes <= 15 THEN
BEGIN
SELECT (WEEK(quince,5) - WEEK(primero,5)) INTO lunes;
END;
ELSE
BEGIN
SELECT (WEEK(LAST_DAY(fecha),5) - WEEK(quince,5)) INTO lunes;
END;
END IF;
return TRUNCATE((((sueldo*12)/52)*(porcentaje*lunes)), 2);
END$$
DELIMITER ;
Algunas observaciones útiles:
DECLARE sirve en ANSI DB2 SQL (la base de mySQL) declarar variables a ser usadas dentro del código.
SELECT INTO … permite hacer un SELECT de un escalar y devolverlo a una variable interna:
SELECT valor INTO porcentaje FROM parametros WHERE parametros = 'sso'
La función calcula el SSO quincenalmente, determinando primero a partir de la fecha actual, en que quincena estamos y retornando la cantidad de lunes de esa semana:
IF mes <= 15 THEN
BEGIN
SELECT (WEEK(quince,5) - WEEK(primero,5)) INTO lunes;
END;
ELSE
BEGIN
SELECT (WEEK(LAST_DAY(fecha),5) - WEEK(quince,5)) INTO lunes;
END;
END IF;
Posteriormente computa el resultado y lo devuelve con dos cifras de precisión usando TRUNCATE().
Le faltan muchas cosas a esta función (como que la fecha sea parametrizada y no que la tome de la actual) y cosas así; pero tomemos en cuenta que no tengo mucha experiencia en administración y esta función la construí con una experta en nómina sentada a mi lado.
Espero que entiendan algunas cosas más de la construcción de procedimientos almacenados y funciones almacenadas en mySQL.
