I am writing a macro that at some point calls some proc SQL code. I want the user to be able to specify arbitrary proc sql options (e.g. inobs=100 could be one of the input arguments to my macro).
I am having a very hard time quoting an argument that has an equality '=' character.
One of the issues is that I should also check if the macro argument is empty or not, and if it is not empty, only then add the specified options to the sql statement.
Below is an example non-working test that does not work and throws the
ERROR: The keyword parameter INOBS was not defined with the macro.
I have read this (http://www2.sas.com/proceedings/sugi28/011-28.pdf) and other SUGI's and tried many possible ways to quote and call the macro.
If somebody could provide a working example of the below function it would be greatly appreciated.
options mprint mlogic;
data have;
length x $8;
input x;
datalines;
one
two
three
;
proc sql inobs=2;
create table sql_output as
select *
from have;
quit;
%macro pass_parameter_with_equal_sign(table=, sqlOptions=);
proc sql
%if "%left(%trim(&sqlOptions.))" ne "" %then %do;
&sqlOptions.
%end;
/* the semicolon to end the proc sql statement */
;
create table macro_output as
select *
from have;
quit;
%mend;
%pass_parameter_with_equal_sign(table=have, sqlOptions=%str(inobs=2))
title "SQL output:";
proc print data=sql_output; run;
title "Macro output:";
proc print data=macro_output; run;
If you remove the %if condition as follows it should work:
%macro pass_parameter_with_equal_sign(table=, sqlOptions=);
proc sql
&sqlOptions.
/* the semicolon to end the proc sql statement */
;
create table macro_output as
select *
from have;
quit;
%mend;
The %if you have used is to check if &sqlOptions is not blank, this shouldn't matter if you use it as it is because its unconditional usage will give either:
proc sql inobs=2; /* in the case of &sqlOptions=inobs=2 */
or if there is no value supplied for &sqlOptions then you should see:
proc sql; /* i.e. no options specified */
So it should work with or without an argument.
Amir's solution is probably correct for your particular use case. But to answer the more general question, we need to look to the seminal paper on macro parameter testing, Chang Chung's Is This Macro Parameter Blank?.
His example C8 is the right one for you here, though some of the others will also work.
%if %sysevalf(%superq(param)=,boolean) %then ... /* C8 */
For example:
%macro test_me(param=);
%if %sysevalf(%superq(param)=,boolean) %then %put Empty;
%else %put Not Empty;;
%mend test_me;
%test_me(param=);
%test_me(param=MyParam);
%test_me(param=param=5);
%SUPERQ is most useful here because it avoids resolving the macro parameter. Instead, it keeps it as a macro parameter value - fully unresolved - and allows you to work with it in that fashion; so you have no risk of that pesky equal sign bothering you.
His C4 (just using SUPERQ without SYSEVALF) also works in this case, although he explains a few situations where it may have difficulty.
Ahh this was actually a tricky little problem you ran into. The issue was actually being caused by the calls to %trim() and %left().
Removing these results in code that works as intended (note I also removed the macro quoting around the parameter):
%macro pass_parameter_with_equal_sign(table=, sqlOptions=);
proc sql
%if "&sqlOptions" ne "" %then %do;
&sqlOptions
%end;
/* the semicolon to end the proc sql statement */
;
create table macro_output as
select *
from &table;
quit;
%mend;
%pass_parameter_with_equal_sign(table=sashelp.class, sqlOptions= inobs=2);
We can re-create the issue you were experiencing like so:
%put %trim(inobs=1);
Because the parameter was resolving to inobs=1, and %trim() doesn't have any named parameters, it was throwing a hissy fit. To correctly pass in a string that contains "inobs=1" we can do so like this:
%let param = inobs=1;
%put %trim(%str(¶m));
Note: Amir's solution of removing the %if statement altogether is also the best way to design code like this. I'm just providing more details as to why you were having this issue.
Additional Explanation 1 - Why %left() and %trim are not needed
The top code snippet provides the same intended functionality as your original code that had the "%left(%trim(&sqlOptions.))". This is because beginning and ending whitespace is dropped from macro variables (including macro parameters) unless it is explicitly retained by using macro quoting. A simple example to show this is:
%let param = lots of spaces ;
%put ***¶m***;
Gives:
***lots of spaces***
You can see that the internal whitespace is kept, but the left and right padding are gone. To keep whitespace, we can simply use the %str() function.
%let param = %str( lots of spaces );
%put ***¶m***;
Gives:
*** lots of spaces ***
Additional Explanation 2 - Working with macros containing whitespace
If you actually did have whitespace on a macro variable that you needed to remove because it was quoted, and you wanted to use %left() and %trim() to do so, then things get a little wacky. Our variable can be created like so:
%let param = %str( inobs = 2 );
You can see we already have quoted the value with %str() in order to create it. This means we can now call one of the functions without having to quote it again:
%put %trim(¶m); * ALREADY QUOTED AT CREATION SO THIS WORKS FINE;
However, if we then try and feed the result into the %left() function we're back to the original issue:
%put %left(%trim(¶m)); * OOPS. DOESNT WORK;
Now I'm guessing here but I believe this is most likely because the %trim() function removes any macro quoting prior to returning a result. Kind of like this:
%put %unquote(%trim(¶m));
This can be circumvented by re-quoting the returned result using %str() again:
%put %left(%str(%trim(¶m)));
... or wrapping the original parameter with a %nrstr():
%let param = %str( inobs = 2 );
%put %left(%trim(%nrstr(¶m)));
... or using %sysfunc() to call a datastep function:
%put %sysfunc(compress(¶m));
Related
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.
I'm using SYSPBUFF to pass through various numbers of parameters into a macro. Specifically, I am passing through a list of states. One of the states being used is Oregon or "OR" and that one state is causing me error.
I get the error "ERROR: A character operand was found in the %EVAL function or %IF condition where a numeric operand is required. The condition was: &ST^=
ERROR: The condition in the %DO %WHILE loop, &ST^=, yielded an invalid or missing value, . The macro will stop executing.
I've used all the various quoting masks to try to resolve this issue but none of it is working.
&STATES includes the following states:
AK,AZ,CA,HI,ID,NV,OR,WA
Here is my current code:
RSUBMIT;
PROC SQL;
connect to oracle
(path=DW user=&USER pw=&PW);
%macro DTCNT() / parmbuff;
%let i=1;
%let ST=%scan(&SYSPBUFF,&I);
%do %while (&ST^=);
CREATE TABLE MD_&ST._IP_ADJDT_CNTS_S1 AS
select *
from connection to oracle
(SELECT adjudication_date,
count (*) as LINE_CNT
from MD_r&NUM..&ST._IP_hdr_f
group by adjudication_date
order by adjudication_date);
%let i=%eval(&I+1);
%let ST=%scan(&SYSPBUFF,&I);
%end;
%mend DTCNT;
%DTCNT(&STATES);
disconnect from oracle;
QUIT;
ENDRSUBMIT;
Any assistance would be greatly appreciated.
Thanks in advance.
The issue here is Oregon. Its abbreviation is OR, which is also a reserved word (oops!). Remember the macro language is just text that is then parsed like normal - so when it finds &ST and translates to OR, it sees that as
%do %while (or ^= )
which causes it to get confused since it doesn't see anything to use with or.
You can use macro quoting here to cause SAS not to treat it like the boolean operator. %SUPERQ is the goto one for me, but a few of them should work.
Here's an example. I added some extra stuff to scan also to handle the parens.
%let states=AK,AZ,CA,HI,ID,NV,OR,WA;
%macro DTCNT() / parmbuff;
%let i=1;
%put &=syspbuff.;
%let ST=%scan(&SYSPBUFF,&I,%str(%(%),));
%put &=st.;
%do %while (%superq(ST)^=);
%put &=st;
%let i=%eval(&i.+1);
%let ST=%scan(&SYSPBUFF,&I,%str(%(%),));
%end;
%mend DTCNT;
%DTCNT(&STATES);
There is a great paper on how to test for empty macro variables, Chang Chung's Is This Macro Parameter Blank.
For your simple program I find it much easier to just use %length() to test for empty macro variables.
%do %while (%length(&ST));
I'm looking to create a loop such that I run two macros for each dataset
%Let Classification = Data1 data2 data3 data4;
%let index = 1;
%do %until (%Scan(&Classification,&index," ")=);
%Macro1;
%Macro2;
%end;
%let index = %eval(&Index + 1);
The problem is my macros are not pre-loaded and are stored in a macro library, is it possible to do this if I run the above as a macro?
Any advice is appreciate in making this loop of macros work
EDIT:
In my ideal situation the loop would run like a macro
%Macro;
where inside it would look like
%Macro Macro;
%let index = 1;
%do %until (%scan(&classification,&index," ")=);
<Lines of Code>
%end;
%let index = %eval(&Index + 1);
%end;
%mend;
Another problem is my macros enclosed in the loop use the &classification to differentiate between data1, data2, data3, data4 as we process through the different lines of code.
It is probably easier to just iterate over the index. Use the countw() function to find how many iterations to do.
%macro loop(list);
%local index next ;
%do index=1 %to %sysfunc(countw(&list,%str( )));
%let next=%scan(&list,&index,%str( ));
... code to process &NEXT ...
%end;
%mend ;
Then pass in the list to the macro as the parameter value.
%Let Classification = Data1 data2 data3 data4;
%loop(&classification);
SAS does not allow the %DO statement in open-code. When you submit an open code loop you will get log messages
ERROR: The %DO statement is not valid in open code.
...
ERROR: The %END statement is not valid in open code.
as #Tom mentioned the macro %SCAN test should check for null string. Another common and more robust way is to check before token extraction. %do %until will iterate poorly when the classification passed is empty. A %do %while tests the classification scan prior to interior macro invocations. Another common test for null macro value is checking for 0 length and leveraging 0=false ^0=true automatic evaluation.
When the loop is to call other macros with the token value the best practice is to pass the token value instead of having the called macro presume the token symbol (aka macro variable) already exists (in a containing scope) prior the iterated macros invocation.
Example
%macro mydispatch (classification=);
%local index token;
%let index = 1;
%do %while ( %length (%scan (&classification, &index)));
%let token = %scan(&classification,&index));
%* emit code specifically for token;
* this is for &token;
%* iterated invocations, perform two analysis for each data set listed in classification;
%* second analysis is passed another argument specifying the data set that should be used to store output;
%analysis_1 (data=&token)
%analysis_2 (data=&token, out=WORK.results_&token.)
%let index = %eval(&index+1);
%end;
%mend mydispatch;
%mydispatch (classification=data1 data2 data3 data4)
The macro being in an autocall library (which is what I assume you refer to?) does not have any impact on how the above would work. If it's not in an autocall library you'll have to hook up the catalog up to the autocall library first.
In re: your edits; yes, you will need this to be in a macro (I assumed it was a subset of a macro initially). %do is not currently allowed in open code (this may change, but not today).
Note you have several significant issues in your code:
the incrementor is not in the loop
the scan function is wrong; macro language does not use quotations, so
%do %until (%Scan(&Classification,&index," ")=);
needs to be
%do %until (%Scan(&Classification,&index)=);
(space is the default separator), and if you really needed to clarify space:
%do %until (%Scan(&Classification,&index,%str( ))=);
Your macros do not utilize parameters; they should. %macro1; apparently uses &classification and &index; instead you should pass it the thing you want (the "word" from &classification) as a parameter.
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');
How do I call a macro variable in the from clause of proc sql if I wish to use it in a libname?
Let me show you what I mean:
options nofmterr;
libname FRST "/ecm/retail/mortgage/nbk6kra/LGD/data/frst_201312bkts";
libname HELN "/ecm/retail/mortgage/nbk6kra/LGD/data/heln_201312bkts";
libname HELC "/ecm/retail/mortgage/nbk6kra/LGD/data/helc_201312bkts";
%let pathLGD = /new/mortgage/2014Q4/LGD;
%let prod = FRST;
/**************** Segment calculation **************** Date filter to be consistent with model documentation for segmented tables****************/
%macro Performance(prod);
proc sql;
create table lgd_seg_&prod as
select distinct
SegDT_LGD_2013,
min(ScoreDT_LGD_2013) as min_range,
max(ScoreDT_LGD_2013) as max_range,
count(*) as count,
mean(lgd_ncl_adjusted) as LGD_actual,
mean(ScorePIT_LGD_2013) as LGD_pred_pit_1,
mean(ScoreDT_LGD_2013) as LGD_pred_dt_1
from "&prod."scored;
where coh_asof_yyyymm > 200612
group by 1;
quit;
PROC EXPORT DATA=lgd_seg_&prod._fs
OUTFILE= "&pathLGD./lgd_seg.xlsx"
DBMS=XLSX REPLACE;
SHEET="&prod._lgd_seg_fs";
RUN;
%mend;
%Performance(prod=FRST);
%Performance(prod=HELN);
%Performance(prod=HELC);
So in the "from" clause, the macro is supposed to read FRST.scored, HELN.scored, and HELC.scored respectively. Currently it cannot find the file, and if I were to remove the quotation marks, then it'd become "work.FRSTscored".
I hope I've made this clear. Any input and comment is appreciated.
When you want to follow the the resolved value of a macro variable with an immediate additional character you should escape the macro variable with a full stop (.). For example:
%let start = one;
%put &start.two;
%put &start..two;
%put &startend;
onetwo
one.two
WARNING: Apparent symbolic reference STARTEND not resolved.
So your code should read from &prod..scored;.
If you ever need to you can also delay the resolution of a macro variable with double ampersand (&&):
%let end = two;
%let onetwo = three;
%put &&one&end;
%put &&&start&end;
Three
Three
Or:
%let three = inception;
%put &&&&&&&start&end;
inception
Remove quotation marks applied outside the macro variable prod and use two dots after macro variable (one to signify end of macro variable name and second one to specify the table name after the libname reference.
from &prod..scored