diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index b83ef2a..4dfe98f 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -15,3 +15,4 @@ What code changes have been made to achieve the intent.
- [ ] Code is formatted correctly (`sasjs lint`).
- [ ] Any new functionality has been unit tested.
- [ ] All unit tests are passing (`sasjs test`).
+- [ ] `all.sas` has been regenerated (`python3 build.py`)
diff --git a/all.sas b/all.sas
index 2e580d4..99df58a 100644
--- a/all.sas
+++ b/all.sas
@@ -2658,6 +2658,100 @@ and %superq(SYSPROCESSNAME) ne %str(Compute Server)
%mend mp_abort;
/** @endcond */
+/**
+ @file
+ @brief Apply leading blanks to align numbers vertically in a char variable
+ @details This is particularly useful when storing numbers (as character) that
+ need to be sorted.
+
+ It works by splitting the number left and right of the decimal place, and
+ aligning it accordingly. A temporary variable is created as part of this
+ process (which is automatically dropped)
+
+ The macro can be used only in data step, eg as follows:
+
+ data _null_;
+ length myvar $50;
+ do i=1 to 1000 by 50;
+ if mod(i,2)=0 then j=ranuni(0)*i*100;
+ else j=i*100;
+
+ %mp_aligndecimal(myvar,width=7)
+
+ leading_spaces=length(myvar)-length(cats(myvar));
+ putlog +leading_spaces myvar;
+ end;
+ run;
+
+ The generated code will look something like this:
+
+ length aligndp4e49996 $7;
+ if index(myvar,'.') then do;
+ aligndp4e49996=cats(scan(myvar,1,'.'));
+ aligndp4e49996=right(aligndp4e49996);
+ myvar=aligndp4e49996!!'.'!!cats(scan(myvar,2,'.'));
+ end;
+ else do;
+ aligndp4e49996=myvar;
+ aligndp4e49996=right(aligndp4e49996);
+ myvar=aligndp4e49996;
+ end;
+
+ Results (myvar variable):
+
+ 0.7683559324
+ 122.8232796
+ 99419.50552
+ 42938.5143414
+ 763.3799189
+ 15170.606073
+ 15083.285773
+ 85443.198707
+ 2022999.2251
+ 12038.658867
+ 1350582.6734
+ 52777.258221
+ 11723.347628
+ 33101.268376
+ 6181622.8603
+ 7390614.0669
+ 73384.537893
+ 1788362.1016
+ 2774586.2219
+ 7998580.8415
+
+
+ @param var The (data step) variable to create
+ @param width= (8) The number of characters BEFORE the decimal point
+
+
SAS Macros
+ @li mf_getuniquename.sas
+
+ Related Programs
+ @li mp_aligndecimal.test.sas
+
+ @version 9.2
+ @author Allan Bowe
+**/
+
+%macro mp_aligndecimal(var,width=8);
+
+ %local tmpvar;
+ %let tmpvar=%mf_getuniquename(prefix=aligndp);
+ length &tmpvar $&width;
+ if index(&var,'.') then do;
+ &tmpvar=cats(scan(&var,1,'.'));
+ &tmpvar=right(&tmpvar);
+ &var=&tmpvar!!'.'!!cats(scan(&var,2,'.'));
+ end;
+ else do;
+ &tmpvar=cats(&var);
+ &tmpvar=right(&tmpvar);
+ &var=&tmpvar;
+ end;
+ drop &tmpvar;
+
+%mend mp_aligndecimal;
/**
@file
@brief Append (concatenate) two or more files.
@@ -3137,6 +3231,7 @@ run;
@param [in] test= (ALLVALS) The test to apply. Valid values are:
@li ALLVALS - Test is a PASS if ALL values have a match in checkvals
@li ANYVAL - Test is a PASS if at least 1 value has a match in checkvals
+ @li NOVAL - Test is a PASS if there are NO matches in checkvals
@param [out] outds= (work.test_results) The output dataset to contain the
results. If it does not exist, it will be created, with the following format:
|TEST_DESCRIPTION:$256|TEST_RESULT:$4|TEST_COMMENTS:$256|
@@ -3192,7 +3287,7 @@ run;
%let test=%upcase(&test);
- %if &test ne ALLVALS and &test ne ANYVAL %then %do;
+ %if &test ne ALLVALS and &test ne ANYVAL and &test ne NOVAL %then %do;
%mp_abort(
mac=&sysmacroname,
msg=%str(Invalid test - &test)
@@ -3203,12 +3298,12 @@ run;
%let result=-1;
%let orig=-1;
proc sql noprint;
- select count(*) into: result
+ select count(*) into: result trimmed
from &lib..&ds
where &col not in (
select &ccol from &clib..&cds
);
- select count(*) into: orig from &lib..&ds;
+ select count(*) into: orig trimmed from &lib..&ds;
quit;
%local notfound tmp1 tmp2;
@@ -3240,7 +3335,7 @@ run;
length test_description $256 test_result $4 test_comments $256;
test_description=symget('desc');
test_result='FAIL';
- test_comments="&sysmacroname: &lib..&ds..&col has &result values "
+ test_comments="&sysmacroname: &lib..&ds..&col has &result/&orig values "
!!"not in &clib..&cds..&ccol.. First 10 vals:"!!symget('notfound');
%if &test=ANYVAL %then %do;
if &result < &orig then test_result='PASS';
@@ -3248,6 +3343,9 @@ run;
%else %if &test=ALLVALS %then %do;
if &result=0 then test_result='PASS';
%end;
+ %else %if &test=NOVAL %then %do;
+ if &result=&orig then test_result='PASS';
+ %end;
%else %do;
test_comments="&sysmacroname: Unsatisfied test condition - &test";
%end;
@@ -4023,6 +4121,7 @@ run;
Related Macros
@li mf_getvarformat.sas
+ @li mp_aligndecimal.sas
@li mp_getformats.sas
@li mp_loadformat.sas
@li mp_ds2fmtds.sas
@@ -4064,13 +4163,13 @@ run;
data &cntlout;
if 0 then set &ddlds;
set &cntlds;
- if type="N" then do;
- start=cats(start);
- end=cats(end);
+ if type in ("I","N") then do; /* numeric (in)format */
+ %mp_aligndecimal(start,width=16)
+ %mp_aligndecimal(end,width=16)
end;
run;
proc sort;
- by fmtname start;
+ by type fmtname start;
run;
proc sql;
@@ -10030,6 +10129,7 @@ select distinct lowcase(memname)
Related Macros
@li mddl_dc_difftable.sas
@li mddl_dc_locktable.sas
+ @li mp_aligndecimal.sas
@li mp_loadformat.test.sas
@li mp_lockanytable.sas
@li mp_stackdiffs.sas
@@ -10119,7 +10219,16 @@ run;
* First, extract only relevant formats from the catalog
*/
proc sql noprint;
-select distinct upcase(fmtname) into: fmtlist separated by ' ' from &libds;
+select distinct
+ case
+ when type='N' then upcase(fmtname)
+ when type='C' then cats('$',upcase(fmtname))
+ when type='I' then cats('@',upcase(fmtname))
+ when type='J' then cats('@$',upcase(fmtname))
+ else "&sysmacroname:UNHANDLED"
+ end
+ into: fmtlist separated by ' '
+ from &libds;
%mp_cntlout(libcat=&libcat,fmtlist=&fmtlist,cntlout=&base_fmts)
@@ -10131,16 +10240,24 @@ select distinct upcase(fmtname) into: fmtlist separated by ' ' from &libds;
data &inlibds;
length &delete_col $3;
if 0 then set &template;
+ length start end $10000;
set &libds;
if &delete_col='' then &delete_col='No';
fmtname=upcase(fmtname);
+ type=upcase(type);
if missing(type) then do;
- if substr(fmtname,1,1)='$' then type='C';
- else type='N';
+ if substr(fmtname,1,1)='@' then do;
+ if substr(fmtname,2,1)='$' then type='J';
+ else type='I';
+ end;
+ else do;
+ if substr(fmtname,1,1)='$' then type='C';
+ else type='N';
+ end;
end;
- if type='N' then do;
- start=cats(start);
- end=cats(end);
+ if type in ('N','I') then do;
+ %mp_aligndecimal(start,width=16)
+ %mp_aligndecimal(end,width=16)
end;
run;
@@ -10154,9 +10271,10 @@ create table &outds_add(drop=&delete_col) as
left join &base_fmts b
on a.fmtname=b.fmtname
and a.start=b.start
+ and a.type=b.type
where b.fmtname is null
and upcase(a.&delete_col) ne "YES"
- order by fmtname, start;;
+ order by type, fmtname, start;
/**
* Identify deleted records
@@ -10167,8 +10285,9 @@ create table &outds_del(drop=&delete_col) as
inner join &base_fmts b
on a.fmtname=b.fmtname
and a.start=b.start
+ and a.type=b.type
where upcase(a.&delete_col)="YES"
- order by fmtname, start;
+ order by type, fmtname, start;
/**
* Identify modified records
@@ -10179,8 +10298,9 @@ create table &outds_mod (drop=&delete_col) as
inner join &base_fmts b
on a.fmtname=b.fmtname
and a.start=b.start
+ and a.type=b.type
where upcase(a.&delete_col) ne "YES"
- order by fmtname, start;
+ order by type, fmtname, start;
options ibufsize=&ibufsize;
@@ -10197,13 +10317,13 @@ options ibufsize=&ibufsize;
&outds_add(in=add)
&outds_del(in=del);
if not del and not mod;
- by fmtname start;
+ by type fmtname start;
run;
data &stagedata;
set &ds1 &outds_mod;
run;
proc sort;
- by fmtname start;
+ by type fmtname start;
run;
%end;
/* mp abort needs to run outside of conditional blocks */
@@ -10251,7 +10371,7 @@ options ibufsize=&ibufsize;
%mp_storediffs(&libcat-FC
,&base_fmts
- ,FMTNAME START
+ ,TYPE FMTNAME START
,delds=&outds_del
,modds=&outds_mod
,appds=&outds_add
@@ -13956,14 +14076,20 @@ ods package close;
proc sql;
create table &libds(
- FMTNAME char(32) label='Format name'
+ TYPE char(1) label='Type of format'
+ ,FMTNAME char(32) label='Format name'
/*
to accommodate larger START values, mp_loadformat.sas will need the
SQL dependency removed (proc sql needs to accommodate 3 index values in
a 32767 ibufsize limit)
*/
,START char(10000) label='Starting value for format'
- ,END char(32767) label='Ending value for format'
+ /*
+ Keep lengths of START and END the same to avoid this err:
+ "Start is greater than end: -<."
+ Similar usage note: https://support.sas.com/kb/69/330.html
+ */
+ ,END char(10000) label='Ending value for format'
,LABEL char(32767) label='Format value label'
,MIN num length=3 label='Minimum length'
,MAX num length=3 label='Maximum length'
@@ -13974,7 +14100,6 @@ create table &libds(
,MULT num label='Multiplier'
,FILL char(1) label='Fill character'
,NOEDIT num length=3 label='Is picture string noedit?'
- ,TYPE char(1) label='Type of format'
,SEXCL char(1) label='Start exclusion'
,EEXCL char(1) label='End exclusion'
,HLO char(13) label='Additional information'