Run Macro inside data _NULL_ - sas

I have this data,
and I want to update a certain table using a macro
DATA WORK.t1;
LENGTH
POLICY_RK 8
POLICY_VERSION 8
TREATMENT_IND 8 ;
FORMAT
POLICY_RK BEST12.
POLICY_VERSION BEST12.
TREATMENT_IND BEST12. ;
INFORMAT
POLICY_RK BEST12.
POLICY_VERSION BEST12.
TREATMENT_IND BEST12. ;
INFILE DATALINES4
DLM='7F'x
MISSOVER
DSD ;
INPUT
POLICY_RK : BEST32.
POLICY_VERSION : BEST32.
TREATMENT_IND : BEST32. ;
DATALINES4;
105000002
114000005
123000007
132000001
141000007
1508
;;;;
I'm trying to run the below code:
%macro storno (pol_rk , pol_ver );
PROC SQL;
UPDATE t1
SET POLICY_VERSION=POLICY_VERSION*3.1113
where POLICY_RK=&pol_rk and policy_Version = &pol_ver;
QUIT;
%mend ;
data _null_;
set t1 ;
IF input(TREATMENT_IND,best12.) eq 1 THEN do;
call symputx("a",policy_rk);
call symputx("b",pol_ver);
end;
%storno(&a, &b);
%put a=&a;
%put b=&b;
run;
but get a warning message:
WARNING: Apparent symbolic reference A not resolved.
WARNING: Apparent symbolic reference B not resolved.
NOTE: Numeric values have been converted to character values at the places given by: (Line):(Column).
19:21
NOTE: There were 6 observations read from the data set WORK.T1.
NOTE: DATA statement used (Total process time):
real time 0.00 seconds
cpu time 0.00 seconds
NOTE: No rows were updated in WORK.T1.
What exactly am I doing wrong here....?

CALL SYMPUTX assigns values to macro variables, but those macro variables aren't available for use until after the end of the DATA step in which they're assigned.
That's because macro language compiles before the results of the data step. So SAS sees your call to %storno(&a, &b) before it processes the DATA step to assign the macro variables &a and &b.
See the "Problem Trying to Reference..." subsection of this link for more: https://support.sas.com/documentation/cdl/en/mcrolref/61885/HTML/default/viewer.htm#a000210266.htm
That's why you're seeing warnings about the symbolic references not being resolved - they don't exist yet when you're trying to use them.

Another good solution to running this - often better, IMO, because there are a few weird consequences of CALL EXECUTE that can affect how macros called from it work vs. called in plain code - is SELECT INTO.
proc sql;
select cats('%storno(',policy_rk,',',pol_ver,')') into :calllist separated by ' '
from t1
where input(TREATMENT_IND,best12.) eq 1
;
quit;
&calllist;
That pulls a list of calls into a macro variable (&calllist) and then runs them in open text just as if you'd run them separately.
You also ought to unwrap the PROC SQL if possible; it's adding a bit of extra overhead to open/close PROC SQL so many times.
Remove the PROC SQL and QUIT from the macro, then call it like this:
proc sql;
&calllist;
quit;

Related

SAS Macro variable escaping apostrophe in variable name Proc Http

