Parameterised SQL WHERE statement and SAS Macro Language problems - sql

I have a piece of code that has parameterised a hundred or so similar, but not identical SQL statements I need to generate data I require. The parameterisation works fine apart from the where statements. I am passing in a macro string of the following format:
where PERIOD_LAST_DTTM = '31DEC2017:23:59:59'dt
and myvar1 = 1 and myvar2 < 0
and myvar3 in ('SOME STRING', 'SOME OTHER STRING')
...with the following syntax:
%if %eval(&inn_sel_var. ^= &comp_var2.) %then %do;
&inn_sel_var.
%end;
/&inn_sel_var is where string &comp_var2 = NULL/
...but am getting the following error:
ERROR: A character operand was found in the %EVAL function or %IF condition where a numeric operand is required. The condition was:
where PERIOD_LAST_DTTM = '31DEC2017:23:59:59'dt and myvar1 = 1 and myvar2 < 0 and myvar3 in ('SOME STRING', 'SOME OTHER STRING')
^= NULL
ERROR: %EVAL function has no expression to evaluate, or %IF statement has no condition.
ERROR: The macro MODEL_CHECKS will stop executing.
...I have tried using %STR, %SUPERQ, %QUOTE, %BQUOTE, %NRQUOTE, %NRBQUOTE and %UNQUOTE, but I keep on hitting errors. Could someone please advise what I need to do in order for feed my parameterised where statements in correctly?
Thanks

You need to show more of your program and/or log.
But using %superq() should prevent that type of error message in your %IF statement.
%if %superq(inn_sel_var) ne %superq(comp_var2) %then &inn_sel_var. ;
Of course the value of the INN_SEL_VAR macro variable might still generate errors in the generated SAS code.

Related

Conditional PROC SQL on SAS

I have a SAS Script containing multiple PROC SQLs. The question is that the SQL Query should be "adapted" based on a SAS variable value, for example :
%let COD_COC =(52624, 52568, 52572);
%let COD_BLOC = ();
proc sql;
create table work.abordados as
select t1.cd_acao,
t1.cd_bloc,
t1.cd_tip_cnl,
t1.cd_cmco,
t1.cd_cli,
datepart(t1.ts_ctt) format=date9. as data_abordagem,
intnx('day',datepart(t1.ts_ctt), &Prazo_Compra) format=date9. as data_limite
from db2coc.ctt_cli_cmco t1
where (t1.cd_acao in &COD_COC)
and (t1.cd_bloc in &COD_BLOC) <<<<<<< facultative filter
;quit;
The question is that the second filter (t1.cd_bloc in &COD_BLOC) should be applied only if the %let COD_BLOC = (); is different of "()".
I´ve been reading about "match/case" on SQL but as far as I know, this test applies to results of queries/values. On my case, what I must test is the SAS variable.
How handle this?
Knowing you want to apply the COD_BLOC in-list filter only when there are one or more values, AND that a proper in-list will have at least 3 source code characters (*), you can test the length as the criteria for using the macro variable.
When the %IF is in open code you need a %do %end block as follows:
...
%if %length(&COD_BLOC) > 2 %then %do;
and t1.cd in &COD_BLOC
%end;
...
When the code is inside a macro, you can use the above or the below
...
%if %length(&COD_BLOC) > 2 %then
and t1.cd in &COD_BLOC
;
...
Another possible coding solution is to use %sysfunc(IFC(...)) to conditionally generate code
...
%sysfunc(ifc(%length(&COD_BLOC) > 2
, and t1.cd in &COD_BLOC
, %str()
))
...
Two good ways to do this, I think.
First: the easy hack.
%let COD_COC =(52624, 52568, 52572);
%let COD_BLOC = (and t1.cd_bloc in (...));
%let COD_BLOC = ;
proc sql;
create table work.abordados as
select t1.cd_acao,
t1.cd_bloc,
t1.cd_tip_cnl,
t1.cd_cmco,
t1.cd_cli,
datepart(t1.ts_ctt) format=date9. as data_abordagem,
intnx('day',datepart(t1.ts_ctt), &Prazo_Compra) format=date9. as data_limite
from db2coc.ctt_cli_cmco t1
where (t1.cd_acao in &COD_COC)
&COD_BLOC <<<<<<< facultative filter
;quit;
Tada, now it is just ignored. Comment out or delete the second line if you want it to be used (and put values in the ... ).
Second, the more proper way, is to use the macro language. This is more commonly done in a macro, but in 9.4m7 (the newest release, and a few years old now) you can do this in open code.
%let COD_COC =(52624, 52568, 52572);
%let COD_BLOC = ();
proc sql;
create table work.abordados as
select t1.cd_acao,
t1.cd_bloc,
t1.cd_tip_cnl,
t1.cd_cmco,
t1.cd_cli,
datepart(t1.ts_ctt) format=date9. as data_abordagem,
intnx('day',datepart(t1.ts_ctt), &Prazo_Compra) format=date9. as data_limite
from db2coc.ctt_cli_cmco t1
where (t1.cd_acao in &COD_COC)
%if %sysevalf(%superq(COD_BLOC) ne %nrstr(%(%)),boolean) %then %do;
and (t1.cd_bloc in &COD_BLOC) <<<<<<< facultative filter
%end;
;quit;
You have to be careful with the ne () bit because () are macro language syntax elements, hence the long %nrstr to make sure they're properly considered characters. (%str would be okay too, I just default to %nrstr.)

