I have a question on how to use the value from a SAS database in another command. In my case, I have a database with two variables (cell and res). "Cell" contains a reference to a cell in an Excel sheet where the value of "res" should be copied.
So I would like to use the value stored in "cell" in my command linking to the Excel sheet. This code does not work (concatenating with || does not work.)
DATA _null_;
SET test;
FILENAME ExcelTmp DDE "EXCEL|[&myInputTemplate.]&mySheet.!" || cell;
FILE ExcelTmp NOTAB LRECL=7000;
PUT res;
RUN;
Error message:
ERROR 23-2: Invalid option name ||.
1491! DDE "EXCEL|[&myInputTemplate.]&mySheet.!" || cell;
ERROR: Error in the FILENAME statement.
ERROR 23-2: Invalid option name cell.
1492 FILE ExcelTmp NOTAB LRECL=7000;
ERROR 23-2: Invalid option name NOTAB.
If I write
FILENAME ExcelTmp DDE "EXCEL|[&myInputTemplate.]&mySheet.!R1C1:R1C1";
then the value is written to cell A1 in Excel.
Is there some similar approach that works without invoking a macro?
Thanks for your help!
Christoph
The usual way to use values from a dataset as a part of command/statement is CALL EXECUTE routine:
DATA _null_;
SET test;
call execute("DATA _NULL_;");
call execute(cats("FILENAME ExcelTmp DDE ""EXCEL|[&myInputTemplate.]&mySheet.!",cell,""";"));
call execute("FILE ExcelTmp NOTAB LRECL=7000;");
call execute("PUT '"||res||"';");
call execute("RUN;");
run;
This code generates DATA-steps that stacked up in a buffer and will be executed after the step above is executed. So basically you will generate as many DATA NULL steps as you have records in your test dataset.
Assuming you're trying to update multiple cells, and cell is in the form RnCn, something like this may work...
You also need to determine the cell range beforehand, e.g. R2C2:R100:C5.
%LET RANGE = R2C2:R100C5 ;
DATA _null_;
SET test;
FILENAME ExcelTmp DDE "EXCEL|[&myInputTemplate.]&mySheet.!&RANGE" ;
FILE ExcelTmp NOTAB LRECL=7000;
put "[select(""" cell """)]" ;
PUT res;
RUN;
Related
I am new to SAS. I was trying to print only rows where the string size is less than 20, for the column words. I tried this, but that doesn't work. What is the right syntax?
FILENAME REFFILE '<path_to_the_file>';
...
PROC PRINT DATA=WORK.IMPORT;
WHERE length("words") < 20;
RUN;
This is the error I get
ERROR: Invalid characters were present in the data.
ERROR: An error occurred while processing text data.
I don't think there is any problem with the data itself, as the following works fine.
PROC PRINT DATA=WORK.IMPORT;
WHERE words = "some string";
RUN;
As it turned out, the problem was not with the code itself, but because I did not specify the encoding. So instead of
FILENAME REFFILE '<path_to_the_file>';
I used the following, which worked.
FILENAME REFFILE '<path_to_the_file>' encoding="latin1";
I am facing this issue with sas data step. My requirement is to get a list of variables such as
total_jun2018 = sum(jun2018, dep_jun2018);
total_jul2018 = sum(jul2018, dep_jul2018);
Data final4;
set final3;
by hh_no;
do i=0 to &tot_bal_mnth.;
bal_mnth = put(intnx('month',"&min_Completed_dt."d, i-1), monyy7.);
call symputx('bal_mnth', bal_mnth);
&bal_mnth._total=sum(&bal_mnth., Dep_&bal_mnth.);
output;
end;
But I am facing error that macro variable bal_mnth not resolved. Also once it did ran successfully but I want that output must be printed sequentially but it only prints output for last loop when i=6 then it prints only Total_DEC2018=sum(DEC2018, DEP_DEC2018);
Any help will be appreciated!
Thanks,
Ajay
This is a common issue when learning SAS Macro. The problem is that the macro processor needs to resolve &bal_mnth to a value when the data step is first submitted for execution, but the CALL SYMPUT doesn't execute until the data step is actually executed, so at the time you submit the code, there is no value available for &bal_mnth.
In this case you don't need bal_mnth to be created as a variable in the data set, so you could replace the line that starts bal_mnth = put(intck(...)) with a %let bal_mnth = ... statement. The %let executes while the data step is being submitted, so that way its value will be available when you need it.
My proposed %let statement will need to wrap the functions in at least one SYSFUNC call, which is left as an exercise for the reader :-)
It looks like you want to generate a series of assignment statements like:
total_jun2018 = sum(jun2018, dep_jun2018);
total_jul2018 = sum(jul2018, dep_jul2018);
...
total_jan2019 = sum(jan2019, dep_jan2019);
What is known as wallpaper code.
If your variables names were easier, such as dep1 to dep18 then it would be easy to use arrays to process the data. With your current naming convention the problem with generating the array statements is not much different than the problem of generating a series of assignment statements.
You can create a macro so that you could use a %DO loop to generate your wallpaper code.
%local i bal_mnth;
%do i=0 %to &tot_bal_mnth.;
%let bal_mnth = %sysfunc(intnx(month,"&min_Completed_dt."d, &i-1), monyy7.);
total_&bal_mnth = sum(&bal_mnth , Dep_&bal_mnth );
%end;
Or you could just generate the code to a file with a data step.
%let tot_bal_mnth = 7;
%let min_Completed_dt=01JUN2018;
filename code temp;
data _null_;
file code;
length bal_mnth $7 ;
do i=0 to &tot_bal_mnth.;
bal_mnth = put(intnx('month',"&min_Completed_dt."d, i-1), monyy7.);
put 'total_' bal_mnth $7. ' = sum(' bal_mnth $7. ', Dep_' bal_mnth $7. ');';
end;
run;
So the generated file of code looks like this:
total_MAY2018 = sum(MAY2018, Dep_MAY2018);
total_JUN2018 = sum(JUN2018, Dep_JUN2018);
total_JUL2018 = sum(JUL2018, Dep_JUL2018);
total_AUG2018 = sum(AUG2018, Dep_AUG2018);
total_SEP2018 = sum(SEP2018, Dep_SEP2018);
total_OCT2018 = sum(OCT2018, Dep_OCT2018);
total_NOV2018 = sum(NOV2018, Dep_NOV2018);
total_DEC2018 = sum(DEC2018, Dep_DEC2018);
You can then use %include to run it in your data step.
data final4;
set final3;
by hh_no;
%include code / source2 ;
run;
I would like to offer another point of view: the difficulty you are having here results from the use of a wide data shape, with lots of columns.
Rather than working with your data in this shape, you could first transpose from wide to long, so that instead of having lots of total_xxx columns you just have 3: total, total_dep and date, with one row per month. Once it's in this format, it will be much easier to work with, potentially allowing you to avoid resorting to macros and wallpaper code.
Suggested reading:
Transpose wide to long with dynamic variables
I'm running a process that lists jobs I want to check the modification date on. I list the jobs in a dataset and then pass these to macro variables with a number.
e.g.
Data List_Prep;
Format Folder
Code $100.;
Folder = 'C:\FilePath\Job ABC'; Code = '01 Job Name.sas'; Output;
Folder = 'C:\FilePath\Job X&Y'; Code = '01 Another Job.sas'; Output;
Run;
%Macro List_Check();
Data List;
Set List_Prep;
Job + 1;
Call Symput (Cats("Folder", Job), Strip(Folder));
Call Symput (Cats("Code", Job), Strip(Code));
Run;
%Put Folder1 = &Folder1;
%Put Folder2 = &Folder2;
%MEnd;
%List_Check;
It prints the %Put statement just fine for foler 1, but folder 2 doesn't work right.
Folder1 = C:\FilePath\Job ABC
WARNING: Apparent symbolic reference Y not resolved.
Folder2 = C:\FilePath\Job X&Y
When I then go in to a loop to check the datasets, again, it work, so looks for Folder1, Code1 etc, but I still get the warnings.
How can I stop these warnings? I've tried %Str("&") instead, but still get the issue.
The %superq() macro function is a great way to mask macro triggers that are already in a macro variable. You could either remember to quote the values when using them,
%put Folder1 = %superq(Folder1) ;
or you could adjust your process to quote them right after creating them.
data List_Prep;
length Folder Code $100;
Folder = 'C:\FilePath\Job ABC'; Code = '01 Job Name.sas'; Output;
Folder = 'C:\FilePath\Job X&Y'; Code = '01 Another Job.sas'; Output;
run;
data List;
set List_Prep;
Job + 1;
length dummy $200 ;
call symputx(cats("Folder", Job), Folder);
dummy = resolve(catx(' ','%let',cats("Folder", Job),'=%superq(',cats("Folder", Job),');'));
call symputx(cats("Code", Job), Code);
dummy = resolve(catx(' ','%let',cats("Code", Job),'=%superq(',cats("Code", Job),');'));
drop dummy;
run;
P.S. Don't use FORMAT to define variables. Use statements like LENGTH or ATTRIB that are designed for defining variables. FORMAT is for attaching formats to variable, not for defining them. The only reason that using FORMAT worked is that it had the side effect of SAS defining the variable's type and length to match the format that you attached to it because it was the first place you referenced the variable in the data step.
You can prevent SAS from trying to resolve the ampersand in the value by using the %superq function
%put Folder2 = %superq(Folder2);
I was assigned a task that I don’t know where to start. Here’s the context:
There’s a variable in the data, say VAR1, indicating the directory to a bunch of image files. So for observation 1, VAR1 may look like D:\Project\Data\Images\Image1.tiff and so on. Of course, those image files exist in the computer.
What I need to do is to figure out SAS program(s) and later run them automatically using batch file. When the batch file runs, it will, in some way, opens the image files one by one. By “one by one”, I mean it firsts open one image file and, upon closing that file, it opens the next image file until the end of the list.
Better yet, the batch file will make a copy of the original image files and put them in some folder (e.g., D:\Project\Data\Temp images) before opening them. That is to make sure original data is left untouched.
Do you know how I can write such a program in SAS? I was given the following SPSS file for reference, which does that job nicely as described. I don’t know enough SPSS to understand every detail how it works. The two variables dir5 and tiff5 specify the location of the image files, and variables SCQID and ohhscqid are just ID variables.
string out2 (a200).
compute out2=concat('copy "', ltrim(rtrim(dir5)),"\", tiff5, '"',' "c:\temp\temp.tiff"').
write outfile='E:\Data\Outcome.bat'/'#echo SCQ ID ' ohhscqid .
write outfile='E:\Data\Outcome.bat'/out2.
write outfile='E:\Data\Outcome.bat'/'#"C:\Program Files\Microsoft Office\Office14\OIS.exe" "c:\temp\temp.tiff"'.
execute.
I did the homework and figured out one way that works as I want it to. Not the optimal way programmingly though, but the idea is like this.
data batwide;set have;
echo = '#echo SCQ ID '||ohhscqid;
predir = 'copy '||'"'||strip(dir5)||strip('\')||strip(tiff5)||strip('"');
preexec = '#'||strip('"')||strip('C:\Program Files\Microsoft Office\Office14\OIS.exe')||strip('"');
temp = '"'||strip('c:\temp\temp.tiff')||strip('"');
run;
data batwide; set batwide;
dir = catx(' ',predir,temp);
exec = catx(' ',preexec,temp);
run;
data batlong;set batwide;
format bat $200.;
bat = echo;output;
bat = dir;output;
bat = exec;output;
keep bat;
run;
data _null_;
set batlong;
file "E:\SAS codes and files\batchfile.bat";
put bat;
run;
Sounds like you are asking how to generate a series of OS commands into a text file? You can use a DATA step for that.
If you want to test if the specified files exist then use the FILEEXIST() function.
So if you have SAS dataset name HAVE with a variable named VAR1 that contains the filename then you probably want a program like this:
data _null_;
set have ;
file 'E:\Data\Outcome.bat';
if fileexist(VAR1) then do;
target=catx('\','D:\Project\Data\Temp images',scan(VAR1,-1,'\'));
put 'copy ' VAR1 :$quote. target :$quote. ;
put '"C:\Program Files\Microsoft Office\Office14\OIS.exe" ' target :$quote.;
end;
else putlog 'WARNING: File not found. ' VAR1=;
run;
I don't know SPSS, but will give you an example using unix commands, you can change them to Windows commands and probably do what you described.
In this example I'll only copy some files, but the logic to "open the files one by one" is the same. You will have to play with the code and adjust it to Windows.
First of all, we looking for csv files inside the /home/user directory. Again, adjust the command to windows.
This will create a sas dataset with all the files
filename dirlist pipe "find /home/user/ | grep csv";
data dirlist ;
infile dirlist lrecl=200 truncover;
input line $200.;
file_name = strip(line);
keep file_name;
run;
Then I'll create a macro variable with the file count, I'll all it cntfiles
proc sql noprint;
select count(*) into: cntfiles from dirlist;
quit;
%let cntfiles=&cntfiles;
%put cntfiles=&cntfiles;
The last thing I'm doing is, I'm looping, getting the filenames one by one and copying them to a new macro variable called ©to
This data step (null) will only copy the files, if you want to do something else with them, you'll have to write the code for it.
%macro process_files;
%let copyto = /home/des/33889897/copyto;
%do i=1 %to &cntfiles;
data _null_;
set dirlist (firstobs=&i.);
put file_name=;
call system("cp -f " || file_name || " ©to");
stop;
run;
%end;
%mend process_files;
%process_files;
Take a look at this link, maybe it can help you.
sample code I use frequently to parse list of files in a directory and extract metadata from the file names. Often feeds a step to generate a sequence of macro variables to use in a macro loop to process each file in turn. Just add any substringing of filenames to extract structured content as with the datetxt and date assigment statements in the example where the filename has a datestamp in it that I want to use.
%let extension=txt;
filename infiles "c:\a\b\c";
Data List_of_files
Not_ext
;
Length
path $255
filename $255
extension $10
;
d_open=dopen("infiles") ;
path=pathname("infiles") ;
nfiles=dnum(d_open) ;
do i=1 to nfiles;
filename =dread(d_open,i) ;
extension=scan(filename,-1,'.') ;
datetxt=scan(filename,2,"_");
date=input(scan(filename,2,"_"),date9.);
if upcase(extension) ne "%upcase(&extension)"
then output Not_ext ;
else output list_of_files ;
end;
d_open=dclose(d_open) ;
keep
filename
path
extension
;
Run ;
filename infiles clear;
I want to insert multiple line into one cell, but DDE does't work with directly put '0A'x.
filename xlSheet1 dde "Excel|c:\test.xlsx.Report!R1.C1:R1.C3" notab;
data _null_;
file xlSheet1;
a = "test";
b = cat("&sysdate","-", "&systime");
c = translate("Hello World", '0A'x, " ",);
put a '09'x b '09'x c ;
run;
Only first part write in the cell.
Any good advice?
Hmm, thought there would be an easier way, but this is the simplest thing I could get working:
filename xlSheet1 dde "Excel|sheet1!R1C1:R1C1" notab;
data _null_;
file xlSheet1;
a = cat('="line1 " & Char(10) & "line2"');
put a;
run;
Basically convert your value into a formula. Use the formula to append the text together and use excel to create the carriage return.
For this to work the cell also needs to be formatted with the 'wrap text' option. In fact, if you go to any cell in excel and use alt-enter to manually create a carraige return, you will notice it automatically turns on 'wrap text' for you so I don't think this part is optional.