I have been working on this for 3 days now and have tried all I can think of including %str(),%bquote(), translate() and tranwrd() to replace single apostrophe with double apostrophe or %’
The below data step and macro work fine until I hit a last name which contains an apostrophe e.g. O’Brien. I then encounter syntax errors due to un closed left parentheses. The below code I have left what I thought was closest to working with the tranwrd included.
Any assistance you can provide is greatly appreciated.
%macro put_data (object1,id);
Proc http
method=“put”
url=“https://myurl/submissionid/&id”
in=&object1;
Headers “content-type”=“application/json”;
Run;
%mend;
data _null_;
Set work.emp_lis;
call execute(catt(‘%put_data(‘,’%quote(‘’{“data”:{“employeeName”:”’,tranwrd(employeeName,”’’”,”’”),’”}}’’),’,id,’)’));
run;
Craig
There are a wide potential of problems in constructing or passing a json string in SAS macro. Proc JSON will produce valid json (in a file) from data and that file in turn can be specified as the input to be consumed by your web service.
Example:
data have;
length id 8 name $25;
input id& name&;
datalines;
1 Homer Simpson
2 Ned Flanders
3 Shaun O'Connor
4 Tom&Bob
5 'X Æ A-12'
6 Goofy "McDuck"
;
%macro put_data (data,id);
filename jsonfile temp;
proc json out=jsonfile;
export &data.(where=(id=&id));
write values "data";
write open object;
write values "name" name;
write close;
run;
proc http
method="put"
url="https://myurl/submissionid/&id"
in=jsonfile
;
headers "content-type"="application/json";
run;
%mend;
data _null_;
set have;
call execute(cats('%nrstr(%put_data(have,',id,'))'));
run;
I was able to find issue with my code with the tranwrd statement was backwards and it needed to be moved to the proc sql create table statement. I also needed to wrap &object1 in %bquote. This was the final code that worked.
When creating table wrap variables in tranwrd as below.
tranwrd(employeeName, “‘“,”’’”)
% macro put_data (object1,id);
Proc http
method=“put”
url=“https://myurl/submissionid/&id”
in=%bquote(&object1);
Headers “content-type”=“application/json”;
Run;
%mend;
data _null_;
Set work.emp_lis;
call execute(catt(‘%put_data(‘,’%quote(‘’{“data”:{“employeeName”:”’,employeeName,’”}}’’),’,id,’)’));
run;
Just use actual quotes and you won't have to worry about macro quoting at all.
So if your macro looks like this:
%macro put_data(object1,id);
proc http method="put"
url="https://myurl/submissionid/&id"
in=&object1
;
headers "content-type"="application/json";
run;
%mend;
Then the value of OBJECT1 would usually be a quoted string literal or a fileref. (There are actually other forms.) Looks like you are trying to generate a quoted string. So just use the QUOTE() function.
So if your data looks like:
data emp_lis;
input id employeeName $50.;
cards;
1234 O'Brien
4567 Smith
;
Then you can use a data step like this to generate one macro call for each observation.
data _null_;
set emp_lis;
call execute(cats
('%nrstr(%put_data)('
,quote(cats('{"data":{"employeeName":',quote(trim(employeeName)),'}}'))
,',',id
,')'
));
run;
And your SAS log will look something like:
NOTE: CALL EXECUTE generated line.
1 + %put_data("{""data"":{""employeeName"":""O'Brien""}}",1234)
NOTE: PROCEDURE HTTP used (Total process time):
real time 2.46 seconds
cpu time 0.04 seconds
2 + %put_data("{""data"":{""employeeName"":""Smith""}}",4567)
NOTE: PROCEDURE HTTP used (Total process time):
real time 2.46 seconds
cpu time 0.04 seconds

SAS macro - rename variables using their label values as their new variable names