How to make exceptions in SAS eg

In python you can make exception like this:
x=0
try:
1/x
except:
1+2
So if you get an error in the first statement the second one is runs
Does SAS EG have something similar?
I try to do something like this:
try:
%_eg_conditional_dropds(WORK.QUERY_FOR_KUNDE_REA_UDL_20_0000);
PROC SQL;
CREATE TABLE WORK.QUERY_FOR_KUNDE_REA_UDL_20_0000 AS
SELECT t1.Z_ORDINATE,
/* KundeNum */
(input(t1.cpr_se,w.)) AS KundeNum
FROM &str_PERIOKVT_PREV_YYMMN6;
QUIT;
except:
%_eg_conditional_dropds(WORK.QUERY_FOR_KUNDE_REA_UDL_20_0000);
PROC SQL;
CREATE TABLE WORK.QUERY_FOR_KUNDE_REA_UDL_20_0000 AS
SELECT t1.Z_ORDINATE,
/* KundeNum */
(input(t1.cpr_se,w.)) AS KundeNum
FROM &str_PERIOKVT_PREV_YYMMN6_v2;
QUIT;
I hope you can point me in the right direction.
SAS does not have try/except blocks, but you can work around it a number of ways. Here are two effective ways of handling it.
The most common way is by specifying the error condition you're looking for. For example, let's say we know our code will fail if &str_PERIOKVT_PREV_YYMMN6 does not exist. We can first check for that before running the code using macro functions:
%if(%sysfunc(exist(&str_PERIOKVT_PREV_YYMMN6))) %then %do;
<code>;
%end;
%else %do;
<code>;
%end;
A more generic way is to use the &syserr. macro variable. This value is updated each time a procedure is run. If this value is > 6, an error has occurred:
/* Try running code */
data test;
set fakedata;
run;
/* If something goes wrong, then do something else */
%if(&syserr. > 6) %then %do;
%put ERROR: Something went wrong! Error code: &syserr.;
%end;
More information on &syserr can be found here:
https://go.documentation.sas.com/doc/en/pgmsascdc/9.4_3.5/mcrolref/n1wrevo4roqsnxn1fbd9yezxvv9k.htm

SAS - Replacing substrings in a macro variable containing semicolons

