Leading space error when using %SYMEXIST - sas

I am using %SYMEXIST to check if a macro variable exists and then continue or skip based on the result. It sounds so simple but SAS is throwing errors for all the approaches I have tried so far.
&num_tables is a macro created from a dataset based on certain conditions.
proc sql noprint;
select distinct data_name into :num_tables separated by ' '
from TP_data
where trim(upcase(Data_Name)) in
(select distinct(trim(upcase(Data_Name))) from Check_table
where COALESCE(Num_Attri_DR,0)-COALESCE(Num_Attri_Data,0) = 0
and Name_Missing_Column eq ' ' and Var_Name eq ' ');
quit;
If this macro var is not resolved or not created (no rows selected from the dataset), I would like to skip. When I used,
%if %symexist(num_tables) %then %do;
SAS gives an error with the message "MACRO variable name X must start with a letter or underscore". So I tried removing leading spaces using all of the following approaches:
%let num_tables = &num_tables; /* approach 1 */
%let num_tables = %sysfunc(trim(&num_tables)) /* approach 2 */
%let num_tables = %trim(&num_tables) /* approach 3 */
But none of these worked. I still get the error "MACRO variable name X must start with a letter or underscore"

Likely you are prefacing the num_tables in symexist with a &. This is the correct way to implement %SYMEXIST in the manner you ask. Note that the argument to %symexist is not &num_Tables but num_tables (the actual name of the macro variable). &num_tables would resolve to whatever its contents would be if you used it with the &.
%macro testshort(char=);
proc sql noprint;
select distinct name into :num_tables separated by ' '
from sashelp.class
where substr(name,1,1)="&char.";
quit;
%if %symexist(num_tables) %then %do;
%put Tables: &num_tables;
%end;
%mend testshort;
%testshort(char=A);
%testshort(char=B);
%testshort(char=Z);

Related

SAS MACRO - concrenate SQL strings in macro

I have a libY.tableX that have for each record some SQL strings like the ones below and other fields to write the result of their execution.
select count(*) from libZ.tableK
select sum(fieldV) from libZ.tableK
select min(dsitact) from libZ.tableK
This my steps:
the user is prompted to select a lib and table and the value is passed to the vars &sel_livraria and &sel_tabela;
My 1st block is a proc sql to get all the sql string from that record.
My 2nd block is trying to concrenate all that strings to use further on to update my table with the results. The macro %isBlank is the one recommended by Chang CHung and John King in their sas papper;
My 3th block is to execute that concrenated sql string and update the table with results.
%macro exec_strings;
proc sql noprint ;
select livraria, tabela, sql_tot_linhas, sql_sum_num, sql_min_data, sql_max_data
into :livraria, :tabela, :sql_tot_linhas, :sql_sum_num, :sql_min_data, :sql_max_data
from libY.tableX
where livraria='&sel_livraria'
and tabela='&sel_tabela';
quit;
%LET mystring1 =%str(tot_linhas=(&sql_tot_linhas));
%LET separador =%str(,);
%if %isBlank(&sql_sum_num) %then %LET mystring2=&mystring1;
%else %LET mystring2= %sysfunc(catx(&separador,&mystring1,%str(sum_num=(&sql_tot_linhas))));
%if %isBlank(&sql_min_data) %then %LET mystring3=&mystring2 ;
%else %LET mystring3= %sysfunc(catx(&separador,&mystring2,%str(min_data=(&sql_min_data))));
%if %isBlank(&sql_max_data) %then %LET mystring0=&mystring3;
%else %LET mystring0= %sysfunc(catx(&separador,&mystring3,%str(max_data=(&sql_min_data))));
%PUT &mystring0;
proc sql noprint;
update libY.tableX
set &mystring0
where livraria='&sel_livraria'
and tabela='&sel_tabela';
quit;
%mend;
My problem with the code above is that iam getting this error in my final concrenated string, &mystring0.
tot_linhas=(&sql_tot_linhas),sum_num=(&sql_tot_linhas),min_data=(&sql_min_data),max_data=(&sql_min_data)
_ _ _ _
ERROR 22-322: Syntax error, expecting one of the following: a name, a quoted string, a numeric constant, a datetime constant, a missing value, BTRIM, INPUT, PUT, SUBSTRING, USER.
Any help appreciated
Ok, so i follow Tom comments and ended with a proc sql solution that works!
proc sql;
select sql_tot_linhas,
(case when sql_sum_num = '' then "0" else sql_sum_num end),
(case when sql_min_data = '' then "." else sql_min_data end),
(case when sql_max_data = '' then "." else sql_max_data end)
into:sql_linhas, :sql_numeros, :sql_mindata, :sql_mxdata
from libY.tableX
where livraria="&sel_livraria"
and tabela="&sel_tabela";
quit;
proc sql;
update libY.tableX
set tot_linhas = (&sql_linhas),
sum_num =(&sql_numeros),
min_data = (&sql_mindata),
max_data = (&sql_mxdata)
where livraria="&sel_livraria"
and tabela="&sel_tabela";
quit;
Tks Tom :)
It is very hard to tell from your description what it is you are trying to do, but there are some clear coding issues in the snippets of code you did share.
First is that macro expressions are not evaluated in string literals bounded by single quotes. You must use double quotes.
where livraria="&sel_livraria"
Second is you do not want to use any of the CAT...() SAS functions in macro code. Mainly because you don't need them. If you want to concatenate values in macro code just type them next to each other. But also because they do not work well with %SYSFUNC() because they allow their arguments to be either numeric or character so %SYSFUNC() will have to guess from the strings you pass it whether it should tell the SAS function those strings are numeric or character values.
So perhaps something like:
%let mystring=tot_linhas=(&sql_tot_linhas);
%if not %isBlank(&sql_sum_num) %then
%LET mystring=&mystring,sum_num=(&sql_tot_linhas)
;
%if not %isBlank(&sql_min_data) %then
%LET mystring=&mystring,min_data=(&sql_min_data)
;
%if not %isBlank(&sql_max_data) %then
%LET mystring=&mystring,max_data=(&sql_max_data)
;
Note that I also cleaned up some obvious errors when modifying that code. Like the extra & in the value passed to the %ISBLANK() macro and the assignment of the min value to the max variable.
But it would probably be easier to generate the strings in a data step where you can test the values of the actual variables and if needed actually use the CATX() function.

