Cómo detener la ejecución en SAS

Hola de nuevo a todos. El tema de hoy es cómo detener el flujo de ejecución de un programa SAS. Normalmente lo haremos cuando se den determinadas condiciones, por ejemplo cuando un fichero no esté disponible, cuando se haya superado una hora de ejecución, etc.
Hay dos formas principales de detener la ejecución y utilizar una y otra depende del contexto dentro del programa:
Si estamos dentro de un paso data podemos utilizar el comando STOP.

data salida;
set entrada;
if _N_ = 100 then stop;
run;

Este ejemplo detendrá el proceso al llegar al registro número 100. La tabla de salida se obtendrá, pero sin el registro 100, por lo que se obtiene una tabla de 99 registros. El comando stop detiene la ejecución del paso data en el sentido de que no se procesan más registros pero no detiene la ejecución totalmente (ya que la tabla se genera).
En cambio, si estamos dentro de una macro utilizaremos una etiqueta y un comando %goto. Las etiquetas son identificadores a lo largo del código que identifican un punto determinado. En este caso tendríamos que situar esa etiqueta antes del %mend que termina la macro, y una condición que si se cumple lleve a esa etiqueta:


%macro ejecucion;
data salida1;
set entrada1;
run;
%if not %sysfunc(exist(entrada2)) %then %goto fin;
data salida2;
set entrada2;
run;
data salida3;
set entrada3;
run;
%fin:
%mend;
%ejecucion;

En este ejemplo el programa comprueba que exista la tabla «entrada2» antes de seguir con el proceso porque en caso contrario lleva el control de ejecución al final de la macro y la ejecución termina.

SAS: Convertir cadenas de texto a número o fecha

Hay varias operaciones que hay que hacer frecuentemente para convertir cadenas de texto a otros tipos de formato o a la inversa.

Empezaremos con lo más simple. Por ejemplo, para convertir un número a texto debemos de utilizar la función put() que admite como parámetro un número y devuelve el formato indicado (también se puede utilizar para dar formatos de fecha):

data salida;
    b = 1;
    b1 = put(b,$2.);
run;

Resultado de conversión número a texto
Y para la operación contraria, convertir una cadena en número, utilizamos la función input() que hace la operación inversa:

data salida;
    a = '1';
    a1 = input(a,8.);
run;

Resultado de conversión texto a número
Convertir una cadena de texto con una fecha en un campo fecha es un poco más complicado. Necesitamos tomar cada valor de mes, día y año de esa cadena e introducirlos como parámetros de la función mdy(). Pero mdy() solo admite números como parámetros por lo que debemos convertirlos con un input() previamente. Esto quedaría así:

data salida;
    f = '01/12/2020';
    mes = input(substr(f,4,2),8.);
    dia = input(substr(f,1,2),8.);
    anyo = input(substr(f,7,4),8.);
    format fecha ddmmyys10.;
    fecha = mdy(mes,dia,anyo);
run;

Convertir texto a fecha
De forma más directa y resumida podríamos expresarlo de la siguiente manera:

data salida;
    f = '25/12/2020';
    format fecha ddmmyys10.;
    fecha = mdy(input(substr(f,4,2),8.),input(substr(f,1,2),8.),input(substr(f,7,4),8.));
run;

Convertir texto a fecha
Únicamente debemos tener en cuenta con este método qué va a pasar con las fechas en los que el día y el mes vengan expresados por un solo dígito: ¿vendrán como 01 o como 1? Para resolver este punto podemos cambiar nuestro substr() por un scan(). Esta función divide una cadena por el carácter que se le indique y nos devuelve la subcadena que ocupe la posición indicada por parámetro. Para el caso anterior, lo expresaríamos así:

data salida;
    f = '01/12/2020';
    mes = input(scan(f,2,'/'),8.);
    dia = input(scan(f,1,'/'),8.);
    anyo = input(scan(f,3,'/'),8.);
    format fecha ddmmyys10.;
    fecha = mdy(mes,dia,anyo);
run;

Convertir texto a fecha
Convertir una fecha en una cadena de texto se consigue por la combinación de las funciones put() e input(). El put() que va en el interior sirve para poner la fecha en el formato adecuado antes de convertirlo en texto. En caso contrario la fecha se convertirá como el número que es, en texto. Una vez tenemos el formato adecuado lo convertimos a texto con un input().

