I am new to SAS and I am trying to decode a SAS code. In the beginning of the script the codes has a line that says -
%let sales=sale_q;
%if (%sysfunc(libref(sales))) %then %do;
libname sales "/data/raw/sales/sales_a";
I have three data files - sales_q for quarterly sales, sales_a for annual sales and sales_m for monthly sales.
My code sets the library sales to use quarterly sales data. What does the %if statement do?
I searched about the libref function and found out that it just verifies the existence of a library path name. But I don't understand the need to use the IF statement.
Thanks for the Help!
SAS Documentation is your friend.
A simple google search has led me to LIBREF Function page of the SAS documentation
It describes LIBREF function as :
specifies the libref to be verified. In a DATA step, libref can be a
character expression, a string enclosed in quotation marks, or a DATA
step variable whose value contains the libref. In a macro, libref can
be any expression
The LIBREF function returns 0 if the libref has been assigned, or
returns a nonzero value if the libref has not been assigned.
So, to answer your question, The %if statement is checking to see if sales has been assigned as a library references or not. If not %then %do part will assign "/data/raw/sales/sales_a" to sales.
The libref is pointing towards a specific "physical" path and can be assigned and unassigned.
A libref may also be e.g an excel spreadsheet, and in that case - assigning and unssigning librefs is important.
Related
I am trying to get insert the timestamp, current date and current time into a table using macro, but values are not getting displayed as expected. Can someone help on this please?
Also i m trying to write the SQL return code and message, but it displayed nothing.
%MACRO INS;
data _NULL_;
call symput('currdatets',datetime());
call symput('currdate',today());
call symput('currtime',timepart(datetime()));
%put currdatets> &currdatets;
%put currdater--2> &currdate;
%put currtime---2> &currtime;
run;
proc sql;
CONNECT TO DB2
insert into table
(entrytime, rundate, runtime)
values
(&currdatets,&currdate,&currtime)
DISCONNECT FROM DB2;
QUIT;
%PUT &SQLXMSG;
%PUT &SQLXRC ;
%MEND;
WARNING: Apparent symbolic reference CURRDATETS not resolved.
currdatets> &currdatets
WARNING: Apparent symbolic reference CURRDATE not resolved.
currdater--2> &currdate
WARNING: Apparent symbolic reference CURRTIME not resolved.
currtime---2> &currtime
WARNING: Apparent symbolic reference SQLXMSG not resolved.
&SQLXMSG
WARNING: Apparent symbolic reference SQLXRC not resolved.
&SQLXRC
Your first three macro-variables are not resolved because you specified the %put statements before the end of the data _null_ step (i.e., before the run;). symput assigns values produced in a DATA step to macro variables during program execution.
Use symputx instead of symput. It does not change the result though, but
symput gives you a message on the log about the conversion, while symputx does not. Moreover, symputx takes the additional step of removing any leading blanks that were caused by the conversion.
As for the two SQL Pass-Through automatic macro-variables you will need to provide us with more information. I don't know if intended or not, but you seem to use an explicit Pass-Through connection. If so, you might be missing information to connect to the server (e.g., connect to db2 (dsn= "xxxx")).
The automatic macro-variables SQLXRC and SQLXMSG are reset after each SQL Procedure Pass-Through Facility statement has been executed. If they are not resolved it means there were not any.
By the way, according to the documentation, you may want to use %SUPERQ() with SQLXMSG
SQLXMSG contains descriptive information and the DBMS-specific return
code for the error that is returned by the pass-through facility.
Note: Because the value of the SQLXMSG macro variable can contain
special characters (such as &, %, /, *, and ;), use the %SUPERQ macro
function when printing the following value: %put %superq(sqlxmsg);
%macro ins();
data _null_;
call symputx('currdatets',datetime());
call symputx('currdate',today());
call symputx('currtime',timepart(datetime()));
run;
%put currdatets> &currdatets. | currdate> &currdate. | currtime> &currtime.;
proc sql;
connect to db2;
insert into table (entrytime, rundate, runtime)
values (&currdatets,&currdate,&currtime);
disconnect from db2
;
quit;
%put %superq(sqlxmsg);
%put &sqlxrc. ;
%mend;
%ins();
currdatets> 1966238593.2 | currdate> 22757 | currtime> 33793.19107
I have the following code and I don’t know what SAS is doing here.
data have;
set folder.pst:;
if.....
run;
Now, there are several datasets that are named ”pst201812”, ”pst201901” and ”pst201902” in the libname called folder. Does the colon in the code above mean that ALL the datasets starting with pst are read by SAS? Or have I misunderstood?
You got it right. The Set Statement with the Colon operator reads all datasets that begin with pst in this case. See a small example below and read the "Using Data Set Lists with SET" section of the Set Statement Documentation.
data pst201812;a=1;run;
data pst201901;a=2;run;
data pst201902;a=3;run;
data want;
set work.pst:;
run;
I am using the following code to change the length of all character variables in my dataset. I am not sure why this loop is not working.
data test ;
set my.data;
array chars[*] _character_;
%do i = 1 %to dim(chars);
length chars[i] $ 30 ;
%end;
run;
You're mixing data step and macro commands, for one. %do is macro only, but the rest of that is data step only. You also need the length statement to be the first time the variable is encountered, not the set statement, as character lengths are not changeable after first encounter.
You either need to do this in the macro language, or do this with some other data-driven programming technique (as user667489 refers to some of). Here are two ways.
Macro based, using the open group of functions, which opens the dataset, counts how many variables there are, then iterates through those variables and calls the length statement for each (you could identically have one length, iterate through the variables, and one number). This is appropriate for a generic macro, but is probably more difficult to maintain.
%macro make_class_longer(varlength=);
data class;
%let did=%sysfunc(open(sashelp.class,i));
%let varcount=%sysfunc(attrn(&did,nvars));
%do _i = 1 %to &varcount;
%if %sysfunc(vartype(&did., &_i.))=C %then %do;
length %sysfunc(varname(&did.,&_i)) $&varlength.;
%end;
%end;
%let qid=%sysfunc(close(&did));
set sashelp.class;
run;
%mend make_class_longer;
%make_class_longer(varlength=30);
Similarly, here is a dictionary.columns solution. That queries the metadata directly and builds a list of character variables in a macro variable which is then used in a normal length statement. Easier to maintain, probably slower (but mostly meaninglessly so).
proc sql;
select name into :charlist separated by ' '
from dictionary.columns
where libname='SASHELP' and memname='CLASS' and type='char'
;
quit;
data class;
length &charlist. $30;
set sashelp.class;
run;
Length of variables is determined when a data step is compiled, so the first statement that mentions a variable usually determines its length. In your example, this is the set statement. Once fixed, a variable's length cannot be changed, unless you rebuild the whole dataset.
To get the result you want here, you would need to move your length statement above your set statement, and consequently you would also need to explicitly specify all the names of the variables whose lengths you want to set, as they would not otherwise exist yet at that point during compilation. You can do this either by hard-coding them or by generating code from sashelp.vcolumn / dictionary.columns.
There are a number of logical and syntactical errors in that code.
The main logical error is that you cannot change the length of a character variable after SAS has already determined what it should be. In your code it is determined when the SET statement is compiled.
Another logical error is using macro %DO loop inside a data step. Why?
Your example LENGTH statement is syntactically wrong. You cannot have an array reference in the LENGTH statement. Just the actual variable names. You could set the length in the ARRAY statement, if it was the first place the variables were defined. But you can't use the _character_ variable list then since for the variable list to find the variables the variables would have to already be defined. Which means it would be too late to change.
You will probably need to revert to a little code generation.
Let's make a sample dataset by using PROC IMPORT. We can use the SASHELP.CLASS example data for this.
filename csv temp;
proc export data=sashelp.class outfile=csv dbms=csv ;run;
proc import datafile=csv out=sample replace dbms=csv ;run;
Resulting variable list:
This is also a useful case as it will demonstrate one issue with changing the length of character variables. If you have assigned a FORMAT to the variable you could end up with the variable length not matching the format width.
Here is one way to dynamically generate code to change the length of the character variables without changing their relative position in the dataset. Basically this will read the metadata for the table and use it to generate a series of name/type+length pairs for each variable.
proc sql noprint ;
select varnum
, catx(' ',name,case when type='num' then put(length,1.) else '$30' end)
into :varlist
, :varlist separated by ' '
from dictionary.columns
where libname='WORK' and memname='SAMPLE'
order by 1
;
quit;
You can then use the generated list in a LENGTH statement to define the variables' type and length. You can also add in FORMAT and INFORMAT statements to remove the $xx. formats and informats that PROC IMPORT (mistakenly) adds to character variables.
data want ;
length &varlist ;
set sample;
format _character_ ;
informat _character_;
run;
I am very new using SAS and Im having hard time trying to assign the output value of a function-like macro to a macro variable. After testing, I have check that the value is computed correctly, but once I tried to assign it the program crashes. here you can find the code
%MACRO TP_BULLET(ZCURVE,TAU,YF=1);
/* KEEP ONLY THE ZERO CURVE UNTIL MATURITY*/
DATA _TEMP;
SET &ZCURVE;
IF MATURITY > &TAU THEN DELETE;
RUN;
PROC SQL NOPRINT;
SELECT DISTINCT 1- DF
INTO :NUME
FROM _TEMP
GROUP BY MATURITY
HAVING MATURITY = MAX(MATURITY);
QUIT;
PROC SQL NOPRINT;
SELECT SUM(DF)
INTO :DENO
FROM _TEMP;
QUIT;
PROC DELETE DATA=_TEMP;RUN;
%LET TP = %SYSEVALF(&YF*&NUME / &DENO);
&TP
%MEND TP_BULLET;
%MACRO TESTER2;
%LET K = %TP_BULLET(ZCURVE,TAU,YF=1);
%PUT .......&K;
%MEND TESTER2;
%TESTER2;
The error I am getting is the following
WARNING: Apparent symbolic reference DENO not resolved.
WARNING: Apparent symbolic reference NUME not resolved.
ERROR: A character operand was found in the %EVAL function or %IF condition where a numeric operand is required. The condition was:
1*&NUME / &DENO
So I suppose that the DATA Step is failing to create the sas table _TEMP, but I have no idea how to solve it. Thanks in advance for any help or sugestion
This is NOT a 'function like' macro - you are mixing SAS Macro language and Base SAS. Remember that the SAS Macro language is a code generator - and you are generating code, which is currently something like:
%let K=data _temp; set ZCURVE; ....
note how the above is not what you wanted to assign to the macro variable K.
To help with this, try running your macro with options mprint - this will show you the code being generated by your macro.
If you want your macro to act like a function, then (at a minimum) you should find NO code being generated via the mprint option..
All philosophical issues aside, you could add a parameter to your macro that specifies the new macrovariable (mv) that you want to create. So instead of
%Let k = %TP_BULLET(ZCURVE,TAU,YF=1);
you could call
%TP_BULLET(ZCURVE,TAU,mvOutput=k,YF=1);;
Your macro would need to be modified slightly with
%MACRO TP_BULLET(ZCURVE,TAU,mvOutput,YF=1);
%GLOBAL &mvOutput;
........ Same code as above .........
%Let &mvOutput = &TP; *Instead of final line with '&TP';
%MEND;
It is not a very SAS-y way to accomplish it, but it can help keep things more modular and comprehensible if you're working with more programming backgrounds, rather than SAS.
This is PROC SQL. Could anyone explain what I am getting as output ? Thanks !
proc sql;
select time into :date from end_date;
quit;
In addition to Chris J's answer, the INTO clause has a very versatile functionality. The following resources will give you very good overview.
Essentially using the INTO clause you can create a macro variable which holds a lists of items seperated by a custom delimiter, create a whole host of macro variables inside a single PROC SQL procedure - a task which could take multiple DATA _NULL_ steps & PROC SORT\MEANS\FREQ steps etc...
It is the PROC SQL equivalent of using %let date = <some time value>; or inside a datastep
DATA _NULL_;
set end_date;
call symputx("date", time);
RUN;
Using the Magical Keyword "INTO:" in PROC SQL
SAS(R) 9.2 Macro Language: Reference: INTO Clause
It simply puts the result into a macro variable, in this case the macro variable 'DATE' contains the time value off the record in the dataset end_date.