Macro quoting issue in function style macro - sas

This is a follow up question to this question.
I'm trying to simplify the way we embed images into our HTML results. The idea for this was inspired by this other question .
Basically what I am trying to do is to write a function-style macro (called %html_embed_image()) that takes an image, and converts it into a base64 format suitable for use in an HTML <img src=""> block.
Given an image such as this:
The usage would be:
data _null_;
file _webout;
put "<img src=""%html_embed_image(iFileName=hi.png)"" />";
run;
And the final output would be:
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAABaSURBVDhP5YxbCsAgDAS9/6XTvJTWNUSIX3ZAYXcdGxW4QW6Khw42Axne81LG0shlRvVVLyeTI2aZ2fcPyXwPdBI8B999NK/gKTaGyxaMX8gTJRkpyREFmegBTt8lFJjOey0AAAAASUVORK5CYII=" />
The question linked above shows how to do this in regular datastep code, but I am having issues getting this working in a function style macro. I posted a simplified problem I was having earlier and Tom was able to solve that simplified issue, but it doesn't seem to be working in the greater context of the function style macro.
Here is my code so far (the line causing issues is wrapped with two put statements indicating that it is the problem):
option mprint symbolgen;
%macro html_embed_image(iFileName=);
%local rc fid rc2 str str_length format_length format_mod base64_format base64_string;
/* ONLY READ IN 16K CHUNKS AS CONVERTING TO BASE64 */
/* INCREASES SIZE AND DONT WANT TO EXCEED 32K. */
%let rc = %sysfunc(filename(filrf, &iFileName, , lrecl=16000));
%let fid = %sysfunc(fopen(&filrf, i, 16000, b));
%if &fid > 0 %then %do;
%let rc = %sysfunc(fread(&fid));
%do %while(&rc eq 0);
%let rc2 = %sysfunc(fget(&fid,str,16000));
%let str = %superq(str);
/* FORMAT LENGTH NEEDS TO BE 4n/3 ROUNDED UP TO NEAREST MULTIPLE OF 4 */
%let str_length = %length(&str);
%let format_length = %sysevalf(4*(&str_length/3));
%let format_mod = %sysfunc(mod(&format_length,4));
%if &format_mod ne 0 %then %do;
%let format_length = %sysevalf(&format_length - &format_mod + 4);
%end;
%let base64_format = %sysfunc(cats($base64x,&format_length,.));
%put &=base64_format;
/* CONVERT THE BINARY DATA TO BASE64 USING THE CALCULATED FORMAT */
%put PROBLEM START;
%let base64_string = %sysfunc(putc(&str,&base64_format));
%put PROBLEM END;
%put &=base64_string;
/*&base64_string*/ /* RETURN RESULT HERE - COMMENTED OUT UNTIL WORKING */
%let rc = %sysfunc(fread(&fid));
%end;
%end;
%else %do;
%put %sysfunc(sysmsg());
%end;
%let rc=%sysfunc(fclose(&fid));
%let rc=%sysfunc(filename(filrf));
%mend;
Test the code:
%put %html_embed_image(iFileName=hi.png);
Results in:
ERROR: Expected close parenthesis after macro function invocation not found.
Any tips on how to fix this, or suggestions for workarounds would be great.

Just write the text using a data step.
%let fname=hi.png;
data _null_;
file _webout recfm=n;
if _n_=1 then put '<img src="data:image/png;base64,';
length str $60 coded $80 ;
infile "&fname" recfm=n eof=eof;
do len=1 to 60;
input ch $char1.;
substr(str,len,1)=ch;
end;
put str $base64x80.;
return;
eof:
len=len-1;
clen=4*ceil(len/3);
coded = putc(substr(str,1,len),cats('$base64x',clen,'.'));
put coded $varying80. clen ;
put '" />';
run;
If you really want to generate text in-line it might be best to add quotes so that you could call the macro in the middle of a PUT statement and not worry about hitting maximum string length.
%macro base64(file);
%local filerc fileref rc fid text len ;
%*----------------------------------------------------------------------
Assign fileref and open the file.
-----------------------------------------------------------------------;
%let fileref = _fread;
%let filerc = %sysfunc(filename(fileref,&file));
%let fid = %sysfunc(fopen(&fileref,s,60,b));
%*----------------------------------------------------------------------
Read file and dump as quoted BASE64 text.
-----------------------------------------------------------------------;
%if (&fid > 0) %then %do;
%do %while(%sysfunc(fread(&fid)) = 0);
%do %while(not %sysfunc(fget(&fid,text,60)));
%let len = %eval(4*%sysfunc(ceil(%length(%superq(text))/3)));
%if (&len) %then "%sysfunc(putc(%superq(text),$base64x&len..))" ;
%end;
%end;
%let rc = %sysfunc(fclose(&fid));
%end;
%*----------------------------------------------------------------------
Clear fileref assigned by macro,
-----------------------------------------------------------------------;
%if ^(&filerc) %then %let rc = %sysfunc(filename(fileref));
%mend base64;
So then your example data step becomes something like this:
%let fname=hi.png;
data _null_;
file _webout recfm=n;
put '<img src="data:image/png;base64,' %base64(&fname) '" />' ;
run;

