1
0
mirror of https://github.com/sasjs/core.git synced 2026-01-05 00:20:05 +00:00

Compare commits

...

9 Commits

Author SHA1 Message Date
Allan Bowe
9e2de81dae Merge pull request #81 from sasjs/issue80
fix: removing nonprintables from cards data.  Closes #80
2021-09-27 22:42:36 +03:00
Allan Bowe
4887f355c8 fix: removing redundant dlm option 2021-09-27 20:14:52 +01:00
Allan Bowe
9b32e6e3f2 fix: removing nonprintables from cards data. Closes #80 2021-09-27 20:12:48 +01:00
Allan Bowe
74790ec80e fix: adding trim to avoid converting trailing blanks 2021-09-27 17:46:53 +01:00
Allan Bowe
afd8a754b4 Merge pull request #79 from sasjs/issue78
feat: adding binary variable support to mp_ds2cards.sas
2021-09-27 18:57:16 +03:00
Allan Bowe
bc1f7b3baa fix: updating test result and making mp_ds2cards header doxygen compliant 2021-09-27 16:39:28 +01:00
Allan Bowe
51690e68dc feat: adding binary variable support to mp_ds2cards.sas, updating documentation, and including two tests. Closes #78 2021-09-27 16:15:25 +01:00
Allan Bowe
0fa076cb73 Merge pull request #77 from sasjs/dictfix
fix: ensuring upcase comparisons for dictionary tables
2021-09-27 15:17:48 +03:00
Allan Bowe
6506993704 fix: ensuring upcase comparisons for dictionary tables 2021-09-27 13:04:32 +01:00
5 changed files with 188 additions and 66 deletions

97
all.sas
View File

