Automatización: Ejecución condicionada

El punto que vamos a tratar aquí es cómo hacer que un programa que tenemos planificado no arranque y se ejecute entero en el momento en que el planificador lance en el proceso, sino que espere a alguna otra dependencia que tenga que, por la razón que sea, no está disponible en el planificador, por ejemplo, si estamos utilizando cron o algún planificador de tareas básico que no pueda gestionar dependencias, puede ser una buena idea comprobar antes que todas las tablas que necesitamos están actualizadas.

Para ello vamos a necesitar una macro (llamada ejecucion en el ejemplo de código), que contiene el bucle y el código a ejecutar. Lo primero que hacemos es evaluar la condición de ejecución (antes del bucle while), si la condición no es cierta, se ejecutará el contenido del bucle y si es correcta ejecutará el código.

En un paso data dentro del bucle calculamos la hora actual y la hora límite que queremos dar al proceso (en este caso, la hora límite son las 22:00PM). Con esta hora límite, evitamos que el proceso se quede embuclado hasta el infinito en caso de que la condición que hemos establecido para num_tablas_actualizadas no se de nunca. Las funciones de fecha que se utilizan las hemos explicado en SAS: Operaciones con fechas y los formatos de fecha en Formatos de fecha en SAS.

La función sleep() debe ir dentro de un paso data y obliga a esperar al proceso por un periodo de tiempo. Puede tener uno o dos parámetros. Si solo le damos uno ese serán el número de unidades a esperar con la particularidad de que las unidades en un sistema Windows serán segundos y en otro basado en Linux, serán milisegundos. La forma de hacer que el código sea portable entre sistemas distintos es darle dos parámetros de forma que el primero siguen siendo el número de unidades y el segundo indicará cual es esa unidad. Por ejemplo, 1 significará segundos y 0.001 milisegundos.

Como último paso del bucle while se vuelve a evaluar la condición de ejecución (en el proc sql) y se comienza una nueva iteración en la que se comprueba esa condición y también se comprueba que no se haya llegado a la hora límite. Si se cumplen las condiciones el flujo se saltará el bucle y comenzará con la ejecución del resto del código.

%macro ejecucion();
proc sql noprint;
select count(*) into :num_tablas_actualizadas
from XXXXXX where YYYYYY
;quit;
%put "WARNING: Hay &num_tablas_actualizadas tablas actualizadas a la hora de inicio del proceso.";

%do %while (&num_tablas_actualizadas ne 5);
data _null_;
call symput('a',put(datetime(),datetime20.));
call symput('b',put(dhms(date(),22,00,0),datetime20.));
x=sleep(15,60);
run;
%put "WARNING: Hay &num_tablas_actualizadas tablas actualizadas a las &a";

proc sql noprint;
select count(*) into :num_tablas_actualizadas
from XXXXXX where YYYYYY
;quit;

data _null_;
if &a > &b then do;
%put "WARNING: Ejecución terminada al superarse la hora límite.";
stop;
end;
run;
%end;

/* inicio del proceso */;
%mend;
%ejecucion;

Truco SAS: Ejecución condicionada con variables de sistema

En ocasiones me ha pasado que estoy trabajando en versionar o modificar un código que ya está planificado y ejecutándose actualmente. Por lo que tengo una versión del programa ejecutando en el servidor y otra versión que desarrollo en el SAS Enterprise Guide. Lo que me pasa es que hay partes que no quiero ejecutar en el Guide, bien porque tardan, bien porque no las necesito o porque solo se pueden ejecutar en el servidor.

Para no tener que estar ejecutando un trozo de código sí, otro no, hay un pequeño truco para dejarlo todo listo para que la misma versión de código pueda correr en ambos entornos pero haciendo en cada uno lo que necesito. Este truco recurre a las variables de sistema de SAS.

SYSUSERID es una variable de sistema de SAS que informa del usuario que está ejecutando. En una instalación típica de SAS, el usuario que utiliza SAS Guide cuando trabajamos con él es «sassrv». Cuando planificamos un proceso en el servidor se ejecuta con «sasbatch». Esta es la diferencia que explotaremos ene l siguiente ejemplo para lograr ejecutar una parte del código solamente en el servidor:

options mlogic;
%macro en_servidor;
    %if "&SYSUSERID"="sasbatch" %then %do;
        data a;
            a=1;
        run;
    %end;
%mend;

%en_servidor;

Si ejecutamos este código en el Guide no sucederá nada: no se creará la tabla A. He añadido la opcion options mlogic; que incluye en el log información sobre las decisiones lógicas que toma (el resultado de los %if). Si revisamos el log podremos ver que indica que el usuario NO ES sasbatch.

MLOGIC(EN_SERVIDOR):  Empezando la ejecución.
MLOGIC(EN_SERVIDOR):  la condición %IF "&SYSUSERID"="sasbacht" es FALSE
MLOGIC(EN_SERVIDOR):  finalizando la ejecución.

Si creamos otra macro en la que modificamos el código utilizando el usuario de SAS Guide (sassrv) veremos que sí se ejecuta:

options mlogic;
%macro en_guide;
    %if "&SYSUSERID"="sassrv" %then %do;
        data a;
            a=1;
        run;
    %end;
%mend;

%en_guide;

Ahora el resultado del %if sí es verdadero, y el paso data se ejecutará.

MLOGIC(EN_GUIDE):  Empezando la ejecución.
MLOGIC(EN_GUIDE): la condición %IF "&SYSUSERID"="sassrv" es VERDADERA
MPRINT(EN_GUIDE):   data a;
MPRINT(EN_GUIDE):   a=1;
MPRINT(EN_GUIDE):   run;