Related

SAS EG export to different test folder conditional statement

My code exports a table from SAS to a excel file in a folder.
My goal to change the export folder to a test folder if I set the variable test to 1.
This is my code:
/*Delete .bak files*/
%macro xlsx_bak_delete(file, file_name) / des='Delete backup spreadsheets';
option mprint notes;
data _null_;
fname = 'todelete';
rc = filename(fname, "&file.&file_name..xlsx.bak");
rc = fdelete(fname);
rc = filename(fname);
run;
%mend xlsx_bak_delete;
/*Export*/
%macro ExportExcel(path,file_name,table_name);
proc export data=&table_name
outfile="&path.&file_name..xlsx"
dbms=xlsx
replace;
;
run;
%xlsx_bak_delete(&export_path, &file_name)
%mend;
%LET test = 1
%IF test = 1 %then %do;
/*TEST export path */
%Let export_path = \\Bfd1\b00369\Afdeling\HS-OKO\Oko\Likviditet\Likviditetsstyring\LCR\Daglig LCR - Axiom\Test\;
%end;
%else %do;
/*Export path*/
%Let export_path = \\Bfd1\b00369\Afdeling\HS-OKO\Oko\Likviditet\Likviditetsstyring\LCR\Daglig LCR - Axiom\Test_prod\;
%end;
/*Datetiemstamp macro*/
%let fileTimeStamp = %sysfunc(date(), ddmmyyd10.)_%sysfunc(putc(%sysfunc(time(), b8601TM6.), $6.)) ;
%put &fileTimeStamp.;
%ExportExcel(&export_path,DAGLIGEKORREKTIONER_&fileTimeStamp.,QUERY_FOR_DAGLIGEKORREKTIONER);
The issue is that my code only exports to the test_prod folder nothing changes if test is = 0 or 1 why is that?
You are incorrectly referencing the macro variable. As a result, it will always end up in the %else %do; part of the %if statement.
You can use the variable by referencing it with an ampersand preceding its name (&test in your case), which is called a macro variable reference.
You are also missing a semicolon ; after the %LET statement.
%LET test = 1;
%IF &test = 1 %then %do;
/*TEST export path */
%Let export_path = \\Bfd1\b00369\Afdeling\HS-OKO\Oko\Likviditet\Likviditetsstyring\LCR\Daglig LCR - Axiom\Test\;
%end;
%else %do;
/*Export path*/
%Let export_path = \\Bfd1\b00369\Afdeling\HS-OKO\Oko\Likviditet\Likviditetsstyring\LCR\Daglig LCR - Axiom\Test_prod\;
%end;

Listing files UNIX SAS

