SAS Macro variable escaping apostrophe in variable name Proc Http - sas

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

Related

Can I nest %sysfunc-functions or achieve similar results?

I have the following strings that are used (in different variations) as variable names:
Data variables;
input variable;
datalines;
Exkl_UtgUtl_Flyg
Exkl_UtgUtl_Tag
Exkl_UtgUtl_Farja
Exkl_UtgUtl_Hyrbil
Exkl_UtgUtl_Bo
Exkl_UtgUtl_Aktiv
Exkl_UtgUtl_Annat
;
run;
In order to reference related variables I need to turn variables of the type "Exkl_UtgUtl_Flyg" to variables of the type "UtgUtl_FlygSSEK_Pers" and "UtgUtl_FlygSSEK_PPmedel".I try to do this in the following macro, along with other manipulations:
%macro imputera_saknad_utgift(variabel);
DATA IBIS3_5;
SET IBIS3_5;
if &variabel=1 and %sysfunc(cats(%qsysfunc(TRANWRD(&variabel,'Exkl_','')),SSEK_Pers))=. then
%sysfunc(cats(%qsysfunc(TRANWRD(&variabel,'Exkl_','')),SSEK_Pers))=%sysfunc(cats(%qsysfunc(TRANWRD(&variabel,'Exkl_','')),SSEK_PPmedel));
RUN;
%mend imputera_saknad_utgift;
The documentation stated that %sysfunc can't be nested, but mentioned something about alternating
%sysfunc- and %qsysfunc-functions so I tried that. I then try to execute the code:
data _null_;
set variabler2;
call execute(cats('%imputera_saknad_utgift(',utgifter_inte_missing,')'));
run;
This does not seem to work however. The cats-function seems to have worked, but not the nested TRANWRD-function:
NOTE: DATA statement used (Total process time):
real time 0.11 seconds
cpu time 0.12 seconds
5 + DATA IBIS3_5; SET IBIS3_5; if Exkl_UtgUtl_Bo=1 and Exkl_UtgUtl_BoSSEK_Pers=. then
Exkl_UtgUtl_BoSSEK_Pers=Exkl_UtgUtl_BoSSEK_PPmedel;
How do I make this work? The output should look something like:
DATA IBIS3_5; SET IBIS3_5; if Exkl_UtgUtl_Bo=1 and UtgUtl_BoSSEK_Pers=. then
UtgUtl_BoSSEK_Pers=UtgUtl_BoSSEK_PPmedel;
I don't think your macro variable values have quote characters in them, so this code is not going to work:
%qsysfunc(TRANWRD(&variabel,'Exkl_',''))
Since it is looking to replace the 7 character string 'Exkl_' with just the two character string '', two quotes next to each other.
You probably meant to search for Exkl_ instead. You probably also do not want to use %QSYSFUNC() here since that will preserve the space that TRANWRD() will insert. You could use %SYSFUNC() to avoid having that leading space as part of the value. Or perhaps use the TRANSTRN() function instead since that function, unlike TRANWRD(), can translate to an empty string instead of a single space.
Example:
439 %let variable=Exkl_UtgUtl_Flyg ;
440 %put %qsysfunc(TRANWRD(&variable,'Exkl_','')) ;
Exkl_UtgUtl_Flyg
441 %put %qsysfunc(TRANWRD(&variable,Exkl_,)) ;
UtgUtl_Flyg
442 %put %sysfunc(TRANWRD(&variable,Exkl_,)) ;
UtgUtl_Flyg
443 %put %qsysfunc(TRANSTRN(&variable,Exkl_,)) ;
UtgUtl_Flyg

SAS - append string macro variable to data set name

