SAS SQL Macro to join multiple datasets into one - sql

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.

Related

Using macro for formula proc sql in SAS

I need some help with macros in SAS. I want to sum variables (for example, from v_1 to v_7) to aggregate them, grouping by year. There are plenty of them, so I want to use macro. However, it doesn't work (I get only v_1) I would really appreciate Your help.
%macro my_macro();
%local i;
%do i = 1 %to 7;
proc sql;
create table my_table as select
year,
sum(v_&i.) as v_&i.
from my_table
group by year
;
quit;
%end;
%mend;
/* I don't know to run this macro - is it ok? */
data run_macro;
set my_table;
%my_macro();
run;
The macro processor just generates SAS code and then passes onto to SAS to run. You are calling a macro that generates a complete SAS step in the middle of your DATA step. So you are trying to run this code:
data run_macro;
set my_table;
proc sql;
create table my_table as select
year,
sum(v_1) as v_1
from my_table
group by year
;
quit;
proc sql;
create table my_table as select
year,
sum(v_1) as v_1
from my_table
group by year
;
quit;
...
So first you make a copy of MY_TABLE as RUN_MACRO. Then you overwrite MY_TABLE with a collapsed version of MY_TABLE that has just two variables and only one observations per year. Then you try to collapse it again but are referencing a variable named V_2 that no longer exists.
If you simply move the %DO loop inside the generation of the SQL statement it should work. Also don't overwrite your input dataset. Here is version of the macro will create a new dataset name MY_NEW_TABLE with 8 variables from the existing dataset named MY_TABLE.
%macro my_macro();
%local i;
proc sql;
create table my_NEW_table as
select year
%do i = 1 %to 7;
, sum(v_&i.) as v_&i.
%end;
from my_table
group by year
;
quit;
%mend;
%my_macro;
Note if this is all you are doing then just use PROC SUMMARY. With regular SAS code instead of SQL code you can use variable lists like v_1-v_7. So there is no need for code generation.
proc summary nway data=my_table ;
class year ;
var v_1 - v_7;
output out=my_NEW_table sum=;
run;

ORA-00928: missing SELECT keyword, SQL statement was not passed to the DBMS, SAS will do the processing

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;

Overwriting/Appending sas variable

%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.

Error Handling in a sas macro

I am writing a simple macro to count distinct values in all the columns of a table.
I need to include an error handler which displays an error information and conitnues to execute the macro further, if certain column is found in the first but not in the second table.
for eg,
lets say, i write a macro to count the distinct values for col1, col2, col3 in a any dataset, and
table1 has columns (col1, col2, col3)
but,
table2 has columns (col2, col3) - hence there will be an error that col1 doesnot exists in table2. i need a way to handle this error.
Love SAS, but I hate error handling in it (as it's near non-existent and almost always needs to be done using macro code... yuck).
Your best bet is to check for any conditions prior to executing the code, and if any requirements are not met then some of your options are:
Abort with a descriptive message prior to the step being run. I find that the %abort cancel statement is the nicest way to stop code in both batch and interactive sessions.
Skip the step that would fail using a %if %then statement.
Have the code gracefully fix the issue and continue (if that's a possibility). This could be done by conditionally running additional code that's not normally part of the regular job flow.
Have it run and then reset the error condition (ie. set &syserr to zero)? I've never really played around with this option so I'm not 100% sure how it works of even if it's feasible.
In your case I imagine your code will look something like:
data have1;
set sashelp.class;
run;
data have2;
set sashelp.class(drop=age);
run;
/* GET A LIST OF COLUMNS IN EACH TABLE */
proc sql noprint;
create table column_list as
select memname, name
from dictionary.columns
where libname = 'WORK'
and memname in ('HAVE1','HAVE2')
order by name
;
quit;
/* CREATE A DATASET CONTAINING COLUMNS THAT ONLY EXISTS IN ONE OF THE TWO TABLES */
/* YOUR LOGIC MAY DIFFER */
data diff_columns;
set column_list;
by name;
if first.name and last.name then do;
output;
end;
run;
%macro error_handling;
%if %nobs(iDs=diff_columns) %then %do;
%put ERROR: TABLES CONTAINED DIFFERENT COLUMNS.;
/* CHOOSE HOW YOU WANT TO HANDLE IT HERE */
%end;
%mend;
%error_handling;
A few things... I've used a macro called %nobs() to help me determine if there are any obersvations in the diff_columns dataset. There are many different versions of %nobs, here is a selection.
If you decide you want to have SAS end without running any more code, a good macro for doing that is shown below. It will quit SAS if running in batch mode, but if you're running interactively it will just cancel the remaining submitted code without leaving SAS:
%macro stop_sas;
%if "&sysenv" eq "FORE" %then %do;
%abort cancel;
%end;
%else %do;
endsas;
%end;
%mend;
If you want to stop SAS from cluttering up the log once it has encountered any errors, consider using the %runquit macro. It looks like this, and usage instructions can be found here:
%macro runquit;
; run; quit;
%if &syserr %then %abort cancel;
%mend;
Error handling in SAS is a pretty messy business and while this gives you somewhere to stat I'm sure this list is by no means comprehensive. I'd suggest just try a few different approaches that I've listed above and then choose whatever worked best for you...

Can this run in a Macro?

I have been trying to resolve an issue and using a different approach to resolve it. I have created a macro to get actual value. The sql generates an HTML view with the values I need.
%macro actualvalue();
proc sql noprint;
%do i=1 %to %wordcount(&fieldlist);
Select %scan(&fieldlist,&i) into :actualvar separated by ' ' FROM TableA Where
IncidentItemId=%scan(&incidentitemlist,&i);
%end;
quit;
%mend actualvalue;
However, the actualvar macro variable does not seem to capture the value. Is there something wrong in the way I am trying to initialize the macro variable or this cannot be performed inside a macro. Any thoughts on this would be appreciated.
I think each time your do loop runs it is overwriting the previous value of actualvar. You need to use something like
select %scan(&fieldlist,&i) into :actualvar&i ...
Then afterwards print out values for &actualvar1 &actualvar2 etc... to check your results.
At least you need to put PROC SQL statement inside the DO loop, since your aim is running proc sql for multiple times
%do i=1 %to %wordcount(&fieldlist);
proc sql noprint;
Select %scan(&fieldlist,&i) into :actualvar separated by ' ' FROM TableA
Where IncidentItemId=%scan(&incidentitemlist,&i);
%end;
I don't see any problem regarding the rest though. Give it a test and report any error.
I will modify this answer accordingly.