Multiple set to macro variable - sas

I'm trying to build an advanced system of reporting and logging, and I have a problem.
Example:
work.check_true is empty.
When this table is empty then variable like 'czy_wyslac' should have value 2.
If this table not empty, variable 'czy_wyslac' should have value from field "gen" (1 or 0) from table work.check_true.
Next step - depending on the value of variable 'czy_wyslac" processing one from three step to next macro. I do not have a problem with this.
Below is my code:
data _null_;
call symput('obscnt',0);
set work.Check_true;
call symput('obscnt',_n_);
stop;
run;
%macro get_table_size();
%global czy_wyslac;
%If &obscnt=0 %then call SYMPUTX('czy_wyslac',2);
%else
proc sql noprint;
select
gen into: czy_wyslac
from work.Check_true
;quit;
%mend;
%macro Create_log_mail();
%if &czy_wyslac. = 1 %then
%do;
data work.maile;
zal = "T:\XXX\XXX\Risk\XXXX\XXXX\OUTPUT\Results_of_compare &calosc..xlsx";
run;
options emailsys=XXemail host=XXXemailport=XXX;
FILENAME mail EMAIL;
DATA _NULL_;
SET WORK.maile END=eof;
FILE mail ENCODING='UTF-8';
PUT '!EM_TO! "XXXXX#XXXXXX"';
PUT 'Szanowni,';
PUT 'Załącznik zawiera znalezione różnice między szablonem kalkulatora a zawartym w systemie ATP.';
PUT 'Wpis _TYPE> = PLIK - baza porównywana';
PUT 'Wpis _TYPE> = ATP - rekord wyciągnięty z ATP';
PUT;
PUT 'Baza zawiera również wynik porównania wyposażenia oraz baseline.';
PUT;
PUT 'Pozdrawiam,';
PUT 'KJ SYSTEM REPORT';
PUT '!EM_FROM! XXXSYSTEM REPORT <noreply#XXXXX.pl>';
PUT '!EM_SENDER! XXXXSYSTEM REPORT <noreply#XXXX.pl>';
PUT '!EM_BCC! ';
PUT '!EM_SUBJECT! XXXXXXXX';
PUT '!EM_ATTACH!' zal;
PUT '!EM_REPLYTO! Please do not reply for this mail - regards :)';
PUT '!EM_SEND!' / '!EM_NEWMSG!';
IF eof THEN PUT '!EM_ABORT!';
RUN;
proc sql;
Create table LOG_CREATE as
Select
distinct
date()*86400 format datetime20. as EXTRACT_DATE,
date()*86400 format datetime20. as REFERENCE_DATE,
'MAIL: Results_of_compare' as STAGE_NAME,
'99_02_MAIL_RESULT' as PROCES_NAME,
'02_CALCULATOR_ATP' as SCHEMA_NAME,
20 as etap_no,
'SENT' as STATUS,
&Count_records_02. as records,
'Wysłano mail' as Comments,
. as alert_records,
'' as Alert_comments,
&_timer_start format datetime20. as START_PROCESS,
datetime() format datetime20. as END_PROCESS,
datetime() - &_timer_start format time13.2 as Duration
FROM work._PRODSAVAIL
;quit;
%end;
%else %if &czy_wyslac. = 0 %then %do;
proc sql;
Create table LOG_CREATE as
Select
distinct
date()*86400 format datetime20. as EXTRACT_DATE,
date()*86400 format datetime20. as REFERENCE_DATE,
'MAIL: Results_of_compare' as STAGE_NAME,
'99_02_MAIL_RESULT' as PROCES_NAME,
'02_CALCULATOR_ATP' as SCHEMA_NAME,
20 as etap_no,
'NOT SENT' as STATUS,
. as records,
'' as Comments,
. as alert_records,
'' as Alert_comments,
&_timer_start format datetime20. as START_PROCESS,
datetime() format datetime20. as END_PROCESS,
datetime() - &_timer_start format time13.2 as Duration
FROM work._PRODSAVAIL
;quit;
%end;
%mend;
%Create_log_mail();

