Input value If macro variable exists else some other value - dynamic

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.

Related

How to execute function with parameter in PostgreSQL using anonymous code block in FireDAC?

My database (PostgreSQL 11) has a function that must be called to execute an action. The return is not important, as long as there's no error. This function has arguments that must be passed as parameters.
I cannot directly use TFDConnection.ExecSQL because I use parameters of type array that is not supported by the method (to my knowledge). So, I use TFDQuery.ExecSQL like this:
msql1: = 'DO $$ BEGIN PERFORM DoIt (:id,:items); END $$; ';
{... create FDQuery (fq), set connection, set SQL.Text to msql1}
fq.Params.ParamByName ('id'). AsInteger: = 1;
{$ REGION 'items'}
fq.ParamByName ('items'). DataType: = ftArray;
fq.ParamByName ('items'). ArrayType: = atTable; // Must be atTable, not atArray
if length (items)> 0 then
begin
fq.ParamByName ('items'). ArraySize: = length (items);
for it: = 0 to length (items) -1 do
fq.ParamByName ('items'). AsIntegers [it]: = items [it];
end;
fq.ExecSQL;
{$ ENDREGION}
When executing the above code, the error above message raises
"Parameter 'id' not found".
After some research that suggested using fq.Params.ParamByName I was also unsuccessful.
However, if you change the way the function is called to
select DoIt (:id,:items); and obviously replacing the execution with fq.Open works perfectly.
Is it possible to execute a PL / pgSQL block that contains parameters in the function called by this block using TFDConnection / TFDQuery?
PS: I'm using Delphi Rio 10.3.3
When you assign a value to TFDQuery.SQL, FireDAC does some pre-processing of the SQL based on various options. ResourceOptions.CreateParams option controls whether parameters should be processed. This is enabled by default.
The preprocessor recognizes string literals in your SQL and doesn't try to look for the parameters in them. You used dollar quoted string constant and that's why FireDAC doesn't recognize parameters in it. Even if you add parameter manually I think that FireDAC would not bind the value.
With that said, the proper way to execute stored procedures/function is to use TFDStoredProc. You just assign StoredProcName and call its Prepare method which will retrieve procedure's metadata (parameters) from database so you don't need to set ArrayType or DataType of the parameter.
In your code you set the DataType to ftArray which is wrong, because in case of array parameter it should be set to array's element type. Anyway, by setting fq.ParamByName ('items').AsIntegers you effectively set parameter's DataType to ftInteger. All you need to do is to set ArraySize
Here's what you should do instead:
procedure DoIt(Connection: TFDConnection; ID: Integer; const Items: TArray<Integer>);
var
StoredProc: TFDStoredProc;
ParamItems: TFDParam;
Index: Integer;
begin
StoredProc := TFDStoredProc.Create(nil);
try
StoredProc.Connection := Connection;
StoredProc.StoredProcName := 'DoIt';
StoredProc.Prepare;
StoredProc.Params.ParamByName('id').AsInteger := ID;
if Length(Items) > 0 then
begin
ParamItems := StoredProc.Params.ParamByName('items');
ParamItems.ArraySize := Length(Items);
for Index := Low(Items) to High(Items) do
ParamItems.AsIntegers[Index] := Items[Index];
end;
StoredProc.ExecProc;
finally
StoredProc.Free;
end;
end;
Alternatively you can use ExecFunc to get the result of stored function.

SQL - How to call a part of query conditionally

I need to write a SQL query like this:
do step1
do step2
do step3
- this step does a lot of stuffs: define variables, use some variables defined in step 1, query a lot of data into temporary table...
if(cond1)
if(cond2)
Begin
do step 4
call step 3 here
End
else
Begin
do step 5
End
else
Begin
call step 3 here
End
How to make step 3 a reusable query to avoid calling step 3 unnecessarily? notice that step 3 should be a part of the whole query
When I try to create a #step3Query NVARCHAR(MAX), SET #step3Query = '...'
Then call this query in appropriated places by call "EXEC sp_executesql #step3Query". But I got a lot of errors, and I am not sure that is a correct way to do task like that.
Any suggestions would be highly appreciated.
Here is one method that could work (depending on your exact circumstance). The main thing for this is that it's easy, as well as easy to read.
You could have 3 variables to use as flags e.g., #Query3_Flag, #Query4_flag, #Query5_flag.
Your conditional checks just set these flags e.g.,
IF (condition1)
BEGIN
SET #Query4_Flag = 1;
SET #Query3_flag = 1;
SET #Query5_flag = 0;
END
Before each of queries 3, 4 and 5, have the IF statement check the flag and only run the query if the flag is set to 1 e.g.,
IF #Query3_Flag = 1
BEGIN
(run Query 3)
END
Note that the queries will need to be in the correct order.