data salida;
    fecha = '25dec2020'd;
    fechado = input(put(fecha,yymmddn8.),$8.);
run;

Convertir fecha a texto

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;

SAS: Operaciones con fechas

Trabajar con fechas y con formatos de fechas es una de las cosas a las que más cuesta acostumbrarse a aquellos que comienzan a trabajar con SAS. Aquí quiero recoger un pequeño resumen de las funciones de fecha más habituales en SAS, cómo funcionan y cuales son las diferencias entre ellas. Aquí veremos solo funciones de fecha, no entraré en las funciones fecha-hora.

Lo primero que hay que saber de las fechas en SAS es que, como en muchos otros lenguajes de programación, en realidad se representan internamente por un número y la fecha que vemos es una máscara o formato de representación de ese número. Al ser números se pueden realizar operaciones aritméticas con ellas directamente sin problema, por lo que el día de ayer se puede calcular como date()-1.

La fecha 0 es en SAS el 1 de enero de 1960. En otros lenguajes la fecha 0 puede ser otra. El 31 de diciembre de 1959 tiene el valor -1.

Para expresar una fecha en SAS podemos hacerlo por un número o por la notación '1jan2021'd. La fecha actual se obtiene con date().

  • datedate()
    Da la fecha del sistema, o sea, la fecha de la máquina en la que corre SAS. El resultado, sin aplicar ningún formato, será un número.

    Ejemplo Resultado
    date() 22240
    put(date(),date9.) 21NOV2020
  • datepartdatepart([datetime])
    Convierte un valor tipo fecha-hora en una fecha.

    Ejemplo Resultado
    datepart(datetime()) 22240
    put(datepart('15apr2020:00:00:00'dt),date9.) 15APR2020
  • intnxintnx([intervalo], [fecha], [incremento], [alineamiento])
    La función intnx() calcula la fecha resultante de aplicar un incremento (positivo o negativo) a la «fecha» indicada. El «incremento» representa el número de unidades del tipo definido en el parámetro «intervalo» que hay que añadir a esa fecha. Este «incremento» puede ser positivo o negativo. El «alineamiento» puede tomar los valores ‘B’ inicio del periodo; ‘E’ fin del periodo; ‘M’ medio del periodo y ‘S’ mismo día del periodo, y significa que si vamos a sumar x meses a una fecha si indicamos «B», la fecha resultante será el primer día del mes después de sumar x meses a la fecha dada. Lo vemos mejor en unos ejemplos:

    Ejemplo Resultado
    intnx('month','31aug2020'd,1) 1SEP2020
    intnx('month','31aug2020'd,1,'S') 30SEP2020
    intnx('month','18jan2020'd,-1,'E') 31DEC2019
    intnx('year','5jun1999'd,0,'B') 01JAN1999
    intnx('week','18apr2020'd,1,'S') 25APR2020
    intnx('qtr','18apr2020'd,0,'E') 30JUN2020
  • intckintck([intervalo], [fecha-inicio], [fecha-fin], [método])
    La función intck() calcula la diferencia entre las dos fechas indicándolo en función de la unidad temporal expresada en el parámetro intervalo. El ‘método’ sirve para indicar cómo se va a realizar el cálculo: puede tomar los valores ‘CONTINUOUS’ o ‘C’ para calcular el número de aniversarios sucedidos antes de la fecha-fin, o ‘DISCRETE’ o ‘D’ que toma el tiempo como una variable discreta y cuenta cuantas unidades hay entre las dos fechas proporcionadas. Esto último se ve mejor en el siguiente ejemplo donde se observa que cuando comienza un nuevo periodo temporal natural (en este caso estoy contando años) se incrementa la cuenta que ofrece el método ‘DISCRETE’.

    Ejemplo Resultado
    intck('year','1jul2000'd,'1jan2001'd,'D'); 1
    intck('year','1jul2000'd,'31dec2001'd,'D') 1
    intck('year','1jul2000'd,'1jan2002'd,'D') 2
    intck('year','1jul2000'd,'1jan2002'd,'C') 1
    yrdif('1jul2000'd,'1jan2002'd) 1
  • DAY(), MONTH(), YEAR()
    Estas funciones dan como resultado un número que representa, respectivamente, el día el mes y el año de la fecha que se indica entre paréntesis.

    Ejemplo Resultado
    day(date()) 21
    month('25may2020'd) 5
    year('25may2020'd) 2020
  • WEEK(), WEEKDAY()
    Estas funciones devuelven, respectivamente, el número que ocupa esa fecha dentro de un año (entre 1 y 53); y el número del día de la semana desde 1-domingo hasta 7-sábado.

    Ejemplo Resultado
    week('21nov2020'd) 46
    weekdate('21nov2020'd) /*sábado*/ 7
    weekdate('22nov2020'd) /*domingo*/ 1
  • QTR()QTR([fecha])
    Da como resultado un entero que indica el trimestre del año (de 1 a 4) en que se encuentra la fecha.

    Ejemplo Resultado
    QTR('21nov2020'd) 4
  • YRDIFYRDIF([fecha-inicio],[fecha-fin])
    YRDIF da la diferencia exacta en años entre dos fechas. El resultado es un número con decimales que representa exactamente la diferencia en años. Se puede añadir un tercer parámetro que hace que se consideren años de meses de 30 días y 360 días por año y otras opciones.

    Ejemplo Resultado
    yrdif('1jul2000'd,'1jan2002'd) 1.504109589