@@ -3315,12 +3315,21 @@ run;
@file @file
@brief Create a CARDS file from a SAS dataset. @brief Create a CARDS file from a SAS dataset.
@details Uses dataset attributes to convert all data into datalines. @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: Usage:
%mp_ds2cards(base_ds=sashelp.class %mp_ds2cards(base_ds=sashelp.class
, tgt_ds=work.class
, cards_file= "C:\temp\class.sas" , cards_file= "C:\temp\class.sas"
, maxobs=5) , showlog=NO
, maxobs=5
)
TODO: TODO:
- labelling the dataset - labelling the dataset
@@ -3331,15 +3340,24 @@ run;
that is converted to a cards file. that is converted to a cards file.
@param [in] tgt_ds= Table that the generated cards file would create. @param [in] tgt_ds= Table that the generated cards file would create.
Optional - if omitted, will be same as BASE_DS. Optional - if omitted, will be same as BASE_DS.
@param [out] cards_file= Location in which to write the (.sas) cards file @param [out] cards_file= ("%sysfunc(pathname(work))/cardgen.sas") Location in
@param [in] maxobs= to limit output to the first <code>maxobs</code> which to write the (.sas) cards file
observations @param [in] maxobs= (max) To limit output to the first <code>maxobs</code>
@param [in] showlog= whether to show generated cards file in the SAS log observations, enter an integer here.
(YES/NO) @param [in] random_sample= (NO) Set to YES to generate a random sample of
@param [in] outencoding= provide encoding value for file statement (eg utf-8) data. Can be quite slow.
@param [in] append= If NO then will rebuild the cards file if it already @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. 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 @version 9.2
@author Allan Bowe @author Allan Bowe
@@ -3364,15 +3382,15 @@ run;
%if (&tgt_ds = ) %then %let tgt_ds=&base_ds; %if (&tgt_ds = ) %then %let tgt_ds=&base_ds;
%if %index(&tgt_ds,.)=0 %then %let tgt_ds=WORK.%scan(&base_ds,2,.); %if %index(&tgt_ds,.)=0 %then %let tgt_ds=WORK.%scan(&base_ds,2,.);
%if ("&outencoding" ne "") %then %let outencoding=encoding="&outencoding"; %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; %else %let append=mod;
/* get varcount */ /* get varcount */
%let nvars=0; %let nvars=0;
proc sql noprint; proc sql noprint;
select count(*) into: nvars from dictionary.columns select count(*) into: nvars from dictionary.columns
where libname="%scan(%upcase(&base_ds),1)" where upcase(libname)="%scan(%upcase(&base_ds),1)"
and memname="%scan(%upcase(&base_ds),2)"; and upcase(memname)="%scan(%upcase(&base_ds),2)";
%if &nvars=0 %then %do; %if &nvars=0 %then %do;
%put %str(WARN)ING: Dataset &base_ds has no variables, will not be converted.; %put %str(WARN)ING: Dataset &base_ds has no variables, will not be converted.;
%return; %return;
@@ -3428,8 +3446,8 @@ proc sql
reset outobs=max; reset outobs=max;
create table datalines1 as create table datalines1 as
select name,type,length,varnum,format,label from dictionary.columns select name,type,length,varnum,format,label from dictionary.columns
where libname="%upcase(%scan(&base_ds,1))" where upcase(libname)="%upcase(%scan(&base_ds,1))"
and memname="%upcase(%scan(&base_ds,2))"; and upcase(memname)="%upcase(%scan(&base_ds,2))";
/** /**
Due to long decimals cannot use best. format Due to long decimals cannot use best. format
@@ -3450,7 +3468,18 @@ data datalines_2;
,put(',name,',best32.-l) ,put(',name,',best32.-l)
,substrn(put(',name,',bestd32.-l),1 ,substrn(put(',name,',bestd32.-l),1
,findc(put(',name,',bestd32.-l),"0","TBK")))'); ,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; run;
proc sql noprint; proc sql noprint;
@@ -3475,7 +3504,8 @@ data _null_;
/* Build input statement */ /* 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; str2=put(name,$33.)||type3;
@@ -3497,11 +3527,12 @@ data _null_;
file &cards_file. &outencoding lrecl=32767 termstr=nl &append; file &cards_file. &outencoding lrecl=32767 termstr=nl &append;
length __attrib $32767; length __attrib $32767;
if _n_=1 then do; if _n_=1 then do;
put '/*******************************************************************'; put '/**';
put " Datalines for %upcase(%scan(&base_ds,2)) dataset "; put ' @file';
put " Generated by %nrstr(%%)mp_ds2cards()"; put " @brief Datalines for %upcase(%scan(&base_ds,2)) dataset";
put " Available on github.com/sasjs/core"; put " @details Generated by %nrstr(%%)mp_ds2cards()";
put '********************************************************************/'; put " Available on github.com/sasjs/core";
put '**/';
put "data &tgt_ds &indexes;"; put "data &tgt_ds &indexes;";
put "attrib "; put "attrib ";
%do i = 1 %to &nvars; %do i = 1 %to &nvars;
@@ -3525,11 +3556,11 @@ data _null_;
put 'run;'; put 'run;';
end; end;
else do; else do;
put "infile cards dsd delimiter=',';"; put "infile cards dsd;";
put "input "; put "input ";
%do i = 1 %to &nvars.; %do i = 1 %to &nvars.;
%if(%length(&&input_stmt_&i..)) %then %if(%length(&&input_stmt_&i..)) %then
put " &&input_stmt_&i.."; put " &&input_stmt_&i..";
; ;
%end; %end;
put ";"; put ";";
@@ -4412,21 +4443,21 @@ run;
/* must use SQL as proc datasets does not support length changes */ /* must use SQL as proc datasets does not support length changes */
proc sql noprint; proc sql noprint;
create table &outds as create table &outds as
select a.TABLE_CATALOG as libref select upcase(a.TABLE_CATALOG) as libref
,a.TABLE_NAME ,upcase(a.TABLE_NAME) as TABLE_NAME
,a.constraint_type ,a.constraint_type
,a.constraint_name ,a.constraint_name
,b.column_name ,b.column_name
from dictionary.TABLE_CONSTRAINTS a from dictionary.TABLE_CONSTRAINTS a
left join dictionary.constraint_column_usage b left join dictionary.constraint_column_usage b
on a.TABLE_CATALOG=b.TABLE_CATALOG on upcase(a.TABLE_CATALOG)=upcase(b.TABLE_CATALOG)
and a.TABLE_NAME=b.TABLE_NAME and upcase(a.TABLE_NAME)=upcase(b.TABLE_NAME)
and a.constraint_name=b.constraint_name and a.constraint_name=b.constraint_name
where a.TABLE_CATALOG="&lib" where upcase(a.TABLE_CATALOG)="&lib"
and b.TABLE_CATALOG="&lib" and upcase(b.TABLE_CATALOG)="&lib"
%if "&ds" ne "" %then %do; %if "&ds" ne "" %then %do;
and a.TABLE_NAME="&ds" and upcase(a.TABLE_NAME)="&ds"
and b.TABLE_NAME="&ds" and upcase(b.TABLE_NAME)="&ds"
%end; %end;
; ;
@@ -4977,7 +5008,7 @@ run;
proc sql noprint; proc sql noprint;
select sysvalue into: schemaactual select sysvalue into: schemaactual
from dictionary.libnames from dictionary.libnames
where libname="&libref" and engine='SQLSVR'; where upcase(libname)="&libref" and engine='SQLSVR';
%let schema=%sysfunc(coalescec(&schemaactual,&schema,&libref)); %let schema=%sysfunc(coalescec(&schemaactual,&schema,&libref));
%do x=1 %to %sysfunc(countw(&dsnlist)); %do x=1 %to %sysfunc(countw(&dsnlist));
@@ -5070,7 +5101,7 @@ run;
proc sql noprint; proc sql noprint;
select sysvalue into: schemaactual select sysvalue into: schemaactual
from dictionary.libnames from dictionary.libnames
where libname="&libref" and engine='POSTGRES'; where upcase(libname)="&libref" and engine='POSTGRES';
%let schema=%sysfunc(coalescec(&schemaactual,&schema,&libref)); %let schema=%sysfunc(coalescec(&schemaactual,&schema,&libref));
data _null_; data _null_;
file &fref mod; file &fref mod;