I want to produce a macro that converts the variable names of a dataset into the variables' labels. I intend to apply this macro for large datasets where manually changing the variable names would be impractical.
I came across this code online from the SAS website, which looked promising but produced errors. I made slight edits to remove some errors. It now works for their sample dataset but not mine. Any assistance with improving this code to work with my sample dataset would be greatly appreciated!
SAS sample dataset (works with code):
data t1;
label x='this_x' y='that_y';
do x=1,2;
do y=3,4;
z=100;
output;
end;
end;
run;
My sample dataset (does not work with code):
data t1;
input number group;
label number = number_lab group = group_lab;
datalines;
1 1
1 .
2 1
2 .
3 2
3 .
4 1
4 .
5 2
5 .
6 1
6 .
;
run;
Code:
%macro chge(dsn);
%let dsid=%sysfunc(open(&dsn));
%let cnt=%sysfunc(attrn(&dsid,nvars));
%do i= 1 %to &cnt;
%let var&i=%sysfunc(varname(&dsid,&i));
%let lab&i=%sysfunc(varlabel(&dsid,&i));
%if lab&i = %then %let lab&i=&&var&i;
%end;
%let rc=%sysfunc(close(&dsid));
proc datasets;
modify &dsn;
rename
%do j = 1 %to &cnt;
%if &&var&j ne &&lab&j %then %do;
&&var&j=&&lab&j
%end;
%end;
quit;
run;
%mend chge;
%chge(t1)
proc contents;
run;
My code produces the following error messages:
ERROR 73-322: Expecting an =.
ERROR 76-322: Syntax error, statement will be ignored.
Mainly you are not closing the RENAME statement with a semi-colon. But it also looks like you have the RUN and QUIT statements in the wrong order.
But note that there is no need for that complex %sysfunc() macro code to get the list of names and labels. Since you are already generating a PROC DATASETS step your macro can generate other SAS code also. Then your macro will be clearer and easier to debug.
%macro chge(dsn);
%local rename ;
proc contents data=&dsn noprint out=__cont; run;
proc sql noprint ;
select catx('=',nliteral(name),nliteral(label))
into :rename separated by ' '
from __cont
where name ne label and not missing(label)
;
quit;
%if (&sqlobs) %then %do;
proc datasets nolist;
modify &dsn;
rename &rename ;
run;
quit;
%end;
%mend chge;
If the list of rename pairs is too long to fit into a single macro variable then you could resort to generating two series of macro variables using PROC SQL and then add back your %DO loop.
Here is SAS log from testing on your sample file.
4156 %chge(t1);
MPRINT(CHGE): proc contents data=t1 noprint out=__cont;
MPRINT(CHGE): run;
NOTE: The data set WORK.__CONT has 2 observations and 41 variables.
NOTE: PROCEDURE CONTENTS used (Total process time):
real time 0.08 seconds
cpu time 0.01 seconds
MPRINT(CHGE): proc sql noprint ;
MPRINT(CHGE): select catx('=',nliteral(name),nliteral(label)) into :rename
separated by ' ' from __cont where name ne label and not missing(label) ;
MPRINT(CHGE): quit;
NOTE: PROCEDURE SQL used (Total process time):
real time 0.04 seconds
cpu time 0.00 seconds
MPRINT(CHGE): proc datasets nolist;
MPRINT(CHGE): modify t1;
MPRINT(CHGE): rename group=group_lab number=number_lab ;
NOTE: Renaming variable group to group_lab.
NOTE: Renaming variable number to number_lab.
MPRINT(CHGE): run;
NOTE: MODIFY was successful for WORK.T1.DATA.
MPRINT(CHGE): quit;
NOTE: PROCEDURE DATASETS used (Total process time):
real time 0.12 seconds
cpu time 0.00 seconds
Note that if I now try to run it again on the modified dataset it does not rename anything.
4157 %chge(t1);
MPRINT(CHGE): proc contents data=t1 noprint out=__cont;
MPRINT(CHGE): run;
NOTE: The data set WORK.__CONT has 2 observations and 41 variables.
NOTE: PROCEDURE CONTENTS used (Total process time):
real time 0.00 seconds
cpu time 0.00 seconds
MPRINT(CHGE): proc sql noprint ;
MPRINT(CHGE): select catx('=',nliteral(name),nliteral(label)) into :rename
separated by ' ' from __cont where name ne label and not missing(label) ;
NOTE: No rows were selected.
MPRINT(CHGE): quit;
NOTE: PROCEDURE SQL used (Total process time):
real time 0.08 seconds
cpu time 0.00 seconds
you're missing a semi-colon here:
&&var&j=&&lab&j
if you still get an error, turn on these options and let us know where the error occurs.
options symbolgen mprint;

PROC SQL "SELECT INTO" does not create macro variable