Run a macro only if the username matches the list

I am trying to run a macro based on a condition of usernames, where do I make the changes:
for ex:
I have the following dataset with usernames:
data users2;
input name$;
cards;
ABC
DEF
YUT
GTR
;
run;
I have a macro to call: %callmacro;
proc sql;
select name into: usernames separated by ',' from users2;
quit;
so I call the macro
%macro NEW();
%if &sysuserid in (&usernames) %then %do;
%callmacro;
%end;
%mend;
%new;
So here I get an error :
ERROR: Required operator not found in expression: ( "&sysuserid" in
(&usernames))
I would like to run a macro only if the username matches in the list. Else is there any way I can call a WINDOWS AD group from SAS macro and check if the sysuserid exixts in that Windows AD group?
You could check the usernames inside the macro
%macro ThisIsConditionallyRestricted(nametable=users2);
proc sql noprint;
select name from &nametable where name = "&sysuserid";
quit;
%if &SQLOBS = 0 %then %do;
%put WARNING: You were not prepared!;
%return;
%end;
…
%mend;
%ThisIsConditionallyRestricted;
You can't use in statement in %if clause without special option. There are two ways to solve the problem.
1. /minoperator option(you need change delimeter in &usernames from ',' to ' '. ):
Firstly, you need to change delimeter in usernames macro variable and use strip function:
proc sql;
select strip(name) into: usernames separated by ' ' from users2;
quit;
Then, your code with option /minoperator.
%macro new() /minoperator;
%if &sysuserid in (&usernames) %then %do;
%callmacro;
%end;
%mend;
%new;
2. The other solution is to use loop by scan function(no need in changing delimeter):
%macro new();
%do i = 1 %to %sysfunc(countw(%bquote(&usernames),%str(%,)));
%if %sysfunc(strip(%str(&sysuserid)))=%sysfunc(strip(%scan(%bquote(&usernames),&i,%str(%,)))) %then %do;
%callmacro;
%end;
%end;
%mend new;
%new();
Don't forget to use strip function, when you compare character
variables and select into. My advise is to change it:
proc sql;
select strip(name) into: usernames separated by ',' from users2;
quit;

SAS: Recursive macro goes into an infinite loop