View File

@@ -2,12 +2,21 @@
@file @file
@brief Create a CARDS file from a SAS dataset. @brief Create a CARDS file from a SAS dataset.
@details Uses dataset attributes to convert all data into datalines. @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: Usage:
%mp_ds2cards(base_ds=sashelp.class %mp_ds2cards(base_ds=sashelp.class
, tgt_ds=work.class
, cards_file= "C:\temp\class.sas" , cards_file= "C:\temp\class.sas"
, maxobs=5) , showlog=NO
, maxobs=5
)
TODO: TODO:
- labelling the dataset - labelling the dataset
@@ -18,15 +27,24 @@
that is converted to a cards file. that is converted to a cards file.
@param [in] tgt_ds= Table that the generated cards file would create. @param [in] tgt_ds= Table that the generated cards file would create.
Optional - if omitted, will be same as BASE_DS. Optional - if omitted, will be same as BASE_DS.
@param [out] cards_file= Location in which to write the (.sas) cards file @param [out] cards_file= ("%sysfunc(pathname(work))/cardgen.sas") Location in
@param [in] maxobs= to limit output to the first <code>maxobs</code> which to write the (.sas) cards file
observations @param [in] maxobs= (max) To limit output to the first <code>maxobs</code>
@param [in] showlog= whether to show generated cards file in the SAS log observations, enter an integer here.
(YES/NO) @param [in] random_sample= (NO) Set to YES to generate a random sample of
@param [in] outencoding= provide encoding value for file statement (eg utf-8) data. Can be quite slow.
@param [in] append= If NO then will rebuild the cards file if it already @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. 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 @version 9.2
@author Allan Bowe @author Allan Bowe
@@ -51,15 +69,15 @@
%if (&tgt_ds = ) %then %let tgt_ds=&base_ds; %if (&tgt_ds = ) %then %let tgt_ds=&base_ds;
%if %index(&tgt_ds,.)=0 %then %let tgt_ds=WORK.%scan(&base_ds,2,.); %if %index(&tgt_ds,.)=0 %then %let tgt_ds=WORK.%scan(&base_ds,2,.);
%if ("&outencoding" ne "") %then %let outencoding=encoding="&outencoding"; %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; %else %let append=mod;
/* get varcount */ /* get varcount */
%let nvars=0; %let nvars=0;
proc sql noprint; proc sql noprint;
select count(*) into: nvars from dictionary.columns select count(*) into: nvars from dictionary.columns
where libname="%scan(%upcase(&base_ds),1)" where upcase(libname)="%scan(%upcase(&base_ds),1)"
and memname="%scan(%upcase(&base_ds),2)"; and upcase(memname)="%scan(%upcase(&base_ds),2)";
%if &nvars=0 %then %do; %if &nvars=0 %then %do;
%put %str(WARN)ING: Dataset &base_ds has no variables, will not be converted.; %put %str(WARN)ING: Dataset &base_ds has no variables, will not be converted.;
%return; %return;
@@ -115,8 +133,8 @@ proc sql
reset outobs=max; reset outobs=max;
create table datalines1 as create table datalines1 as
select name,type,length,varnum,format,label from dictionary.columns select name,type,length,varnum,format,label from dictionary.columns
where libname="%upcase(%scan(&base_ds,1))" where upcase(libname)="%upcase(%scan(&base_ds,1))"
and memname="%upcase(%scan(&base_ds,2))"; and upcase(memname)="%upcase(%scan(&base_ds,2))";
/** /**
Due to long decimals cannot use best. format Due to long decimals cannot use best. format
@@ -137,7 +155,18 @@ data datalines_2;
,put(',name,',best32.-l) ,put(',name,',best32.-l)
,substrn(put(',name,',bestd32.-l),1 ,substrn(put(',name,',bestd32.-l),1
,findc(put(',name,',bestd32.-l),"0","TBK")))'); ,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; run;
proc sql noprint; proc sql noprint;
@@ -162,7 +191,8 @@ data _null_;
/* Build input statement */ /* 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; str2=put(name,$33.)||type3;
@@ -184,11 +214,12 @@ data _null_;
file &cards_file. &outencoding lrecl=32767 termstr=nl &append; file &cards_file. &outencoding lrecl=32767 termstr=nl &append;
length __attrib $32767; length __attrib $32767;
if _n_=1 then do; if _n_=1 then do;
put '/*******************************************************************'; put '/**';
put " Datalines for %upcase(%scan(&base_ds,2)) dataset "; put ' @file';
put " Generated by %nrstr(%%)mp_ds2cards()"; put " @brief Datalines for %upcase(%scan(&base_ds,2)) dataset";
put " Available on github.com/sasjs/core"; put " @details Generated by %nrstr(%%)mp_ds2cards()";
put '********************************************************************/'; put " Available on github.com/sasjs/core";
put '**/';
put "data &tgt_ds &indexes;"; put "data &tgt_ds &indexes;";
put "attrib "; put "attrib ";
%do i = 1 %to &nvars; %do i = 1 %to &nvars;
@@ -212,11 +243,11 @@ data _null_;
put 'run;'; put 'run;';
end; end;
else do; else do;
put "infile cards dsd delimiter=',';"; put "infile cards dsd;";
put "input "; put "input ";
%do i = 1 %to &nvars.; %do i = 1 %to &nvars.;
%if(%length(&&input_stmt_&i..)) %then %if(%length(&&input_stmt_&i..)) %then
put " &&input_stmt_&i.."; put " &&input_stmt_&i..";
; ;
%end; %end;
put ";"; put ";";

