From 4c333ae7b384aaa1d732f50afdadb9d50548dc6b Mon Sep 17 00:00:00 2001
From: Allan Bowe <>
Date: Wed, 23 Dec 2020 00:33:59 +0000
Subject: [PATCH] feat: mp_prevobs.sas macro
---
all.sas | 145 ++++++++++++++++++++++++++++++++++++++++++++
base/mp_prevobs.sas | 88 +++++++++++++++++++++++++++
2 files changed, 233 insertions(+)
create mode 100644 base/mp_prevobs.sas
diff --git a/all.sas b/all.sas
index 6bd5462..8151753 100644
--- a/all.sas
+++ b/all.sas
@@ -4066,6 +4066,93 @@ select distinct lowcase(memname)
,dttm=%sysfunc(datetime());
quit;
+%mend;/**
+ @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 action Either FETCH a current or previous record, or INITialise.
+ @param record The relative (to current) position of the previous observation
+ to return.
+ @param history= The number of records to retain in the hash table. Default=5
+ @param prefix= the prefix to give to the variables used to store the hash name
+ and index. Default=mp_prevobs
+
+ @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;/**
@file
@brief Returns all children from a hierarchy table for a specified parent
@@ -4667,6 +4754,64 @@ proc sql
%mp_binarycopy(inloc="&inloc",outref=_webout)
%end;
+%mend;/**
+ @file mp_testwritespeedlibrary.sas
+ @brief Tests the write speed of a new table in a SAS library
+ @details Will create a new table of a certain size in an
+ existing SAS library. The table will have one column,
+ and will be subsequently deleted.
+
+ %mp_testwritespeedlibrary(
+ lib=work
+ ,size=0.5
+ ,outds=work.results
+ )
+
+ @param lib= (WORK) The library in which to create the table
+ @param size= (0.1) The size in GB of the table to create
+ @param outds= (WORK.RESULTS) The output dataset to be created.
+
+
+
+ Credit is made to `data _null_` for authoring this very helpful paper:
+ https://www.lexjansen.com/pharmasug/2008/cc/CC08.pdf
+
+ @param action Either FETCH a current or previous record, or INITialise.
+ @param record The relative (to current) position of the previous observation
+ to return.
+ @param history= The number of records to retain in the hash table. Default=5
+ @param prefix= the prefix to give to the variables used to store the hash name
+ and index. Default=mp_prevobs
+
+ @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;
\ No newline at end of file