If work.check is empty, the select gen into :czy_wyslac from work.check_true will not return anything, thereby retaining any previous value in &CZY_WYSLAC.
Therefore the below will accomplish what you want :
%LET CZY_WYSLAC = 2 ; /* default value */
proc sql noprint ;
select gen into :CZY_WYSLAC from work.check_true ;
quit ;

Related

Calling different call execute on if condition - SAS

My intent is to build a dataset in a two step process based on 'frame' dataset using call execute. I need 'a_dataset'. It doesn't exist.
I read the row of 'frame' dataset:
in the first row since 'a_dataset' doesn't exist i do macro nexds.
Second row of 'frame' , i check if 'a_dataset' exist, i found it and i do macro exds.
Unfortunately my code is not working. For each row in 'frame' the condition of existence is always false and it runs two times the nexds macro.
DATA WORK.frame;
INFILE DATALINES4
/*DLM='7F'x*/
DLM=' '
MISSOVER
DSD ;
INPUT
from : $CHAR25.
tojoin : $CHAR7. ;
DATALINES4;
dataset join1
dataset join2
;;;;
proc delete data=a_dataset;run;
%macro exds(dsn,varname);
data &dsn.;
set &dsn.;
&varname. = 'exist';
run;
%mend;
%macro nexds(dsn,varname);
data &dsn.;
&varname. = 'notexist';
run;
%mend;
data _null_;
set frame;
name = strip(tojoin);
dsname = cat('a_',strip(from));
if ~exist(dsname) then put 'notexist';
else put 'exist';
if ~exist(dsname) then call execute('%nexds('||dsname||','||name||')');
else call execute('%exds('||dsname||','||name||')');
run;
It runs this two lines of code:
1 + data a_dataset; join1 = 'notexist'; run;
2 + data a_dataset; join2 = 'notexist'; run;
instead i want:
1 + data a_dataset; join1 = 'notexist'; run;
2 + data a_dataset; set a_dataset; join2 = 'exist'; run;
in the log at the put call :
notexist
notexist
it seems like the if condition is checked at the beginning for each row of 'frame' and not after each it read a single row of 'frame'.
You are not understanding how CALL EXECUTE() works. The code you generate is stored up to run after the current step finishes.
Since both observations in FRAME have the same value of FROM the test for whether or not the dataset exists will have the same value both times since any code generated by the first observation has not had a chance to run yet.
Move the test and branching logic into the macro instead.
DATA frame;
INPUT from :$25. tojoin :$7. ;
DATALINES4;
dataset join1
dataset join2
;;;;
%macro make(dsn,varname);
data &dsn ;
%if %sysfunc(exist(&dsn)) %then %do;
set &dsn;
&varname = 'exist';
%end;
%else %do;
&varname = 'notexist';
%end;
run;
%mend make;
proc delete data=a_dataset;run;
options mprint;
data _null_;
set frame;
call execute(cats('%nrstr(%make)(', 'a_', from, ',' , tojoin, ')' ));
run;

In SAS, verify if a specified column exist in every table listed in a table

