diff --git a/packages/SQLinDS/000_libname/dssql.sas b/packages/SQLinDS/000_libname/dssql.sas new file mode 100644 index 0000000..8b520bf --- /dev/null +++ b/packages/SQLinDS/000_libname/dssql.sas @@ -0,0 +1,29 @@ +/*** HELP START ***/ + +/* >>> dsSQL library: <<< + * + * The dsSQL library stores temporary views + * generated during the %SQL() macro execution. + * If possible a subdirectory of WORK is created as: + + LIBNAME dsSQL BASE "%sysfunc(pathname(WORK))/dsSQLtmp"; + + * if not possible then redirects to WORK as: + + LIBNAME dsSQL BASE "%sysfunc(pathname(WORK))"; + +**/ + +/*** HELP END ***/ + +data _null_; + length rc0 $ 32767 rc1 rc2 8; + rc0 = DCREATE("dsSQLtmp", "%sysfunc(pathname(work))/" ); + rc1 = LIBNAME("dsSQL", "%sysfunc(pathname(work))/dsSQLtmp", "BASE"); + rc2 = LIBREF ("dsSQL" ); + if rc2 NE 0 then + rc1 = LIBNAME("dsSQL", "%sysfunc(pathname(work))", "BASE"); +run; + +/* list details about the library in the log */ +libname dsSQL LIST; diff --git a/packages/SQLinDS/001_macro/dssql_inner.sas b/packages/SQLinDS/001_macro/dssql_inner.sas new file mode 100644 index 0000000..b179e5c --- /dev/null +++ b/packages/SQLinDS/001_macro/dssql_inner.sas @@ -0,0 +1,65 @@ +/*** HELP START ***/ + +/* >>> %dsSQL_Inner() macro: <<< + * + * Internal macro called by dsSQL() function. + * The macro generates a uniqualy named sql view on the fly + * which is stored in DSSQL library. + * + * Recommended 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 tempfile1 tempfile2 ps_tmp; + %let query = %superq(query_arg); + %let query = %sysfunc(dequote(&query)); + + %let viewname = dsSQL.dsSQLtmpview&UNIQUE_INDEX_2.; + + %let tempfile1 = A%sysfunc(datetime(), hex7.); + %let tempfile2 = B%sysfunc(datetime(), hex7.); + + filename &tempfile1. temp; + filename &tempfile2. temp; + + %let ps_tmp = %sysfunc(getoption(ps)); + options ps = MAX; + proc printto log = &tempfile1.; + run; + /* get the query shape i.e. the executed one */ + proc sql feedback noexec; + &query + ; + quit; + proc printto; + run; + options ps = &ps_tmp.; + + %put *** executed as ***; + data _null_; + infile &tempfile1. FIRSTOBS = 2; /* <- 2 to ignore header */ + file &tempfile2.; + /* create the view name */ + if _N_ = 1 then + put " create view &viewname. as "; + input; + put _infile_; + putlog ">" _infile_; + run; + %put *****************; + + proc sql; + %include &tempfile2.; /* &query */ + ; + quit; + filename &tempfile1. clear; + filename &tempfile2. clear; +%MEND dsSQL_Inner; diff --git a/packages/SQLinDS/001_macro/sql.sas b/packages/SQLinDS/001_macro/sql.sas new file mode 100644 index 0000000..67f0ecf --- /dev/null +++ b/packages/SQLinDS/001_macro/sql.sas @@ -0,0 +1,56 @@ +/*** HELP START ***/ + +/* >>> %SQL() macro: <<< + * + * Main macro which allows to use + * SQL's queries in the data step. + * Recommended 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 + * + * SYNTAX: + + %sql() + + * The sql querry code is limited to 32000 bytes. + * + * EXAMPLE 1: simple sql query + + data class_subset; + set %SQL(select name, sex, height from sashelp.class where age > 12); + run; + + * EXAMPLE 2: query 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 ***/ + + +/* Main User 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 query ***; /* print out the query 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/packages/SQLinDS/002_function/dssql.sas b/packages/SQLinDS/002_function/dssql.sas new file mode 100644 index 0000000..da6ffd3 --- /dev/null +++ b/packages/SQLinDS/002_function/dssql.sas @@ -0,0 +1,42 @@ +/*** HELP START ***/ + +/* >>> dsSQL() function: <<< + * + * Internal function called by %SQL() macro. + * The function pass query code from the %SQL() + * macro to the %dsSQL_Inner() innternal macreo. + * + * Recommended 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 query 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/packages/SQLinDS/999_test/test1.sas b/packages/SQLinDS/999_test/test1.sas new file mode 100644 index 0000000..8cde321 --- /dev/null +++ b/packages/SQLinDS/999_test/test1.sas @@ -0,0 +1,10 @@ +proc sort data=sashelp.class out=test1; + by age name; +run; + +data class; + set %SQL(select * from sashelp.class order by age, name); +run; + +proc compare base = test1 compare = class; +run; diff --git a/packages/SQLinDS/999_test/test2.sas b/packages/SQLinDS/999_test/test2.sas new file mode 100644 index 0000000..d016da2 --- /dev/null +++ b/packages/SQLinDS/999_test/test2.sas @@ -0,0 +1,29 @@ +data class_work; + set sashelp.class; +run; + +data test_work; + set %sql(select * from class_work); +run; + +options dlcreatedir; +libname user "%sysfunc(pathname(work))/user"; +%put *%sysfunc(pathname(user))*; + +data cars_user cars_user2; + set sashelp.cars; +run; + +data test_user; + set %sql(select * from cars_user); +run; + +data test_user2; + set %sql(select * from user.cars_user2); +run; + +libname user clear; +%put *%sysfunc(pathname(user))*; + +proc datasets lib = work; +run; diff --git a/packages/SQLinDS/description.sas b/packages/SQLinDS/description.sas new file mode 100644 index 0000000..91214c3 --- /dev/null +++ b/packages/SQLinDS/description.sas @@ -0,0 +1,45 @@ +/* This is the description file for the package. */ +/* The colon (:) 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: 2.1 :/*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, names of required SAS products, 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 is available at: +https://support.sas.com/resources/papers/proceedings12/004-2012.pdf + +Package provides ability to "execute" SQL queries inside a datastep, e.g. + + data class; + set %SQL(select * from sashelp.class); + run; + +SQLinDS package contains the following components: + + 1) %SQL() macro - the main package macro available for the User + + 2) dsSQL() function (internal) + 3) %dsSQL_inner() macro (internal) + 4) Library DSSQL (created in a subdirectory of the WORK library) + +See help for the %SQL() macro to find more examples. + +DESCRIPTION END: diff --git a/packages/SQLinDS/generate_package_sqlinds.sas b/packages/SQLinDS/generate_package_sqlinds.sas new file mode 100644 index 0000000..b5c5fdf --- /dev/null +++ b/packages/SQLinDS/generate_package_sqlinds.sas @@ -0,0 +1,25 @@ + + +filename packages "C:\SAS_PACKAGES\SASPackagesFramework"; +%include packages(SPFinit.sas); + +ods html; +%generatePackage(filesLocation=C:\SAS_PACKAGES_DEV\SQLinDS) + + +/* + * filename reference "packages" and "package" are keywords; + * the first one should be used to point folder with packages; + * the second is used internally by macros; + +filename packages "C:\SAS_PACKAGES"; +%include packages(SPFinit.sas); + +dm 'log;clear'; +%loadpackage(SQLinDS) + +%helpPackage(SQLinDS) +%helpPackage(SQLinDS,*) + +%unloadPackage(SQLinDS) +*/ diff --git a/packages/SQLinDS/license.sas b/packages/SQLinDS/license.sas new file mode 100644 index 0000000..3d62e9f --- /dev/null +++ b/packages/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/packages/SQLinDS/macrofunctionsandwich.sas b/packages/SQLinDS/macrofunctionsandwich.sas new file mode 100644 index 0000000..6dd2092 --- /dev/null +++ b/packages/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; diff --git a/packages/sqlinds.zip b/packages/sqlinds.zip new file mode 100644 index 0000000..5ca7fd6 Binary files /dev/null and b/packages/sqlinds.zip differ