Ejecutar un programa SAS pasando parámetros

La entrada de hoy es la solución a un problema muy fácil: se trata de planificar un programa sas pasándole algún parámetros desde línea de comandos, en este caso, la solución aplica a Unix/Linux.

Para ello, debemos crear un fichero de script (.sh) que contenga la llamada y los parámetros a enviar:

sh [ruta_SAS]/Lev1/SASApp/sas.sh -set fecha 20201111 -set hora 17:28 -sysin [ruta_programa]/programa.sas -log [ruta_log]/fichero.log

La llamada a sas.sh ejecuta sas.exe con las opciones que estén configuradas para el usuario. El parámetro set contiene una dupla clave valor con el nombre de la variable a enviar al programa en ejecución y su valor. Se pueden concatenar varios parámetros set. En el ejemplo se pasa la fecha y la hora. Finalmente, los parámetros sysin y log indican la ruta y fichero del programa sas y del log que se debe generar, respectivamente.

Una vez hecho esto, solo queda recoger en el programa los valores que hemos enviado. Esto podemos hacerlo de la siguiente forma:

%let fecha=%sysget(fecha);
%let hora=%sysget(hora);

Con ello, podemos utilizar los parámetros que hemos enviado como simples macrovariables: &fecha y &hora.

Unix: Extensión de un fichero Unix

En esta ocasión vamos a ver cómo podemos hacer con un script de Unix una tarea que en realidad también podemos hacer con SAS. Se trata de renombrar ficheros *.zip a *.zip.old, en este caso modificando su extensión. Para ello, debemos poder extraer las dos partes del nombre de un fichero: el nombre y la extensión.

FICHERO="${i%%.*}"
EXTENSION=$([[ "$i" = *.* ]] && echo "${i#.}" || echo "${i##.}")

Si queremos procesar todos los ficheros zip de un directorio para renombrarlos recursivamente a zip.old (por ejemplo) podemos usar el siguiente script. Lo primero que hace es listar los ficheros que tienen zip en su nombre (o extensión). Luego revisa, para cada uno de ellos, cual es exactamente su extensión, y si finalmente tienen extensión zip los renombra (con mv) a *.zip.old:

for i in $(ls -1 | grep .zip);
do FICHERO="${i%%.*}";
EXTENSION=$([[ "$i" = *.* ]] && echo "${i#.}" || echo "${i##.}");
if [ "$EXTENSION" == "zip" ];
then mv $FICHERO.zip $FICHERO.zip.old;
fi;
done

Este script puede ser llamado también desde SAS para hacer esa misma tarea con el comando X:

x"for i in $(ls -1 | grep .zip); do FICHERO="${i%%.*}"; EXTENSION=$([[ "$i" = *.* ]] && echo "${i#.}" || echo "${i##.}"); if [ "$EXTENSION" == "zip" ]; then mv $FICHERO.zip $FICHERO.zip.old; fi; done";

Formatos de fecha en SAS

Existen infinidad de formatos de fecha o fecha-hora en SAS, literalmente cientos, en todos los idiomas y en todos los tipos de calendarios. Creo que conocer lo que SAS nos puede ofrecer en cuanto a estos formatos nos puede ayudar mucho. Y además, si esto no es suficiente para ti, aún te puedes crear un formato personalizado.

Vamos a hacer un repaso de los formatos de fecha que me parecen más interesantes. No creo que nadie utilice todos, pero desde luego aquí hay para todas las preferencias. Os los ofrezco clasificados por el uso más natural que me parece que tiene cada formato. Incluyo también algunos formatos de fecha en español.

Formato Resultado Uso
Partes de la fecha
DAY. 19 Día del mes.
WEEKDAY. 1 Día de la semana (1=domingo).
DOWNAME. Sunday Nombre del día de la semana en inglés.
ESPDFDWN. domingo Nombre del día de la semana en español.
MONTH. 4 Número del mes.
MONNAME. April Nombre del mes en inglés.
ESPDFMN. abril Nombre del mes en español.
QTR. 2 Número del trimestre.
YEAR. 2020 Año
Fechas solo con números.
DDMMYY10. 19/04/2020 Compatible con Excel.
DDMMYYB10. 19 04 2020
DDMMYYC10. 19:04:2020
DDMMYYD10. 19-04-2020 Compatible con Excel.
DDMMYYP10. 19.04.2020
YYMMDDS10. 2020/04/19
YYMMDDB10. 2020 04 19
YYMMDDC10. 2020:04:19
YYMMDDD10. 2020-04-19 Compatible con Excel.
YYMMDDP10. 2020.04.19
Fechas con el nombre del mes.
DATE. 19apr20
DATE9. 19apr2020 Formato habitual de trabajo.
ESPDFDE. 20abr2020 Igual al anterior, en español.
Para fechar nombres de fichero o tabla
DDMMYYN10. 19042020 Para fechar (orden invertido).
YYMMDDN10. 20200419 Para fechar ficheros.
YYMMN6. 202004 Para fechar ficheros mensuales.
Fechas para informes
YYWEEKW7. 2020W04 Informes semanales.
YYMMN6. 202004 Informes mensuales.
YYMMS. 2020/04 Informes mensuales.
MMYYS. 04/2020 Informes mensuales.
YYMON. 2020APR Informes mensuales.
MONYY. APR20 Informes mensuales.
YYQ6. 2020Q2 Informes trimestrales
YYS6. 2020S1 Informes semestrales.
Fechas para texto o documentos.
WORDDATE. April 19, 2020 Fecha en texto en inglés.
WORDDATX. 19 April 2020
ESPDFWDX. 30 de enero de 2011 Como los anteriores pero en formato español.
WEEKDATE. Sunday, April 19, 2020 Fecha en texto con día de la semana en inglés.
ESPDFWKX. domingo, 30 de enero de 2011 Como el anterior pero en formato español.

Hay muchísimos más formatos de fecha que se pueden encontrar en la documentación de SAS.

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.

Importar ficheros XML con SAS

En otra entrada previa hemos visto cómo importar ficheros json, en esta nos vamos a fijar en la importación de ficheros xml.

Empezaré diciendo que XML significa «Extensible Markup Language». Son ficheros de texto donde el contenido viene marcado con unas etiquetas con una sintaxis parecida a la del HTML, lo que le confiere una estructura a la información que contiene. En realidad XML es la generalización del lenguaje HTML. El contenido de un fichero XML puede ser tan complejo que más que ser una tabla es más bien una pequeña base de datos con varias tablas relacionadas entre si.

El procedimiento de importación de un XML es parecido al de un json. Se crea una librería donde se importan las distintas tablas de la estructura del fichero.

Para este ejemplo tomaré un fichero de la Agencia Española de Meteorología que publica a través de su web Opendata y revisando la documentación del fichero que publica una organización de estándares llamada OASIS obtengo un fichero XSD que contiene la definición del fichero XML.

Con todo ello cargo los dos ficheros XML y XSD con un filename y a través de un libname creo la librería AEMET que contendrá los ficheros. Estos pueden ser copiados luego normalmente a cualquier otra librería si tienen que ser manipulados.

filename AEMET "&ruta/input/Z_CAP_C_LEMM_20200322101800_AFAZ691703COCO2321.xml";
filename map "&ruta/CAP.xsd";
libname AEMET xmlv2 xmlmap=map automap=replace;

Lo importante aquí en el comando libname es que el parámetro que indica el motor (o tipo) de la librería puede ser xml o xmlv2. Yo he conseguido hacerlo funcionar con la versión 2 y además he tenido que añadir el parámetro automap porque tras varios intentos de importar el XML no me estaba funcionando porque el mapeo del fichero que hizo SAS en un principio parece que no se refrescaba. Esta opción fuerza que vuelva a mapear el fichero con el fichero que le pasamos con el xmlmap.

Este es el resultado de la importación del fichero: una nueva librería AEMET con seis tablas:

libreria_AEMET

El fichero XSD sirve además, normalmente, para hacer una validación del contenido y estructura del XML. Lamentablemente libname no realiza esa función en SAS 9.4.

SAS: Bucles do y %do

En SAS se pueden utilizar tres tipos de bucles que además pueden utilizarse en código abierto o en lenguaje macro. Los tipos de bucles son: do, while y until. Lo relevante es que el uso de estos bucles en código abierto o lenguaje macro es muy diferente.

Lo primero que hay que saber es que los bucles de macro solo se pueden utilizar dentro de una macro y debido al momento en que se resuelve el código hay que tener ciertas consideraciones.

código abierto lenguaje macro

do x=1 to 10 by 1;
[comandos]
end;

%do x=1 %to 10 %by 1;
[comandos]
%end;

El bucle do (y %do) repite la ejecución de una parte de un código un número de veces que va representado por el valor de la variable indicada (x, en este caso) que comienza en 1 y termina con valor 10, saltando valores de 1 en 1. Tanto 1 como 10 se ejecutan dentro del bucle.

La diferencia entre usar el bucle do con código abierto o lenguaje macro está en que la x que se itera, es un campo en código abierto (que saldrá en la tabla de destino) y una macrovariable en lenguaje macro. Esto afecta a la forma de uso:

código abierto lenguaje macro

data salida1;
    do x=1 to 10;
        a=x;
        output;
    end;
run;

%macro iter_do;
    data  salida2;
        %do x=1 %to 10;
                a=&x;
                output;
        %end;
    run;
%mend;
%iter_do;

Hay también limitaciones en lenguaje macro en el sentido en que no se puede incluir como condición del bucle una referencia o cálculo sobre un campo de la tabla de entrada. La razón es que el código SAS que se va a ejecutar se «escribe» en el momento de interpretar la macro, pero no se ejecuta, por lo que la referencia al contenido de la tabla no existe aún. En este caso, por ejemplo, donde se usa el campo «a» de la tabla que antes hemos creado:

%macro iter_do;
    data  salida2;
        set salida1;
        %do x=1 %to a;
                a=&x;
                output;
        %end;
    run;
%mend;
%iter_do;

Obtenemos como salida un error del tipo:

"ERROR: A character operand was found in the %EVAL function or 
%IF condition where a numeric operand is required. The condition was: a".

Se puede superar este error declarando una macrovariable que tome el valor de "a" antes del inicio del bucle, pero no puede hacer referencia a un valor que no sea constante para todos los registros de la tabla. Si esto te pasa es síntoma de que tienes que estar utilizando un bucle do, no uno %do.

En otras ocasiones en el %to se incluyen ciertos cálculos que no son resueltos correctamente en la declaración del bucle. Esto puede requerir declarar una macrovariable previamente o utilizar un %sysfunc() por cada función de SAS que estemos utilizando. %sysfunc permite utilizar la mayoría de las funciones SAS en tiempo de ejecución macro. (Existen notables excepciones como put, que no admite %sysfunc). Un ejemplo de esto sería:

%let inicio=22000;  /* Fecha de 26mar2020 */;

%macro iter_do;
    data  salida3;
        %do x=&inicio %to date();
                a=put(&x,date9.);
                output;
        %end;
    run;
%mend;
%iter_do;

Que daría un error como el que sigue:

ERROR: Required operator not found in expression: date() 
ERROR: The %TO value of the %DO X loop is invalid.
ERROR: The macro ITER_DO will stop executing.

La solución es usar %sysfunc(date()). Es necesario usar un %sysfunc para cada función de SAS que esté incluida en la declaración del bucle de forma que se anidarán unas dentro de otras tanto como sea necesario. Atención a los paréntesis en las estructuras más complejas.