I am stuck trying to iterate through a list of values in a macro %do loop. Each value is supposed to be used as a variable suffix.
The approach is based on SAS documentation: http://support.sas.com/kb/26/155.html
The (simplified) code is:
%macro loop(values);
%let count=%sysfunc(countw(&values));
%do i = 1 %to &count;
%let value=%qscan(values,i,%str(,));
proc sql;
select count(distinct hut_id) as prefix_&value.
from saslib.tl1_results_eval
group by plan_cell;
quit;
%end;
%mend;
%loop(%str(a,b,c,d))
The resulting error message is:
MLOGIC(LOOP): %DO loop beginning; index variable I; start value is 1; stop value is 4; by value
is 1.
MLOGIC(LOOP): %LET (variable name is VALUE)
MPRINT(LOOP): proc sql;
22: LINE and COLUMN cannot be determined.
NOTE 242-205: NOSPOOL is on. Rerunning with OPTION SPOOL might allow recovery of the LINE and
COLUMN where the error has occurred.
ERROR 22-322: Syntax error, expecting one of the following: ',', AS.
MPRINT(LOOP): select count(distinct hut_id) as prefix_a from group by plan_cell;
MPRINT(LOOP): quit;
Interestingly enough, if I remove "prefix_" from "count(distinct hut_id) as prefix_&value." - it works absolutely fine. Unfortunately, I do need the prefix there. Also, as you can see in the log message, the PROC SQL statement does get compiled correctly in the end, but the code errors out before getting executed.
Any ideas what's happening here? Thank you!
You are inserting invisible quoting characters due to the qscan function. Try:
%macro loop(values);
proc sql;
%let count=%sysfunc(countw(&values));
%do i = 1 %to &count;
%let value=%qscan(values,i,%str(,));
select count(distinct hut_id) as prefix_%unquote(&value)
from saslib.tl1_results_eval
group by plan_cell;
%end;
quit;
%mend;
%loop(%str(a,b,c,d))
Note the movement of the proc sql; statement - there are few good reasons to quit; and reinstantiate the SQL procedure between each SQL statement, which incurs overhead..
Related
I'm doing a crash course on SAS macros and I'm stuck at one exercise. I have to create a macro, that will create a proc contents tables for every data set, that contains a keyword. I know how to do that using call execute, but I need this using proc sql and %do loop.
My attempt:
%macro contents(data=&syslast);
proc contents data=&data;
title "&data";
run;
%mend contents;
%macro ContentsAll(keyword);
select libname||'.'||memname
into :dsn1-
from sashelp.vstabvw
where upcase(memname) like %upcase("%quote(%)&&keyword%")
;
quit;
%do i=1 %to &sqlobs;
%contents(data=&&dsn&i);
%end;
%mend ContentsAll;
options mlogic mprint;
%ContentsAll(class);
options nomlogic nomprint;
I know there is some issue with a select statement, but I have no idea how to fix it. And where statement has an unprotected variable (my attempts at fixing it just break the where clause alltogether.
First of all, good job. It's so good that I'm almost sorry you're only missing the Proc SQL Statement :-)
%macro contents(data=&syslast);
proc contents data=&data;
title "&data";
run;
%mend contents;
%macro ContentsAll(keyword);
proc sql noprint;
select libname||'.'||memname
into :dsn1-
from sashelp.vstabvw
where upcase(memname) like %upcase("%quote(%)&&keyword%")
;
quit;
%do i=1 %to &sqlobs;
%contents(data=&&dsn&i);
%end;
%mend ContentsAll;
options mlogic mprint;
%ContentsAll(class);
options nomlogic nomprint;
There is no need to create all of those macro variables. Just keep the list of names in actual data. You can use CALL EXECUTE() to generate the code you want to run for each member in the list.
Note that the variables LIBNAME and MEMNAME will already be in uppercase when pulled from the DICTIONARY.MEMBERS metadata that the view SASHELP.VSTABVW uses. But the user passing in a value for the KEYWORD parameter might not have entered uppercase letters.
%macro ContentsAll(keyword);
data _null_;
set sashelp.vstabvw ;
where memname contains "%qupcase(&keyword)" ;
call execute(cats('%nrstr(%contents)(data=',libname,'.',memname,')'));
run;
%mend ContentsAll;
I have this code :
%global nb_usag;
%global usager_entr;
%let nb_usag=0;
%syslput nb_usag=&nb_usag;
%let usager_entr=u;
PROC SQL noprint ;
select count(distinct no_usager_entr) into :nb_usag from &lib..INSCRITS_USA_1
;
quit;
data _null_;
if &nb_usag > 0 then do;
call execute
("PROC SQL noprint ;
select distinct no_usager_entr INTO :usager_entr separated by ','
from &lib..INSCRITS_USA_1;")
;
if &usager_entr ne "u" then do;
call prxchange('s/,/\",\"',-1,&usager_entr);
end;
end;
run;
%let usager_entr="&usager_entr";
%syslput usager_entr=&usager_entr;
%put &nb_usag;
%put &usager_entr;
But the code generate this error for the function prxchange:
ERROR 135-185: Attempt to change the value of the constant 's/,/\",\"' in the PRXCHANGE subroutine call.
What I am doing wrong?
I want to modify every , in the variable usager_entr with ",".
For example, if usager_entr = 12121212,34343434,56565656 it will become 12121212","34343434","56565656.
In my case the table &lib..INSCRITS_USA_1 is empty, then nb_usag=0.
Thanks!
Too much fooling around! Instead, create the double quoted list of values directly from Proc SQL
Presuming the list will be used in a later clause with the construct IN (&myList).
%let usager_entr_dq_csv_list = "redundant safety value that will never match anything";
PROC SQL noprint ;
select distinct quote(trim(no_usager_entr))
INTO :usager_entr_dq_csv_list separated by ','
from &lib..INSCRITS_USA_1;
The redundant value is needed because if it were empty you would later generate IN () and have a syntax error.
If the list is being used in pass-through SQL you will want to use the additional arguments of QUOTE function to bound the values with a single quote.
For modify your macro variable from usager_entr = 12121212,34343434,56565656 to "12121212","34343434","56565656", you could use prxchange:
%let usager_entr =12121212,34343434,56565656;
%put &usager_entr;
%let New_usager_entr=%sysfunc(prxchange(s/([1-9]+)/"$1"/, -1,%quote(&usager_entr)));
%put &New_usager_entr;
I have a list of IDs that contain multiple "XO codes". I want to create a macro that will loop through these IDs and create a table for each using a where statement that corresponds to the appropriate XO code. EX:
%let ID_77= '35X02','35X04';
%let DnO_IDs= &ID_77; /intends to add more &ID_ numbers/
%macro loop;
proc sql;
%do k=1 %to %sysfunc(countw(&DnO_IDs_Ids.));
%let ID= %scan(&DnO_IDs.,&k.);
create table EP_&ID as
select * from table
where XO in ("&ID.") and AY>=(&CurrY-14);
%end;
quit;
%mend;
%loop;
I am receiving this error:
ERROR: A character operand was found in the %EVAL function or %IF condition where a numeric operand is required. The condition was:
'35X04'
ERROR: Argument 2 to macro function %SCAN is not a number.
ERROR: The macro LOOP will stop executing.
The quotes are an issue and should not be needed. Using comma as your delimiter can also cause trouble. It would work better to use spaces.
%let ID_77= 35X02 35X04;
%let DnO_IDs= &ID_77; /intends to add more &ID_ numbers/
%let CurrY=2015;
%macro loop;
%local k id ;
proc sql;
%do k=1 %to %sysfunc(countw(&DnO_IDs));
%let ID= %scan(&DnO_IDs,&k);
create table EP_&ID as
select * from table
where XO in ("&ID") and AY>=(&CurrY-14)
;
%end;
quit;
%mend;
%loop;
If you do want to use the values as quoted comma separated lists then you need to modify your COUNTW() and %SCAN() function calls appropriately and add a call to DEQUOTE() to remove the quotes.
%let ID_77= '35X02','35X04';
... %sysfunc(countw(%superq(DnO_IDs),%str(,)));
%let ID= %sysfunc(dequote(%scan(%superq(DnO_IDs),&k,%str(,))));
I think this should resolve the issue:
%do k=1 %to %eval(%sysfunc(countw(&DnO_IDs_Ids.)));
Might be %sysfunc return value is considered non integer.
I have multiple tables in a library call snap1:
cust1, cust2, cust3, etc
I want to generate a loop that gets the records' count of the same column in each of these tables and then insert the results into a different table.
My desired output is:
Table Count
cust1 5,000
cust2 5,555
cust3 6,000
I'm trying this but its not working:
%macro sqlloop(data, byvar);
proc sql noprint;
select &byvar.into:_values SEPARATED by '_'
from %data.;
quit;
data_&values.;
set &data;
select (%byvar);
%do i=1 %to %sysfunc(count(_&_values.,_));
%let var = %sysfunc(scan(_&_values.,&i.));
output &var.;
%end;
end;
run;
%mend;
%sqlloop(data=libsnap, byvar=membername);
First off, if you just want the number of observations, you can get that trivially from dictionary.tables or sashelp.vtable without any loops.
proc sql;
select memname, nlobs
from dictionary.tables
where libname='SNAP1';
quit;
This is fine to retrieve number of rows if you haven't done anything that would cause the number of logical observations to differ - usually a delete in proc sql.
Second, if you're interested in the number of valid responses, there are easier non-loopy ways too.
For example, given whatever query that you can write determining your table names, we can just put them all in a set statement and count in a simple data step.
%let varname=mycol; *the column you are counting;
%let libname=snap1;
proc sql;
select cats("&libname..",memname)
into :tables separated by ' '
from dictionary.tables
where libname=upcase("&libname.");
quit;
data counts;
set &tables. indsname=ds_name end=eof; *9.3 or later;
retain count dataset_name;
if _n_=1 then count=0;
if ds_name ne lag(ds_name) and _n_ ne 1 then do;
output;
count=0;
end;
dataset_name=ds_name;
count = count + ifn(&varname.,1,1,0); *true, false, missing; *false is 0 only;
if eof then output;
keep count dataset_name;
run;
Macros are rarely needed for this sort of thing, and macro loops like you're writing even less so.
If you did want to write a macro, the easier way to do it is:
Write code to do it once, for one dataset
Wrap that in a macro that takes a parameter (dataset name)
Create macro calls for that macro as needed
That way you don't have to deal with %scan and troubleshooting macro code that's hard to debug. You write something that works once, then just call it several times.
proc sql;
select cats('%mymacro(name=',"&libname..",memname,')')
into :macrocalls separated by ' '
from dictionary.tables
where libname=upcase("&libname.");
quit;
¯ocalls.;
Assuming you have a macro, %mymacro, which does whatever counting you want for one dataset.
* Updated *
In the future, please post the log so we can see what is specifically not working. I can see some issues in your code, particularly where your macro variables are being declared, and a select statement that is not doing anything. Here is an alternative process to achieve your goal:
Step 1: Read all of the customer datasets in the snap1 library into a macro variable:
proc sql noprint;
select memname
into :total_cust separated by ' '
from sashelp.vmember
where upcase(memname) LIKE 'CUST%'
AND upcase(libname) = 'SNAP1';
quit;
Step 2: Count the total number of obs in each data set, output to permanent table:
%macro count_obs;
%do i = 1 %to %sysfunc(countw(&total_cust) );
%let dsname = %scan(&total_cust, &i);
%let dsid=%sysfunc(open(&dsname) );
%let nobs=%sysfunc(attrn(&dsid,nobs) );
%let rc=%sysfunc(close(&dsid) );
data _total_obs;
length Member_Name $15.;
Member_Name = "&dsname";
Total_Obs = &nobs;
format Total_Obs comma8.;
run;
proc append base=Total_Obs
data=_total_obs;
run;
%end;
proc datasets lib=work nolist;
delete _total_obs;
quit;
%mend;
%count_obs;
You will need to delete the permanent table Total_Obs if it already exists, but you can add code to handle that if you wish.
If you want to get the total number of non-missing observations for a particular column, do the same code as above, but delete the 3 %let statements below %let dsname = and replace the data step with:
data _total_obs;
length Member_Name $7.;
set snap1.&dsname end=eof;
retain Member_Name "&dsname";
if(NOT missing(var) ) then Total_Obs+1;
if(eof);
format Total_Obs comma8.;
run;
(Update: Fixed %do loop in step 2)
Can someone please help me.
I have the code below. It does not generate an error but at the same time does not achieve what I want it to.
The intention is to create macro variables for each cell across a number of columns using a 'Select into'. I think the problem is the fact that the 'Select into' contains macro variables too.
%macro ld_macrovar;
proc sql noprint;
select count(portfolio)
into :a
from split_D;
%do i=1 %to &max_comb.; /*already defined elsewhere -actual value=2 */
select _&i.LGD
into :_&i.LGD1 - :_&i.LGD%left(&a)
from split_D;
%end;
quit;
%mend;
%ld_macrovar;
Thanks
Your macro variables are being assigned to the local scope of the macro. So if you want to access them outside the macro you will have to manually assign them to the global scope. This can be achieved by using the %global statement. Alternatively you can perform the processing that requires the macro variables inside the macro.
You can check the scope of your variables by running %put _ALL_; or %put _USER_.
%macro ld_macrovar(max_comb);
proc sql noprint;
select count(*) into :rows
from split_D;
quit;
%do i = 1 %to &max_comb.;
%do j = 1 %to &rows.;
%global _&i.LGD&j.;
%end;
%end;
proc sql noprint;
%do i = 1 %to &max_comb.;
select _&i.LGD
into :_&i.LGD1 -
from split_D;
%end;
quit;
%mend;
/* Dummy data */
data split_D;
do i = 1 to 10;
_1LGD = i**2;
_2LGD = exp(i);
output;
end;
run;
%ld_macrovar(2);
/* Print out all the user defined macro variables */
%put _USER_;
You can also avoid the need to use %left with &a by adding the trimmed option to your fist into statement (in SAS 9.3 and later or separated by "" in other versions).
A word of caution: If you are planning to use the values for further analysis or there are a lot of rows there ma be a better way to achieve what you want. Macro variables store only text and when the values are stored some precision may be lost. In general it's best to use data sets for moving/manipulating data and macro variables for when you need to parametrise your code.