I use SAS 9.4.
I have a table of table names, having two columns: libname and memname, which is the name of library and name of table, as example blow:
libname | memname
lib1 | table1
lib2 | table2
For every record, I would like to verify if a column LIKE '%CLI%' with type string and contains only digits. Every table contains at most one column satisfying these conditions.
Finally, I would like to have add the name of the found column to the table of table name as a new column:
libname | memname | colname
lib1 | table1 | client
lib2 | table2 | cli_num
Thanks a lot for your help.
Checking existence and type can be done without actually opening the data sets. But we need to open them to check that the values are all digits. Here is a looping construct you could use, including relevant tests and example data.
/* CLI exists, contains only digits*/
data ex1;
input other $ CLI $;
datalines;
x 1234
x 5787
;
/* CLI exists, doesn't contains only digits*/
data ex2;
input other $ CLI $;
datalines;
x 123a
x 5787
;
/* CLI exists, isn't character*/
data ex3;
input other $ CLI;
datalines;
x 1234
x 5787
;
/* CLI doesn't exist*/
data ex4;
input other $ other2;
datalines;
x 1234
x 5787
;
/* Makes a table of the datasets*/
data tableoftables;
input libname $ memname $;
datalines;
work ex1
work ex2
work ex3
work ex4
;
%macro do_stuff(indata=);
/* A data set to collect test results and present them to user.*/
data out;
set &indata;
length exists $3 type $9. alldigits $3.;
run;
/* Put libname and data set names into macro variables*/
data _null_;
set &indata;
call symput(cat('libname', _n_), libname);
call symput(cat('memname', _n_), memname);
run;
/* Find the number of datasets to loop through*/
proc sql noprint;
select count(*)
into :rows
from &indata;
/* Loop over data sets*/
%do i=1 %to &rows;
/*If CLI was in a specific place, you could use varnum, like here: https://communities.sas.com/message/154973. To use like, it's easier to go through proc contents*/
/* Create data ser with variables*/
proc contents noprint data=&&libname&i...&&memname&i out=row&i;
run;
/* Test 1: Is variable in file*/
/* Infile is initiated as 0, but turns to 1 if there is at least one variable like '%CLI%'. Type and name are collected*/
%let infile=0;
data test&i;
set row&i;
where name like '%CLI%';
call symput('infile', 1);
call symput('type', type);
call symput('name', name);
run;
/* Test 2: Is variable character*/
%let typetext=-;
%if &infile=1 %then %do;
%if &type=2 %then %let typetext=Character;
%else %if &type=1 %then %let typetext=Numeric;
%end;
/* Test 3: Does variable only contain digits*/
%let alldigits=-;
data test3&i;
set &&libname&i...&&memname&i end=eof;
retain test;
if _n_=1 then test=0;
notdigit=notdigit(strip(&name));
test+notdigit;
if eof then do;
if test=0 then call symput('alldigits', "YES");
else call symput('alldigits', "NO");
end;
run;
data out;
set out;
if _n_=&i then do;
if &infile=1 then exists="YES";
else exists="NO";
type="&typetext";
alldigits="&alldigits";
end;
run;
%end;
proc print data=out;
run;
%mend;
%do_stuff(indata=tableoftables)

Checking for variable type in SAS-Macro

I am trying to summarize my variables using proc sql and proc freq procedures in a macro.
Here is the code:
%macro des_freq(input= ,vars= );
%let n=%sysfunc(countw(&vars));
%let binary=NO;
%do i = 1 %to &n;
%let values = %scan(&vars, &i);
%if %datatyp(&values)=NUMERIC %then %do;
proc summary data = &input;
output out=x min(&values)=minx max(&values)=maxx;
run;
data _null_;
set x;
if minx = 0 and maxx = 1 then call symputx('binary','YES');
run;
%if &binary = YES %then %do;
proc sql;
select segment_final,
(sum(case when &values = 1 then 1 else 0 end)/ count(*)) * 100 as &values._percent
from &input
group by segment_final;
quit;
%end;
%else %do;
proc freq data =&input;
tables segment_final*&values/nofreq nopercent nocol;
run;
%end;
%end;
%else %do;
proc freq data =&input;
tables segment_final*&values/nofreq nopercent nocol;
run;
%end;
%end;
%mend;
My variables can be numeric or character. If it's numeric, it can 2 more distinct values.
I want % of 1's in a binary variable by segments(hence proc sql) and % of all distinct variables for each segment(hence proc freq).
My first if statement is checking whether the variable if numeric or not and then if its numeric, next few steps is checking if its binary or not. If its binary then execute the proc sql else execute proc freq.
If the variable is character then just execute the proc freq.
I am not able to figure out how to check if my variable is numeric or not. I tried %SYSFUNC(Vartype), %isnum and %DATATYP. None of them seem to work. Please help!!
First you can look into sashelp.vcolumn table to check variables types:
data want(keep=libname memname name type);
set sashelp.vcolumn( where= (libname='SASHELP' and memname='CLASS'));
run;
If you don't want to use vcolumn table, you can use vtype() data step function as #Tom suggest:
data _NULL_;
set &input (obs=1);
call symput('binary',ifc(vtype(&values)='N','YES','NO' ));
run;