i found a code that list all directories and sub directories from a path.
but it brings only the directory and the name of the file.
could you guys please help me how to bring the owner and the file size?
%macro list_files(dir);
%local filrf rc did memcnt name i;
%let rc=%sysfunc(filename(filrf,&dir));
%let did=%sysfunc(dopen(&filrf));
%if &did eq 0 %then %do;
%put Directory &dir cannot be open or does not exist;
%return;
%end;
%do i = 1 %to %sysfunc(dnum(&did));
%let name=%qsysfunc(dread(&did,&i));
%if %index(%qscan(&name,-1,'/'),.) gt 0 %then %do;
data _tmp;
length dir $512 name $100;
dir=symget("dir");
name=symget("name");
run;
proc append base=want data=_tmp;
run;quit;
%end;
%else %if %qscan(&name,2,.) = %then %do;
%list_files(&dir/&name)
%end;
%end;
%let rc=%sysfunc(dclose(&did));
%let rc=%sysfunc(filename(filrf));
%mend list_files;
DOPTNAME is your friend here.
Read SAS documentation for "Example 1: Using DOPTNAME to Retrieve Directory Attribute Information"
This example opens the directory with the fileref MYDIR, retrieves all system-dependent directory information items, writes them to the SAS log, and closes the directory:
%let rc=%sysfunc(filename(filrf, physical-name));
%let did=%sysfunc(dopen(&filrf));
%let infocnt=%sysfunc(doptnum(&did));
%do j=1 %to &infocnt;
%let opt=%sysfunc(doptname(&did, &j));
%put Directory information=&opt;
%end;
%let rc=%sysfunc(dclose(&did));
%macro test;
%let filrf=mydir;
%let rc=%sysfunc(filename(filrf, physical-name));
%let did=%sysfunc(dopen(&filrf));
%let infocnt=%sysfunc(doptnum(&did));
%do j=1 %to &infocnt;
%let opt=%sysfunc(doptname(&did, &j));
%put Directory information=&opt;
%end;
%let rc=%sysfunc(dclose(&did));
%mend test;
%test
Use the finfo() function. You can do this all in a single data step.
Documentation
/* Macro variable to store the directory. Do not keep ending slash. */
%let directory = /my/directory;
filename mydir "&directory";
data file_list;
length directory
filetype $15.
filename $1000.
owner $100.
size 8.
;
directory = "&directory/";
/* Open the directory */
did = dopen("mydir");
/* If the directory exists, loop through all files in the directory */
if(did) then do;
do i = 1 to dnum(did);
/* Get the filename */
filename = dread(did, i);
/* Create a filename variable and create a file ID to read its attributes */
rc = filename('infile', cats(directory,filename));
fid = fopen('infile');
owner = finfo(fid, 'Owner Name');
size = finfo(fid, 'File Size (bytes)');
/* Flag if it's a directory or file */
if(missing(size)) then filetype = 'Directory';
else filetype = 'File';
/* Close the file */
rc = fclose(fid);
output;
end;
end;
/* Close the directory */
rc = close(did);
keep directory filename owner size filetype;
run;

SAS: lookup data inside a function/subroutine / return an array