NOTE: The data set WORK.A has 1 observations and 1 variables.
NOTE: Sentencia DATA used (Total process time):
      real time           0.00 seconds
      cpu time            0.00 seconds

MLOGIC(EN_GUIDE):  finalizando la ejecución.

Automatización del borrado de ficheros

La automatización del borrado de ficheros es una tarea necesaria cuando, por ejemplo, estamos generando en un proceso planificado ficheros fechados periódicamente. Hacer exhaustivamente esta tarea es complicado y no siempre la realizamos por esa razón (yo al menos). Esta macro da solución a esa necesidad, os animo a usarla.

Lo primero que hacemos es crear un stream de datos, df, que recoge el resultado del comando ls. El parámetro F marca los directorios con una barra «/» al final, de forma que podemos eliminarlos de la salida del ls con un grep -v.

Usando df como entrada para nuestro paso data todo lo que queda es procesar las información que hemos generado con un scan, que divide la cadena por los espacios. Finalmente solo nos queda montar la fecha de creación del fichero teniendo en cuenta lo siguiente: Linux devuelve la fecha en dos formatos distintos dependiendo de si hace menos de 6 meses que se ha generado el fichero o si hace más. Este es un ejemplo:

Formatos de fecha en un ls

Finalmente, filtramos los registros que contienen los nombres de los ficheros que queremos eliminar: ficheros anteriores a 2020 y que no sean tablas SAS. Un call system ejemcutará un comando rm con cada registro resultante. Esta es la macro:

%macro borrado(ruta=,fecha_limite=);
    x "cd &ruta";
    filename df pipe "ls -lahF &ruta | grep -v /";

    data _null_;
        infile df;
        input todo $300.;
        format fecha date9. fichero $100.;
        dia = scan(todo,7," ");
        if index(scan(todo,8," "),":") then anyo=year(date());
           else anyo=input(put(scan(todo,8," "),$5.),8.);
        if compress(scan(todo,6," "))='Jan' then mes=1;
        else if compress(scan(todo,6," "))='Feb' then mes=2;
        else if compress(scan(todo,6," "))='Mar' then mes=3;
        else if compress(scan(todo,6," "))='Apr' then mes=4;
        else if compress(scan(todo,6," "))='May' then mes=5;
        else if compress(scan(todo,6," "))='Jun' then mes=6;
        else if compress(scan(todo,6," "))='Jul' then mes=7;
        else if compress(scan(todo,6," "))='Aug' then mes=8;
        else if compress(scan(todo,6," "))='Sep' then mes=9;
        else if compress(scan(todo,6," "))='Oct' then mes=10;
        else if compress(scan(todo,6," "))='Nov' then mes=11;
        else if compress(scan(todo,6," "))='Dec' then mes=12;
        fecha = mdy(mes,dia,anyo);
        if fecha > date() then fecha=intnx('year',fecha,-1,'S');
        fichero = compress(scan(todo,9," "),'*');
        if fecha < &fecha_limite and not index(fichero,'.sas7bdat');

        call system('rm '||fichero);
    run;
%mend;

%borrado(ruta=[rute],fecha_limite='1jan2020'd);

Automatización del borrado de tablas

En ocasiones queremos automatizar un proceso que genera tablas diaria o semanalmente y rápidamente se acumulan un montón de datos en nuestra librería de salida. Necesitamos gestionar esto, pero no podemos estar pendientes de ello. Utilizaremos entonces una macro que nos permite desatender el borrado con unas especificaciones y nos libere de ese trabajo.

Listado de tablas en la librería SASHELP

La macro está mínimamente parametrizada y utiliza el diccionario de tablas de SAS y el proc datasets. Tal como está definida realiza un borrado de la WORK de tablas de más de 3 meses de antigüedad. Evidentemente, no está pensada para usarla contra la WORK.

%macro borrado_historico;
    %let libreria = WORK;
    %let tablas = ;
    data _null_;
        call symput('fecha_limite',put(intnx('month',date(),-3,'S'),date9.));
    run;

    proc sql noprint; 
        select memname into :tablas separated by ' '
        from DICTIONARY.TABLES 
        where libname= "&libreria" and
              memname ne '_PRODSAVAIL' and
              datepart(crdate) < "&fecha_limite"d;
    quit;

    proc datasets lib=&libreria noprint;
        delete _PRODSAVAIL &tablas;
    run;
%mend;
%borrado_historico;

Primero realiza una consulta al diccionario de tablas (DICTIONARY.TABLES) por la fecha de creación de la tabla crdate (también se puede hacer por la fecha de modificación de la tabla: modate) y listo las tablas que serán borradas separadas por espacios para que puedan ser utilizadas directamente por el próximo paso. Añadir al filtro una condición substr(memname,1,9)='CAMPANAS_' nos permitirá borrar solo las tablas de determinado tipo, por supuesto.

En el proc datasets definimos la librería que queremos atacar y le pasamos al statement delete el listado de variables que previamente hemos calculado. Aquí he usado un pequeño truco para evitar que se produzca un error en el caso de que el listado de tablas esté vacío: y es que en el proc sql había excluido la tabla _PRODSAVAIL que añado ahora aquí para que siempre tenga algo que borrar.

¿Qué es la tabla _PRODSAVAIL? Pues contiene un listado de los módulos licenciados en SAS Enterprise Guide para que la herramienta lo pueda consultar rápidamente. Se genera automáticamente con cada sesión que se genera y no produce ningún efecto adverso en el Guide su borrado.