I've run into an issue creating macro variables using proc sql select into : logic. The code seems to be pretty straightforward, but I should note that the macro that is causing problems is called from several other macros.
Here is the snippet that is causing issues.
%do year_num=1 %to 5;
%if &year_num=1 %then %let min_date = %eval(&max2.-17);
%else %let min_date = %eval(&min_date.-12);
data tmp;
set bf(firstobs=&min_date obs=%eval(11+&min_date));
run;
data tmp2;
set bf(firstobs=%eval(5+&min_date) obs=%eval(7+&min_date));
run;
proc sql noprint;
select sum(EP), sum(ExpectedLoss)
into :totep, :totexpt
from tmp;
select sum(EP), sum(ExpectedLoss)
into :partep, :partexpt
from tmp2;
quit;
%put _LOCAL_;
*Other code...;
%end;
For some reason, the variables totep, totexpt, partep and pqrtexpt are not getting created and I can't find any useful information in the log that may shed light on the situation.
Here is part of the log, including the output from _LOCAL_.
SYMBOLGEN: Macro variable YEAR_NUM resolves to 1
SYMBOLGEN: Macro variable MAX2 resolves to 96
MPRINT(BFMETHOD): data tmp;
SYMBOLGEN: Macro variable MIN_DATE resolves to 79
SYMBOLGEN: Macro variable MIN_DATE resolves to 79
MPRINT(BFMETHOD): set bf(firstobs=79 obs=90);
MPRINT(BFMETHOD): run;
MPRINT(BFMETHOD): data tmp2;
SYMBOLGEN: Macro variable MIN_DATE resolves to 79
SYMBOLGEN: Macro variable MIN_DATE resolves to 79
MPRINT(BFMETHOD): set bf(firstobs=84 obs=86);
MPRINT(BFMETHOD): run;
MPRINT(BFMETHOD): proc sql noprint;
MPRINT(BFMETHOD): select sum(EP), sum(ExpectedLoss) into :totep, :totexpt from tmp;
MPRINT(BFMETHOD): select sum(EP), sum(ExpectedLoss) into :partep, :partexpt from tmp2;
MPRINT(BFMETHOD): quit;
BFMETHOD I 12
BFMETHOD DSET all
BFMETHOD SEAS_MIN 0.6
BFMETHOD YEAR_NUM 1
BFMETHOD SEAS_MAX 1.66666666666666
BFMETHOD MIN_DATE 79
data tmp; set bf(firstobs=79 obs=90); run;
NOTE: There were 12 observations read from the data set WORK.BF.
NOTE: The data set WORK.TMP has 12 observations and 35 variables.
NOTE: Compressing data set WORK.TMP increased size by 100.00 percent.
Compressed is 2 pages; un-compressed would require 1 pages.
NOTE: DATA statement used (Total process time):
real time 0.00 seconds
cpu time 0.00 seconds
109 + data tmp2; set bf(firstobs=84 obs=86); run;
NOTE: There were 3 observations read from the data set WORK.BF.
NOTE: The data set WORK.TMP2 has 3 observations and 35 variables.
NOTE: Compressing data set WORK.TMP2 increased size by 100.00 percent.
Compressed is 2 pages; un-compressed would require 1 pages.
NOTE: DATA statement used (Total process time):
real time 0.00 seconds
cpu time 0.00 seconds
109 + proc sql noprint;
109 + select sum(EP), sum(ExpectedLoss) into :totep, :totexpt from tmp;
109 +
select sum(EP), sum(ExpectedLoss) into :partep, :partexpt from tmp2;
109 +
quit;
NOTE: PROCEDURE SQL used (Total process time):
real time 0.00 seconds
cpu time 0.00 seconds
If I remove noprint from the proc sql statement then the correct values are output so I'm not sure what's going on. Any help would be appreciated.
Your issue is almost certainly related to execution timing. Particularly when using call execute, you can easily run into problems with timing where the macro variable is resolved by the SAS processor earlier than it's given a useful value.
Here's a simplified example. I specifically make a dataset so you can see when that happens (it's not a necessary step nor does it affect the example, it just makes a useful log entry).
Notice how when the macro runs on its own, everything works how you'd expect; but when it's run with call execute the %put executes before the macro actually executes.
The third example uses %nrstr to force SAS to not try to resolve the macro before actually running it - which causes it to be submitted properly.
Effectively, the first call execute version has SAS process the macro text then submit it to sas.exe - which you do not really want. Adding %nrstr fixes that.
%macro do_something();
%local mlist;
data class_m;
set sashelp.class;
where sex='M';
run;
proc sql;
select name into :mlist separated by ' '
from class_m;
quit;
%put &=mlist;
%mend do_something;
%put Macro run on its own;
%do_something;
%put Macro run via call execute;
options mprint symbolgen;
data _null_;
set sashelp.class;
if _n_=1 then call execute('%do_something()');
stop;
run;
%put Macro run with nrstr and call execute;
data _null_;
set sashelp.class;
if _n_=1 then call execute('%nrstr(%do_something())');
stop;
run;

