diff --git a/all.sas b/all.sas index 36c6a50..72d469c 100644 --- a/all.sas +++ b/all.sas @@ -1,29707 +1,29707 @@ - -/** - @file - @brief Auto-generated file - @details - This file contains all the macros in a single file - which means it can be - 'included' in SAS with just 2 lines of code: - - filename mc url - "https://raw.githubusercontent.com/sasjs/core/main/all.sas"; - %inc mc; - - The `build.py` file in the https://github.com/sasjs/core repo - is used to create this file. - - @author Allan Bowe -**/ -options noquotelenmax; -/** - @file - @brief Abort, ungracefully - @details Will abort with a straightforward %abort if the condition is true. - - @param [in] mac= (mf_abort.sas) Name of calling macro (is printed to the log) - @param [in] msg= ( ) Additional string to print to the log - @param [in] iftrue= (%str(1=1)) Conditional logic under which to perform the - abort - -
- &sysuserid or SYS_COMPUTE_SESSION_OWNER if it exists.
- In a Stored Process session, &sysuserid
- resolves to a system account (default=sassrv) and instead there are several
- metadata username variables to choose from (_metauser, _metaperson
- ,_username, _secureusername). The OS account is represented by
- _secureusername whilst the metadata account is under
- _metaperson.
-
- %let user= %mf_getUser();
- %put &user;
-
- @return SYSUSERID (if workspace server)
- @return _METAPERSON (if stored process server)
- @return SYS_COMPUTE_SESSION_OWNER (if Viya compute session)
-
- @version 9.2
- @author Allan Bowe
-**/
-
-%macro mf_getuser(
-)/*/STORE SOURCE*/;
- %local user;
-
- %if %symexist(_sasjs_username) %then %let user=&_sasjs_username;
- %else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;
- %let user=&SYS_COMPUTE_SESSION_OWNER;
- %end;
- %else %if %symexist(_metaperson) %then %do;
- %if %length(&_metaperson)=0 %then %let user=&sysuserid;
- /* sometimes SAS will add @domain extension - remove for consistency */
- /* but be sure to quote in case of usernames with commas */
- %else %let user=%unquote(%scan(%quote(&_metaperson),1,@));
- %end;
- %else %let user=&sysuserid;
-
- %quote(&user)
-
-%mend mf_getuser;
-/**
- @file
- @brief Retrieves a value from a dataset. If no filter supplied, then first
- record is used.
- @details Be sure to %quote() your where clause. Example usage:
-
- %put %mf_getvalue(sashelp.class,name,filter=%quote(age=15));
- %put %mf_getvalue(sashelp.class,name);
-
- %mf_getattrn().
-
- %put Number of observations=%mf_nobs(sashelp.class);
-
- getattrs= is used.
-
- maxobs
- observations, enter an integer here.
- @param [in] random_sample= (NO) Set to YES to generate a random sample of
- data. Can be quite slow.
- @param [in] showlog= (YES) Whether to show generated cards file in the SAS
- log. Valid values:
- @li YES
- @li NO
- @param [in] outencoding= Provide encoding value for file statement (eg utf-8)
- @param [in] append= (NO) If NO then will rebuild the cards file if it already
- exists, otherwise will append to it. Used by the mp_lib2cards.sas macro.
-
- %mp_ds2cards() macro.
- Usage:
-
- %mp_lib2cards(lib=sashelp
- , outloc= C:\temp )
-
- The output will be one cards file in the `outloc` directory per dataset in the
- input `lib` library. If the `outloc` directory does not exist, it is created.
-
- To create a single SAS file with the first 1000 records of each table in a
- library you could use this syntax:
-
- %mp_lib2cards(lib=sashelp
- , outloc= /tmp
- , outfile= myfile.sas
- , maxobs= 1000
- )
-
- maxobs rows
-
- @version 9.2
- @author Allan Bowe
-**/
-
-%macro mp_lib2cards(lib=
- ,outloc=%sysfunc(pathname(work))
- ,maxobs=max
- ,random_sample=NO
- ,outfile=0
-)/*/STORE SOURCE*/;
-
-/* Find the tables */
-%local x ds memlist;
-proc sql noprint;
-select distinct lowcase(memname)
- into: memlist
- separated by ' '
- from dictionary.tables
- where upcase(libname)="%upcase(&lib)";
-
-/* trim trailing slash, if provided */
-%let outloc=%mf_trimstr(&outloc,/);
-%let outloc=%mf_trimstr(&outloc,\);
-
-/* create the output directory */
-%mf_mkdir(&outloc)
-
-/* create the cards files */
-%do x=1 %to %sysfunc(countw(&memlist));
- %let ds=%scan(&memlist,&x);
- %mp_ds2cards(base_ds=&lib..&ds
- ,maxobs=&maxobs
- ,random_sample=&random_sample
- %if "&outfile" ne "0" %then %do;
- ,append=YES
- ,cards_file="&outloc/&outfile"
- %end;
- %else %do;
- ,append=NO
- ,cards_file="&outloc/&ds..sas"
- %end;
- )
-%end;
-
-%mend mp_lib2cards;/**
- @file
- @brief Convert all data in a library to SQL insert statements
- @details Gets list of members then calls the %mp_ds2inserts()
- macro.
- Usage:
-
- %mp_getddl(sashelp, schema=work, fref=tempref)
-
- %mp_lib2inserts(sashelp, schema=work, outref=tempref)
-
- %inc tempref;
-
-
- The output will be one file in the outref fileref.
-
-
- work.mp_perflog;
-
- @version 9.2
- @author Allan Bowe
- @source https://github.com/sasjs/core
-
-**/
-
-%macro mp_perflog(label,libds=work.mp_perflog
-)/*/STORE SOURCE*/;
-
- %if not (%mf_existds(&libds)) %then %do;
- data &libds;
- length sysjobid $10 label $256 dttm 8.;
- format dttm datetime19.3;
- call missing(of _all_);
- stop;
- run;
- %end;
-
- proc sql;
- insert into &libds
- set sysjobid="&sysjobid"
- ,label=symget('label')
- ,dttm=%sysfunc(datetime());
- quit;
-
-%mend mp_perflog;/**
- @file
- @brief Enables previous observations to be re-instated
- @details Remembers the last X observations by storing them in a hash table.
- Is a convenience over the use of lag() or retain, when an entire observation
- needs to be restored.
-
- This macro will also restore automatic variables (such as _n_ and _error_).
-
- Example Usage:
-
- data example;
- set sashelp.class;
- calc_var=_n_*3;
- %* initialise hash and save from PDV ;
- %mp_prevobs(INIT,history=2)
- if _n_ =10 then do;
- %* fetch previous but 1 record;
- %mp_prevobs(FETCH,-2)
- put _n_= name= age= calc_var=;
- %* fetch previous record;
- %mp_prevobs(FETCH,-1)
- put _n_= name= age= calc_var=;
- %* reinstate current record ;
- %mp_prevobs(FETCH,0)
- put _n_= name= age= calc_var=;
- end;
- run;
-
- Result:
-
-
-
- Credit is made to `data _null_` for authoring this very helpful paper:
- https://www.lexjansen.com/pharmasug/2008/cc/CC08.pdf
-
- @param [in] action Either FETCH a current or previous record, or INITialise.
- @param [in] record The relative (to current) position of the previous row
- to return.
- @param [in] history= (5) The number of records to retain in the hash table.
- @param [in] prefix= (mp_prevobs) The prefix to give to the variables used to
- store the hash name and index.
-
- @version 9.2
- @author Allan Bowe
-
-**/
-
-%macro mp_prevobs(action,record,history=5,prefix=mp_prevobs
-)/*/STORE SOURCE*/;
-%let action=%upcase(&action);
-%let prefix=%upcase(&prefix);
-%let record=%eval((&record+0) * -1);
-
-%if &action=INIT %then %do;
-
- if _n_ eq 1 then do;
- attrib &prefix._VAR length=$64;
- dcl hash &prefix._HASH(ordered:'Y');
- &prefix._KEY=0;
- &prefix._HASH.defineKey("&prefix._KEY");
- do while(1);
- call vnext(&prefix._VAR);
- if &prefix._VAR='' then leave;
- if &prefix._VAR eq "&prefix._VAR" then continue;
- else if &prefix._VAR eq "&prefix._KEY" then continue;
- &prefix._HASH.defineData(&prefix._VAR);
- end;
- &prefix._HASH.defineDone();
- end;
- /* this part has to happen before FETCHing */
- &prefix._KEY+1;
- &prefix._rc=&prefix._HASH.add();
- if &prefix._rc then putlog 'adding' &prefix._rc=;
- %if &history>0 %then %do;
- if &prefix._key>&history+1 then
- &prefix._HASH.remove(key: &prefix._KEY - &history - 1);
- if &prefix._rc then putlog 'removing' &prefix._rc=;
- %end;
-%end;
-%else %if &action=FETCH %then %do;
- if &record>&prefix._key then putlog "Not enough records in &Prefix._hash yet";
- else &prefix._rc=&prefix._HASH.find(key: &prefix._KEY - &record);
- if &prefix._rc then putlog &prefix._rc= " when fetching " &prefix._KEY=
- "with record &record and " _n_=;
-%end;
-
-%mend mp_prevobs;/**
- @file
- @brief Returns all children from a hierarchy table for a specified parent
- @details Where data stores hierarchies in a simple parent / child mapping,
- it is not always straightforward to extract all the children for a
- particular parent. This problem is known as a recursive self join. This
- macro will extract all the descendents for a parent.
- Usage:
-
- data have;
- p=1;c=2;output;
- p=2;c=3;output;
- p=2;c=4;output;
- p=3;c=5;output;
- p=6;c=7;output;
- p=8;c=9;output;
- run;
-
- %mp_recursivejoin(base_ds=have
- ,outds=want
- ,matchval=1
- ,parentvar=p
- ,childvar=c
- )
-
- @param [in] base_ds= base table containing hierarchy (not modified)
- @param [out] outds= the output dataset to create with the generated hierarchy
- @param [in] matchval= the ultimate parent from which to filter
- @param [in] parentvar= name of the parent variable
- @param [in] childvar= () name of the child variable (should be same type as
- parent)
- @param [in] mdebug= set to 1 to prevent temp tables being dropped
-
-
- @returns outds contains the following variables:
- - level (0 = top level)
- - &parentvar
- - &childvar (null if none found)
-
- @version 9.2
- @author Allan Bowe
-
-**/
-
-%macro mp_recursivejoin(base_ds=
- ,outds=
- ,matchval=
- ,parentvar=
- ,childvar=
- ,iter= /* reserved for internal / recursive use by the macro itself */
- ,maxiter=500 /* avoid infinite loop */
- ,mDebug=0);
-
-%if &iter= %then %do;
- proc sql;
- create table &outds as
- select 0 as level,&parentvar, &childvar
- from &base_ds
- where &parentvar=&matchval;
- %if &sqlobs.=0 %then %do;
- %put NOTE: &sysmacroname: No match for &parentvar=&matchval;
- %return;
- %end;
- %let iter=1;
-%end;
-%else %if &iter>&maxiter %then %return;
-
-proc sql;
-create table _data_ as
- select &iter as level
- ,curr.&childvar as &parentvar
- ,base_ds.&childvar as &childvar
- from &outds curr
- left join &base_ds base_ds
- on curr.&childvar=base_ds.&parentvar
- where curr.level=%eval(&iter.-1)
- & curr.&childvar is not null;
-%local append_ds; %let append_ds=&syslast;
-%local obs; %let obs=&sqlobs;
-insert into &outds select distinct * from &append_ds;
-%if &mdebug=0 %then drop table &append_ds;;
-
-%if &obs %then %do;
- %mp_recursivejoin(iter=%eval(&iter.+1)
- ,outds=&outds,parentvar=&parentvar
- ,childvar=&childvar
- ,base_ds=&base_ds
- )
-%end;
-
-%mend mp_recursivejoin;
-/**
- @file
- @brief Performs a text substitution on a file
- @details Performs a find and replace on a file, either in place or to a new
- file. Can be used on files where lines are longer than 32767.
-
- Works by reading in the file byte by byte, then marking the beginning and end
- of each matched string, before finally doing the replace.
-
- Full credit for this highly efficient and syntactically satisfying SAS logic
- goes to [Bartosz Jabłoński](https://www.linkedin.com/in/yabwon), founder of
- the [SAS Packages](https://github.com/yabwon/SAS_PACKAGES) framework.
-
- Usage:
-
- %let file="%sysfunc(pathname(work))/file.txt";
- %let str=replace/me;
- %let rep=with/this;
- data _null_;
- file &file;
- put 'blahblah';
- put "blahblah&str.blah";
- put 'blahblahblah';
- run;
- %mp_replace(&file, findvar=str, replacevar=rep)
- data _null_;
- infile &file;
- input;
- list;
- run;
-
- Note - if you are running a version of SAS that will allow the io package in
- LUA, you can also use this macro: mp_gsubfile.sas
-
- @param [in] infile The QUOTED path to the file on which to perform the
- substitution
- @param [in] findvar= Macro variable NAME containing the string to search for
- @param [in] replacevar= Macro variable NAME containing the replacement string
- @param [out] outfile= (0) Optional QUOTED path to the adjusted output file (to
- avoid overwriting the first file).
-
- | Transaction Type | Key Behaviour | Column Behaviour | -
|---|---|---|
| Deletes | -- The row is added to `&outDEL.` UNLESS it no longer exists - in the base table, in which case it is added to `&errDS.` instead. - | -- Deletes are unaffected by the addition or removal of non Primary-Key - columns. - | -
| Inserts | -
- Previously newly added rows are added to the `outADD` table UNLESS they
- are present in the Base table. In this case they are added to the - `&errDS.` table instead. - |
- - Inserts are unaffected by the addition of columns in the Base Table - (they are padded with blanks). Deleted columns are only a problem if - they appear on the previous insert - in which case the record is added - to `&errDS.`. - | -
| Updates | -
- Previously modified rows are merged with base table values such that
- only the individual cells that were _previously_ changed are re-applied.
- Where the row contains cells that were not marked as having changed in
- the prior transaction, the 'blanks' are filled with base table values in
- the `outMOD` table. - If the row no longer exists on the base table, then the row is added to - the `errDS` table instead. - |
- - Updates are unaffected by the addition of columns in the Base Table - - the new cells are simply populated with Base Table values. Deleted - columns are only a problem if they relate to a modified cell - (`is_diff=1`) - in which case the record is added to `&errDS.`. - | -
tree= parameter.
- @param [out] outds= the dataset to create that contains the list of stps.
- @param [in] mDebug= set to 1 to show debug messages in the log
- @param [in] showDesc= provide a non blank value to return stored process
- descriptions
- @param [in] showUsageVersion= ()
- Provide a non blank value to return the UsageVersion.
- This is either 1000000 (type 1, 9.2) or 2000000 (type2, 9.3 onwards).
-
- @returns outds dataset containing the following columns
- - stpuri
- - stpname
- - treeuri
- - stpdesc (if requested)
- - usageversion (if requested)
-
- @version 9.2
- @author Allan Bowe
-
-**/
-
-%macro mm_getstps(
- tree=
- ,name=
- ,outds=work.mm_getstps
- ,mDebug=0
- ,showDesc=
- ,showUsageVersion=
-)/*/STORE SOURCE*/;
-
-%local mD;
-%if &mDebug=1 %then %let mD=;
-%else %let mD=%str(*);
-%&mD.put Executing mm_getstps.sas;
-%&mD.put _local_;
-
-data &outds;
- length stpuri stpname usageversion treeuri stpdesc $256;
- call missing (of _all_);
-run;
-
-%if %length(&tree)>0 %then %do;
- /* get tree info */
- %mm_gettree(tree=&tree,inds=&outds, outds=&outds, mDebug=&mDebug)
- %if %mf_nobs(&outds)=0 %then %do;
- %put NOTE: Tree &tree did not exist!!;
- %return;
- %end;
-%end;
-
-
-data &outds ;
- set &outds(rename=(treeuri=treeuri_compare));
- length treeuri query stpuri $256;
- i+1;
-%if %length(&name)>0 %then %do;
- query="omsobj:ClassifierMap?@PublicType='StoredProcess' and @Name='&name'";
- putlog query=;
-%end;
-%else %do;
- query="omsobj:ClassifierMap?@PublicType='StoredProcess'";
-%end;
-%if &mDebug=1 %then %do;
- putlog 'start' (_all_)(=);
-%end;
- do while(0&outds) containing both the path
- and uri.
-
- Usage:
-
- %mm_getTree(tree=/User Folders/sasdemo)
-
-
- @param [in] tree= the BIP Tree folder path or uri
- @param [out] outds= the dataset to create that contains the tree path & uri
- @param [in] inds= an optional input dataset to augment with treepath & treeuri
- @param [in] mDebug= set to 1 to show debug messages in the log
-
- @returns outds dataset containing the following columns:
- - treeuri
- - treepath
-
- @version 9.2
- @author Allan Bowe
-
-**/
-
-%macro mm_getTree(
- tree=
- ,inds=
- ,outds=work.mm_getTree
- ,mDebug=0
-)/*/STORE SOURCE*/;
-
-%local mD;
-%if &mDebug=1 %then %let mD=;
-%else %let mD=%str(*);
-%&mD.put Executing mm_getTree.sas;
-%&mD.put _local_;
-
-data &outds;
- length treeuri __parenturi __type __name $256 treepath $512;
-%if %length(&inds)>0 %then %do;
- set &inds;
-%end;
- __rc1=metadata_resolve("&tree",__type,treeuri);
-
- if __type='Tree' then do;
- __rc2=metadata_getattr(treeuri,"Name",__name);
- treepath=cats('/',__name);
- /* get parents */
- do while (metadata_getnasn(treeuri,"ParentTree",1,__parenturi)>0);
- __rc3=metadata_getattr(__parenturi,"Name",__name);
- treepath=cats('/',__name,treepath);
- treeuri=__parenturi;
- end;
- treeuri="&tree";
- end;
- else do;
- __rc2=metadata_pathobj(' ',"&tree",'Folder',__type,treeuri);
- treepath="&tree";
- end;
-
- &mD.put (_all_)(=);
- drop __:;
- if treeuri ne "" and treepath ne "" then output;
- stop;
-run;
-%mend mm_getTree;/**
- @file
- @brief Creates a dataset with all metadata types
- @details Usage:
-
- %mm_gettypes(outds=types)
-
- @param [in] outds= (work.mm_gettypes)
- The dataset to create that contains the list of types
- @returns outds dataset containing all types
- @warning The following filenames are created and then de-assigned:
-
- filename sxlemap clear;
- filename response clear;
- libname _XML_ clear;
-
- @version 9.2
- @author Allan Bowe
-
-**/
-
-%macro mm_gettypes(
- outds=work.mm_gettypes
-)/*/STORE SOURCE*/;
-
-* use a temporary fileref to hold the response;
-filename response temp;
-/* get list of libraries */
-proc metadata in=
- '
- %* compile macros ;
- filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
- %inc mc;
-
- %* parmcards lets us write to a text file from open code ;
- filename ft15f001 temp;
- parmcards4;
- %webout(FETCH)
- %* do some sas, any inputs are now already WORK tables;
- data example1 example2;
- set sashelp.class;
- run;
- %* send data back;
- %webout(OPEN)
- %webout(ARR,example1) * Array format, fast, suitable for large tables ;
- %webout(OBJ,example2) * Object format, easier to work with ;
- %webout(CLOSE)
- ;;;;
- %ms_createwebservice(path=/Public/app/common,name=appInit,code=ft15f001)
-
-
-
- For more examples of using these web services with the SASjs Adapter, see:
- https://github.com/sasjs/adapter#readme
-
- @param [in] path= (0) The full SASjs Drive path in which to create the service
- @param [in] name= Stored Program name
- @param [in] desc= The description of the service (not implemented yet)
- @param [in] precode= Space separated list of filerefs, pointing to the code
- that needs to be attached to the beginning of the service (optional)
- @param [in] code= (ft15f001) Space seperated fileref(s) of the actual code to
- be added
- @param [in] mDebug= (0) set to 1 to show debug messages in the log
-
-
+ &sysuserid or SYS_COMPUTE_SESSION_OWNER if it exists.
+ In a Stored Process session, &sysuserid
+ resolves to a system account (default=sassrv) and instead there are several
+ metadata username variables to choose from (_metauser, _metaperson
+ ,_username, _secureusername). The OS account is represented by
+ _secureusername whilst the metadata account is under
+ _metaperson.
+
+ %let user= %mf_getUser();
+ %put &user;
+
+ @return SYSUSERID (if workspace server)
+ @return _METAPERSON (if stored process server)
+ @return SYS_COMPUTE_SESSION_OWNER (if Viya compute session)
+
+ @version 9.2
+ @author Allan Bowe
+**/
+
+%macro mf_getuser(
+)/*/STORE SOURCE*/;
+ %local user;
+
+ %if %symexist(_sasjs_username) %then %let user=&_sasjs_username;
+ %else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;
+ %let user=&SYS_COMPUTE_SESSION_OWNER;
+ %end;
+ %else %if %symexist(_metaperson) %then %do;
+ %if %length(&_metaperson)=0 %then %let user=&sysuserid;
+ /* sometimes SAS will add @domain extension - remove for consistency */
+ /* but be sure to quote in case of usernames with commas */
+ %else %let user=%unquote(%scan(%quote(&_metaperson),1,@));
+ %end;
+ %else %let user=&sysuserid;
+
+ %quote(&user)
+
+%mend mf_getuser;
+/**
+ @file
+ @brief Retrieves a value from a dataset. If no filter supplied, then first
+ record is used.
+ @details Be sure to %quote() your where clause. Example usage:
+
+ %put %mf_getvalue(sashelp.class,name,filter=%quote(age=15));
+ %put %mf_getvalue(sashelp.class,name);
+
+ %mf_getattrn().
+
+ %put Number of observations=%mf_nobs(sashelp.class);
+
+ getattrs= is used.
+
+ maxobs
+ observations, enter an integer here.
+ @param [in] random_sample= (NO) Set to YES to generate a random sample of
+ data. Can be quite slow.
+ @param [in] showlog= (YES) Whether to show generated cards file in the SAS
+ log. Valid values:
+ @li YES
+ @li NO
+ @param [in] outencoding= Provide encoding value for file statement (eg utf-8)
+ @param [in] append= (NO) If NO then will rebuild the cards file if it already
+ exists, otherwise will append to it. Used by the mp_lib2cards.sas macro.
+
+ %mp_ds2cards() macro.
+ Usage:
+
+ %mp_lib2cards(lib=sashelp
+ , outloc= C:\temp )
+
+ The output will be one cards file in the `outloc` directory per dataset in the
+ input `lib` library. If the `outloc` directory does not exist, it is created.
+
+ To create a single SAS file with the first 1000 records of each table in a
+ library you could use this syntax:
+
+ %mp_lib2cards(lib=sashelp
+ , outloc= /tmp
+ , outfile= myfile.sas
+ , maxobs= 1000
+ )
+
+ maxobs rows
+
+ @version 9.2
+ @author Allan Bowe
+**/
+
+%macro mp_lib2cards(lib=
+ ,outloc=%sysfunc(pathname(work))
+ ,maxobs=max
+ ,random_sample=NO
+ ,outfile=0
+)/*/STORE SOURCE*/;
+
+/* Find the tables */
+%local x ds memlist;
+proc sql noprint;
+select distinct lowcase(memname)
+ into: memlist
+ separated by ' '
+ from dictionary.tables
+ where upcase(libname)="%upcase(&lib)";
+
+/* trim trailing slash, if provided */
+%let outloc=%mf_trimstr(&outloc,/);
+%let outloc=%mf_trimstr(&outloc,\);
+
+/* create the output directory */
+%mf_mkdir(&outloc)
+
+/* create the cards files */
+%do x=1 %to %sysfunc(countw(&memlist));
+ %let ds=%scan(&memlist,&x);
+ %mp_ds2cards(base_ds=&lib..&ds
+ ,maxobs=&maxobs
+ ,random_sample=&random_sample
+ %if "&outfile" ne "0" %then %do;
+ ,append=YES
+ ,cards_file="&outloc/&outfile"
+ %end;
+ %else %do;
+ ,append=NO
+ ,cards_file="&outloc/&ds..sas"
+ %end;
+ )
+%end;
+
+%mend mp_lib2cards;/**
+ @file
+ @brief Convert all data in a library to SQL insert statements
+ @details Gets list of members then calls the %mp_ds2inserts()
+ macro.
+ Usage:
+
+ %mp_getddl(sashelp, schema=work, fref=tempref)
+
+ %mp_lib2inserts(sashelp, schema=work, outref=tempref)
+
+ %inc tempref;
+
+
+ The output will be one file in the outref fileref.
+
+
+ work.mp_perflog;
+
+ @version 9.2
+ @author Allan Bowe
+ @source https://github.com/sasjs/core
+
+**/
+
+%macro mp_perflog(label,libds=work.mp_perflog
+)/*/STORE SOURCE*/;
+
+ %if not (%mf_existds(&libds)) %then %do;
+ data &libds;
+ length sysjobid $10 label $256 dttm 8.;
+ format dttm datetime19.3;
+ call missing(of _all_);
+ stop;
+ run;
+ %end;
+
+ proc sql;
+ insert into &libds
+ set sysjobid="&sysjobid"
+ ,label=symget('label')
+ ,dttm=%sysfunc(datetime());
+ quit;
+
+%mend mp_perflog;/**
+ @file
+ @brief Enables previous observations to be re-instated
+ @details Remembers the last X observations by storing them in a hash table.
+ Is a convenience over the use of lag() or retain, when an entire observation
+ needs to be restored.
+
+ This macro will also restore automatic variables (such as _n_ and _error_).
+
+ Example Usage:
+
+ data example;
+ set sashelp.class;
+ calc_var=_n_*3;
+ %* initialise hash and save from PDV ;
+ %mp_prevobs(INIT,history=2)
+ if _n_ =10 then do;
+ %* fetch previous but 1 record;
+ %mp_prevobs(FETCH,-2)
+ put _n_= name= age= calc_var=;
+ %* fetch previous record;
+ %mp_prevobs(FETCH,-1)
+ put _n_= name= age= calc_var=;
+ %* reinstate current record ;
+ %mp_prevobs(FETCH,0)
+ put _n_= name= age= calc_var=;
+ end;
+ run;
+
+ Result:
+
+
+
+ Credit is made to `data _null_` for authoring this very helpful paper:
+ https://www.lexjansen.com/pharmasug/2008/cc/CC08.pdf
+
+ @param [in] action Either FETCH a current or previous record, or INITialise.
+ @param [in] record The relative (to current) position of the previous row
+ to return.
+ @param [in] history= (5) The number of records to retain in the hash table.
+ @param [in] prefix= (mp_prevobs) The prefix to give to the variables used to
+ store the hash name and index.
+
+ @version 9.2
+ @author Allan Bowe
+
+**/
+
+%macro mp_prevobs(action,record,history=5,prefix=mp_prevobs
+)/*/STORE SOURCE*/;
+%let action=%upcase(&action);
+%let prefix=%upcase(&prefix);
+%let record=%eval((&record+0) * -1);
+
+%if &action=INIT %then %do;
+
+ if _n_ eq 1 then do;
+ attrib &prefix._VAR length=$64;
+ dcl hash &prefix._HASH(ordered:'Y');
+ &prefix._KEY=0;
+ &prefix._HASH.defineKey("&prefix._KEY");
+ do while(1);
+ call vnext(&prefix._VAR);
+ if &prefix._VAR='' then leave;
+ if &prefix._VAR eq "&prefix._VAR" then continue;
+ else if &prefix._VAR eq "&prefix._KEY" then continue;
+ &prefix._HASH.defineData(&prefix._VAR);
+ end;
+ &prefix._HASH.defineDone();
+ end;
+ /* this part has to happen before FETCHing */
+ &prefix._KEY+1;
+ &prefix._rc=&prefix._HASH.add();
+ if &prefix._rc then putlog 'adding' &prefix._rc=;
+ %if &history>0 %then %do;
+ if &prefix._key>&history+1 then
+ &prefix._HASH.remove(key: &prefix._KEY - &history - 1);
+ if &prefix._rc then putlog 'removing' &prefix._rc=;
+ %end;
+%end;
+%else %if &action=FETCH %then %do;
+ if &record>&prefix._key then putlog "Not enough records in &Prefix._hash yet";
+ else &prefix._rc=&prefix._HASH.find(key: &prefix._KEY - &record);
+ if &prefix._rc then putlog &prefix._rc= " when fetching " &prefix._KEY=
+ "with record &record and " _n_=;
+%end;
+
+%mend mp_prevobs;/**
+ @file
+ @brief Returns all children from a hierarchy table for a specified parent
+ @details Where data stores hierarchies in a simple parent / child mapping,
+ it is not always straightforward to extract all the children for a
+ particular parent. This problem is known as a recursive self join. This
+ macro will extract all the descendents for a parent.
+ Usage:
+
+ data have;
+ p=1;c=2;output;
+ p=2;c=3;output;
+ p=2;c=4;output;
+ p=3;c=5;output;
+ p=6;c=7;output;
+ p=8;c=9;output;
+ run;
+
+ %mp_recursivejoin(base_ds=have
+ ,outds=want
+ ,matchval=1
+ ,parentvar=p
+ ,childvar=c
+ )
+
+ @param [in] base_ds= base table containing hierarchy (not modified)
+ @param [out] outds= the output dataset to create with the generated hierarchy
+ @param [in] matchval= the ultimate parent from which to filter
+ @param [in] parentvar= name of the parent variable
+ @param [in] childvar= () name of the child variable (should be same type as
+ parent)
+ @param [in] mdebug= set to 1 to prevent temp tables being dropped
+
+
+ @returns outds contains the following variables:
+ - level (0 = top level)
+ - &parentvar
+ - &childvar (null if none found)
+
+ @version 9.2
+ @author Allan Bowe
+
+**/
+
+%macro mp_recursivejoin(base_ds=
+ ,outds=
+ ,matchval=
+ ,parentvar=
+ ,childvar=
+ ,iter= /* reserved for internal / recursive use by the macro itself */
+ ,maxiter=500 /* avoid infinite loop */
+ ,mDebug=0);
+
+%if &iter= %then %do;
+ proc sql;
+ create table &outds as
+ select 0 as level,&parentvar, &childvar
+ from &base_ds
+ where &parentvar=&matchval;
+ %if &sqlobs.=0 %then %do;
+ %put NOTE: &sysmacroname: No match for &parentvar=&matchval;
+ %return;
+ %end;
+ %let iter=1;
+%end;
+%else %if &iter>&maxiter %then %return;
+
+proc sql;
+create table _data_ as
+ select &iter as level
+ ,curr.&childvar as &parentvar
+ ,base_ds.&childvar as &childvar
+ from &outds curr
+ left join &base_ds base_ds
+ on curr.&childvar=base_ds.&parentvar
+ where curr.level=%eval(&iter.-1)
+ & curr.&childvar is not null;
+%local append_ds; %let append_ds=&syslast;
+%local obs; %let obs=&sqlobs;
+insert into &outds select distinct * from &append_ds;
+%if &mdebug=0 %then drop table &append_ds;;
+
+%if &obs %then %do;
+ %mp_recursivejoin(iter=%eval(&iter.+1)
+ ,outds=&outds,parentvar=&parentvar
+ ,childvar=&childvar
+ ,base_ds=&base_ds
+ )
+%end;
+
+%mend mp_recursivejoin;
+/**
+ @file
+ @brief Performs a text substitution on a file
+ @details Performs a find and replace on a file, either in place or to a new
+ file. Can be used on files where lines are longer than 32767.
+
+ Works by reading in the file byte by byte, then marking the beginning and end
+ of each matched string, before finally doing the replace.
+
+ Full credit for this highly efficient and syntactically satisfying SAS logic
+ goes to [Bartosz Jabłoński](https://www.linkedin.com/in/yabwon), founder of
+ the [SAS Packages](https://github.com/yabwon/SAS_PACKAGES) framework.
+
+ Usage:
+
+ %let file="%sysfunc(pathname(work))/file.txt";
+ %let str=replace/me;
+ %let rep=with/this;
+ data _null_;
+ file &file;
+ put 'blahblah';
+ put "blahblah&str.blah";
+ put 'blahblahblah';
+ run;
+ %mp_replace(&file, findvar=str, replacevar=rep)
+ data _null_;
+ infile &file;
+ input;
+ list;
+ run;
+
+ Note - if you are running a version of SAS that will allow the io package in
+ LUA, you can also use this macro: mp_gsubfile.sas
+
+ @param [in] infile The QUOTED path to the file on which to perform the
+ substitution
+ @param [in] findvar= Macro variable NAME containing the string to search for
+ @param [in] replacevar= Macro variable NAME containing the replacement string
+ @param [out] outfile= (0) Optional QUOTED path to the adjusted output file (to
+ avoid overwriting the first file).
+
+ | Transaction Type | Key Behaviour | Column Behaviour | +
|---|---|---|
| Deletes | ++ The row is added to `&outDEL.` UNLESS it no longer exists + in the base table, in which case it is added to `&errDS.` instead. + | ++ Deletes are unaffected by the addition or removal of non Primary-Key + columns. + | +
| Inserts | +
+ Previously newly added rows are added to the `outADD` table UNLESS they
+ are present in the Base table. In this case they are added to the + `&errDS.` table instead. + |
+ + Inserts are unaffected by the addition of columns in the Base Table + (they are padded with blanks). Deleted columns are only a problem if + they appear on the previous insert - in which case the record is added + to `&errDS.`. + | +
| Updates | +
+ Previously modified rows are merged with base table values such that
+ only the individual cells that were _previously_ changed are re-applied.
+ Where the row contains cells that were not marked as having changed in
+ the prior transaction, the 'blanks' are filled with base table values in
+ the `outMOD` table. + If the row no longer exists on the base table, then the row is added to + the `errDS` table instead. + |
+ + Updates are unaffected by the addition of columns in the Base Table - + the new cells are simply populated with Base Table values. Deleted + columns are only a problem if they relate to a modified cell + (`is_diff=1`) - in which case the record is added to `&errDS.`. + | +
tree= parameter.
+ @param [out] outds= the dataset to create that contains the list of stps.
+ @param [in] mDebug= set to 1 to show debug messages in the log
+ @param [in] showDesc= provide a non blank value to return stored process
+ descriptions
+ @param [in] showUsageVersion= ()
+ Provide a non blank value to return the UsageVersion.
+ This is either 1000000 (type 1, 9.2) or 2000000 (type2, 9.3 onwards).
+
+ @returns outds dataset containing the following columns
+ - stpuri
+ - stpname
+ - treeuri
+ - stpdesc (if requested)
+ - usageversion (if requested)
+
+ @version 9.2
+ @author Allan Bowe
+
+**/
+
+%macro mm_getstps(
+ tree=
+ ,name=
+ ,outds=work.mm_getstps
+ ,mDebug=0
+ ,showDesc=
+ ,showUsageVersion=
+)/*/STORE SOURCE*/;
+
+%local mD;
+%if &mDebug=1 %then %let mD=;
+%else %let mD=%str(*);
+%&mD.put Executing mm_getstps.sas;
+%&mD.put _local_;
+
+data &outds;
+ length stpuri stpname usageversion treeuri stpdesc $256;
+ call missing (of _all_);
+run;
+
+%if %length(&tree)>0 %then %do;
+ /* get tree info */
+ %mm_gettree(tree=&tree,inds=&outds, outds=&outds, mDebug=&mDebug)
+ %if %mf_nobs(&outds)=0 %then %do;
+ %put NOTE: Tree &tree did not exist!!;
+ %return;
+ %end;
+%end;
+
+
+data &outds ;
+ set &outds(rename=(treeuri=treeuri_compare));
+ length treeuri query stpuri $256;
+ i+1;
+%if %length(&name)>0 %then %do;
+ query="omsobj:ClassifierMap?@PublicType='StoredProcess' and @Name='&name'";
+ putlog query=;
+%end;
+%else %do;
+ query="omsobj:ClassifierMap?@PublicType='StoredProcess'";
+%end;
+%if &mDebug=1 %then %do;
+ putlog 'start' (_all_)(=);
+%end;
+ do while(0&outds) containing both the path
+ and uri.
+
+ Usage:
+
+ %mm_getTree(tree=/User Folders/sasdemo)
+
+
+ @param [in] tree= the BIP Tree folder path or uri
+ @param [out] outds= the dataset to create that contains the tree path & uri
+ @param [in] inds= an optional input dataset to augment with treepath & treeuri
+ @param [in] mDebug= set to 1 to show debug messages in the log
+
+ @returns outds dataset containing the following columns:
+ - treeuri
+ - treepath
+
+ @version 9.2
+ @author Allan Bowe
+
+**/
+
+%macro mm_getTree(
+ tree=
+ ,inds=
+ ,outds=work.mm_getTree
+ ,mDebug=0
+)/*/STORE SOURCE*/;
+
+%local mD;
+%if &mDebug=1 %then %let mD=;
+%else %let mD=%str(*);
+%&mD.put Executing mm_getTree.sas;
+%&mD.put _local_;
+
+data &outds;
+ length treeuri __parenturi __type __name $256 treepath $512;
+%if %length(&inds)>0 %then %do;
+ set &inds;
+%end;
+ __rc1=metadata_resolve("&tree",__type,treeuri);
+
+ if __type='Tree' then do;
+ __rc2=metadata_getattr(treeuri,"Name",__name);
+ treepath=cats('/',__name);
+ /* get parents */
+ do while (metadata_getnasn(treeuri,"ParentTree",1,__parenturi)>0);
+ __rc3=metadata_getattr(__parenturi,"Name",__name);
+ treepath=cats('/',__name,treepath);
+ treeuri=__parenturi;
+ end;
+ treeuri="&tree";
+ end;
+ else do;
+ __rc2=metadata_pathobj(' ',"&tree",'Folder',__type,treeuri);
+ treepath="&tree";
+ end;
+
+ &mD.put (_all_)(=);
+ drop __:;
+ if treeuri ne "" and treepath ne "" then output;
+ stop;
+run;
+%mend mm_getTree;/**
+ @file
+ @brief Creates a dataset with all metadata types
+ @details Usage:
+
+ %mm_gettypes(outds=types)
+
+ @param [in] outds= (work.mm_gettypes)
+ The dataset to create that contains the list of types
+ @returns outds dataset containing all types
+ @warning The following filenames are created and then de-assigned:
+
+ filename sxlemap clear;
+ filename response clear;
+ libname _XML_ clear;
+
+ @version 9.2
+ @author Allan Bowe
+
+**/
+
+%macro mm_gettypes(
+ outds=work.mm_gettypes
+)/*/STORE SOURCE*/;
+
+* use a temporary fileref to hold the response;
+filename response temp;
+/* get list of libraries */
+proc metadata in=
+ '
+ %* compile macros ;
+ filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
+ %inc mc;
+
+ %* parmcards lets us write to a text file from open code ;
+ filename ft15f001 temp;
+ parmcards4;
+ %webout(FETCH)
+ %* do some sas, any inputs are now already WORK tables;
+ data example1 example2;
+ set sashelp.class;
+ run;
+ %* send data back;
+ %webout(OPEN)
+ %webout(ARR,example1) * Array format, fast, suitable for large tables ;
+ %webout(OBJ,example2) * Object format, easier to work with ;
+ %webout(CLOSE)
+ ;;;;
+ %ms_createwebservice(path=/Public/app/common,name=appInit,code=ft15f001)
+
+
+
+ For more examples of using these web services with the SASjs Adapter, see:
+ https://github.com/sasjs/adapter#readme
+
+ @param [in] path= (0) The full SASjs Drive path in which to create the service
+ @param [in] name= Stored Program name
+ @param [in] desc= The description of the service (not implemented yet)
+ @param [in] precode= Space separated list of filerefs, pointing to the code
+ that needs to be attached to the beginning of the service (optional)
+ @param [in] code= (ft15f001) Space seperated fileref(s) of the actual code to
+ be added
+ @param [in] mDebug= (0) set to 1 to show debug messages in the log
+
+
- %* compile macros ;
- filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
- %inc mc;
-
- %* parmcards lets us write to a text file from open code ;
- filename ft15f001 temp;
- parmcards4;
- %webout(FETCH)
- %* do some sas, any inputs are now already WORK tables;
- data example1 example2;
- set sashelp.class;
- run;
- %* send data back;
- %webout(OPEN)
- %webout(ARR,example1) * Array format, fast, suitable for large tables ;
- %webout(OBJ,example2) * Object format, easier to work with ;
- %webout(CLOSE)
- ;;;;
- %ms_createwebservice(path=/Public/app/common,name=appInit,code=ft15f001)
-
-
-
- For more examples of using these web services with the SASjs Adapter, see:
- https://github.com/sasjs/adapter#readme
-
- @param [in] path= (0) The full SASjs Drive path in which to create the service
- @param [in] name= Stored Program name
- @param [in] desc= The description of the service (not implemented yet)
- @param [in] precode= Space separated list of filerefs, pointing to the code
- that needs to be attached to the beginning of the service (optional)
- @param [in] code= (ft15f001) Space seperated fileref(s) of the actual code to
- be added
- @param [in] mDebug= (0) set to 1 to show debug messages in the log
-
-
+ %* compile macros ;
+ filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
+ %inc mc;
+
+ %* parmcards lets us write to a text file from open code ;
+ filename ft15f001 temp;
+ parmcards4;
+ %webout(FETCH)
+ %* do some sas, any inputs are now already WORK tables;
+ data example1 example2;
+ set sashelp.class;
+ run;
+ %* send data back;
+ %webout(OPEN)
+ %webout(ARR,example1) * Array format, fast, suitable for large tables ;
+ %webout(OBJ,example2) * Object format, easier to work with ;
+ %webout(CLOSE)
+ ;;;;
+ %ms_createwebservice(path=/Public/app/common,name=appInit,code=ft15f001)
+
+
+
+ For more examples of using these web services with the SASjs Adapter, see:
+ https://github.com/sasjs/adapter#readme
+
+ @param [in] path= (0) The full SASjs Drive path in which to create the service
+ @param [in] name= Stored Program name
+ @param [in] desc= The description of the service (not implemented yet)
+ @param [in] precode= Space separated list of filerefs, pointing to the code
+ that needs to be attached to the beginning of the service (optional)
+ @param [in] code= (ft15f001) Space seperated fileref(s) of the actual code to
+ be added
+ @param [in] mDebug= (0) set to 1 to show debug messages in the log
+
+