Getting information about your SAS-installation

Below is a couple of commands that can be used to get information about your SAS-installation.

proc setinit;
run;

The Setinit-command will give you information about the SAS-products that your SAS-installation is licensed for.

proc product_status;
run;

The Product_status-command will give you information about the SAS-products installed on your system.
Keep in mind that there could be a difference between what’s licensed on your system and what’s actually installed.
The commands above will only give you information about the products installed for base-SAS. If you want information about what other SAS-products is installed on you system, then you can get this SAS-program from SAS and run it on your system.

SAS Encode Password

The code below lets you encode a password in SAS, based on the methods for encoding passwords available in SAS.
You can see a description of the methods for encoding below. Be aware that a password encoded with base64 (sas001) can be very easily discovered. See this blogpost of how to decode BASE64 directly in SAS.

SASPWEncode

Be aware of this website (Thanks Dmitriy for the comment below). That shows how to decode all passwords encoded with the above methods in SAS. And this older website, not showing the exact method but outlining the concept of how to do it.
If the website is not available, I have saved the webpage as pdf.

Encoding vs. encryption
PROC PWENCODE uses encoding to disguise passwords. With encoding, one character set is translated to another character set through some form of table lookup. Encryption, by contrast, involves the transformation of data from one form to another through the use of mathematical operations and, usually, a “key” value. Encryption is generally more difficult to break than encoding. PROC PWENCODE is intended to prevent casual, non-malicious viewing of passwords. You should not depend on PROC PWENCODE for all your data security needs; a determined and knowledgeable attacker can decode the encoded passwords.

Encoding is a process of converting one set of meaningful characters into another set. By converting into a different set the characters become unreadable and the meanings of the characters are disguised from the public.
Encryption is a method to transform data from plain text to cipher text through the use of a mathematical algorithmic scheme. Any plain text through encryption process becomes cipher text and is illegible to anyone without a special key.
Though both processes involve converting data from one format to another, encoding process is designed for disguising the data to be revealed casually. Encoding and decoding processes do not require a special key. On the other hand, encryption method is used to protect data from to be revealed to anyone other than the intended recipient. In order to read the encrypted text both the encryption key and the mathematical algorithm are required.
Encoding is intended to disguise data from to be revealed in public. For our purpose, it works well to prevent casual, non-malicious viewing of password in the SAS programs. Because of the special key and mathematical algorithm involved encryption is generally more difficult to break. It is designed for maintaining data confidentiality. Encoding and encryption are developed for different purposes. One should not replace another.

The code below saves a file to drive containing a %let statement with the encoded password.

%macro EncodePwd(SavePath, Method);

        filename clp clipbrd;

        proc pwencode in="&Password." out=clp method=&Method.;
        run;

        data _null_;
            infile clp length=len;
            length encodedPW $100;
            input ;
            encodedPW = substr(_infile_, 1, len); /* Remove linefeed */
            call symput('encodedPW', compress(encodedPW));
        run;
        filename clp clear;

        filename encoded "&SavePath.";
        data _NULL_;
                file encoded(pwd.sas);
                tmp='%let pw='||"&encodedPW"||';';
                put tmp;
        run;
        filename encoded clear;
%mend;
%EncodePwd(C:\, sas001);
%EncodePwd(D:\, sas002);

 

Reading data from api.statbank.dk into SAS

The code below lets you read data from api.statbank.dk into SAS. api.statbank.dk is an api provided by Statistics Denmark (Danmarks Statistik) for reading their public data.

The code below gets it’s data from the table FOLK1. You can get a list of avaliable tables from this url – http://api.statbank.dk/v1/tables?format=CSV. The code also sets some filters on the tables, some of the information used in this post comes from here.

filename dst
url
'http://api.statbank.dk/v1/data/Folk1/CSV?valuePresentation=Value&Tid=*&K%C3%B8n=*&Omr%C3%A5de=*&alder=*'
                        proxy='http://<your proxy:and port>/'
                        debug
                        lrecl=8192
                        puser="<your userid to proxy>"
                        ppass="<your password to proxy>";

data test;
        infile dst dlm=';' encoding="utf-8";
        format Quarter Sex Municipality Age $50.;
        input Quarter $ Sex $ Municipality $ Age $ Count;
run;

 

Reading multiple sheets from Excel in SAS

The code below let’s you import the sheets found in an Excel-file to SAS datasets.

/*
ExcelFile: The Excell-file containing the sheets that you want to import.
Prefix: Prefix of the datasets imported into work.
*/

