Let's say I'm trying to do the following:
%macro test(a=);
%do i=1 %to &a;
proc iml;
b=b//(2*i);
quit;
%end;
proc iml;
print sum(b);
quit;
%mend;
%test(a=2);
In the code I'm trying to write, I can't put it all in one IML (I need a proc freq within the do loop). The code above gives the error "Matrix b not set to a value." How do I tell SAS what b is so that I can still access it after I've quit the iml statement?
Two suggestions:
1) Use the STORE statement to write the matrix B to disk at the end of the first call, then use the LOAD statement to read it in during the second call:
store B;
quit;
proc freq data=...;
run;
proc iml;
load B;
...
2) An alternative approach is to call PROC FREQ from within your PROC IML program by using the SUBMIT and ENDSUBMIT statements:
/* compute B */
submit;
proc freq data=...;
run;
endsubmit;
s = sum(b): /* B is still in scope */
You need to rework things so the PROC IML; and QUIT; are outside of the macro. This is good practice most of the time even in other scenarios where it's not that important, but here it's necessary.
IE
%macro test(a=);
%do i=1 %to &a;
b=b//(2*i);
%end;
proc iml;
%test(a=5);
quit;
QUIT ends the PROC IML session and clears its memory.
Related
I am so frustrated with this piece of code. I'm trying to pass in values using syspbuff which I do all the time. However, I want to pass in multiple values but for this UNION code I'm trying to do, it's giving me trouble. I am going from Oracle to SAS which I assume is causing the problem but I'd like an answer as to why. Previously, I had the source tables in temp space (SAS) and I didn't get this error. But when I had to create the tables in MYDB (Oracle) because of a specific reason, I started getting the large log with "failure to pass through" errors.
Interestingly, the code actually works and it does what I want it to but the problem is that I get a pop up that the log is too large and will open externally. Then it opens a text file that is HUGE and has tons of errors basically saying that it couldn't pass through the code into implicit pass through. I wasn't trying to do pass through for this particular piece of code. So, again, it works and I ultimately get what I want but the log issue is driving me bonkers.
%macro ALLPROVTYPE() / parmbuff;
%do ii = 1 %to %sysfunc(countw(%bquote(&syspbuff.)));
%let FT=%scan(%bquote(&SYSPBUFF),&ii);
CREATE TABLE MYSASLIB.ALLST_PROV_&FT._NULL AS
SELECT "AK" AS STATE,*
FROM MYDB.AK_PROV_&FT
%macro JNSTS() / parmbuff;
%do i = 1 %to %sysfunc(countw(%bquote(&syspbuff.)));
%let ST=%scan(%bquote(&SYSPBUFF),&i);
UNION CORR
SELECT "&ST" AS STATE,*
FROM MYDB.&ST._PROV_&FT
%end;
%mend JNSTS;
%JNSTS(&&PROVALL&FT);
;
%end;
%mend ALLPROVTYPE;
PROC SQL;
%ALLPROVTYPE(&PROVNUMS);
QUIT;
ACCESS ENGINE: ERROR: ORACLE prepare error: ORA-00928: missing SELECT keyword. SQL statement: DEBUG: DBMS engine returned an error - NO Implicit Passthru.
DEBUG: Error during prepare of:
The way I understood this query is, you are creating multiple tables, and each table is created as a select statement which is constructed through multiple select statements that are joined via a UNION CORR. Essentially something like:
create table <something> as
(select <something> as state, * from <something> union corr
select <something> as state, * from <something> union corr
select <something> as state, * from <something>);
Is this correct?
If yes, your macro code had some syntactically problematic nesting going on. Try the following code (though I wasn't able to fully verify it since I don't have information about the inputs to the macros):
/* Since this needs to be passed between the two macros */
%global FT;
%macro ALLPROVTYPE() / parmbuff;
%do ii = 1 %to %sysfunc(countw(%bquote(&syspbuff.)));
%let FT=%scan(%bquote(&SYSPBUFF),&ii);
CREATE TABLE MYSASLIB.ALLST_PROV_&FT._NULL AS (
%JNSTS(&&PROVALL&FT)
);
%end;
%mend;
%macro JNSTS() / parmbuff;
%do jj = 1 %to %sysfunc(countw(%bquote(&syspbuff.)));
%let ST=%scan(%bquote(&SYSPBUFF),&jj);
SELECT "&ST" AS STATE,* FROM MYDB.&ST._PROV_&FT
%if &jj NE %sysfunc(countw(%bquote(&syspbuff.))) %then
%do;
UNION CORR
%end;
%end;
%mend;
PROC SQL;
%ALLPROVTYPE(&PROVNUMS);
QUIT;
%let rows = "";
%macro test;
proc sql noprint;
select count(ID)
into: sqlRows
from mytbl;
quit;
%do i = 1 %to &sqlRows; * loop from 1 to sqlRows;
proc sql noprint;
select ID
into: ColumnID
from mytbl(firstobs= &i);
quit;
%if &rows eq "" %then %do
%let rows = "<tr><td>&ColumnID</td></tr>";
%end;
%if &rows ne "" %then %do
%let rows = "&rows<tr><td>&ColumnID</td></tr>";
%end;
%end;*End loop;
%mend;
%test;
%put &rows;
Hi I want to put all data of column ID data of mytbl into a variable.
I've created a variable named rows and assigned empty value in it. Then using loop I'm getting the values one by one of mytab and saving them in columnID variable. if rows variable is empty then only add tr and td with columnID data. if rows variable is not empty then append it. but it's only giving me the last record of my table.
lets say mytbl has data 1,2 and 3 in ID column
rows variable should have data as
<tr><td>1</td></tr><tr><td>2</td></tr><tr><td>3</td></tr>
but its only showing me data of last row as
<tr><td>3</td></tr>
You've got a few different problems, starting with some missing semicolons. More importantly, your code is more complex than it needs to be. You can get what you want with one PROC SQL step using SELECT INTO:, you don't need a separate PROC SQL step for each record. Play around with:
data have;
do ID=1 to 3;
output;
end;
run;
proc sql noprint;
select cats('<tr><td>',ID,'</td></tr>')
into :Rows
separated by ""
from have;
quit;
%put &rows;
I think you're severely misunderstanding what macro variables are, as opposed to regular variables, in SAS. You don't say exactly what you're going to eventually do with this, but nonetheless.
First off, macro variables don't take quotation marks; if they contain them, they're treated just as regular characters. So:
%let var = "";
%let var = "&var.123";
%put &=var.;
will return
"""123"
since it doesn't really know much about the quotation marks (it is somewhat aware of them, but it doesn't treat them the way a normal SAS variable does).
Second, as Quentin correctly points out, why on earth are you using SQL to go a row at a time? That's basically the opposite reason as what you'd use SQL for. SQL is great for doing something to the whole dataset at once, it's absolutely horrible at one row at a time- that's what the data step is for.
If you actually want a SAS variable, or you want to process things a row at a time, you should just use the data step:
data want;
set mytbl end=eof;
retain rows; *do not need to initialize to missing, that is normal;
length rows $32767;
rows = cats(rows,"<tr><td>",ColID,"</td></tr>");
if eof then output;
run;
You'd usually do that if you were going to use call execute, for example if you planned to put this to an HTML page (in a stored proc for example) with some wrapper code that you wanted to execute, in if _n_=1 for the start and if eof for the end.
I use the following code to insert rows to the table:
proc sql;
create table business_keys as
select name, memname
from sashelp.vcolumn
where 1=0;
quit;
%macro insert(list);
proc sql;
%do i=1 %to &max;
%let val = %scan(&list,&i);
insert into business_keys
select distinct name, memname
from sashelp.vcolumn
where upcase(memname) = "&list"
and upcase(name) like '%_ZRODLO_ID%'
and length(name) = 12;
%end;
quit;
%mend;
%insert(&name1);
Now it inserts me the same row many &max times.
I have to execute it for all macro variables (&name#), not only for &name1. How I can pass all variables at the same time? In principle, I want to loop through all of these table names:
%insert(&name1-&name&max)
%name1 = PEOPLE, %name2 = CREDITS, ... %name%max = ANY_TABLE_NAME
Where &name# is table name and &max is number of tables.
ok, now i understand what you want to do, it is actually quite simple:
%macro insert;
proc sql;
%do i=1 %to &max;
insert into business_keys
select distinct name, memname
from sashelp.vcolumn
where upcase(memname) = upcase("&&name&i") and upcase(name) like '%_ZRODLO_ID%' and length(name) = 12;
%end;
quit;
%mend;
%insert;
&&name&i resolves to &name1\&name2...\&namex which resolves to PEOPLE\CREDITS...\ANY_TABLE_NAME depending on i.
Sounds like you want to pass a "macro array" to your macro to process. Where by "macro array" I mean a series of macro variables that all consist of a base name and a numeric suffix. Like NAME1, NAME2, etc. It would be easier to do that by passing two parameters to your macro. One for the basename of the array and one for the upper limit (or max) index.
%macro insert(basename,max);
%local i;
...
%do i=1 %to &max ;
... &&basename&i ...
%end;
...
%mend insert;
So you might call the macro like this:
%let name1=PEOPLE;
%let name2=CREDITS;
%insert(NAME,2);
Personally I would avoid the macro array and instead store the list in a single macro variable. If the list is just SAS names (datasets, libraries, variables, formats, etc.) then just use space for the delimiter. If it is something like labels that could include spaces then use some other character like | for the delimiter. Then your macro would look more like this.
%macro insert(memlist);
%local i;
...
%do i=1 %to %sysfunc(countw(&memlist,%str( ))) ;
... %scan(&memlist,&i,%str( )) ...
%end;
...
%mend insert;
So you might call the macro like this:
%insert(PEOPLE CREDITS);
If list looks like PEOPLE,CREDITS,...,ANY_TABLE_NAME
you should define max variable as following:
%let max = %sysfunc(countw(&list,',')).
You will know the number of iterations.
%macro insert(list);
%let max = %sysfunc(countw(&list,',')).
proc sql;
%do i=1 %to &max;
%let val = %scan(&list,&i);
insert into business_keys
select distinct name, memname
from sashelp.vcolumn
where upcase(memname) = "&val" and upcase(name) like '%_ZRODLO_ID%' and length(name) = 12;
%end;
quit;
%mend;
I'm new to SAS and trying to create a user defined function that involves proc sql, the simplified version of the function is below;
proc fcmp outlib=work.funcs.test;
function calculate(table1, var1, motherTable);
proc sql noprint;
create table table1 as
select var1
from motherTable;
quit;
return();
endsub;
However, when I run the program I get the following:
ERROR: Subroutine 'calculate' was not terminated with ENDSUB.
ERROR: File WORK.MOTHERTABLE.DATA does not exist.
I am terminating the function with endsub(), and I know that motherTable doesn't exist because it's an argument to the function that hasn't been defined yet. Does anyone know what the problem could be? Thank you so much!
First, what you're doing is probably better done in a macro. That's how you do things like this most of the time in SAS.
%macro calc_func(in_table=, out_table=, var=);
proc sql noprint;
create table &out_table. as
select &var.
from &in_table.
;
quit;
%mend calc_func;
Second of all, you could do this in a user defined function (or a user defined call routine, more likely, as there's nothing being returned here); but you'd have to do it through a macro, if my understanding is right.
Check this paper for more information, or see the below example.
%macro calc_func();
%let table1=%sysfunc(dequote(&table1.));
%let var1=%sysfunc(dequote(&var1.));
%let motherTable=%sysfunc(dequote(&motherTable.));
%put _all_;
proc sql;
create table &table1. as (
select &var1.
from sashelp.&motherTable.)
;
quit;
%mend calc_func;
proc fcmp outlib=work.funcs.test;
function calculate(table1 $, var1 $, motherTable $);
rc = run_macro('calc_func', motherTable, table1, var1 );
return(rc);
endsub;
quit;
options cmplib=work.funcs;
data _null_;
x = calculate('newclass', 'age', 'class');
put x=;
run;
Basically, RUN_MACRO takes the macro name as an argument, and then allows FCMP to create macro variables with the names of the FCMP variables (or passed parameters). However, you have to remove their quotes, which is ... irritating. Good reason not to do this, unless it's truly necessary, I suppose.
The PROC SQL statement is ending the PROC FCMP compilation. You should just write that as a macro.
%macro calculate(table1, var1, motherTable);
proc sql noprint;
create table &table1 as
select &var1
from &motherTable
;
quit;
%mend calculate;
What it the problem with the below SQL Macro?
I have multiple datasets called "C_out1" 2,3,4 and so on and I would like to extract just one number from each dataset into one new table. I have searched and searched for help but without any luck.
I have tested the code without the macro element on just one dataset and that works fine, but when I try to make it dynamic with the below code, it fails.
I'm able to do the job with a simple datastep, but I'm fairly new to the SQL language, and I would really like to be able to do this. There's almost 200.000 datasets I have to join so I'm guessing that SQL is preferable to the data step.
I get the error:
"NOTE: Line generated by the macro variable "I".
1 C_out5
------
78
ERROR 78-322: Expecting a ','.
974 where label2='c';
975 quit;
Code:
%macro loop(prefix2);
%do i=1 %to 5;
&prefix2&i
%let i = %eval(&i+1);
%end;
%mend loop;
PROC SQL;
create table CTOTAL as
select nvalue2
from %loop(C_out)
where label2='c';
quit;
The data step is a significantly better method to do this than SQL.
data CTOTAL;
set C_out:;
where label2='c';
run;
If you're not using all of the c_out datasets, you might need to do some work to improve this, but if you are using all c_out datasets then this will work as is.
Joe's answer is best.
If you must use SQL:
Your SQL is not correct. You need to do this with a UNION ALL. This solution will work for SQL:
%macro loop(prefix2);
%do i=1 %to 4;
select nvalue from &prefix2&i where str='c' union all
%end;
select nvalue from &prefix2&i where str='c'
%mend loop;
PROC SQL;
create table CTOTAL as
%loop(C_out)
;
quit;
No need to manually increment the &i value.
The loop will put &i to (n+1) at the final loop, so make the loop go to (n-1) and just output the last statement outside the loop.