Let's say I stored a macro the following way :
options mstored sasmstore=FOO;
%macro hello_world() / STORE SOURCE DES='hello world';
%put hello world;
%mend;
Now I want to copy this macro from library FOO to library BAR, I would like something like (this obviously doesn't work):
%copy hello_world /source LIB = BAR;
This would be the equivalent of doing :
options mstored sasmstore=BAR;
%macro hello_world() / STORE SOURCE DES='hello world';
%put hello world;
%mend;
The goal is to conveniently copy macros from a development library to a production library. How can I do this ?
Proc CATALOG is used to copy entries from one member to another
%macro One;
%put ONE;
%mend;
proc catalog catalog=work.sasmacr;
copy out=work.holdmacr;
select one / et=macro;
run;
An alternative to copying entries is to use the concatenation feature that is provided automatically with LIBNAME.
From SAS Help
Example 3: Concatenating SAS Catalogs
This example concatenates three SAS libraries by specifying the
physical filename of each and assigns the libref ALLMINE to the
concatenated libraries:
libname allmine ('file-1' 'file-2' 'file-3');
If each library contains a SAS catalog named MYCAT, then using
ALLMINE.MYCAT as a libref.catref provides access to the catalog entries
that are stored in all three catalogs named MYCAT. To logically
concatenate SAS catalogs with different names, see the CATNAME
Statement.
If the same name catalogs contain same named entries, the entry in the earliest library is used.
This is really handy during unit tests as the original does not have to be altered until updates are proven to be non-damaging. You have integration unit tests right ? ;-)
If you just want to move the macro to production, PROC CATALOG is the correct way to do it.
*define foo;
libname foo "c:\temp";
*create the stored macro;
options mstored sasmstore=FOO;
%macro hello_world() / STORE SOURCE DES='hello world';
%put hello world;
%mend;
*define bar;
libname bar "c:\otherdir";
*proc catalog to copy it over;
PROC Catalog catalog = foo.sasmacr;
copy out=bar.sasmacr;
run;
***reset SAS session before continuing to clear libname foo***;
*now redirect SAS to BAR;
libname bar "c:\otherdir";
options mstored sasmstore=bar;
*and run hello_World- see it works!;
%hello_world;
Now, I probably wouldn't do it this way - I'd use git to store and deploy the source files - but if you prefer stored compiled macros, this is the best way.
Related
I want to attach a SAS dataset in my work folder to an email. But copying that directly from the "Explorer" pane in SAS 9.4 does not work. How do I get SAS to print the SAS work folder's actual location on disc?
Here are a couple ways to assign to macro variables.
proc options option=(work);
run;
%let workfolder=%sysfunc(pathname(WORK,L));
%put NOTE: &=workfolder;
%let %sysfunc(getoption(WORK,keyword));
%put NOTE: &=WORK;
The following prints the location of the work folder (usually multiple entries) from a SAS internal database.
proc sql;
select path from dictionary.libnames
where libname='WORK';
quit;
For my installation, this yields a location in C:\Users\USER_NAME\AppData\Local\Temp\SAS Temporary Files.
Context: We use SAS 9.4 and Enterprise Guide 7.15. Currently, we are implementing some new Macros and of course have to change a lot along the way. Sometimes smaller, sometimes bigger changes. The problem is, that in order to get the changes to work, SAS needs us to manually compile the Macro code or to restart the session which is a bit tedious.
This is the current setup in our main file (which calls all the Macros):
/* Macro options */
MAUTOSOURCE sasautos = "<path to macro>" mlogic mlogicnest mprint mprintnest MRECALL
Is it possible, while using the MAUTOSOURCE */ sasautos ="" option, to tell SAS every time the Macro is called to actually also compile the Macro instead of using the session-stored Macro? Ideally, the Macro would only be compiled when the whole line of code from the main file (MAUTOSOURCE */ sasautos ="" etc.) is executed, otherwise it should keep a compiled version in the session.
I found this paper (The Autocall Macro Facility in the SAS for Windows Environment) which states in the conclusion
After that SAS will use the code that has already
been compiled. If changes are made to the macro, it must be
compiled again before the changes go into effect.
which I hope doesn't mean that I have to do it manually. Is there any Macro option to set?
In SAS 9.3 they added the %SYSMACDELETE macro function. So if you just want to let autocall redefine a single macro then use that to remove the current definition.
%symacdelete mymacro;
Here is a utility macro that will query SASHELP.VCATALG view to find compiled macros in the WORK library and delete them. It has options to either list names of macros to delete or keep. Note that normal SAS sessions use WORK.SASMACR to store the compiled macros. But SAS/Studio and EG (and perhaps other ways of running SAS) use WORK.SASMAC1 instead.
https://github.com/sasutils/macros/blob/master/macdelete.sas
%macro macdelete(delete,keep);
/*----------------------------------------------------------------------------
Remove compiled macros using %SYSMACDELETE macro statement.
Use DELETE parameter to list macro names to delete.
Use KEEP parameter to list macro names to NOT delete.
Calling it with no values will delete all macros not currently running.
----------------------------------------------------------------------------*/
%local libname memname objname objtype fid i;
%do i=1 %to %sysmexecdepth;
%let keep=%sysmexecname(&i) &keep;
%end;
%if %length(&delete) %then %let delete=and findw("&delete",objname,',','sit');
%let fid=%sysfunc(open( sashelp.vcatalg(keep=libname memname objname objtype
where=(libname='WORK' and objtype='MACRO' and memname like 'SASMAC_'
and not findw("&keep",objname,',','sit') &delete))));
%if (&fid) %then %do;
%syscall set(fid);
%do %while(0=%sysfunc(fetch(&fid)));
%put %sysfunc(compbl(Removing &objname from &libname catalog &memname));
%sysmacdelete &objname;
%end;
%let fid=%sysfunc(close(&fid));
%end;
%else %put %qsysfunc(sysmsg());
%mend macdelete;
Example:
3348 %macro test1; %mend;
3349 %macro test2; %mend;
3350 %macro test3; %mend;
3351 %macro test4; %mend;
3352 %macdelete(test1 test3);
Removing TEST1 from WORK catalog SASMACR
Removing TEST3 from WORK catalog SASMACR
3353 %macdelete(keep=test2);
Removing TEST4 from WORK catalog SASMACR
Example when running SAS/Studio or Enterprise Guide:
97 %macro test1; %mend;
98 %macro test2; %mend;
99 %macro test3; %mend;
100 %macro test4; %mend;
101 %macdelete(test1 test3);
Removing TEST1 from WORK catalog SASMAC1
Removing TEST3 from WORK catalog SASMAC1
If you delete the compiled macro from WORK.SASMACR SAS will have to re-compile the macro when you call it again.
proc catalog c=work.sasmacr;
*contents;
delete your-macro-to-recompile.macro;
run;
quit;
For your normal users you should set a release schedule of changes. The users will know they need to re-start a new session after the release is made.
For your developers that are testing the changes as they are made they just need to use %INCLUDE to re-compile the macro. So if you know that macro XYZ is changed then just run:
%include maclib('xyz.sas');
Or you could brute force it and recompile all of the macros in your autocall library.
%incldue maclib('*.sas');
You could get fancier and make a macro that cleans out the actual catalog of compiled macros. Something like:
%macro clean_autocall;
proc catalog force c=work.sasmacr;
save clean_autocall /et=macro;
quit;
options mrecall mautosource;
%mend clean_autocall;
But if you are using Enterprise Guide there are two issues.
First for some reason it uses a different catalog to store the compiled macros. (Why?) I think it is WORK.SASMAC1 instead of WORK.SASMACR.
Second EG will manually compile a bunch of helper macros that it needs. I am not sure if there is an official source for the complete list of these macros? You could try adding code to your project to automatically create the list based on what entries are in the catalog when your project starts. Here is a list I made 10+ years ago when I tried using EG with production environment. But I am sure that it is out of date.
%let s_eg_save= checkfmt checkhotfix
eclibassign eclibunassign enterpriseguide gaccessible
_eg_conditional_dropds
;
I have numerous SAS datasets on my Windows 7 / SAS 9.4 machine:
data_19921.sas7bdat
data_19922.sas7bdat
data_19923.sas7bdat
....
data_200212.sas7bdat
etc. The format is data_YYYYM.sas7bdat or data_YYYYMM.sas7bdat (the latter for two digit months) and every dataset has identical variables and formatting. I'm trying to iterate over all of these files and append them into one big SAS dataset. The datasets were created on some Unix machine elsewhere in my company that I don't have access to. When I try to append the files:
%let root = C:\data;
libname in "&raw\in";
libname out "&raw\out";
/*****************************************************************************/
* initialize final data set but don't add any observations to it;
data out.alldata;
set in.data_19921;
stop;
run;
%macro append_files;
%do year=1992 %to 2002;
%do month=1 %to 12;
proc append data=out.alldata base=in.data_&year&month;
run;
%end;
%end;
%mend;
%append_files;
I get errors that say
File in.data_19921 cannot be updated because its encoding does not match the session encoding or the file is in a format native to another host, such as LINUX_32, INTEL_ABI
I don't have access to the Unix machine that created these datasets (or any Unix machine right now), so I don't think I can use proc cport and proc cimport.
How can I read/append these data sets on my Windows machine?
You can use the colon operator or dash to shortcut your process:
data out.alldata;
set in.data_:;
run;
OR
data out.alldata;
set in.data19921-in.data200212;
run;
If you have variables that are different lengths you may get truncation. SAS will warn you though:
WARNING: Multiple lengths were specified for the variable name by input data set(s). This may
cause truncation of data.
One way to avoid it is to create a false table first, using the original table. The following SQL code will generate the CREATE Table statements and you can modify the lengths manually as needed.
proc sql;
describe table in.data19921;
quit;
*run create table statement here with modified lengths;
data out.alldata;
set fake_data (obs=0)
in.data19921-in.data200212;
run;
By default it is defined to store macros at WORK.SASMACR.
However at my site location for macros storage is different and I do not know where it is.
Can I find the default macros catalog and view what is inside?
Please see below effects of system options SASMSTORE + MSTORED and STORE option in macro definition on location of the compiled macros - could be something similar was used on your site.
option sasmstore=sasuser mstored;
%macro _mstore /store;
%put This is macro with mstore;
%mend;
%macro _nomstore;
%put This is macro without mstore;
%mend;
proc options option=sasmstore;
run;
Use DICTIONARY.CATALOGS to list macros compiled in your session.
proc sql;
create table macros as
select * from dictionary.catalogs where objtype='MACRO';
quit;
You have three option to find macros `
1.
proc catalog catalog=work.SASMACR;
contents;
run;
2.
proc catalog catalog=work.SASMAC1;
contents;
run;
3.
proc sql;
create table macros as
select * from dictionary.catalogs where objtype='MACRO';
quit;
I may or may not have a library named qa.my_library
I have a temp library (same columns, different data) in work.my_library_temp
My goal is to achieve this pseudo code
if qa.my_library doesn't exists
then qa.my_library = work.my_library_temp;
else qa.my_library = SQL UNION (qa.my_library, work.my_library_temp)
How would you write the code for it ?
To determine if a LIBRARY exists use the LIBREF function. To determine if a DATA SET exists use the EXIST function.
Here is some sample code:
%Global LIBEXISTS DSNEXISTS;
Options source source2 notes symbolgen mlogic mprint ;
%Macro Check(Lib=,DSN=);
%Let LIBEXISTS=0;
/* Outside the DATA STEP, use %SYSFUNC */
%IF %SYSFUNC(LIBREF(&LIB)) = 0 %THEN %Let LIBEXISTS=1; ;
%Let DSNEXISTS=0;
%IF %SYSFUNC(EXIST(&DSN)) > 0 %THEN %Let DSNEXISTS=1; ;
%Mend;
libname test 'c:\';
Data Test.Temp;
Test="Test";
Run;
%Check(Lib=test,dsn=test.temp) ;
%Put LIB EXISTS? &libexists;
%Put DSN EXISTS? &dsnexists;
libname test clear;
%Check(Lib=test,dsn=test.temp) ;
%Put LIB EXISTS? &libexists;
%Put DSN EXISTS? &dsnexists;
One of the reasons for the concept of libraries is to avoid name collisions. What you ask would be unable to deal with such collision (i.e., a dataset with the same name in both libraries). If you can be certain that there will be no collision, than there is actually no reason for what you want: there is no fundamental difference between remembering (and parsing) a unique dataset name or a unique dataset name with its library.
Leaves only the situation where there can be collisions, but you have one library who has priority then. You can write something that gets somewhat close to what you want:
Using SASHELP library, identify every dataset in library A and B. Assuming that A takes priority: for every dataset in B which is not in A, run the following:
data A.dataset_from_B /view=A.dataset_from_B;
set B.dataset_from_B;
run;
Again, it is not exactly the same, but probably serves your needs. But I would be really interested in knowing why you want this though.