%macro ReadExcelSheets(ExcelFile=, Prefix=, OutLib=work);

     libname XLData "&ExcelFile." access=readonly;

     %let Delimitor = ¤;

     proc sql noprint;
       select compress(memname,"$") into :Sheets separated by '&Delimitor.'
             from dictionary.tables
             where libname = "XLDATA" and upcase(memname) like '%$'
       ;
     quit;

     %put Sheets          : &Sheets.;
     %let NumberOfSheets = %sysfunc(countw(&Sheets., &Delimitor.));
     %put Number of sheets: &NumberOfSheets.;

     %do J=1 %to &NumberOfSheets.;
         %let Sheet = %scan(&Sheets., &J, &Delimitor.);
         %put Processing: &Sheet.;

         %if %length(&Prefix) %then %let Prefix = &Prefix._;
         proc import out=&OutLib..&Prefix.&Sheet. datafile="&ExcelFile."
                dbms=xls replace;
                sheet="&Sheet.";
                getnames=yes;
         run;

     %end;

     libname XLData clear;
%mend ReadExcelSheets;

%ReadExcelSheets(ExcelFile=c:\temp\excelfile.xls, Prefix=test);

 

ODS tagsets.ExcelXP

Creating files for Excel in SAS can be done in different ways. One of the ways is to use the ODS tagsets.ExcelXP. The code below shows how to use the ODS tagsets.ExcelXP. It creates an XML-file that conforms to the Microsoft XML Spreadsheet Specifications and can be opened with Microsoft Excel 2002 (also known as Excel XP) or later versions.
Compared to other ways of creating Excel-files in SAS the ODS tagsets.ExcelXP is very diverse and has a lot of options for doing extra things in your Excel-sheets – like frozen headers etc.
NB! One downside of using ODS tagsets.ExcelXP is that is do not support graphics.

The code below manually creates two sheets in the Excel-file, populates them with data through PROC REPORT (eg PROC PRINT can also be used) and gives two columns (variables) a different color.

ods listing close;

/*
This prevents the open and save dialog from showing when the Excel-file
is finished processing. This has to be used if you automatically want to 
convert the .XML file to .XLS or.XLSX.
*/
ods noresults;

ods tagsets.ExcelXP file="c:\output\ExcelFile.xml" style=Meadow
options(sheet_name="001_Sheet" autofilter='all'
frozen_headers='yes' width_fudge='.8' hidden_columns='');
        proc report data=sashelp.class nowd;
                columns _all_;
                compute Age;
                    call define(_COL_, 'style', 'style=[background=LightGray]');
                endcomp;
                compute Height;
                   call define(_COL_, 'style', 'style=[background=LightGray]');
                endcomp;
        run;

ods tagsets.excelxp options(sheet_name=
"002_Sheet" autofilter='all' frozen_headers='yes' width_fudge='.8' hidden_columns='');
        proc report data=sashelp.class nowd;
               columns _all_;
        run;

ods tagsets.excelxp close;
ods listing;

The Excel-XML files created can become very big, that’s why it’s sometimes a good idea to convert it to a native Excel-format like .XLS or .XLSX and still keep the changes made to the Excel-sheets. The macro below will convert the XML-files found in a folder to .XLS or .XLSX files using VBS (Visual Basic Script).
There can be a problem using the .VBS-script, if SAS is set to automatically start Excel to show the created file. The problem is that SAS is starting Excel to show the just created file, but also creates a lock on the file, that can collide with the .VBS-scrip trying to modify the file. This option for starting Excel can be removed in the SAS editor (Display Manager). Go to Tools -> Options -> Preferences -> Results (tab) and remove the tick from “View results as they are generated“. This will stop SAS from starting Excel when the .XML-file is created.