I need to be able to copy paste a long SQL script that contains variables back and forth between Aginity Workbench and SAS. To make this easier, I've been storing the SQL query in a macro variable like this:
(take note of the Netezza style variables)
%let myQuery = %str(
DROP TABLE this;
SELECT *
INTO SomeTable
FROM OtherTable
WHERE field = ${myVariable};
UPDATE TABLE foo
SET x = 1
WHERE
field = ${anotherVariable};
);
When my SAS program runs, I need it to replace the ${netezzaVariables} with text from other macro variables that are determined earlier in the process flow. So far, I've not been able to successfully replace text within this macro variable and I am suspicious that the semicolons are causing issues.
Here's what I'm attempting to do below:
%let formattedText = %sysfunc(tranwrd(&myQuery,'${myVariable}','replacementText'));
The log for that shows:
NOTE: Line generated by the macro function "SYSFUNC".
DROP TABLE this;
! SELECT <the rest of the query is printed to console here>
I didn't go on writing the rest of the log above, because the error is on the word SELECT, with a red line underneath it. Just below this red line is the text:
ERROR 180-322: Statement is not valid or it is used out of proper order.
In fact, if I go on scrolling through the log, every first set of characters following a semicolon in the query is underlined with that same exact error code.
This leads me to believe that SAS picked up the semicolon before that SELECT, used it to terminate what I was doing, and now thinks the text following this semicolon is out in the open code.
In conclusion, I need to know how to replace substrings within a macro variable who's value is a large string containing semicolons.
Thanks in advance!
You don't need to use quotes when using string functions with %sysfunc(). In this case %qsysfunc is what you're looking for, I think:
%let myQuery = %str(
DROP TABLE this;
SELECT *
INTO SomeTable
FROM OtherTable
WHERE field = ${myVariable};
UPDATE TABLE foo
SET x = 1
WHERE
field = ${anotherVariable};
);
%put &myQuery;
%let formattedText = %qsysfunc(tranwrd(&myQuery,${myVariable},replacementText));
%put &formattedText;
Not the most elegant of solutions but it does the job:
%let myQuery = %str(
DROP TABLE this;
SELECT *
INTO SomeTable
FROM OtherTable
WHERE field = ${myVariable};
UPDATE TABLE foo
SET x = 1
WHERE
field = ${anotherVariable};
);
data _null_;
call symput('formattedtext',tranwrd("%quote(%superq(myquery))","${myVariable}","replacementText"));
run;
%put %superq(formattedText);
There's probably a way to do it using only macro functions but I couldn't get it to work.
For your particular example, the call symput could have been simplified to
call symput('formattedtext',tranwrd("&myquery","${myVariable}","replacementText"));
but this would fail if your query contained double quotes whereas the way I wrote it above supports that.
Joshua:
A general purpose resolver is useful if your 'templated' expression has many parameters. Note: a template is different than a parameterized query, and potentially more dangerous.
Without a resolver you will need to code a TRANWRD for each parameter.
Consider this macro that assumes a template contains parameters that are specified by ${macro-var} and parameters are replaced with the macro-var value. Also presume there are no parameters that start with underscore (_) which could collide with the macros internal variables.
%macro resolver(_template);
%local _result;
%local _tokenRx;
%local _start _stop _position _length _token _macrovar _guard;
%let _tokenRx = %sysfunc(prxparse(m/\${([^}]+)}/));
/*%put &=_tokenRx;*/
%let _guard = 0;
%let _start = 1;
%let _stop = %length(&_template);
%let _position = 0;
%let _length = 0;
%let _result = &_template;
%syscall prxnext(_tokenRx, _start, _stop, _template, _position, _length);
%do %while (&_position > 0);
/* %put &=_start &=_stop &=_position &=_length; */
%let _token = %qsubstr(&_template,&_position,&_length);
%let _macrovar = %substr(&_token,3,%eval(%length(&_token)-3));
/*
%put &=_token;
%put &=_macrovar;
*/
%if %symexist(&_macrovar) %then %do;
%let _result = %qsysfunc(tranwrd(&_result,&_token,&&&_macrovar));
%end;
%syscall prxnext(_tokenRx, _start, _stop, _template, _position, _length);
%let _guard = %eval (&_guard+1);
%if &_guard > 1000 %then %let _position = 0;
%end;
%syscall prxfree(_tokenRx);
%superq(_result)
%mend;
Here is the resolver applied to your templated SQL query (adjusted to Proc SQL).
%let myQuery = %str(
DROP TABLE this
;
INSERT INTO SomeTable /* sas insert syntax */
SELECT * FROM OtherTable
WHERE ${field} = ${target}
;
UPDATE foo
SET x = 1
WHERE
field = ${anotherVariable}
;
);
%let field = name;
%let target = 'Jane';
%let myVariable = XYZ;
%let anotherVariable = 'John';
%put %resolver (%superq(myQuery));
proc sql;
create table this (id int);
create table SomeTable like sashelp.class;
create table OtherTable as select * from sashelp.class;
create table foo as select name as field, 0 as x from sashelp.class;
%unquote(%resolver(%superq(myQUery)))
quit;