Could someone please explain why it is happening?
I'm trying to find objects dependencies tree in a DB.
Let's say view5 is a view sits on top view4 which sits on top view1.
Also,
view3 sits on top view2 sits on top view1.
So,
the when I query the macro for view1, I should get back view4, view5, view2 and view3.
This is the macro:
%macro dependencies(obj=);
%let dependent_objectname =;
proc sql noprint;
select "'"||trim(dependent_objectname)||"'"
into :dependent_objectname separated by ", "
from &_input.
where src_objectname in (&obj.);
quit;
%put &dependent_objectname.;
%let dependent_objectname = (&dependent_objectname.);
%put &dependent_objectname.;
%if %length("&dependent_objectname")>0 %then
%dependencies(obj = &dependent_objectname.);
%mend dependencies;
%let source = 'ditemp.depend_test1';
%put &source.;
%dependencies(obj = &source.);
First iteration works well,
I get the objects sit on top depend_test1
in a form of "('ditemp.depend_test2','ditemp.depend_test3')"
then I'm checking for the length of variable dependent_objectname (greater than zero)
and calling the macro again,
only it never stops...
I see a couple problems.
The statement:
%if %length("&dependent_objectname")>0 %then %do;
will always return true, even if the value of &dependent_objectname is null. Because the quotes are part of the value in the macro language. You probably want:
%if %length(&dependent_objectname)>0 %then %do;
That test for nullness usually works. Or see this paper for better methods. http://support.sas.com/resources/papers/proceedings09/022-2009.pdf
Before that, the statement:
%let dependent_objectname = (&dependent_objectname.);
is adding parentheses to your value. So again, even if &dependent_objectname were null, it would be () after this. It looks like you don't need these parentheses, so I would skip this statement.
I would also add:
%local dependent_objectname ;
to the top of the macro. That way each invocation of the macro will have its own local macro variable, rather than having them all use the macro variable created in the first iteration (or worse yet, all use a global macro variable).
You have sensibly added %PUT statements to help with debugging. I would expect they would show that the value of &dependent_objectname is always non-null as currently written. You could also add:
%put The length is: %length(&dependent_objectname.) ;
Since you are using an SQL query to generate the dependent list you can use the automatic variable SQLOBS in your test to break the recursion.
%if &sqlobs %then %do;
%dependencies(obj = &dependent_objectname.);
%end;
Also do NOT use a comma as the delimiter between the items listed in the OBJ parameter. The IN operator in SAS doesn't need them and they will cause trouble in the macro call.
select * from sashelp.class where name in ('Alfred' 'Alice') ;
So your macro could look like this:
%macro dependencies(object_list);
%local dependent_list ;
proc sql noprint;
select catq('1as',dependent_objectname)
into :dependent_list separated by ' '
from &_input.
where src_objectname in (&object_list)
and dependent_objectname is not null
;
quit;
%put Dependent Objects of (&object_list) = (&dependent_list);
%if &sqlobs %then %dependencies(&dependent_list);
%mend dependencies;
And here is a test case.
%let _input=sample;
data sample;
length src_objectname dependent_objectname $41 ;
input (_all_) (:) ;
cards;
object1 object2
object2 object3
object2 object4
;;;;
%dependencies('object1');

SAS conditional logic to execute another sas program based on condition

I have a dataset naming error_table as follows. All the variables are character
Errorno Error Resolution
001 login check
002 datacheck check
I wanted a logic that executes a sas program If the Errorno is not in 001 and 002. Else stop execution and display the error_table.
I tried the following
%macro test();
proc sql;
select trim(Error_No) into: num from error_table;
quit;
%if &num. not in ("001","002") %then %do;
%include "/path/dev/program.sas";
%end;
%else %do;
proc print data = error_table;
run;
%end;
%mend;
%test;
But, it is throwing an error.
Can anyone please correct the logic.
You need to watch out for the case when the SELECT returns zero rows. You should set a default value to the macro variable NUM.
Is your dataset variable numeric or character? Use the TRIMMED or SEPARATED BY clause instead of the TRIM() function to prevent spaces in the macro variable that is generated by the INTO clause.
%let num=NONE;
select Error_No into: num trimmed from error_table;
Remember that to the macro processor everything is a string so don't but quotes around the values you are trying to match unless they are actually part of the value.
%if NOT (&num. in (001,002)) %then %do;
Also to use the IN operator in macro code you need to make sure you have set the MINDELIMITER option.
I would sugest moving condition with error codes to proc sql.
proc sql;
select count(*) into :num_errors
from error_table
where Errorno in ("001", "002");
quit;
Then in macrovariable you have number of errors that are 001 or 002.
Next step is to check macro-condition:
%if &num_errors. > 0 %then %do;
%include "/path/dev/program.sas";
%end;
%else %do;
proc print data = error_table;
run;
%end;
%mend;