Conditional processing in proc sql(SAS) using a macro variable

I need to select the name of states that do not start with M if the the macro variable M=N but return only the names of states that start with M if macro variable is equal to any other variable using conditional processing.
for example:
%let M=N;
proc sql;
select states,profit,
case
when .....
else
end
from geography_dim
quit;
For the sake of argument, suppose you change the name of the macro variable M to something more ridiculously expressive, such as YN_OPTION_SELECT_M_STATES
%let YN_OPTION_SELECT_M_STATES = N;
proc sql;
select states,profit,
case
when .....
else
end
from geography_dim
/* add this */
where
("&YN_OPTION_SELECT_M_STATES" eq 'N' & STATE not like 'M%')
or
("&YN_OPTION_SELECT_M_STATES" ne 'N' & STATE like 'M%')
;
quit;
Revert to macro variable M if you must, however the code will be somewhat opaque.
It is not SQL but very simple in datastep. If you want check the staring with M macro values in that case "N" you can do like:
/*test data*/
data geography_dim ;
states="Aaaaa";profit=10;output;
states="Naaaa";profit=10;output;
run;
/*set macro variable*/
%let M=N;
/*check if you want*/
%put "&M";
/*your case in datastep*/
data test;
set geography_dim;
if substr(states,1,1) eq "&M" then profit=profit*10;
else profit=0;
run;
/* results
states profit
Aaaaa 0
Naaaa 100
*/

Parameterised SQL WHERE statement and SAS Macro Language problems

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.

SAS If then else Statement with Do loop

I need to get a percentage for 75 values in 75 columns individually. And I want to use a do loop so I don't have to hard code it 75 times. There are some conditions so there will be a where statement.
I am not getting the do loop correctly but I am using the below to get a percentage
case when (SUM(t1.sam)) >0 then
((SUM(t1.sam))/(SUM(t1.sam_Threshold)))*100
else 0
end
I tried the below and its a bit better:
data test;
i_1=4;
i_2=8;
i_3=4;
i_4=8;
V_ANN_V_INSP=24;
run;
%macro loop();
%let numcols=4;
proc sql;
create table test3 as
select V_ANN_V_INSP,
%do i=1 %to &numcols;
(i_&i/V_ANN_V_INSP)*100 as i_&i._perc
%if &i<&numcols %then %do;,
%end;
%end;
from test;
quit;
%mend;
%loop();
CASE WHEN is a SQL statement, not a data step statement, so you can't use a DO loop there. Depending on what you're doing exactly, there are a lot of possible solutions here. Posting additional code would help to get a more precise answer, but I can give you a few suggestions.
First, take it into a data step. Then you can use a do loop.
data want;
set have;
array nums sam1-sam75;
array denoms threshold1-threshold75;
array pct[75];
do _t = 1 to dim(nums);
pct[_t]=nums[_t]/denoms[_t];
end;
run;
Second, if you need to do this in SQL for some reason, you can write out the SQL code either in a macro or in a data step in a pre-processing step.
%macro do_sql_st;
%do _t = 1 to 75;
case when (SUM(t1.sam&_t.)) >0 then
((SUM(t1.sam&_t.))/(SUM(t1.sam_Threshold&_t.)))*100
else 0
end
as pct&_t.
%end;
%mend do_sql_st;
proc sql;
select %do_sql_st from t1 where ... ;
quit;
These are not terribly flexible; unless you have very specifically named variables, they won't work as is. You're more likely to want to do some sort of data step preprocessing I suspect, but that's very hard to explain without more detail as to how the variables are named (ie, if there is a relationship between them).