View File

@@ -39,21 +39,21 @@
/* must use SQL as proc datasets does not support length changes */ /* must use SQL as proc datasets does not support length changes */
proc sql noprint; proc sql noprint;
create table &outds as create table &outds as
select a.TABLE_CATALOG as libref select upcase(a.TABLE_CATALOG) as libref
,a.TABLE_NAME ,upcase(a.TABLE_NAME) as TABLE_NAME
,a.constraint_type ,a.constraint_type
,a.constraint_name ,a.constraint_name
,b.column_name ,b.column_name
from dictionary.TABLE_CONSTRAINTS a from dictionary.TABLE_CONSTRAINTS a
left join dictionary.constraint_column_usage b left join dictionary.constraint_column_usage b
on a.TABLE_CATALOG=b.TABLE_CATALOG on upcase(a.TABLE_CATALOG)=upcase(b.TABLE_CATALOG)
and a.TABLE_NAME=b.TABLE_NAME and upcase(a.TABLE_NAME)=upcase(b.TABLE_NAME)
and a.constraint_name=b.constraint_name and a.constraint_name=b.constraint_name
where a.TABLE_CATALOG="&lib" where upcase(a.TABLE_CATALOG)="&lib"
and b.TABLE_CATALOG="&lib" and upcase(b.TABLE_CATALOG)="&lib"
%if "&ds" ne "" %then %do; %if "&ds" ne "" %then %do;
and a.TABLE_NAME="&ds" and upcase(a.TABLE_NAME)="&ds"
and b.TABLE_NAME="&ds" and upcase(b.TABLE_NAME)="&ds"
%end; %end;
; ;

View File

@@ -211,7 +211,7 @@ run;
proc sql noprint; proc sql noprint;
select sysvalue into: schemaactual select sysvalue into: schemaactual
from dictionary.libnames from dictionary.libnames
where libname="&libref" and engine='SQLSVR'; where upcase(libname)="&libref" and engine='SQLSVR';
%let schema=%sysfunc(coalescec(&schemaactual,&schema,&libref)); %let schema=%sysfunc(coalescec(&schemaactual,&schema,&libref));
%do x=1 %to %sysfunc(countw(&dsnlist)); %do x=1 %to %sysfunc(countw(&dsnlist));
@@ -304,7 +304,7 @@ run;
proc sql noprint; proc sql noprint;
select sysvalue into: schemaactual select sysvalue into: schemaactual
from dictionary.libnames from dictionary.libnames
where libname="&libref" and engine='POSTGRES'; where upcase(libname)="&libref" and engine='POSTGRES';
%let schema=%sysfunc(coalescec(&schemaactual,&schema,&libref)); %let schema=%sysfunc(coalescec(&schemaactual,&schema,&libref));
data _null_; data _null_;
file &fref mod; file &fref mod;

View 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
)