I'm trying to append a string macro variable to a data set name in SAS. I want to create datasets that read something like work.cps2020jan and work.cps2020feb. But that's not what I am getting. My code:
%macro loop(values);
%let count=%sysfunc(countw(&values));
%do i = 1 %to &count;
%let value=%qscan(&values,&i,%str(,));
%put &value;
data work.cps2020&value.;
set "A:\cpsb2020&value" ;
mth = "&value.";
keep
PEMLR
mth
;
run;
%end;
%mend;
%loop(%str(jan,feb));
Running this code results in the following output in the log:
NOTE: There were 138697 observations read from the data set
A:\cpsb2020jan.
NOTE: The data set WORK.CPS2020 has 138697 observations and 2 variables.
NOTE: The data set WORK.JAN has 138697 observations and 2 variables.
NOTE: DATA statement used (Total process time):
real time 4.29 seconds
cpu time 0.20 seconds
feb
NOTE: There were 139248 observations read from the data set
A:\cpsb2020feb.
NOTE: The data set WORK.CPS2020 has 139248 observations and 2 variables.
NOTE: The data set WORK.FEB has 139248 observations and 2 variables.
NOTE: DATA statement used (Total process time):
real time 4.44 seconds
cpu time 0.15 seconds
I don't understand why my macro creates two datasets per loop instead of one dataset per loop called work.cps2020jan and work.cps2020feb. If I change &value. to &i. SAS outputs work.cps20201 and work.cps20202. But that's not what I want.
Any insights?
The %QSCAN macro function will mask it's result with special invisible (non-printable) characters only visible to the macro processor system.
What happened is that
data work.cps2020&value.;
was seen as
data work.cps2020<mask-character><non-masked part of symbol value><mask-character>;
during executor processing, which treated the non-printable mask character as a non-syntax token separator, resulting in a DATA statement listing two output tables.
data work.cps2020 jan;
The positions of mask characters in a macro variable can be observed (in the LOG) using %put _user_, or, the actual symbol contents can be captured from a metadata view such as SASHELP.VMACRO or DICTIONARY.MACRO
Let's simplify your macro and add some logging and symbol capture
%macro loop(values);
%local count i;
%let count=%sysfunc(countw(&values));
%do i = 1 %to &count;
%let value=%qscan(&values,&i,%str(,));
%put _user_; %*--- log them masks;
data x&i; %* --- symbol capture;
set sashelp.vmacro;
where name like '%VALUE%';
value_hex = put (value,$HEX40.);
run;
%* --- do the step that creates two tables;
data work.cps2020&value.;
set sashelp.class;
run;
%end;
%mend;
options nomprint nosymbolgen nomlogic;
%loop(%str(jan,feb));
proc print data=x1 noobs style(data)=[fontsize=14pt fontfamily="Courier"];
var value:;
run;
LOG snippet, those little boxes are the special invisible masking characters (I am showing them in image captures because stack overflow / html won't show non-printable characters)
Same LOG text, copy and pasted into Notepad2 show the mask characters as control characters
The Proc PRINT of the captured macro symbol data will expose the hexadecimal masking characters
06 macro %quote start
08 macro %quote end
01 macro %str start
02 macro %str end
1E masked version of comma
&value is returned as quoted by %qscan(). Use %scan() instead. Quoted macro variables can sometimes cause issues on resolution when they're used in this way. It's best to only quote them when needed, such as in a %put statement that has a % sign within it.
You don't need %qscan(). If the value contained any characters that need macro quoting then they would be invalid for use in a member name anyway. So use %scan() instead.
But when used inside of a macro the tokenizer will sometimes mistakenly see things like xxx&mvar as two tokens even when there are no special characters in &mvar. You can group the value you are generating to work around that.
For example by making a new macro variable
%let dsn=cps2020&value.;
data work.&dsn. ;
Or use the %unquote() function:
data %unquote(work.cps2020&value.);
Or use a name literal:
data work."cps2020&value."n;

Creating a filename with today's date in SAS.

I would like to be able to save a file in SAS with today's date. I'm having trouble creating the file path with today's date.
Given that today's current date is 3/27/2018. I would like the created file path to be this:
"/sasFolder/MyFileName(3-27-2018).xlsx"
My current code is:
data _null_;
call symput('dateMonth', month(date()));
call symput('dateDay', day(date()));
call symput('dateYear', year(date()));
run;
%let filePath = "/sasFolder/MyFileName(&dateMonth.-&dateDay.-&dateYear.).xlsx";
data _null_;
put &filePath;
run;
Currently my output is this, with _ representing spaces.
"/sasFolder/MyFileName(___________3-__________26-________2018).xlsx"
I would like for the filename to not have all theses extra spaces in the name. Any ideas on how to do this?
You can do this very easily without a data step using %sysfunc() - this lets you call a SAS function and apply a format at the same time, eg:
%let filePath = "/sasFolder/MyFileName(%sysfunc(today(), mmddyyd10.)).xlsx";
%put &=filepath;
Which gives:
FILEPATH="/sasFolder/MyFileName(03-27-2018).xlsx"
There is a format for that date style already. Looks like you using the MMDDYYD10. format. You could use it in your data step code.
data _null_;
call symputx('datestamp', put(date(),mmddyyd10.));
run;
%let filePath="/sasFolder/MyFileName(&datestamp).xlsx";
Or skip the data step and use the %sysfunc() macro function to call the date() function and apply the format.
%let datestamp=%sysfunc(date(),mmddyyd10.);
You can even eliminate the extra macro variable.
%let filePath="/sasFolder/MyFileName(%sysfunc(date(),mmddyyd10.)).xlsx";
Note that I recommend that you switch to using YYMMDD format instead of MMDDYY format. First it will prevent your English friends from confusing December tenth and October twelfth. Second it will allow your file names to sort in chronological order.
Figured it out. Needed to change symput to symputx to remove the spaces.
The code is:
data _null_;
call symputx('dateMonth', month(date()));
call symputx('dateDay', day(date()));
call symputx('dateYear', year(date()));
run;
%let filePath = "/sasFolder/MyFileName(&dateMonth.-&dateDay.-&dateYear.).xlsx";
data _null_;
put &filePath;
run;

Embed SAS Macro Results in SAS code

I would like to include the results of a macro function call in a data step. I can do this indirectly, by first assigning the macro function output to a macro variable, and then using that macro variable within my function, but this seems inelegant.
data dataset_employee;
input name $ dept $;
datalines;
John Sales
Mary Acctng
;
data dataset_employee;
input name $ dept $;
datalines;
John Sales
Mary Acctng
;
data dataset_manager;
input name $ dept $;
datalines;
Will Sales
Sue Acctng
;
It seems like SAS doesn't realize that the macro call is complete and I'm switching to regular SAS code.
/*this works*/
%let var = %DO_OVER(VALUES=employee, PHRASE=dataset_?) dataset_manager;
data combined1;
set &var dataset_manager;
run;
/*this fails*/
data combined;
set %DO_OVER(VALUES=employee manager, PHRASE=dataset_?);
dataset_manager;
run;
/*this works*/
data combined;
set dataset_manager %DO_OVER(VALUES=employee manager, PHRASE=dataset_?);
;
run;
Can anyone help me understand what is going on here?
It seems that the failing attempt is due to an extra ; at the end of the macro invocation. Try removing it.
A macro call doesn't require a semicolon.
The first example works without a semicolon after the macro call (pay attention, you are using the dataset_manager dataset twice, in the %let and again in the set statement).
The third example would work even if you remove one of the two semicolons (one is required to end the set statement).

Run Macro inside data _NULL_

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;