Suppose I like to do something like the following (with exemplary variable names for better readability):
take a parameter InParameter and match it to the variable MyVar1 in a dataset MyData
return all values for the variable MyVar2 for the filtered observations
from a subroutine/function
that i can use inside proc sql/datastep
This is what I got so far (clearly not working):
proc fcmp outlib=work.funcs.MyFunction;
function MyFunction(InParameter $);
array MyArray ... ; /* Here: Create an array with something like SELECT MyVar2 FROM MyData WHERE MyVar1 = Inparameter */
return(MyArray{});
endsub;
;
quit;
options cmplib=work.funcs;
data MyOutput;
set Somedata;
if MyVar2 in MyFunction("H20") then output;
run;
In short:
can data in datasets be accessed from inside a function/subroutine?
can a function/subroutine return an array?
Thanks for your help!
We created a utility macro called %ds2list() that will perform your desired process. It doesn't use an array statement but it achieves the same result.
The macro simply returns values from a dataset in a list format. Here's an example of calling it:
%put %ds2list(iDs=sashelp.class, iField=name, iQuote=1);
This would return:
'Alfred','Alice','Barbara','Carol','Henry','James','Jane','Janet','Jeffrey','John','Joyce','Judy','Louise','Mary','Philip','Robert','Ronald','Thomas','William'
The default behavior for %ds2list() is to comma separate the returned values but it is very flexible. You can change the delimiter to a value of your choice (or no delimiter), you can turn the quotes on or off, or change them from single to double quotes, and you can provide any dataset options you would normally use on a set statement such as a where=() statement.
Additionally because the macro is pure macro code you can use this literally anywhere in SAS. In any proc/data/macro you like. We use it extensively for calls to ODBC passthrough when we have a large list of IDs we want to be returned.
Here's an example of how you could use it. First create a table that will contain values to compare against the list values:
data keep;
input name $;
datalines;
Alfred
Carol
Janet
run;
Iterate over the values we want to check against the list:
data want;
set keep;
if name in (%ds2list(iDs=sashelp.class, iField=name, iQuote=1, iDsOptions=where=(sex='F'))) then do;
output;
end;
run;
Returns:
Obs name
=== =====
1 Carol
2 Janet
You can see Alfred was excluded from the result because he was filtered out by the where=() clause.
Here is the macro, I suggest putting it in your macro autocall library:
/***************************************************************************
** PROGRAM: MACRO.DS2LIST.SAS
**
** UTILITY PROGRAM THAT DETECTS RETURNS A LIST OF FIELD VALUES FROM A
** DATASET IN DELIMITED FORMAT.
**
** PARAMETERS:
** iDs : THE LIBNAME.DATASET NAME THAT YOU WANT TO CHECK.
** iField : THE FIELD THAT CONTAINS THE VALUES YOU WANT RETURNED IN A
** DELIMITED FORMAT.
** iDelimiter: DEFAULT IS A COMMA. THE DELIMITER TO USE FOR THE RETURNED LIST.
** iDsOptions: ANY STANDARD DATASET OPTIONS THAT YOU WOULD LIKE TO APPLY SUCH
** AS A WHERE STATEMENT.
** iQuote : (0=NO,1=YES). DEFAULT=0/NO. DETERMINES WHETHER THE RETURNED
** LIST IS QUOTED OR NOT.
** iQuoteChar: (SINGLE,DOUBLE) DEFAULT=SINGLE. SPECIFIES WHETHER SINGLE
** OR DOUBLE QUOTES ARE USED WHEN QUOTING THE RETURNED LIST
**
*****************************************************************************/
%macro ds2list(iDs=, iField=, iDsOptions=, iDelimiter=%str(,), iQuote=0, iQuoteChar=single);
%local dsid pos rc result cnt quotechar value;
%let result=;
%let cnt=0;
%if &iQuote %then %do;
%if "%upcase(&iQuoteChar)" eq "DOUBLE" %then %do;
%let quotechar = %nrstr(%");
%end;
%else %if "%upcase(&iQuoteChar)" eq "SINGLE" %then %do;
%let quotechar = %nrstr(%');
%end;
%else %do;
%let quotechar = %nrstr(%");
%put WARNING: MACRO.DS2LIST.SAS: PARAMETER IQUOTECHAR INCORRECT. DEFAULTED TO DOUBLE;
%end;
%end;
%else %do;
%let quotechar = ;
%end;
/*
** ENSURE ALL THE REQUIRED PARAMETERS WERE PASSED IN.
*/
%if "&iDs" ne "" and "&iField" ne "" %then %do;
%let dsid=%sysfunc(open(&iDs(&iDsOptions),i));
%if &dsid %then %do;
%let pos=%sysfunc(varnum(&dsid,&iField));
%if &pos %then %do;
%let rc=%sysfunc(fetch(&dsid));
%do %while (&rc eq 0);
%if "%sysfunc(vartype(&dsid,&pos))" = "C" %then %do;
%let value = %qsysfunc(getvarc(&dsid,&pos));
%if "%trim(&value)" ne "" %then %do;
%let value = %qtrim(&value);
%end;
%end;
%else %do;
%let value = %sysfunc(getvarn(&dsid,&pos));
%end;
/* WHITESPACE/CARRIAGE RETURNS REMOVED IN THE BELOW LINE */
/* TO ENSURE NO WHITESPACE IS RETURNED IN THE OUTPUT. */
%if &cnt ne 0 %then %do;%unquote(&iDelimiter)%end;%unquote(&quotechar&value&quotechar.)
%let cnt = %eval(&cnt + 1);
%let rc = %sysfunc(fetch(&dsid));
%end;
%if &rc ne -1 %then %do;
%put WARNING: MACRO.DS2LIST.SAS: %sysfunc(sysmsg());
%end;
%end;
%else %do;
%put ERROR: MACRO.DS2LIST.SAS: FIELD &iField NOT FOUND IN DATASET %upcase(&iDs).;
%end;
%end;
%else %do;
%put ERROR: MACRO.DS2LIST.SAS: DATASET %upcase(&iDs) COULD NOT BE OPENED.;
%end;
%let rc=%sysfunc(close(&dsid));
%end;
%else %do;
%put ERROR: MACRO.DS2LIST.SAS: YOU MUST SPECIFY BOTH THE IDS AND IFIELD PARAMETERS TO CALL THIS MACRO.;
%end;
%mend;
Not sure that a function would work with the IN operator. You might need to wrap the function call with a macro to generate the proper syntax. In which case why not just make a macro to begin with?
Here is generic macro to extract the values from a variable in a dataset.
%macro varlist
/*----------------------------------------------------------------------
Generate list of values from dataset
----------------------------------------------------------------------*/
(dataset /* Input dataset */
,variable /* Variable Name */
,quote=1 /* Add quotes around values? 1=Single 2=Double */
,comma=1 /* Add comma between values? */
,paren=1 /* Add parentheses around results? */
);
%local did sep &variable ;
%if &paren=1 %then (;
%let did=%sysfunc(open(&dataset));
%syscall set(did);
%do %while(0=%sysfunc(fetch(&did)));
%let &variable=%qsysfunc(trim(%superq(&variable)));
%if &quote=1 %then &sep.%sysfunc(quote(&&&variable,%str(%')));
%else %if &quote=2 %then &sep.%sysfunc(quote(&&&variable));
%else &sep.&&&variable;
%if &comma=1 %then %let sep=,;
%end;
%let did=%sysfunc(close(&did));
%if &paren=1 %then );
%mend varlist;
Example calls:
%put %varlist(sashelp.class,name);
%put %varlist(sashelp.class(where=(sex='M')),age,quote=0,comma=0);
So in your case you might use it like this:
data MyOutput;
set Somedata;
where MyVar2 in %varlist(Mydata(where=(MyVar1="H20")),MyVar2) ;
run;
You are better off with a macro.
%macro subset(inParameter, indata, outdata);
proc sql noprint;
create table &outdata as
select * from &indata
where myVar2 in (select distinct myVar2 from myData where myVar1 = "&inParameter);
quit;
%mend;
%subst(H20,Somedata,MyOutput);

Get list of files in directory under NOXCMD

Can not use Xcommands in SAS EG. No access to SAS Management Console. How can I get a list of files in a directory without using Xcommands?
Tried DINFO but can only get 1 piece of info. Need a list of all files in the selected directory. Am I missing something here?
data a;
rc=filename("mydir", c:\");
put "rc = 0 if the directory exists: " rc=;
did=dopen("mydir");
put did=;
numopts=doptnum(did);
put numopts=;
do i = 1 to numopts;
optname = doptname(did,i);
put i= optname=;
optval=dinfo(did,optname);
put optval=;
output;
end;
run;
I've not used Enterprise Guide but how about using a pipe'd filename? You cn then use that with the infile statement to put the result of the query into a dataset...
filename dirlist pipe "ls /<your-path>/*";
data dirlist ;
infile dirlist ;
format fname $300. ;
input fname $ ;
run;
Here's a couple of macros we use to do this. The main macro is %file_list but it also requires the %isDir macro in order to run. Some usage examples:
%put %file_list(iPath=e:\blah\); * TEST AGAINST A DIR THAT DOESNT EXIST;
%put %file_list(iPath=e:\SASDev); * TEST AGAINST A DIR THAT EXISTS;
%put %file_list(iPath=e:\SASDev\,iFiles_only=1); * LIST ONLY FILES;
%put %file_list(iPath=e:\sasdev\,iFiles_only=1,iFilter=auto); * FILTER TO ONLY FILES THAT CONTAIN THE STRING AUTO;
%isDir macro definition:
/******************************************************************************
** PROGRAM: CMN_MAC.ISDIR.SAS
**
** DESCRIPTION: DETERMINES IF THE SPECIFIED PATH EXISTS OR NOT.
** RETURNS: 0 IF THE PATH DOES NOT EXIST OR COULD NOT BE OPENED.
** 1 IF THE PATH EXISTS AND CAN BE OPENED.
**
** PARAMETERS: iPath: THE FULL PATH TO EXAMINE. NOTE THAT / AND \ ARE TREATED
** THE SAME SO &SASDIR/COMMON/MACROS IS THE SAME AS
** &SASDIR\COMMON\MACROS.
**
******************************************************************************/
%macro isDir(iPath=,iQuiet=1);
%local result dname did rc;
%let result = 0;
%let check_file_assign = %sysfunc(filename(dname,&iPath));
%put ASSIGNED FILEREF (0=yes, 1=no)? &check_file_assign &iPath;
%if not &check_file_assign %then %do;
%let did = %sysfunc(dopen(&dname));
%if &did %then %do;
%let result = 1;
%end;
%else %if not &iQuiet %then %do;
%put &err: (ISDIR MACRO).;
%put %sysfunc(sysmsg());
%end;
%let rc = %sysfunc(dclose(&did));
%end;
%else %if not &iQuiet %then %do;
%put &err: (ISDIR MACRO).;
%put %sysfunc(sysmsg());
%end;
&result
%mend;
%filelist macro definition:
/******************************************************************************
** PROGRAM: MACRO.FILE_LIST.SAS
**
** DESCRIPTION: RETURNS THE LIST OF FILES IN A DIRECTORY SEPERATED BY THE
** SPECIFIED DELIMITER. RETURNS AN EMPTY STRING IF THE THE
** DIRECTORY CAN'T BE READ OR DOES NOT EXIST.
**
** PARAMETERS: iPath: THE FULL PATH TO EXAMINE. NOTE THAT / AND \ ARE TREATED
** THE SAME SO &SASDIR/COMMON/MACROS IS THE SAME AS
** &SASDIR\COMMON\MACROS. WORKS WITH BOTH UNIX AND WINDOWS.
**
******************************************************************************/
/*
** TODO. THERES ABOUT 100 WAYS THIS COULD BE IMPROVED SUCH AS SIMPLIFYING IF STATEMENTS FOR FILTERS...
*/
%macro file_list(iPath=, iFilter=, iFiles_only=0, iDelimiter=|);
%local result did dname cnt num_members filename rc check_dir_exist check_file_assign;
%let result=;
%let check_dir_exist = %isDir(iPath=&iPath);
%let check_file_assign = %sysfunc(filename(dname,&iPath));
%put The desired path: &iPath;
%if &check_dir_exist and not &check_file_assign %then %do;
%let did = %sysfunc(dopen(&dname));
%let num_members = %sysfunc(dnum(&did));
%do cnt=1 %to &num_members;
%let filename = %qsysfunc(dread(&did,&cnt));
%if "&filename" ne "" %then %do;
%if "&iFilter" ne "" %then %do;
%if %index(%lowcase(&filename),%lowcase(&iFilter)) eq 0 %then %do;
%goto next;
%end;
%end;
%if &iFiles_only %then %do;
%if %isDir(iPath=%nrbquote(&iPath/&filename)) %then %do;
%goto next;
%end;
%end;
%let result = &result%str(&iDelimiter)&filename;
%next:
%end;
%else %do;
%put ERROR: (CMN_MAC.FILE_LIST) FILE CANNOT BE READ.;
%put %sysfunc(sysmsg());
%end;
%end;
%let rc = %sysfunc(dclose(&did));
%end;
%else %do;
%put ERROR: (CMN_MAC.FILE_LIST) PATH DOES NOT EXIST OR CANNOT BE OPENED.;
%put %sysfunc(sysmsg());
%put DIRECTORY EXISTS (1-yes, 0-no)? &check_dir_exist;
%put ASSIGN FILEREF SUCCESSFUL (0-yes, 1-no)? &check_file_assign;
%end;
/*
** RETURN THE RESULT. TRIM THE LEADING DELIMITER OFF THE FRONT OF THE RESULTS.
*/
%if "&result" ne "" %then %do;
%qsubstr(%nrbquote(&result),2)
%end;
%mend;
%let path=C:\ETC;
filename parent "&path\Data\CSV";
data files;
length file_name $50;
drop rc did i;
did=dopen("parent");
if did > 0 then do;
do i=1 to dnum(did);
file_name=dread(did,i);
output;
end;
rc=dclose(did);
end;
else put 'Could not open directory';
run;
* Some additions;
%global name;
%global count2;
%let name=;
%let count2=;
proc sql;
select file_name into :name separated by '*' from work.files;
%let count2 = &sqlobs;
quit;
This works fine. I use &name for other macro and do something with files... (load from CSV, for example).

A macro function to produce a macro variable from a data variable

data sample;
input x $;
datalines;
one
two
three
;
%macro variable_to_macvar(variable=, dataset=);
proc sql noprint;
select &variable into : outlist separated by ' '
from &dataset;
quit;
&outlist
%mend variable_to_macvar;
%put %variable_to_macvar(variable=x, dataset=sample);
Expected output: one two three. Instead I get an error. Why? Is this fixable?
I've successfully created other macros of a very similar form, where the function "returns" a value using the &macrovariable at the end of the macro without a semicolon. For example, here is a similar type of function that works:
%macro zcat(first=5, last=15, prefix=freq);
%let x=;
%do i = &first %to &last;
%let x=&x &prefix.&i;
%end;
&x
%mend zcat;
%put %zcat();
You cannot execute a macro that involves running a proc or a data step in the way that you're trying to do here. You would need to use something like %sysfunc(dosubl(proc sql...)) in order for that to work (assuming you have SAS 9.3+ - see Joe's answer above). Otherwise, you can't use proc sql within a function-style macro.
More details about dosubl:
http://support.sas.com/documentation/cdl/en/lefunctionsref/67398/HTML/default/viewer.htm#p09dcftd1xxg1kn1brnjyc0q93yk.htm
It would be a bit fiddly, but if you really wanted to make this work as a function-style macro in earlier versions of SAS, you could construct it using the open, fetchobs and getvarc functions instead.
Update: Here's an example (using call set rather than getvarc, as this turned out to be simpler), in case anyone needs to do this in SAS 9.2 or earlier.
%macro variable_to_macvar(var,ds);
%local rc dsid i;
%let &var =;
%global outlist;
%let outlist=;
%let dsid = %sysfunc(open(&ds,i));
%syscall set(dsid);
%let rc = 0;
%let i = 0;
%do %while(&rc = 0);
%let i = %eval(&i + 1);
%let rc = %sysfunc(fetchobs(&dsid,&i));
%if &rc = 0 %then %let outlist = &outlist &&&var;
%end;
%let rc = %sysfunc(close(&dsid));
&outlist
%mend;
%put %variable_to_macvar(var=x, ds=sample);
Now works for views as well as ordinary datasets.
DOSUBL is available (but experimental) in 9.3 (at least, 9.3TS1M2, which I have). This is how you'd do it.
data sample;
input x $;
datalines;
one
two
three
;
%macro variable_to_macvar(variable=, dataset=);
%let rc=%sysfunc(dosubl(%str(
proc sql noprint;
select &variable into : outlist separated by ' '
from &dataset;
quit;
)));
&outlist
%mend variable_to_macvar;
%put %variable_to_macvar(variable=x, dataset=sample);;
If you can't use DOSUBL, or want to avoid experimental things, you can do this with PROC FCMP rather than a macro. If you like to write functions, PROC FCMP is probably for you: actually being able to write functions, rather than having to deal with the annoyances of the macro language.
Alter your code at the end to
%global outlist;
%variable_to_macvar(variable=x, dataset=sample);
%put &outlist;
The %put wants to resolve only a macro variable or a single value. It cannot call a procedure. So call your macro and then print the result.
Also, delete the &outlist from the macro definition. Sorry I missed that initially.
EDIT: Alternative.
Change your macro definition to
%macro variable_to_macvar(variable=, dataset=);
proc sql noprint;
select &variable into : outlist separated by ' '
from &dataset;
quit;
%put &outlist
%mend variable_to_macvar;
Just do the %put inside the macro.
%variable_to_macvar(variable=x, dataset=sample);
will print the string to the log.
We have a utility macro that is probably one of our most used pieces of code that does this for us. It is similar to the code that #user667489 provided but includes some nice features including error catching, allows both character and numeric vars, allows you to specify seperators, quotes, quote characters, filters to the dataset, etc....
We just put this macro in our autocall library so that it's avaialble to all of our programs. Some examples of running the macro:
Example 1 - Default behaviour:
%put %variable_to_macvar(var=x, ds=samplex);
Result 1:
one,two,three
Not quite the desired output as the default seperator is a comma, this is easily changed though...
Example 2 - Specify to use a space character as a delimiter:
%put %ds2list(iDs=samplex, iField=x, iDelimiter=%str( ));
Result 2:
one two three
Example 3 - Quoting & example usage
data names;
input name $;
datalines;
John
Jim
Frankie
;
run;
%put %ds2list(iDs=names, iField=name, iQuote=1);
proc sql noprint;
create table xx as
select *
from sashelp.class
where name in (%ds2list(iDs=names, iField=name, iQuote=1))
;
quit;
Result 3:
The below is printed to the log:
'John','Jim','Frankie'
Notice how we don't need to even save the result to a macro variable to use it in the SQL statement! Swweeet! This works just as well for SQL passthrough queries, and any other data step or proc statement that you can throw it at. In the above example, a single row is returned as 'John' is the only match found...
Anyway, that's our solution here... been using this for >10 years and works well for me. Here is the macro:
/***************************************************************************
** PROGRAM: MACRO.DS2LIST.SAS
**
** UTILITY PROGRAM THAT DETECTS RETURNS A LIST OF FIELD VALUES FROM A
** DATASET IN DELIMITED FORMAT.
**
** PARAMETERS:
** iDs : THE LIBNAME.DATASET NAME THAT YOU WANT TO CHECK.
** iField : THE FIELD THAT CONTAINS THE VALUES YOU WANT RETURNED IN A
** DELIMITED FORMAT.
** iDelimiter: DEFAULT IS A COMMA. THE DELIMITER TO USE FOR THE RETURNED LIST.
** iDsOptions: ANY STANDARD DATASET OPTIONS THAT YOU WOULD LIKE TO APPLY SUCH
** AS A WHERE STATEMENT.
** iQuote : (0=NO,1=YES). DEFAULT=0/NO. DETERMINES WHETHER THE RETURNED
** LIST IS QUOTED OR NOT.
** iQuoteChar: (SINGLE,DOUBLE) DEFAULT=SINGLE. SPECIFIES WHETHER SINGLE
** OR DOUBLE QUOTES ARE USED WHEN QUOTING THE RETURNED LIST
**
*****************************************************************************
** VERSION:
** 1.8 MODIFIED: 11-OCT-2010 BY: KN
** ALLOW BLANK CHARACTER VALUES AND ALSO REMOVED TRAILING
** ALLOW PARENTHESES IN CHARACTER VALUES
*****************************************************************************/
%macro ds2list(iDs=, iField=, iDsOptions=, iDelimiter=%str(,), iQuote=0, iQuoteChar=single);
%local dsid pos rc result cnt quotechar;
%let result=;
%let cnt=0;
%if &iQuote %then %do;
%if "%upcase(&iQuoteChar)" eq "DOUBLE" %then %do;
%let quotechar = %nrstr(%");
%end;
%else %if "%upcase(&iQuoteChar)" eq "SINGLE" %then %do;
%let quotechar = %nrstr(%');
%end;
%else %do;
%let quotechar = %nrstr(%");
%put WARNING: MACRO.DS2LIST.SAS: PARAMETER IQUOTECHAR INCORRECT. DEFAULTED TO DOUBLE;
%end;
%end;
%else %do;
%let quotechar = ;
%end;
/*
** ENSURE ALL THE REQUIRED PARAMETERS WERE PASSED IN.
*/
%if "&iDs" ne "" and "&iField" ne "" %then %do;
%let dsid=%sysfunc(open(&iDs(&iDsOptions),i));
%if &dsid %then %do;
%let pos=%sysfunc(varnum(&dsid,&iField));
%if &pos %then %do;
%let rc=%sysfunc(fetch(&dsid));
%do %while (&rc eq 0);
%if "%sysfunc(vartype(&dsid,&pos))" = "C" %then %do;
%let value = %qsysfunc(getvarc(&dsid,&pos));
%if "%trim(&value)" ne "" %then %do;
%let value = %qsysfunc(cats(%nrstr(&value)));
%end;
%end;
%else %do;
%let value = %sysfunc(getvarn(&dsid,&pos));
%end;
/* WHITESPACE/CARRIAGE RETURNS REMOVED IN THE BELOW LINE */
/* TO ENSURE NO WHITESPACE IS RETURNED IN THE OUTPUT. */
%if &cnt ne 0 %then %do;%unquote(&iDelimiter)%end;%unquote(&quotechar&value&quotechar.)
%let cnt = %eval(&cnt + 1);
%let rc = %sysfunc(fetch(&dsid));
%end;
%if &rc ne -1 %then %do;
%put WARNING: MACRO.DS2LIST.SAS: %sysfunc(sysmsg());
%end;
%end;
%else %do;
%put ERROR: MACRO.DS2LIST.SAS: FIELD &iField NOT FOUND IN DATASET %upcase(&iDs).;
%end;
%end;
%else %do;
%put ERROR: MACRO.DS2LIST.SAS: DATASET %upcase(&iDs) COULD NOT BE OPENED.;
%end;
%let rc=%sysfunc(close(&dsid));
%end;
%else %do;
%put ERROR: MACRO.DS2LIST.SAS: YOU MUST SPECIFY BOTH THE IDS AND IFIELD PARAMETERS TO CALL THIS MACRO.;
%end;
%mend;