%
******************************************************************************************;
%*
%*  MACRO: CONVERT_FILES
%*
%*  USAGE: %convert_files(arg1=value, arg2=value, ... );
%*
%*  DESCRIPTION:
%*    This macro is used to convert files generated with ODS destinations
%*    to native Excel files. For more information see the below link.
%*    http://www.pharmasug.org/proceedings/2011/SAS/PharmaSUG-2011-SAS-TT02.pdf?page=12
%*
%*
%*
%*
%*******************************************************************************************;
options noxsync noxwait;
/* Example: %ConvertXML(Default=&SASProgramPath.\output\,
Store=&SASProgramPath.\output\temp.vbs); */
%macro ConvertXML(default=c:\temp\convert, store=c:\temp\, ext=xml);
  data _null_;
     file "&store";
     put " Dim oXL ";
     put " Dim oFolder";
     put " Dim aFile";
     put " Dim FSO";
     put " Set oXL = CreateObject(""Excel.Application"")";
     put " Set FSO = CreateObject(""Scripting.FileSystemObject"")";
     put " oXL.DefaultFilePath = ""&default""";
     put " oXL.DisplayAlerts = False";
     put "  if FSO.FolderExists(oXL.DefaultFilePath) then";
     put "   Set oFolder = FSO.GetFolder(oXL.DefaultFilePath)";
     put "     For each aFile in oFolder.Files ";
     put "       If Right(LCase(aFile.Name), 4) = "".&ext"" Then";
     put "        oXL.Workbooks.Open(aFile.Name)";
     put "        oXL.Visible = false";
     put "        if (oXL.Version) >= 12 Then" ;
     put "        oXL.ActiveWorkBook.SaveAs Left(aFile, Len(aFile) -4) & "".xlsx"",51";
     put "    Else";
     put "       oXL.ActiveWorkBook.SaveAs Left(aFile, Len(aFile) -4) & "".xls"",-4143";
     put "   End If";
     put " oXL.ActiveWorkBook.Close SaveChanges = True";
     put " End If";
     put " Next";
     put " Set oFolder = Nothing";
     put " end if";
     put " FSO.DeleteFile(""&default\*.&ext""), DeleteReadOnly";
     put " oXL.DisplayAlerts = True";
     put " oXL.Quit";
     put " Set oXL = Nothing";
     call system("&store");
 run;
%mend;

If you don’t want to remove the tick in  “View results as they are generated” a little change in the program above is possible.

%
******************************************************************************************;
%*
%*  MACRO: CONVERT_FILES
%*
%*  USAGE: %convert_files(arg1=value, arg2=value, ... );
%*
%*  DESCRIPTION:
%*    This macro is used to convert files generated with ODS destinations
%*    to native Excel files. For more information see the below link.
%*    http://www.pharmasug.org/proceedings/2011/SAS/PharmaSUG-2011-SAS-TT02.pdf?page=12
%*
%*
%*
%*
%*******************************************************************************************;
options noxsync noxwait;
/* Example: %ConvertXML(Default=&SASProgramPath.\output\,
Store=&SASProgramPath.\output\temp.vbs); */
%macro ConvertXML(default=c:\temp\convert, store=c:\temp\, ext=xml);
  data _null_;
     file "&store";
     put " Dim oXL ";
     put " Dim oFolder";
     put " Dim aFile";
     put " Dim FSO";
     put " Set oXL = CreateObject(""Excel.Application"")";
     put " Set FSO = CreateObject(""Scripting.FileSystemObject"")";
     put " oXL.DefaultFilePath = ""&default""";
     put " oXL.DisplayAlerts = False";
     put "  if FSO.FolderExists(oXL.DefaultFilePath) then";
     put "   Set oFolder = FSO.GetFolder(oXL.DefaultFilePath)";
     put "     For each aFile in oFolder.Files ";
     put "       If Right(LCase(aFile.Name), 4) = "".&ext"" Then";
     put "        oXL.Workbooks.Open(aFile.Name)";
     put "        oXL.Visible = false";
     put "        if (oXL.Version) >= 12 Then" ;
     put "        oXL.ActiveWorkBook.SaveAs Left(aFile, Len(aFile) -4) & "".xlsx"",51";
     put "    Else";
     put "       oXL.ActiveWorkBook.SaveAs Left(aFile, Len(aFile) -4) & "".xls"",-4143";
     put "   End If";
     put " oXL.ActiveWorkBook.Close SaveChanges = True";
     put " End If";
     put " Next";
     put " Set oFolder = Nothing";
     put " end if";
     put " FSO.DeleteFile(""&default\*.&ext""), DeleteReadOnly";
     put " oXL.DisplayAlerts = True";
     put " oXL.Quit";
     put " Set oXL = Nothing";
     *call system("&store");
 run;

x "cscript ""&store""";
%mend;

You replace the call system with and x-command. If you use this option the XML-files created will still be locked and can therefore not be deleted. But it is possible to create the .XLS or.XLSX file.

SYSCC

SYSCC contains the current condition code that SAS returns to your operating environment and it can be used to check the error-state of your SAS-program.
But SYSCC doesn’t catch all SAS errors. If you want it to catch more SAS errors you need to set the option ERRORCHECK=STRICT.

options errorcheck=strict;

Setting this option will catch errors in libname, filename and include statements in SAS.

 

 

 

Finding unique and dublicates in SAS