Compress Newline character for dynamic varaibles

Dataset: Have
F1 F2
Student Section
Name No
Dataset "Have". Data has new line character.
I need to compress the newline character from the data.
I want to do this dynamically as sometimes the "Have" dataset may contain new variables like F3,F4,F5 etc.,
I have written as macro to do this.. However it is not working as expected.
When i execute the below code, first time I am getting error as invalid reference newcnt. If i execute for second time in the same session, i am not getting error.
PFB my code:
%macro update_2(newcnt);
data HAVE;
set HAVE;
%do i= 1 %to &newcnt;
%let colname = F&i;
&colname=compress(&colname,,'c');
%end;
run;
%mend update_2;
%macro update_1();
proc sql noprint;
select count(*) into :cnt from dictionary.columns where libname="WORK" and memname="HAVE";
quit;
%update_2(&cnt)
%mend update_1;
Note: All the variables have name as F1,F2,F3,F4.,
Please tell me what is going wrong..
If there is any other procedures, please help me.
In your macro %update_1 you're creating a macro variable called &cnt, but when you call %update_2 you refer to another macro variable, &colcnt. Try fixing this reference and see if your code behaves as expected.
We created our own function to clean unwanted characters from strings using proc fcmp. In this case, our function cleans tab characters, line feeds, and carriage returns.
proc fcmp outlib=common.funcs.funcs; /* REPLACE TARGET DESTINATION AS NECESSARY */
function clean(iField $) $200;
length cleaned $200;
bad_char_list = byte(10) || byte(9) || byte(13);
cleaned = translate(iField," ",bad_char_list);
return (cleaned );
endsub;
run;
Create some test data with a new line character in the middle of it, then export it and view the results. You can see the string has been split across lines:
data x;
length employer $200;
employer = cats("blah",byte(10),"diblah");
run;
proc export data=x outfile="%sysfunc(pathname(work))\x.csv" dbms=csv replace;
run;
Run our newly created clean() function against the string and export it again. You can see it is now on a single line as desired:
data y;
set x;
employer = clean(employer);
run;
proc export data=y outfile="%sysfunc(pathname(work))\y.csv" dbms=csv replace;
run;
Now to apply this method to all character variables in our desired dataset. No need for macros, just define an array referencing all the character variables, and iterate over them applying the clean() function as we go:
data cleaned;
set x;
array a[*] _char_;
do cnt=lbound(a) to hbound(a);
a[cnt] = clean(a[cnt]);
end;
run;
EDIT : Also note that fcmp may have some performance considerations to consider. If you are working with very large amounts of data, there may be other solutions that will perform better.
EDIT 6/15/2020 : Corrected missing length statement that could result in truncated responses.
Here's an example of Robert Penridge's function, as a call routine with an array as an argument. This probably only works in 9.4+ or possibly later updates of 9.3, when permanent arrays began being allowed to be used as arguments in this way.
I'm not sure if this could be done flexibly with an array as a function; without using macros (which require recompilation of the function constantly) I don't know how one could make the right size of array be returned without doing it as a call routine.
I added 'Z' to the drop list so it's obvious that it works.
options cmplib=work.funcs;
proc fcmp outlib=work.funcs.funcs;
sub clean(iField[*] $);
outargs iField;
bad_char_list = byte(11)|| byte(10) || byte(9) || byte(13)||"Z";
do _i = 1 to dim(iField);
iField[_i] = translate(iField[_i],trimn(" "),bad_char_list);
end;
endsub;
quit;
data y;
length employer1-employer5 $20;
array employer[4] $;
do _i = 1 to dim(employer);
employer[_i] = "Hello"||byte(32)||"Z"||"Goodbye";
end;
employer5 = "Hello"||byte(32)||"Z"||"Goodbye";
call clean(employer);
run;
proc print data=y;
run;
Here is another alternative. If newline is the only thing you want to remove, then we are talking about Char only, you may leverage implicit array and Do over,
data want;
set have;
array chr _character_;
do over chr;
chr=compress(chr,,'c');
end;
run;