Find a string in SAS and capture variable name and position of the variable in macro variables

I have a very large number of datasets that are not consistently formatted - I am trying to read them into SAS and normalize them.
The basic need here is to locate a 'key column' that contains a certain string - from there I know what to do with all the variables to the left and right of that column.
The 'GREP' macro from the sas website (http://support.sas.com/kb/33/078.html) seems like it can handle this, but I need help adapting the code in the following ways:
1 - I only need to search one dataset at a time, already in the 'work' library.
2 - I need to capture the name of the variable (and the position number of it) that prints to the log at the end of this macro. This seems like it would be easy but it just returns the last column in the dataset instead of the (correct) column that prints to the log at the end.
Current code below:
%macro grep(librf,string); /* parameters are unquoted, libref name, search string */
%let librf = %upcase(&librf);
proc sql noprint;
select left(put(count(*),8.)) into :numds
from dictionary.tables
where libname="&librf";
select memname into :ds1 - :ds&numds
from dictionary.tables
where libname="&librf";
%do i=1 %to &numds;
proc sql noprint;
select left(put(count(*),8.)) into :numvars
from dictionary.columns
where libname="&librf" and memname="&&ds&i" and type='char';
/* create list of variable names and store in a macro variable */
%if &numvars > 0 %then %do;
select name into :var1 - :var&numvars
from dictionary.columns
where libname="&librf" and memname="&&ds&i" and type='char';
quit;
data _null_;
set &&ds&i;
%do j=1 %to &numvars;
if &&var&j = "&string" then
put "String &string found in dataset &librf..&&ds&i for variable &&var&j";
%end;
run;
%end;
%end;
%mend;
%grep(work,Source Location);
The log returns: "String Source Location found in dataset WORK.RAW_IMPORT for variable C" (the third), which is correct.
I just need usable macro variables equal to "C" and "3" at the end. This macro will be part of a larger macro (or a prelude to it) so the two macro variables need to reset with each dataset I run through it. Thanks for any help offered.
Please find the modification below, basically what I have done was to create global macro variables for dataset name and variable name which will feed as input to get the variable position using VARNUM function as below, ( change identified by **** )
%macro grep(librf,string);
%let librf = %upcase(&librf);
proc sql noprint;
select left(put(count(*),8.)) into :numds
from dictionary.tables
where libname="&librf";
select memname into :ds1 - :ds&numds
from dictionary.tables
where libname="&librf";
%do i=1 %to &numds;
proc sql noprint;
select left(put(count(*),8.)) into :numvars
from dictionary.columns
where libname="&librf" and memname="&&ds&i" and type='char';
/* create list of variable names and store in a macro variable */
%if &numvars > 0 %then %do;
select name into :var1 - :var&numvars
from dictionary.columns
where libname="&librf" and memname="&&ds&i" and type='char';
quit;
%global var_pos var_nm var_ds;
data _null_;
set &&ds&i;
%do j=1 %to &numvars;
**** ADDED NEW CODE HERE ****;
if &&var&j = "&string" then do; /* IF-DO nesting */;
call symputx("var_nm","&&var&j"); /*Global Macro variable for Variable Name */
call symputx("var_ds","&&ds&i"); /*Global Macro variable for Dataset Name */
put "String &string found in dataset &librf..&&ds&i for variable &&var&j";
%end;
run;
**** ADDED NEW CODE HERE ****;
%let dsid=%sysfunc(open(&var_ds,i)); /* Open Data set */
%let var_pos=%sysfunc(varnum(&dsid,&var_nm)); /* Variable Position */
%let rc=%sysfunc(close(&dsid)); /* Close Data set */;
%end;
%end;
%mend;
%grep(work,Source Location);
%put &=var_nm &=var_ds &=var_pos;