How to scan a numeric variable

I have a table like this:
Lista_ID 1 4 7 10 ...
in total there are 100 numbers.
I want to call each one of these numbers to a macro i created. I was trying to use 'scan' but read that it's just for character variables.
the error when i runned the following code was
there's the code:
proc sql;
select ID INTO: LISTA_ID SEPARATED BY '*' from
WORK.AMOSTRA;
run;
PROC SQL;
SELECT COUNT(*) INTO: NR SEPARATED BY '*' FROM
WORK.AMOSTRA;
RUN;
%MACRO CICLO_teste();
%LET LIM_MSISDN = %EVAL(NR);
%LET I = %EVAL(1);
%DO %WHILE (&I<= &LIM_MSISDN);
%LET REF = %SCAN(LISTA_ID,&I,,'*');
DATA WORK.UP&REF;
SET WORK.BASE&REF;
FORMAT PERC_ACUM 9.3;
IF FIRST.ID_CLIENTE THEN PERC_ACUM=0;
PERC_ACUM+PERC;
RUN;
%LET I = %EVAL(&I+1);
%END;
%MEND;
%CICLO_TESTE;
the error was that:
VARIABLE PERC IS UNITIALIZED and
VARIABLE FIRST.ID_CLIENTE IS UNITIALIZED.
What I want is to run this macro for each one of the Id's in the List I showed before, and that are referenced in work.base&ref and work.up&ref.
How can I do it? What I'm doing wrong?
thanks!
Here's the CALL EXECUTE version.
%MACRO CICLO_teste(REF);
DATA WORK.UP&REF;
SET WORK.BASE&REF;
BY ID_CLIENTE;
FORMAT PERC_ACUM 9.3;
IF FIRST.ID_CLIENTE THEN PERC_ACUM=0;
PERC_ACUM+PERC;
RUN;
%CICLO_TESTE;
DATA _NULL_;
SET amostra;
*CREATE YOUR MACRO CALL;
STR = CATT('%CLIO_TESTE(', ID, ')');
CALL EXECUTE(STR);
RUN;
First you should note that SAS macro variable resolve is intrinsically a "text-based" copy-paste action. That is, all the user-defined macro variables are texts. Therefore, %eval is unnecessary in this case.
Other miscellaneous corrections include:
Check the %scan() function for correct usage. The first argument should be a text string WITHOUT QUOTES.
run is redundant in proc sql since each sql statement is run as soon as they are sent. Use quit; to exit proc sql.
A semicolon is not required for macro call (causes unexpected problems sometimes).
use %do %to for loops
The code below should work.
data work.amostra;
input id;
cards;
1
4
7
10
;
run;
proc sql noprint;
select id into :lista_id separated by ' ' from work.amostra;
select count(*) into :nr separated by ' ' from work.amostra;
quit;
* check;
%put lista_id=&lista_id nr=&nr;
%macro ciclo_teste();
%local ref;
%do i = 1 %to &nr;
%let ref = %scan(&lista_id, &i);
%*check;
%put ref = &ref;
/* your task below */
/* data work.up&ref;*/
/* set work.base&ref;*/
/* format perc_acum 9.3;*/
/* if first.id_cliente then perc_acum=0;*/
/* perc_acum + perc;*/
/* run; */
%end;
%mend;
%ciclo_teste()
tested on SAS 9.4 win7 x64
Edited:
In fact I would recommend doing this to avoid scanning a long string which is inefficient.
%macro tester();
/* get the number of obs (a more efficient way) */
%local NN;
proc sql noprint;
select nobs into :NN
from dictionary.tables
where upcase(libname) = 'WORK'
and upcase(memname) = 'AMOSTRA';
quit;
/* assign &ref by random access */
%do i = 1 %to &NN;
data _null_;
a = &i;
set work.amostra point=a;
call symputx('ref',id,'L');
stop;
run;
%*check;
%put ref = &ref;
/* your task below */
%end;
%mend;
%tester()
Please let me know if you have further questions.
Wow that seems like a lot of work. Why not just do the following:
data work.amostra;
input id;
cards;
1
4
7
10
;
run;
%macro test001;
proc sql noprint;
select count(*) into: cnt
from amostra;
quit;
%let cnt = &cnt;
proc sql noprint;
select id into: x1 - :x&cnt
from amostra;
quit;
%do i = 1 %to &cnt;
%let x&i = &&x&i;
%put &&x&i;
%end;
%mend test001;
%test001;
now in variables &x1 - &&x&cnt you have your values and you can process them however you like.
In general if your list is small enough (macro variables are limited to 64K characters) then you are better off passing the list in a single delimited macro variable instead of multiple macro variables.Remember that PROC SQL will automatically set the count into the macro variable SQLOBS so there is no need to run the query twice. Or you can use %sysfunc(countw()) to count the number of entries in your delimited list.
proc sql noprint ;
select id into :idlist separated by '|' from .... ;
%let nr=&sqlobs;
quit;
...
%do i=1 %to &nr ;
%let id=%scan(&idlist,&i,|);
data up&id ;
...
%end;
If you do generate multiple macro variables there is no need to set the upper bound in advance as SAS will only create the number of macro variables it needs based on the number of observations returned by the query.
select id into :idval1 - from ... ;
%let nr=&sqlobs;
If you are using an older version of SAS the you need set an upper bound on the macro variable range.
select id into :idval1 - :idval99999 from ... ;

