diff --git a/SQLinDS/000_libname/dssql.sas b/SQLinDS/000_libname/dssql.sas new file mode 100644 index 0000000..3281737 --- /dev/null +++ b/SQLinDS/000_libname/dssql.sas @@ -0,0 +1,41 @@ +/*** HELP START ***/ + +/* >>> dsSQL library: <<< + * + * The dsSQL library stores temporary views + * generated during %SQL() macro's execution. + * If possible, created as a subdirectory of WORK: + + options dlCreateDir; + LIBNAME dsSQL BASE "%sysfunc(pathname(WORK))/dsSQLtmp"; + + * if not then redirected to WORK + + LIBNAME dsSQL BASE "%sysfunc(pathname(WORK))"; + +**/ + +/*** HELP END ***/ + +data WORK._%sysfunc(datetime(), hex16.)_; + length option $ 64; + option = getoption("dlCreateDir"); +run; + +options dlCreateDir; + +data _null_; + set _LAST_; + rc1 = LIBNAME("dsSQL", "%sysfunc(pathname(work))/dsSQLtmp", "BASE"); + rc2 = LIBREF("dsSQL"); + if rc2 NE 0 then + rc1 = LIBNAME("dsSQL", "%sysfunc(pathname(work))", "BASE"); + call execute ("options " || strip(option) || ";"); +run; + +proc delete data = _last_; +run; + +libname dsSQL LIST; +; + diff --git a/SQLinDS/001_macro/dssql_inner.sas b/SQLinDS/001_macro/dssql_inner.sas new file mode 100644 index 0000000..5066b71 --- /dev/null +++ b/SQLinDS/001_macro/dssql_inner.sas @@ -0,0 +1,29 @@ +/*** HELP START ***/ + +/* >>> %dsSQL_Inner() macro: <<< + * + * Internal macro called by dsSQL() function. + * + * Recomnended for SAS 9.3 and higher. + * Based on paper: + * "Use the Full Power of SAS in Your Function-Style Macros" + * by Mike Rhoads, Westat, Rockville, MD + * https://support.sas.com/resources/papers/proceedings12/004-2012.pdf + * +**/ + +/*** HELP END ***/ + +/* inner macro */ +%MACRO dsSQL_Inner() / SECURE; + %local query; + %let query = %superq(query_arg); + %let query = %sysfunc(dequote(&query)); + + %let viewname = dsSQL.dsSQLtmpview&UNIQUE_INDEX_2.; + proc sql; + create view &viewname as + &query + ; + quit; +%MEND dsSQL_Inner; diff --git a/SQLinDS/001_macro/sql.sas b/SQLinDS/001_macro/sql.sas new file mode 100644 index 0000000..07937d1 --- /dev/null +++ b/SQLinDS/001_macro/sql.sas @@ -0,0 +1,50 @@ +/*** HELP START ***/ + +/* >>> %SQL() macro: <<< + * + * Main macro which allows to use + * SQL's queries in the data step. + * Recomnended for SAS 9.3 and higher. + * Based on paper: + * "Use the Full Power of SAS in Your Function-Style Macros" + * by Mike Rhoads, Westat, Rockville, MD + * https://support.sas.com/resources/papers/proceedings12/004-2012.pdf + * + * EXAMPLE 1: simple sql querry + + data class_subset; + set %SQL(select name, sex, height from sashelp.class where age > 12); + run; + + * EXAMPLE 2: with dataset options + + data renamed; + set %SQL(select * from sashelp.class where sex = "F")(rename = (age=age2)); + run; + + * EXAMPLE 3: dictionaries in datastep + + data dictionary; + set %SQL(select * from dictionary.macros); + run; + +**/ + +/*** HELP END ***/ + + +/* outer macro */ +%MACRO SQL() / PARMBUFF SECURE; + %let SYSPBUFF = %superq(SYSPBUFF); /* macroquoting */ + %let SYSPBUFF = %substr(&SYSPBUFF, 2, %LENGTH(&SYSPBUFF) - 2); /* remove brackets */ + %let SYSPBUFF = %superq(SYSPBUFF); /* macroquoting */ + %let SYSPBUFF = %sysfunc(quote(&SYSPBUFF)); /* quotes */ + %put NOTE-***the querry***; /* print out the querry in the log */ + %put NOTE-&SYSPBUFF.; + %put NOTE-****************; + + %local UNIQUE_INDEX; /* internal variable, a unique index for views */ + %let UNIQUE_INDEX = &SYSINDEX; + %sysfunc(dsSQL(&UNIQUE_INDEX, &SYSPBUFF)) /* <-- call dsSQL() function, + see the WORK.SQLinDSfcmp dataset */ +%MEND SQL; diff --git a/SQLinDS/002_function/dssql.sas b/SQLinDS/002_function/dssql.sas new file mode 100644 index 0000000..c73b1c6 --- /dev/null +++ b/SQLinDS/002_function/dssql.sas @@ -0,0 +1,40 @@ +/*** HELP START ***/ + +/* >>> dsSQL() function: <<< + * + * Internal function called by %SQL() macro. + * + * Recomnended for SAS 9.3 and higher. + * Based on paper: + * "Use the Full Power of SAS in Your Function-Style Macros" + * by Mike Rhoads, Westat, Rockville, MD + * https://support.sas.com/resources/papers/proceedings12/004-2012.pdf + * +**/ + +/*** HELP END ***/ + +proc fcmp + inlib = work.&packageName.fcmp + outlib = work.&packageName.fcmp.package +; + function dsSQL(unique_index_2, query $) $ 41; + length + query query_arg $ 32000 /* max querry length */ + viewname $ 41 + ; + query_arg = dequote(query); + rc = run_macro('dsSQL_Inner' /* <-- inner macro */ + ,unique_index_2 + ,query_arg + ,viewname + ); + if rc = 0 then return(trim(viewname)); + else + do; + put 'ERROR:[function dsSQL] A problem with the dsSQL() function'; + return(" "); + end; + endsub; +run; +quit; diff --git a/SQLinDS/description.sas b/SQLinDS/description.sas new file mode 100644 index 0000000..83e1c10 --- /dev/null +++ b/SQLinDS/description.sas @@ -0,0 +1,36 @@ +/* This is the description file for the package. */ +/* The collon (:) is a field separator and is restricted */ +/* in lines of the header part. */ + +/* **HEADER** */ +Type: Package :/*required, not null, constant value*/ +Package: SQLinDS :/*required, not null, up to 24 characters, naming restrictions like for a dataset name! */ +Title: SQL queries in Data Step :/*required, not null*/ +Version: 1.0 :/*required, not null*/ +Author: Mike Rhoads (RhoadsM1@Westat.com) :/*required, not null*/ +Maintainer: Bartosz Jablonski (yabwon@gmail.com) :/*required, not null*/ +License: MIT :/*required, not null, values: MIT, GPL2, BSD, etc.*/ +Encoding: UTF8 :/*required, not null, values: UTF8, WLATIN1, LATIN2, etc. */ + +Required: "Base SAS Software" :/*optional, COMMA separated, QUOTED list, values must be like from proc setinit;run; output */ + +/* **DESCRIPTION** */ +/* All the text below will be used in help */ +DESCRIPTION START: + +The SQLinDS package is an implementation of +the macro-function-sandwich concept introduced in: +"Use the Full Power of SAS in Your Function-Style Macros" +the article by Mike Rhoads, Westat, Rockville, MD + +Copy of the article can be found at: +https://support.sas.com/resources/papers/proceedings12/004-2012.pdf + +SQLinDS package provides following components: + 1) %dsSQL_inner() macro + 2) dsSQL() function + 3) %SQL() macro + +Library DSSQL is created in a subdirectory of the WORK library. + +DESCRIPTION END: diff --git a/SQLinDS/generate_package_sqlinds.sas b/SQLinDS/generate_package_sqlinds.sas new file mode 100644 index 0000000..c6e0d7f --- /dev/null +++ b/SQLinDS/generate_package_sqlinds.sas @@ -0,0 +1,23 @@ + +%include "C:\SAS_PACKAGES\generatePackage.sas"; + +ods html; +%generatePackge(filesLocation=C:\SAS_PACKAGES\SQLinDS) + + +/* + * filename reference "packages" and "package" are keywords; + * the first one should be used to point folder with packages; + * the second is used internaly by macros; + +filename packages "C:\SAS_PACKAGES"; +%include packages(loadpackage.sas); + +dm 'log;clear'; +%loadpackage(SQLinDS) + +%helpPackage(SQLinDS) +%helpPackage(SQLinDS,*) + +%unloadPackage(SQLinDS) +*/ diff --git a/SQLinDS/license.sas b/SQLinDS/license.sas new file mode 100644 index 0000000..3d62e9f --- /dev/null +++ b/SQLinDS/license.sas @@ -0,0 +1,19 @@ +Copyright (c) 2012 Mike Rhoads + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/SQLinDS/macrofunctionsandwich.sas b/SQLinDS/macrofunctionsandwich.sas new file mode 100644 index 0000000..6dd2092 --- /dev/null +++ b/SQLinDS/macrofunctionsandwich.sas @@ -0,0 +1,82 @@ + +options dlCreateDir; +libname dsSQL "%sysfunc(pathname(work))/dsSQLtmp"; + +/* makro zewnetrzne */ +%MACRO SQL() / PARMBUFF SECURE; + %let SYSPBUFF = %superq(SYSPBUFF); /* maskujemy znaki specjalne */ + %let SYSPBUFF = %substr(&SYSPBUFF,2,%LENGTH(&SYSPBUFF) - 2); /* kasujemy otwierający i zamykający nawias */ + %let SYSPBUFF = %superq(SYSPBUFF); /* maskujemy jeszcze raz */ + %let SYSPBUFF = %sysfunc(quote(&SYSPBUFF)); /* dodajemy cudzyslowy */ + %put ***the querry***; + %put &SYSPBUFF.; + %put ****************; + + %local UNIQUE_INDEX; /* dodatkowa zmienna indeksujaca, zeby tworzony widok byl unikalny */ + %let UNIQUE_INDEX = &SYSINDEX; /* przypisujemy jej wartosc */ + %sysfunc(dsSQL(&UNIQUE_INDEX, &SYSPBUFF)) /* <-- wywolulemy funkcje dsSQL */ +%MEND SQL; + +/* funkcja */ +%macro MacroFunctionSandwich_functions(); + +%local _cmplib_; +options APPEND=(cmplib = WORK.DATASTEPSQLFUNCTIONS) ; +%let _cmplib_ = %sysfunc(getoption(cmplib)); +%put NOTE:[&sysmacroname.] *&=_cmplib_*; + +options cmplib = _null_; + +proc fcmp outlib=work.datastepSQLfunctions.package; + function dsSQL(unique_index_2, query $) $ 41; + + length query query_arg $ 32000 viewname $ 41; /* query_arg mozna zmienic na dluzszy, np. 32000 :-) */ + query_arg = dequote(query); + rc = run_macro('dsSQL_Inner', unique_index_2, query_arg, viewname); /* <-- wywolulemy makro wewnetrzne dsSQL_Inner */ + if rc = 0 then return(trim(viewname)); + else do; + return(" "); + put 'ERROR:[function dsSQL] A problem with the function'; + end; + endsub; +run; + +options cmplib = &_cmplib_.; +%let _cmplib_ = %sysfunc(getoption(cmplib)); +%put NOTE:[&sysmacroname.] *&=_cmplib_*; + +%mend MacroFunctionSandwich_functions; +%MacroFunctionSandwich_functions() + +/* delete macro MacroFunctionSandwich_functions since it is not needed */ +proc sql; + create table _%sysfunc(datetime(), hex16.)_ as + select memname, objname + from dictionary.catalogs + where + objname = upcase('MACROFUNCTIONSANDWICH_FUNCTIONS') + and objtype = 'MACRO' + and libname = 'WORK' + order by memname, objname + ; +quit; +data _null_; + set _last_; + call execute('proc catalog cat = work.' !! strip(memname) !! ' et = macro force;'); + call execute('delete ' !! strip(objname) !! '; run;'); + call execute('quit;'); +run; +proc delete data = _last_; +run; + +/* makro wewnetrzne */ +%MACRO dsSQL_Inner() / SECURE; + %local query; + %let query = %superq(query_arg); + %let query = %sysfunc(dequote(&query)); + + %let viewname = dsSQL.dsSQLtmpview&UNIQUE_INDEX_2; + proc sql; + create view &viewname as &query; + quit; +%MEND dsSQL_Inner;