How can I perform a macro for each observation in sas data set?

Here is the macro code.....
libname myfmt "&FBRMrootPath./Formats";
%macro CreateFormat(DSN,Label,Start,fmtname,type);
options mprint mlogic symbolgen;
%If &type='n' %then %do;
proc sort data=&DSN out=Out; by &Label;
Run;
Data ctrl;
set Out(rename=(&Label=label &Start=start )) end=last;
retain fmtname &fmtname type &type;
output;
If last then do;
hlo='O';
label='*ERROR';
output;
End;
Run;
%End;
%Else %do;
proc sort data=&DSN out=Out; by &Start;
Run;
Data ctrl;
set Out(rename=(&Start=label &Label=start )) end=last;
retain fmtname &fmtname type &type;
output;
If last then do;
hlo='O';
label='*ERROR';
output;
End;
Run;
%End;
proc format library=myfmt cntlin=ctrl;
Run;
%Mend CreateFormat;
Here is the code for control data set through which above macro should run for each observation of the data set and the values of the observations are inputs for varibales in the macro....
Data OPER.format_control;
Input DSN :$12. Label :$15. Start :$15. fmtName :$8. type :$1. fmt_Startdt :mmddyy. fmt_Enddt :mmddyy.;
format fmt_Startdt fmt_Enddt date9.;
Datalines;
ssin.prd prd_nm prd_id mealnm n . 12/31/9999
ssin.prd prd_id prd_nm mealid c . 12/31/9999
ssin.fac fac_nm onesrc_fac_id fac1SRnm n . 12/31/9999
ssin.fac fac_nm D3_fac_id facD3nm n . 12/31/9999
ssin.fac onesrc_fac_id D3_fac_id facD31SR n . 12/31/9999
oper.wrkgrp wrkgrp_nm wrkgrp_id grpnm n . 12/31/9999
;
Something like this.
proc sql;
select catx(',',cats('%CreateFormat(',DSN),Label,Start,fmtname,cats(type,')');
into :formcreatelist separated by ' '
from oper.format_control;
quit;
You may need to PUT some of your variables to get the format you want into the macro variable. I use the slightly cludgy cats/catx combo here, you could cats once with ',' added in a bunch of times also.
You do have a limit here - around 20,000 characters total in a macro variable. If it's over that, you either have to use CALL EXECUTE (which has some quirky features) or you can put the macro call into a text file and %INCLUDE it.
There is a better way to do this rather than select ... into a macro variable. Use a temp file like this:
filename dyncode temp;
data _null_;
file dyncode;
set OPER.format_control;
put '%createformat ....';
run;
%include dyncode;
filename dyncode clear;
This technique is not limited by the 32k length limitation on macro variables.
Note that you should definitely use single quotes around the %createformat to prevent SAS from invoking the macro just prior to data step compilation. You want the macro to run when the %include runs.
The above approach is analogous to call execute, but call execute is evil because it does not execute the macro and embedded data/proc code within the macro in the expected order. Avoid call execute.
Finally, if you are running interactive SAS and using the technique there is a neat trick you can use to debug. Comment out the last two lines of code -- the include and the filename clear. After you run the remaining code, enter the SAS command "fslist dyncode" in the command window. This will pop up a notepad view on the dynamic code you just generated. You can review it and make sure you got what you intended.
Here's a call execute solution, just for completeness:
data _null_;
set OPER.format_control;
call execute('%CreateFormat(' || DSN || ',' || Label || ',' || Start || ',' || fmtname || ',' || type || ');');
run;