Unable to match macro variable with dataset variable

The character variable in dataset never matches with the macro variable. The %IF loop never comes true. Kindly advice.
I am trying to match by months and accordingly trying to create array and put counts only for specific months. Not working because the month macro variable never matches with dataset variable having month.
/*create dummy data*/
data datefile;
input tran_date date9. cnt 3.;
datalines;
13feb2015 5
10feb2015 4
11feb2015 3
05feb2015 8
08feb2015 5
01jan2015 1
20dec2014 1
31jan2015 2
23dec2014 2
12jan2015 1
;
/*calculate month*/
data datefile11;
set datefile;
tran_mon=year(tran_date)*100+month(tran_date);
run;
/*select distinct month*/
proc sql;
create table datefile12 as select distinct(tran_mon)
from datefile11 order by tran_mon;
quit;
/*convert month from numeric to character*/
data datefile11(drop=tran_mon);
informat tran_mon2 $6.;
set datefile11;
tran_mon2=tran_mon;
run;
/*create macro variables through datastep*/
data datefile13;
set datefile12;
monum = cat('mnth',_N_);
run;
data _null_;
set datefile13;
call symput(monum,trim(left(tran_mon)));
run;
/*use array to make separate column for each month and
put split count for each month to each colunms*/
%macro c;
proc sql noprint;
select count(1) into :nrow from datefile13;
quit;
%let nrow = &nrow;
data datefile14;
set datefile11;
array mon{*} mon_1 - mon_&nrow;
%do i=1 %to &nrow;
%if tran_mon2 = &&mnth&i %then %do; %put tran_mon2;
mon_&i = cnt; %end;
%else %do; mon_&i = 0 ; %end;
%end;
run;
%mend c;
%c
Your macro %if %then %do check executes while the data step is still being compiled - by the time the data step has begun to execute, there is no further opportunity to use macro logic like that.
Try doing it the other way round - write your loop using if then do data step logic instead.