mirror of
https://github.com/sasjs/core.git
synced 2025-12-24 11:41:20 +00:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e2de81dae | ||
|
|
4887f355c8 | ||
|
|
9b32e6e3f2 | ||
|
|
74790ec80e | ||
|
|
afd8a754b4 | ||
|
|
bc1f7b3baa | ||
|
|
51690e68dc | ||
|
|
0fa076cb73 | ||
|
|
6506993704 | ||
|
|
a69db2ebfb | ||
|
|
d72ca7cb24 | ||
|
|
52dfa7b8f7 | ||
|
|
dae03c5730 | ||
|
|
14efe5d3fd | ||
|
|
653244d737 | ||
|
|
086831b3f5 | ||
|
|
6eca585fc1 | ||
|
|
f6ba36fc28 |
251
all.sas
251
all.sas
@@ -1841,6 +1841,7 @@ Usage:
|
||||
put ",""SYSERRORTEXT"" : " syserrortext;
|
||||
put ",""SYSHOSTNAME"" : ""&syshostname"" ";
|
||||
put ",""SYSJOBID"" : ""&sysjobid"" ";
|
||||
put ",""SYSSCPL"" : ""&sysscpl"" ";
|
||||
put ",""SYSSITE"" : ""&syssite"" ";
|
||||
sysvlong=quote(trim(symget('sysvlong')));
|
||||
put ',"SYSVLONG" : ' sysvlong;
|
||||
@@ -1855,7 +1856,7 @@ Usage:
|
||||
|
||||
%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;
|
||||
data _null_;
|
||||
putlog 'stpsrvset program error and syscc';
|
||||
putlog 'stpsrvset program err and syscc';
|
||||
rc=stpsrvset('program error', 0);
|
||||
call symputx("syscc",0,"g");
|
||||
run;
|
||||
@@ -1901,6 +1902,62 @@ Usage:
|
||||
%mend mp_abort;
|
||||
|
||||
/** @endcond *//**
|
||||
@file
|
||||
@brief Append (concatenate) two or more files.
|
||||
@details Will append one more more `appendrefs` (filerefs) to a `baseref`.
|
||||
Uses a binary mechanism, so will work with any file type. For that reason -
|
||||
use with care! And supply your own trailing carriage returns in each file..
|
||||
|
||||
Usage:
|
||||
|
||||
filename tmp1 temp;
|
||||
filename tmp2 temp;
|
||||
filename tmp3 temp;
|
||||
data _null_; file tmp1; put 'base file';
|
||||
data _null_; file tmp2; put 'append1';
|
||||
data _null_; file tmp3; put 'append2';
|
||||
run;
|
||||
%mp_appendfile(baseref=tmp1, appendrefs=tmp2 tmp3)
|
||||
|
||||
|
||||
@param [in] baseref= Fileref of the base file (should exist)
|
||||
@param [in] appendrefs= One or more filerefs to be appended to the base
|
||||
fileref. Space separated.
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe, source: https://github.com/sasjs/core
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mp_abort.sas
|
||||
@li mp_binarycopy.sas
|
||||
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_appendfile(
|
||||
baseref=0,
|
||||
appendrefs=0
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%mp_abort(iftrue= (&baseref=0)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(Baseref NOT specified!)
|
||||
)
|
||||
%mp_abort(iftrue= (&appendrefs=0)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(Appendrefs NOT specified!)
|
||||
)
|
||||
|
||||
%local i;
|
||||
%do i=1 %to %sysfunc(countw(&appendrefs));
|
||||
%mp_abort(iftrue= (&syscc>0)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(syscc=&syscc)
|
||||
)
|
||||
%mp_binarycopy(inref=%scan(&appendrefs,&i), outref=&baseref, mode=APPEND)
|
||||
%end;
|
||||
|
||||
%mend mp_appendfile;/**
|
||||
@file
|
||||
@brief Generic assertion
|
||||
@details Useful in the context of writing sasjs tests. The results of the
|
||||
@@ -2131,6 +2188,7 @@ Usage:
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_existds.sas
|
||||
@li mf_getuniquename.sas
|
||||
@li mf_nobs.sas
|
||||
@li mp_abort.sas
|
||||
|
||||
@@ -2216,6 +2274,26 @@ Usage:
|
||||
select count(*) into: orig from &lib..&ds;
|
||||
quit;
|
||||
|
||||
%local notfound tmp1 tmp2;
|
||||
%let tmp1=%mf_getuniquename();
|
||||
%let tmp2=%mf_getuniquename();
|
||||
|
||||
/* this is a bit convoluted - but using sql outobs=10 throws warnings */
|
||||
proc sql noprint;
|
||||
create view &tmp1 as
|
||||
select distinct &col
|
||||
from &lib..&ds
|
||||
where &col not in (
|
||||
select &ccol from &clib..&cds
|
||||
);
|
||||
data &tmp2;
|
||||
set &tmp1;
|
||||
if _n_>10 then stop;
|
||||
run;
|
||||
proc sql;
|
||||
select distinct &col into: notfound separated by ' ' from &tmp2;
|
||||
|
||||
|
||||
%mp_abort(iftrue= (&syscc ne 0)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(syscc=&syscc after macro query)
|
||||
@@ -2226,7 +2304,7 @@ Usage:
|
||||
test_description=symget('desc');
|
||||
test_result='FAIL';
|
||||
test_comments="&sysmacroname: &lib..&ds..&col has &result values "
|
||||
!!"not in &clib..&cds..&ccol ";
|
||||
!!"not in &clib..&cds..&ccol.. First 10 vals:"!!symget('notfound');
|
||||
%if &test=ANYVAL %then %do;
|
||||
if &result < &orig then test_result='PASS';
|
||||
%end;
|
||||
@@ -3237,12 +3315,21 @@ run;
|
||||
@file
|
||||
@brief Create a CARDS file from a SAS dataset.
|
||||
@details Uses dataset attributes to convert all data into datalines.
|
||||
Running the generated file will rebuild the original dataset.
|
||||
Running the generated file will rebuild the original dataset. Includes
|
||||
support for large decimals, binary data, PROCESSED_DTTM columns, and
|
||||
alternative encoding. If the input dataset is empty, the cards file will
|
||||
still be created.
|
||||
|
||||
Additional support to generate a random sample and max rows.
|
||||
|
||||
Usage:
|
||||
|
||||
%mp_ds2cards(base_ds=sashelp.class
|
||||
, tgt_ds=work.class
|
||||
, cards_file= "C:\temp\class.sas"
|
||||
, maxobs=5)
|
||||
, showlog=NO
|
||||
, maxobs=5
|
||||
)
|
||||
|
||||
TODO:
|
||||
- labelling the dataset
|
||||
@@ -3253,15 +3340,24 @@ run;
|
||||
that is converted to a cards file.
|
||||
@param [in] tgt_ds= Table that the generated cards file would create.
|
||||
Optional - if omitted, will be same as BASE_DS.
|
||||
@param [out] cards_file= Location in which to write the (.sas) cards file
|
||||
@param [in] maxobs= to limit output to the first <code>maxobs</code>
|
||||
observations
|
||||
@param [in] showlog= whether to show generated cards file in the SAS log
|
||||
(YES/NO)
|
||||
@param [in] outencoding= provide encoding value for file statement (eg utf-8)
|
||||
@param [in] append= If NO then will rebuild the cards file if it already
|
||||
@param [out] cards_file= ("%sysfunc(pathname(work))/cardgen.sas") Location in
|
||||
which to write the (.sas) cards file
|
||||
@param [in] maxobs= (max) To limit output to the first <code>maxobs</code>
|
||||
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.
|
||||
|
||||
<h4> Related Macros </h4>
|
||||
@li mp_lib2cards.sas
|
||||
@li mp_ds2inserts.sas
|
||||
@li mp_mdtablewrite.sas
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
@@ -3286,15 +3382,15 @@ run;
|
||||
%if (&tgt_ds = ) %then %let tgt_ds=&base_ds;
|
||||
%if %index(&tgt_ds,.)=0 %then %let tgt_ds=WORK.%scan(&base_ds,2,.);
|
||||
%if ("&outencoding" ne "") %then %let outencoding=encoding="&outencoding";
|
||||
%if ("&append" = "") %then %let append=;
|
||||
%if ("&append" = "" or "&append" = "NO") %then %let append=;
|
||||
%else %let append=mod;
|
||||
|
||||
/* get varcount */
|
||||
%let nvars=0;
|
||||
proc sql noprint;
|
||||
select count(*) into: nvars from dictionary.columns
|
||||
where libname="%scan(%upcase(&base_ds),1)"
|
||||
and memname="%scan(%upcase(&base_ds),2)";
|
||||
where upcase(libname)="%scan(%upcase(&base_ds),1)"
|
||||
and upcase(memname)="%scan(%upcase(&base_ds),2)";
|
||||
%if &nvars=0 %then %do;
|
||||
%put %str(WARN)ING: Dataset &base_ds has no variables, will not be converted.;
|
||||
%return;
|
||||
@@ -3350,8 +3446,8 @@ proc sql
|
||||
reset outobs=max;
|
||||
create table datalines1 as
|
||||
select name,type,length,varnum,format,label from dictionary.columns
|
||||
where libname="%upcase(%scan(&base_ds,1))"
|
||||
and memname="%upcase(%scan(&base_ds,2))";
|
||||
where upcase(libname)="%upcase(%scan(&base_ds,1))"
|
||||
and upcase(memname)="%upcase(%scan(&base_ds,2))";
|
||||
|
||||
/**
|
||||
Due to long decimals cannot use best. format
|
||||
@@ -3372,7 +3468,18 @@ data datalines_2;
|
||||
,put(',name,',best32.-l)
|
||||
,substrn(put(',name,',bestd32.-l),1
|
||||
,findc(put(',name,',bestd32.-l),"0","TBK")))');
|
||||
else dataline=name;
|
||||
/**
|
||||
* binary data must be converted, to store in text format. It is identified
|
||||
* by the presence of the $HEX keyword in the format.
|
||||
*/
|
||||
else if upcase(format)=:'$HEX' then
|
||||
dataline=cats('put(trim(',name,'),',format,')');
|
||||
/**
|
||||
* There is no easy way to store line breaks in a cards file.
|
||||
* To discuss this, use: https://github.com/sasjs/core/issues/80
|
||||
* Removing all nonprintables with kw (keep writeable)
|
||||
*/
|
||||
else dataline=cats('compress(',name,', ,"kw")');
|
||||
run;
|
||||
|
||||
proc sql noprint;
|
||||
@@ -3397,7 +3504,8 @@ data _null_;
|
||||
|
||||
|
||||
/* Build input statement */
|
||||
if type='char' then type3=':$char.';
|
||||
if upcase(format)=:'$HEX' then type3=':'!!format;
|
||||
else if type='char' then type3=':$char.';
|
||||
str2=put(name,$33.)||type3;
|
||||
|
||||
|
||||
@@ -3419,11 +3527,12 @@ data _null_;
|
||||
file &cards_file. &outencoding lrecl=32767 termstr=nl &append;
|
||||
length __attrib $32767;
|
||||
if _n_=1 then do;
|
||||
put '/*******************************************************************';
|
||||
put " Datalines for %upcase(%scan(&base_ds,2)) dataset ";
|
||||
put " Generated by %nrstr(%%)mp_ds2cards()";
|
||||
put " Available on github.com/sasjs/core";
|
||||
put '********************************************************************/';
|
||||
put '/**';
|
||||
put ' @file';
|
||||
put " @brief Datalines for %upcase(%scan(&base_ds,2)) dataset";
|
||||
put " @details Generated by %nrstr(%%)mp_ds2cards()";
|
||||
put " Available on github.com/sasjs/core";
|
||||
put '**/';
|
||||
put "data &tgt_ds &indexes;";
|
||||
put "attrib ";
|
||||
%do i = 1 %to &nvars;
|
||||
@@ -3447,11 +3556,11 @@ data _null_;
|
||||
put 'run;';
|
||||
end;
|
||||
else do;
|
||||
put "infile cards dsd delimiter=',';";
|
||||
put "infile cards dsd;";
|
||||
put "input ";
|
||||
%do i = 1 %to &nvars.;
|
||||
%if(%length(&&input_stmt_&i..)) %then
|
||||
put " &&input_stmt_&i..";
|
||||
put " &&input_stmt_&i..";
|
||||
;
|
||||
%end;
|
||||
put ";";
|
||||
@@ -4230,6 +4339,70 @@ filename &fref1 clear;
|
||||
|
||||
%mend mp_filtervalidate;
|
||||
/**
|
||||
@file
|
||||
@brief Creates a dataset with column metadata.
|
||||
@details This macro takes the `proc contents` output and "tidies it up" in the
|
||||
following ways:
|
||||
|
||||
@li Blank labels are filled in with column names
|
||||
@li Formats are reconstructed with default values
|
||||
@li Types such as DATE / TIME / DATETIME are inferred from the formats
|
||||
|
||||
Example usage:
|
||||
|
||||
%mp_getcols(sashelp.airline,outds=work.myds)
|
||||
|
||||
@param ds The dataset from which to obtain column metadata
|
||||
@param outds= (work.cols) The output dataset to create. Sample data:
|
||||
|NAME $|LENGTH 8|VARNUM 8|LABEL $|FORMAT $49|TYPE $1 |DDTYPE $|
|
||||
|---|---|---|---|---|---|---|
|
||||
|AIR|8|2|international airline travel (thousands)|8.|N|NUMERIC|
|
||||
|DATE|8|1|DATE|MONYY.|N|DATE|
|
||||
|REGION|3|3|REGION|$3.|C|CHARACTER|
|
||||
|
||||
<h4> Related Macros </h4>
|
||||
@li mf_getvarlist.sas
|
||||
@li mm_getcols.sas
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_getcols(ds, outds=work.cols);
|
||||
|
||||
proc contents noprint data=&ds
|
||||
out=_data_ (keep=name type length label varnum format:);
|
||||
run;
|
||||
data &outds(keep=name type length varnum format label ddtype);
|
||||
set &syslast(rename=(format=format2 type=type2));
|
||||
name=upcase(name);
|
||||
if type2=2 then do;
|
||||
length format $49.;
|
||||
if format2='' then format=cats('$',length,'.');
|
||||
else if formatl=0 then format=cats(format2,'.');
|
||||
else format=cats(format2,formatl,'.');
|
||||
type='C';
|
||||
ddtype='CHARACTER';
|
||||
end;
|
||||
else do;
|
||||
if format2='' then format=cats(length,'.');
|
||||
else if formatl=0 then format=cats(format2,'.');
|
||||
else if formatd=0 then format=cats(format2,formatl,'.');
|
||||
else format=cats(format2,formatl,'.',formatd);
|
||||
type='N';
|
||||
if format=:'DATETIME' then ddtype='DATETIME';
|
||||
else if format=:'DATE' or format=:'DDMMYY' or format=:'MMDDYY'
|
||||
or format=:'YYMMDD' or format=:'E8601DA' or format=:'B8601DA'
|
||||
or format=:'MONYY'
|
||||
then ddtype='DATE';
|
||||
else if format=:'TIME' then ddtype='TIME';
|
||||
else ddtype='NUMERIC';
|
||||
end;
|
||||
if label='' then label=name;
|
||||
run;
|
||||
|
||||
%mend mp_getcols;/**
|
||||
@file mp_getconstraints.sas
|
||||
@brief Get constraint details at column level
|
||||
@details Useful for capturing constraints before they are dropped / reapplied
|
||||
@@ -4270,21 +4443,21 @@ filename &fref1 clear;
|
||||
/* must use SQL as proc datasets does not support length changes */
|
||||
proc sql noprint;
|
||||
create table &outds as
|
||||
select a.TABLE_CATALOG as libref
|
||||
,a.TABLE_NAME
|
||||
select upcase(a.TABLE_CATALOG) as libref
|
||||
,upcase(a.TABLE_NAME) as TABLE_NAME
|
||||
,a.constraint_type
|
||||
,a.constraint_name
|
||||
,b.column_name
|
||||
from dictionary.TABLE_CONSTRAINTS a
|
||||
left join dictionary.constraint_column_usage b
|
||||
on a.TABLE_CATALOG=b.TABLE_CATALOG
|
||||
and a.TABLE_NAME=b.TABLE_NAME
|
||||
on upcase(a.TABLE_CATALOG)=upcase(b.TABLE_CATALOG)
|
||||
and upcase(a.TABLE_NAME)=upcase(b.TABLE_NAME)
|
||||
and a.constraint_name=b.constraint_name
|
||||
where a.TABLE_CATALOG="&lib"
|
||||
and b.TABLE_CATALOG="&lib"
|
||||
where upcase(a.TABLE_CATALOG)="&lib"
|
||||
and upcase(b.TABLE_CATALOG)="&lib"
|
||||
%if "&ds" ne "" %then %do;
|
||||
and a.TABLE_NAME="&ds"
|
||||
and b.TABLE_NAME="&ds"
|
||||
and upcase(a.TABLE_NAME)="&ds"
|
||||
and upcase(b.TABLE_NAME)="&ds"
|
||||
%end;
|
||||
;
|
||||
|
||||
@@ -4835,7 +5008,7 @@ run;
|
||||
proc sql noprint;
|
||||
select sysvalue into: schemaactual
|
||||
from dictionary.libnames
|
||||
where libname="&libref" and engine='SQLSVR';
|
||||
where upcase(libname)="&libref" and engine='SQLSVR';
|
||||
%let schema=%sysfunc(coalescec(&schemaactual,&schema,&libref));
|
||||
|
||||
%do x=1 %to %sysfunc(countw(&dsnlist));
|
||||
@@ -4928,7 +5101,7 @@ run;
|
||||
proc sql noprint;
|
||||
select sysvalue into: schemaactual
|
||||
from dictionary.libnames
|
||||
where libname="&libref" and engine='POSTGRES';
|
||||
where upcase(libname)="&libref" and engine='POSTGRES';
|
||||
%let schema=%sysfunc(coalescec(&schemaactual,&schema,&libref));
|
||||
data _null_;
|
||||
file &fref mod;
|
||||
@@ -6030,14 +6203,14 @@ select distinct lowcase(memname)
|
||||
We take the standard definition one step further by embedding the informat
|
||||
in the table header row, like so:
|
||||
|
||||
|var1:$|var2:best.|var3:date9.|
|
||||
|var1:$32|var2:best.|var3:date9.|
|
||||
|---|---|---|
|
||||
|some text|42|01JAN1960|
|
||||
|blah|1|31DEC1999|
|
||||
|
||||
Which resolves to:
|
||||
|
||||
|var1:$|var2:best.|var3:date9.|
|
||||
|var1:$32|var2:best.|var3:date9.|
|
||||
|---|---|---|
|
||||
|some text|42|01JAN1960|
|
||||
|blah|1|31DEC1999|
|
||||
@@ -9986,6 +10159,7 @@ data _null_;
|
||||
put ' put ",""SYSERRORTEXT"" : ""&syserrortext"" "; ';
|
||||
put ' put ",""SYSHOSTNAME"" : ""&syshostname"" "; ';
|
||||
put ' put ",""SYSJOBID"" : ""&sysjobid"" "; ';
|
||||
put ' put ",""SYSSCPL"" : ""&sysscpl"" "; ';
|
||||
put ' put ",""SYSSITE"" : ""&syssite"" "; ';
|
||||
put ' sysvlong=quote(trim(symget(''sysvlong''))); ';
|
||||
put ' put '',"SYSVLONG" : '' sysvlong; ';
|
||||
@@ -13481,6 +13655,7 @@ run;
|
||||
put ",""SYSERRORTEXT"" : ""&syserrortext"" ";
|
||||
put ",""SYSHOSTNAME"" : ""&syshostname"" ";
|
||||
put ",""SYSJOBID"" : ""&sysjobid"" ";
|
||||
put ",""SYSSCPL"" : ""&sysscpl"" ";
|
||||
put ",""SYSSITE"" : ""&syssite"" ";
|
||||
sysvlong=quote(trim(symget('sysvlong')));
|
||||
put ',"SYSVLONG" : ' sysvlong;
|
||||
@@ -14952,6 +15127,7 @@ data _null_;
|
||||
put ' put ",""SYSCC"" : ""&syscc"" "; ';
|
||||
put ' put ",""SYSERRORTEXT"" : ""&syserrortext"" "; ';
|
||||
put ' put ",""SYSHOSTNAME"" : ""&syshostname"" "; ';
|
||||
put ' put ",""SYSSCPL"" : ""&sysscpl"" "; ';
|
||||
put ' put ",""SYSSITE"" : ""&syssite"" "; ';
|
||||
put ' sysvlong=quote(trim(symget(''sysvlong''))); ';
|
||||
put ' put '',"SYSVLONG" : '' sysvlong; ';
|
||||
@@ -18791,6 +18967,7 @@ filename &fref1 clear;
|
||||
put ",""SYSCC"" : ""&syscc"" ";
|
||||
put ",""SYSERRORTEXT"" : ""&syserrortext"" ";
|
||||
put ",""SYSHOSTNAME"" : ""&syshostname"" ";
|
||||
put ",""SYSSCPL"" : ""&sysscpl"" ";
|
||||
put ",""SYSSITE"" : ""&syssite"" ";
|
||||
sysvlong=quote(trim(symget('sysvlong')));
|
||||
put ',"SYSVLONG" : ' sysvlong;
|
||||
|
||||
@@ -193,6 +193,7 @@
|
||||
put ",""SYSERRORTEXT"" : " syserrortext;
|
||||
put ",""SYSHOSTNAME"" : ""&syshostname"" ";
|
||||
put ",""SYSJOBID"" : ""&sysjobid"" ";
|
||||
put ",""SYSSCPL"" : ""&sysscpl"" ";
|
||||
put ",""SYSSITE"" : ""&syssite"" ";
|
||||
sysvlong=quote(trim(symget('sysvlong')));
|
||||
put ',"SYSVLONG" : ' sysvlong;
|
||||
@@ -207,7 +208,7 @@
|
||||
|
||||
%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;
|
||||
data _null_;
|
||||
putlog 'stpsrvset program error and syscc';
|
||||
putlog 'stpsrvset program err and syscc';
|
||||
rc=stpsrvset('program error', 0);
|
||||
call symputx("syscc",0,"g");
|
||||
run;
|
||||
|
||||
57
base/mp_appendfile.sas
Normal file
57
base/mp_appendfile.sas
Normal file
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
@file
|
||||
@brief Append (concatenate) two or more files.
|
||||
@details Will append one more more `appendrefs` (filerefs) to a `baseref`.
|
||||
Uses a binary mechanism, so will work with any file type. For that reason -
|
||||
use with care! And supply your own trailing carriage returns in each file..
|
||||
|
||||
Usage:
|
||||
|
||||
filename tmp1 temp;
|
||||
filename tmp2 temp;
|
||||
filename tmp3 temp;
|
||||
data _null_; file tmp1; put 'base file';
|
||||
data _null_; file tmp2; put 'append1';
|
||||
data _null_; file tmp3; put 'append2';
|
||||
run;
|
||||
%mp_appendfile(baseref=tmp1, appendrefs=tmp2 tmp3)
|
||||
|
||||
|
||||
@param [in] baseref= Fileref of the base file (should exist)
|
||||
@param [in] appendrefs= One or more filerefs to be appended to the base
|
||||
fileref. Space separated.
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe, source: https://github.com/sasjs/core
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mp_abort.sas
|
||||
@li mp_binarycopy.sas
|
||||
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_appendfile(
|
||||
baseref=0,
|
||||
appendrefs=0
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%mp_abort(iftrue= (&baseref=0)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(Baseref NOT specified!)
|
||||
)
|
||||
%mp_abort(iftrue= (&appendrefs=0)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(Appendrefs NOT specified!)
|
||||
)
|
||||
|
||||
%local i;
|
||||
%do i=1 %to %sysfunc(countw(&appendrefs));
|
||||
%mp_abort(iftrue= (&syscc>0)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(syscc=&syscc)
|
||||
)
|
||||
%mp_binarycopy(inref=%scan(&appendrefs,&i), outref=&baseref, mode=APPEND)
|
||||
%end;
|
||||
|
||||
%mend mp_appendfile;
|
||||
@@ -30,6 +30,7 @@
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_existds.sas
|
||||
@li mf_getuniquename.sas
|
||||
@li mf_nobs.sas
|
||||
@li mp_abort.sas
|
||||
|
||||
@@ -115,6 +116,26 @@
|
||||
select count(*) into: orig from &lib..&ds;
|
||||
quit;
|
||||
|
||||
%local notfound tmp1 tmp2;
|
||||
%let tmp1=%mf_getuniquename();
|
||||
%let tmp2=%mf_getuniquename();
|
||||
|
||||
/* this is a bit convoluted - but using sql outobs=10 throws warnings */
|
||||
proc sql noprint;
|
||||
create view &tmp1 as
|
||||
select distinct &col
|
||||
from &lib..&ds
|
||||
where &col not in (
|
||||
select &ccol from &clib..&cds
|
||||
);
|
||||
data &tmp2;
|
||||
set &tmp1;
|
||||
if _n_>10 then stop;
|
||||
run;
|
||||
proc sql;
|
||||
select distinct &col into: notfound separated by ' ' from &tmp2;
|
||||
|
||||
|
||||
%mp_abort(iftrue= (&syscc ne 0)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(syscc=&syscc after macro query)
|
||||
@@ -125,7 +146,7 @@
|
||||
test_description=symget('desc');
|
||||
test_result='FAIL';
|
||||
test_comments="&sysmacroname: &lib..&ds..&col has &result values "
|
||||
!!"not in &clib..&cds..&ccol ";
|
||||
!!"not in &clib..&cds..&ccol.. First 10 vals:"!!symget('notfound');
|
||||
%if &test=ANYVAL %then %do;
|
||||
if &result < &orig then test_result='PASS';
|
||||
%end;
|
||||
|
||||
@@ -2,12 +2,21 @@
|
||||
@file
|
||||
@brief Create a CARDS file from a SAS dataset.
|
||||
@details Uses dataset attributes to convert all data into datalines.
|
||||
Running the generated file will rebuild the original dataset.
|
||||
Running the generated file will rebuild the original dataset. Includes
|
||||
support for large decimals, binary data, PROCESSED_DTTM columns, and
|
||||
alternative encoding. If the input dataset is empty, the cards file will
|
||||
still be created.
|
||||
|
||||
Additional support to generate a random sample and max rows.
|
||||
|
||||
Usage:
|
||||
|
||||
%mp_ds2cards(base_ds=sashelp.class
|
||||
, tgt_ds=work.class
|
||||
, cards_file= "C:\temp\class.sas"
|
||||
, maxobs=5)
|
||||
, showlog=NO
|
||||
, maxobs=5
|
||||
)
|
||||
|
||||
TODO:
|
||||
- labelling the dataset
|
||||
@@ -18,15 +27,24 @@
|
||||
that is converted to a cards file.
|
||||
@param [in] tgt_ds= Table that the generated cards file would create.
|
||||
Optional - if omitted, will be same as BASE_DS.
|
||||
@param [out] cards_file= Location in which to write the (.sas) cards file
|
||||
@param [in] maxobs= to limit output to the first <code>maxobs</code>
|
||||
observations
|
||||
@param [in] showlog= whether to show generated cards file in the SAS log
|
||||
(YES/NO)
|
||||
@param [in] outencoding= provide encoding value for file statement (eg utf-8)
|
||||
@param [in] append= If NO then will rebuild the cards file if it already
|
||||
@param [out] cards_file= ("%sysfunc(pathname(work))/cardgen.sas") Location in
|
||||
which to write the (.sas) cards file
|
||||
@param [in] maxobs= (max) To limit output to the first <code>maxobs</code>
|
||||
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.
|
||||
|
||||
<h4> Related Macros </h4>
|
||||
@li mp_lib2cards.sas
|
||||
@li mp_ds2inserts.sas
|
||||
@li mp_mdtablewrite.sas
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
@@ -51,15 +69,15 @@
|
||||
%if (&tgt_ds = ) %then %let tgt_ds=&base_ds;
|
||||
%if %index(&tgt_ds,.)=0 %then %let tgt_ds=WORK.%scan(&base_ds,2,.);
|
||||
%if ("&outencoding" ne "") %then %let outencoding=encoding="&outencoding";
|
||||
%if ("&append" = "") %then %let append=;
|
||||
%if ("&append" = "" or "&append" = "NO") %then %let append=;
|
||||
%else %let append=mod;
|
||||
|
||||
/* get varcount */
|
||||
%let nvars=0;
|
||||
proc sql noprint;
|
||||
select count(*) into: nvars from dictionary.columns
|
||||
where libname="%scan(%upcase(&base_ds),1)"
|
||||
and memname="%scan(%upcase(&base_ds),2)";
|
||||
where upcase(libname)="%scan(%upcase(&base_ds),1)"
|
||||
and upcase(memname)="%scan(%upcase(&base_ds),2)";
|
||||
%if &nvars=0 %then %do;
|
||||
%put %str(WARN)ING: Dataset &base_ds has no variables, will not be converted.;
|
||||
%return;
|
||||
@@ -115,8 +133,8 @@ proc sql
|
||||
reset outobs=max;
|
||||
create table datalines1 as
|
||||
select name,type,length,varnum,format,label from dictionary.columns
|
||||
where libname="%upcase(%scan(&base_ds,1))"
|
||||
and memname="%upcase(%scan(&base_ds,2))";
|
||||
where upcase(libname)="%upcase(%scan(&base_ds,1))"
|
||||
and upcase(memname)="%upcase(%scan(&base_ds,2))";
|
||||
|
||||
/**
|
||||
Due to long decimals cannot use best. format
|
||||
@@ -137,7 +155,18 @@ data datalines_2;
|
||||
,put(',name,',best32.-l)
|
||||
,substrn(put(',name,',bestd32.-l),1
|
||||
,findc(put(',name,',bestd32.-l),"0","TBK")))');
|
||||
else dataline=name;
|
||||
/**
|
||||
* binary data must be converted, to store in text format. It is identified
|
||||
* by the presence of the $HEX keyword in the format.
|
||||
*/
|
||||
else if upcase(format)=:'$HEX' then
|
||||
dataline=cats('put(trim(',name,'),',format,')');
|
||||
/**
|
||||
* There is no easy way to store line breaks in a cards file.
|
||||
* To discuss this, use: https://github.com/sasjs/core/issues/80
|
||||
* Removing all nonprintables with kw (keep writeable)
|
||||
*/
|
||||
else dataline=cats('compress(',name,', ,"kw")');
|
||||
run;
|
||||
|
||||
proc sql noprint;
|
||||
@@ -162,7 +191,8 @@ data _null_;
|
||||
|
||||
|
||||
/* Build input statement */
|
||||
if type='char' then type3=':$char.';
|
||||
if upcase(format)=:'$HEX' then type3=':'!!format;
|
||||
else if type='char' then type3=':$char.';
|
||||
str2=put(name,$33.)||type3;
|
||||
|
||||
|
||||
@@ -184,11 +214,12 @@ data _null_;
|
||||
file &cards_file. &outencoding lrecl=32767 termstr=nl &append;
|
||||
length __attrib $32767;
|
||||
if _n_=1 then do;
|
||||
put '/*******************************************************************';
|
||||
put " Datalines for %upcase(%scan(&base_ds,2)) dataset ";
|
||||
put " Generated by %nrstr(%%)mp_ds2cards()";
|
||||
put " Available on github.com/sasjs/core";
|
||||
put '********************************************************************/';
|
||||
put '/**';
|
||||
put ' @file';
|
||||
put " @brief Datalines for %upcase(%scan(&base_ds,2)) dataset";
|
||||
put " @details Generated by %nrstr(%%)mp_ds2cards()";
|
||||
put " Available on github.com/sasjs/core";
|
||||
put '**/';
|
||||
put "data &tgt_ds &indexes;";
|
||||
put "attrib ";
|
||||
%do i = 1 %to &nvars;
|
||||
@@ -212,11 +243,11 @@ data _null_;
|
||||
put 'run;';
|
||||
end;
|
||||
else do;
|
||||
put "infile cards dsd delimiter=',';";
|
||||
put "infile cards dsd;";
|
||||
put "input ";
|
||||
%do i = 1 %to &nvars.;
|
||||
%if(%length(&&input_stmt_&i..)) %then
|
||||
put " &&input_stmt_&i..";
|
||||
put " &&input_stmt_&i..";
|
||||
;
|
||||
%end;
|
||||
put ";";
|
||||
|
||||
65
base/mp_getcols.sas
Normal file
65
base/mp_getcols.sas
Normal file
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
@file
|
||||
@brief Creates a dataset with column metadata.
|
||||
@details This macro takes the `proc contents` output and "tidies it up" in the
|
||||
following ways:
|
||||
|
||||
@li Blank labels are filled in with column names
|
||||
@li Formats are reconstructed with default values
|
||||
@li Types such as DATE / TIME / DATETIME are inferred from the formats
|
||||
|
||||
Example usage:
|
||||
|
||||
%mp_getcols(sashelp.airline,outds=work.myds)
|
||||
|
||||
@param ds The dataset from which to obtain column metadata
|
||||
@param outds= (work.cols) The output dataset to create. Sample data:
|
||||
|NAME $|LENGTH 8|VARNUM 8|LABEL $|FORMAT $49|TYPE $1 |DDTYPE $|
|
||||
|---|---|---|---|---|---|---|
|
||||
|AIR|8|2|international airline travel (thousands)|8.|N|NUMERIC|
|
||||
|DATE|8|1|DATE|MONYY.|N|DATE|
|
||||
|REGION|3|3|REGION|$3.|C|CHARACTER|
|
||||
|
||||
<h4> Related Macros </h4>
|
||||
@li mf_getvarlist.sas
|
||||
@li mm_getcols.sas
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_getcols(ds, outds=work.cols);
|
||||
|
||||
proc contents noprint data=&ds
|
||||
out=_data_ (keep=name type length label varnum format:);
|
||||
run;
|
||||
data &outds(keep=name type length varnum format label ddtype);
|
||||
set &syslast(rename=(format=format2 type=type2));
|
||||
name=upcase(name);
|
||||
if type2=2 then do;
|
||||
length format $49.;
|
||||
if format2='' then format=cats('$',length,'.');
|
||||
else if formatl=0 then format=cats(format2,'.');
|
||||
else format=cats(format2,formatl,'.');
|
||||
type='C';
|
||||
ddtype='CHARACTER';
|
||||
end;
|
||||
else do;
|
||||
if format2='' then format=cats(length,'.');
|
||||
else if formatl=0 then format=cats(format2,'.');
|
||||
else if formatd=0 then format=cats(format2,formatl,'.');
|
||||
else format=cats(format2,formatl,'.',formatd);
|
||||
type='N';
|
||||
if format=:'DATETIME' then ddtype='DATETIME';
|
||||
else if format=:'DATE' or format=:'DDMMYY' or format=:'MMDDYY'
|
||||
or format=:'YYMMDD' or format=:'E8601DA' or format=:'B8601DA'
|
||||
or format=:'MONYY'
|
||||
then ddtype='DATE';
|
||||
else if format=:'TIME' then ddtype='TIME';
|
||||
else ddtype='NUMERIC';
|
||||
end;
|
||||
if label='' then label=name;
|
||||
run;
|
||||
|
||||
%mend mp_getcols;
|
||||
@@ -39,21 +39,21 @@
|
||||
/* must use SQL as proc datasets does not support length changes */
|
||||
proc sql noprint;
|
||||
create table &outds as
|
||||
select a.TABLE_CATALOG as libref
|
||||
,a.TABLE_NAME
|
||||
select upcase(a.TABLE_CATALOG) as libref
|
||||
,upcase(a.TABLE_NAME) as TABLE_NAME
|
||||
,a.constraint_type
|
||||
,a.constraint_name
|
||||
,b.column_name
|
||||
from dictionary.TABLE_CONSTRAINTS a
|
||||
left join dictionary.constraint_column_usage b
|
||||
on a.TABLE_CATALOG=b.TABLE_CATALOG
|
||||
and a.TABLE_NAME=b.TABLE_NAME
|
||||
on upcase(a.TABLE_CATALOG)=upcase(b.TABLE_CATALOG)
|
||||
and upcase(a.TABLE_NAME)=upcase(b.TABLE_NAME)
|
||||
and a.constraint_name=b.constraint_name
|
||||
where a.TABLE_CATALOG="&lib"
|
||||
and b.TABLE_CATALOG="&lib"
|
||||
where upcase(a.TABLE_CATALOG)="&lib"
|
||||
and upcase(b.TABLE_CATALOG)="&lib"
|
||||
%if "&ds" ne "" %then %do;
|
||||
and a.TABLE_NAME="&ds"
|
||||
and b.TABLE_NAME="&ds"
|
||||
and upcase(a.TABLE_NAME)="&ds"
|
||||
and upcase(b.TABLE_NAME)="&ds"
|
||||
%end;
|
||||
;
|
||||
|
||||
|
||||
@@ -211,7 +211,7 @@ run;
|
||||
proc sql noprint;
|
||||
select sysvalue into: schemaactual
|
||||
from dictionary.libnames
|
||||
where libname="&libref" and engine='SQLSVR';
|
||||
where upcase(libname)="&libref" and engine='SQLSVR';
|
||||
%let schema=%sysfunc(coalescec(&schemaactual,&schema,&libref));
|
||||
|
||||
%do x=1 %to %sysfunc(countw(&dsnlist));
|
||||
@@ -304,7 +304,7 @@ run;
|
||||
proc sql noprint;
|
||||
select sysvalue into: schemaactual
|
||||
from dictionary.libnames
|
||||
where libname="&libref" and engine='POSTGRES';
|
||||
where upcase(libname)="&libref" and engine='POSTGRES';
|
||||
%let schema=%sysfunc(coalescec(&schemaactual,&schema,&libref));
|
||||
data _null_;
|
||||
file &fref mod;
|
||||
|
||||
@@ -14,14 +14,14 @@
|
||||
We take the standard definition one step further by embedding the informat
|
||||
in the table header row, like so:
|
||||
|
||||
|var1:$|var2:best.|var3:date9.|
|
||||
|var1:$32|var2:best.|var3:date9.|
|
||||
|---|---|---|
|
||||
|some text|42|01JAN1960|
|
||||
|blah|1|31DEC1999|
|
||||
|
||||
Which resolves to:
|
||||
|
||||
|var1:$|var2:best.|var3:date9.|
|
||||
|var1:$32|var2:best.|var3:date9.|
|
||||
|---|---|---|
|
||||
|some text|42|01JAN1960|
|
||||
|blah|1|31DEC1999|
|
||||
|
||||
@@ -385,6 +385,7 @@ data _null_;
|
||||
put ' put ",""SYSERRORTEXT"" : ""&syserrortext"" "; ';
|
||||
put ' put ",""SYSHOSTNAME"" : ""&syshostname"" "; ';
|
||||
put ' put ",""SYSJOBID"" : ""&sysjobid"" "; ';
|
||||
put ' put ",""SYSSCPL"" : ""&sysscpl"" "; ';
|
||||
put ' put ",""SYSSITE"" : ""&syssite"" "; ';
|
||||
put ' sysvlong=quote(trim(symget(''sysvlong''))); ';
|
||||
put ' put '',"SYSVLONG" : '' sysvlong; ';
|
||||
|
||||
@@ -155,6 +155,7 @@
|
||||
put ",""SYSERRORTEXT"" : ""&syserrortext"" ";
|
||||
put ",""SYSHOSTNAME"" : ""&syshostname"" ";
|
||||
put ",""SYSJOBID"" : ""&sysjobid"" ";
|
||||
put ",""SYSSCPL"" : ""&sysscpl"" ";
|
||||
put ",""SYSSITE"" : ""&syssite"" ";
|
||||
sysvlong=quote(trim(symget('sysvlong')));
|
||||
put ',"SYSVLONG" : ' sysvlong;
|
||||
|
||||
41
tests/crossplatform/mp_appendfile.test.sas
Normal file
41
tests/crossplatform/mp_appendfile.test.sas
Normal file
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
@file
|
||||
@brief Testing mp_appendfile.sas macro
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mp_appendfile.sas
|
||||
@li mp_assert.sas
|
||||
|
||||
**/
|
||||
|
||||
|
||||
filename tmp1 temp;
|
||||
filename tmp2 temp;
|
||||
filename tmp3 temp;
|
||||
data _null_; file tmp1; put 'base file';
|
||||
data _null_; file tmp2; put 'append1';
|
||||
data _null_; file tmp3; put 'append2';
|
||||
run;
|
||||
%mp_appendfile(baseref=tmp1, appendrefs=tmp2 tmp3)
|
||||
data _null_;
|
||||
infile tmp1;
|
||||
input;
|
||||
put _infile_;
|
||||
call symputx(cats('check',_n_),_infile_);
|
||||
run;
|
||||
%global check1 check2 check3;
|
||||
%mp_assert(
|
||||
iftrue=("&check1"="base file"),
|
||||
desc=Line 1 of file tmp1 is correct,
|
||||
outds=work.test_results
|
||||
)
|
||||
%mp_assert(
|
||||
iftrue=("&check2"="append1"),
|
||||
desc=Line 2 of file tmp1 is correct,
|
||||
outds=work.test_results
|
||||
)
|
||||
%mp_assert(
|
||||
iftrue=("&check3"="append2"),
|
||||
desc=Line 3 of file tmp1 is correct,
|
||||
outds=work.test_results
|
||||
)
|
||||
60
tests/crossplatform/mp_ds2cards.test.sas
Normal file
60
tests/crossplatform/mp_ds2cards.test.sas
Normal file
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
@file
|
||||
@brief Testing mp_ds2cards.sas macro
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mp_ds2cards.sas
|
||||
@li mp_assert.sas
|
||||
|
||||
**/
|
||||
|
||||
/**
|
||||
* test 1 - rebuild an existing dataset
|
||||
* Cars is a great dataset - it contains leading spaces, and formatted numerics
|
||||
*/
|
||||
|
||||
%mp_ds2cards(base_ds=sashelp.cars
|
||||
, tgt_ds=work.test
|
||||
, cards_file= "%sysfunc(pathname(work))/cars.sas"
|
||||
, showlog=NO
|
||||
)
|
||||
%inc "%sysfunc(pathname(work))/cars.sas"/source2;
|
||||
|
||||
proc compare base=sashelp.cars compare=work.test;
|
||||
quit;
|
||||
|
||||
%mp_assert(
|
||||
iftrue=(&sysinfo=1),
|
||||
desc=sashelp.cars is identical except for ds label,
|
||||
outds=work.test_results
|
||||
)
|
||||
|
||||
/**
|
||||
* test 2 - binary data compare
|
||||
*/
|
||||
data work.binarybase;
|
||||
format bin $hex500. z $hex.;
|
||||
do x=1 to 250;
|
||||
z=byte(x);
|
||||
bin=trim(bin)!!z;
|
||||
output;
|
||||
end;
|
||||
run;
|
||||
|
||||
%mp_ds2cards(base_ds=work.binarybase
|
||||
, showlog=YES
|
||||
, cards_file="%sysfunc(pathname(work))/c2.sas"
|
||||
, tgt_ds=work.binarycompare
|
||||
, append=
|
||||
)
|
||||
|
||||
%inc "%sysfunc(pathname(work))/c2.sas"/source2;
|
||||
|
||||
proc compare base=work.binarybase compare=work.binarycompare;
|
||||
run;
|
||||
|
||||
%mp_assert(
|
||||
iftrue=(&sysinfo=0),
|
||||
desc=work.binarybase dataset is identical,
|
||||
outds=work.test_results
|
||||
)
|
||||
33
tests/crossplatform/mp_getcols.test.sas
Normal file
33
tests/crossplatform/mp_getcols.test.sas
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
@file
|
||||
@brief Testing mp_getcols macro
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mp_getcols.sas
|
||||
@li mp_assertcolvals.sas
|
||||
@li mp_assertdsobs.sas
|
||||
|
||||
**/
|
||||
|
||||
|
||||
/* valid filter */
|
||||
%mp_getcols(sashelp.airline,outds=work.info)
|
||||
|
||||
|
||||
%mp_assertdsobs(work.info,
|
||||
desc=Has 3 records,
|
||||
test=EQUALS 3,
|
||||
outds=work.test_results
|
||||
)
|
||||
|
||||
data work.check;
|
||||
length val $10;
|
||||
do val='NUMERIC','DATE','CHARACTER';
|
||||
output;
|
||||
end;
|
||||
run;
|
||||
%mp_assertcolvals(work.info.ddtype,
|
||||
checkvals=work.check.val,
|
||||
desc=All values have a match,
|
||||
test=ALLVALS
|
||||
)
|
||||
@@ -590,6 +590,7 @@ data _null_;
|
||||
put ' put ",""SYSCC"" : ""&syscc"" "; ';
|
||||
put ' put ",""SYSERRORTEXT"" : ""&syserrortext"" "; ';
|
||||
put ' put ",""SYSHOSTNAME"" : ""&syshostname"" "; ';
|
||||
put ' put ",""SYSSCPL"" : ""&sysscpl"" "; ';
|
||||
put ' put ",""SYSSITE"" : ""&syssite"" "; ';
|
||||
put ' sysvlong=quote(trim(symget(''sysvlong''))); ';
|
||||
put ' put '',"SYSVLONG" : '' sysvlong; ';
|
||||
|
||||
@@ -215,6 +215,7 @@
|
||||
put ",""SYSCC"" : ""&syscc"" ";
|
||||
put ",""SYSERRORTEXT"" : ""&syserrortext"" ";
|
||||
put ",""SYSHOSTNAME"" : ""&syshostname"" ";
|
||||
put ",""SYSSCPL"" : ""&sysscpl"" ";
|
||||
put ",""SYSSITE"" : ""&syssite"" ";
|
||||
sysvlong=quote(trim(symget('sysvlong')));
|
||||
put ',"SYSVLONG" : ' sysvlong;
|
||||
|
||||
Reference in New Issue
Block a user