The code below shows you how to find unique and duplicate values in a dataset and get them seperated into two different datasets.
The variables you want to examin for uniqueness has to be in the by-statement and each have an not(first.<variable> and last.variable). Be aware that in SAS 9.3 there is an easier solution using proc sort.

data unique dups;
 set sashelp.class;
 by Age Height Name Weight;
 if not(first.Age and last.Age) 
 and not(first.Height and last.Height) 
 and not(first.Name and last.Name)
 and not(first.weight and last.Weight) then output dups;
 else output unique;
run;

This code is different than using proc sort prior to SAS 9.3

proc sort data=sashelp.class nodupkey out=unique dupout=dups;
 by Age Height Name Weight;
run;

The code above will take the first of the dublicates and put it into the unique-dataset. It will not completely seperate unique and duplicate rows from each other.

In SAS 9.3 proc sort has a new parameter uniqueout. This can be used to do the trick of the datastep much easier. I haven’t tried it, but I imagine that this is how it works.

proc sort data=sashelp.class nouniquekeys uniqueout=singles out=dublet; 
 by Age Height Name Weight;
run;

 

Get overview of distinct values in dataset

The code below will make distinct values of all the variables in a dataset and list them besides each other for a better overview.

%let Delimitor = ¤;

%macro PrepareData(InLib=, Inds=);
 %let InLib = %upcase(&InLib);
 %let InDS = %upcase(&InDS);

 proc sql noprint;
  create table CON_&InDS. as
   select *
   from dictionary.columns
   where upcase(LibName) eq "&InLib." and upcase(MemName) eq "&InDS.";
  quit;

  %global Columns;

  proc sql noprint;
   select name into :Columns separated by "&Delimitor"
   from CON_&InDS.;
  quit;

  %put Columns: &Columns;

  proc sql noprint;
   create table RES_&InDS. like &InDS.;
  quit;

  data RES_&InDS. (drop = i);
   do i = 1 to 200;
    ID + 1;
    output;
   end;
   set RES_&InDS.;
  run;
%mend;

%macro MakeDistinct(InDS=,OutDS=);
 %let NumberOfColumns = %sysfunc(countw(&Columns., &Delimitor.));
 %put Number of columns: &NumberOfColumns.;

 %do J=1 %to &NumberOfColumns.;
  %let Column = %scan(&Columns., &J, &Delimitor.);
  %put Processing: &Column.;

  proc sql noprint;
   create table tmpDS as
    select distinct(&Column.) as &Column.
    from &InDS.;
  quit;

  data tmpDS;
   ID + 1;
   set tmpDS;
  run;

  data &OutDS.;
   merge &OutDS. (in=a) tmpDS (in=b);
   by id;
  run;
 %end;
%mend;

%PrepareData(InDS=sashelp, Inds=class);
%MakeDistinct(InDS=class, OutDS=Result_Class);

Copy .sas files (program files) with SAS

The code below copies all the SAS-programs (*.sas) files in a directory to another directory. This solution should be used if you don’t want to use an OS-command that copies the files. Using an OS-command is a lot easier and doesn’t require as much code. But of course depends on the OS your running on. This solution is OS independent.

%let SourcePath = C:\test\source;
%let DestinationPath = C:\test\destination;

/*
Reads the .sas files in &location.
*/
%macro GetFilenames(location);
 filename _dir_ "%bquote(&location.)";
 data filenames(keep=memname);
  handle=dopen( '_dir_' );
  if handle > 0 then do;
   count=dnum(handle);
   do i=1 to count;
    memname=dread(handle,i);
    output filenames;
   end;
  end;
  rc=dclose(handle);
 run;

 filename _dir_ clear;
%mend;

%GetFilenames(&SourcePath.);

/* We only want SAS-files and not SAS-datasets. */
data filenames;
 set filenames;
 if index(memname, '.sas') eq 0 or index(memname, '.sas7bdat') eq 1 then delete;
run;

%let Delimitor = ¤;

proc sql noprint;
 select memname into :Files separated by "&Delimitor"
 from FileNames;
quit;

%put Files to copy: &Files;

%macro CopySASFiles;
 %let NumberOfFiles = %sysfunc(countw(&Files., &Delimitor.));
 %put Number of files: &NumberOfFiles.;

 %do J=1 %to &NumberOfFiles.;
  %let File = %scan(&Files., &J, &Delimitor.);
  %put Copying SAS-program: &File.;

  data _null_;
   infile "&SourcePath.\&File."  lrecl=32767;
   file "&DestinationPath.\&File.";
   input;
   put _infile_;
  run;
 %end;
%mend;
%CopySASFiles;