Input value If macro variable exists else some other value

The need is to perform valuation of a macro variable outside datastep and depending on the existence of the variable, perform insertion:
data my_dataSet;
set ...
....
if %SYMEXIST(Variable_from_prior_code) = 1 then do;
dataset_variable = &Variable_from_prior_code.;
end;
else do;
dataset_variable = &Some_default_value_from_prior_code;
end;
However, this fails at compiler when trying to run it as the " Apparent symbolic reference &Variable_from_prior_code." has not been resolved. Ie. the compiler checks the contents of the if statement even as the condition is not met.
I came up with silly work-around: to approach this from opposite directon, but feels more stupid than bag of badgers:
if %SYMEXIST(Variable_from_prior_code) = 0 then do;
dataset_variable = &Some_default_value_from_prior_code
%let Variable_from_prior_code=0; /*Dummy value*/
end;
else do;
dataset_variable = &Variable_from_prior_code.;
end;
Any way to restrict the compiler from evaluating content, which it shouldn't due to condition?
Or alternatively, more elegant work-around, which do not require creation of the variable?
I'd say, avoid macro logic unless necessary! Here's a pure data step approach:
%symdel Variable_from_prior_code; /* make sure variable does not exist */
%let Some_default_value=test; /* populate macro variable */
data my_dataSet;
if SYMEXIST('Variable_from_prior_code') = 1 then do; /* use data step function */
/* note variable name is quoted, else would reference a data step variable value */
dataset_variable = symget('Variable_from_prior_code');
end;
else do;
/* had to shorten this name to less than max allowed 32 chars */
dataset_variable = symget('Some_default_value');
end;
run;
As Tom mentions, you are currently mixing up macro and data step logic. Macro is used to write data step code (so is essentially a program generator), and that resultant data step code is executed long after the macro statements are compiled / resolved / executed.

Evaluate Character and Numeric Variables in PROC SQL [duplicate]

%macro pp();
data temp1;
set dir.data
call symput('prod', product);
run;
%put "&prod";
%if prod = "&prod" %then %do;
%put "&prod";
%end;
%mend;
%pp();
Why does the if statement evaluate to false?
In the SAS macro language, everything is a character string, so your statement
%if prod = "&prod" %then %do;
Will never be true; the string prod will never equal the string "&prod" if only because one string includes double-quotes and the other does not.
So use double-quotes on both sides or not at all. Either of these will be better:
%if "prod" = "&prod" %then %do;
%if prod = &prod %then %do;
Also, note that after this repair, the statement will be "true' only if the macro variable you created has the exact value of prod (those four characters). Case matters: prod is not equal to PROD.
The best way to debug something like this is to put it in a %put statement which I can see you have tried to do but it can get a little tricky. Because your comparison statement is:
%if prod = "&prod" %then %do;
Then to debug it with a %put you should have included the full comparison (both sides) to make it stand out more:
%put prod = "&prod";
The output from this would show you that the string to the left of the equals sign does not equate to the string to the right. Part of the problem is that you are quoting the string on the right, but not on the left. Even if your macro variable &prod contained the value prod you are basically testing this condition:
prod = "prod"
A better comparison would be to wrap both strings in quotes like so:
%if "prod" = "&prod" %then %do;
In fact in the macro language a double quote is almost-similar to any other character. So we could have wrapped them in characters other than a double quotes:
%if ###prod## = ###&prod## %then %do;
The important thing is to treat them the same. You could even omit the double quotes although I think this sometimes leads to problems because it makes it hard to debug if the string contains macro quoted blank spaces or non-printable chars:
%if prod = &prod %then %do; /* LEAST FAVOURITE OPTION AS IT CAN BE HARD TO DEBUG */
Hope this helps.