I'm new to SAS so please bear with me.
I have monthly data for the trailing 7 months. It goes through a PROC TRANSPOSE such that the resulting table has columns named FEB2015, MAR2015,...,AUG2015. These columns will change each month I rerun my program so that the earliest month will go from Feb to Mar, etc. in successive months of reruns. I want to be able to reference this "earliest month" later on in the program. For example, I'd like to run a PROC SQL that returns rows that have no values in the FEB2015 column but a value under 1000 in the AUG2015 Column and I'd like to do this based on the fact that the columns are named after last month, and the month 7 months ago.
Here's an example of code I'd be trying to run. Assume the table has columns row_ID, FEB2015, MAR2015, APR2015, MAY2015, JUN2015, JUL2015, AUG2015, all integers.
%let first = put(intnx('month',today(),-7,'begin'), MONYY7.);
%let second = put(intnx('month',today(),-6,'begin'), MONYY7.);
%let last = put(intnx('month',today(),-1,'begin'), MONYY7.);
PROC SQL noprint;
SELECT row_id, &first, &second, &last
FROM mytable
WHERE &first is missing
and &second is not missing
and &last < 1000;
QUIT;
I think the values in the macro variables are just being read as strings and are not being recognized as the name of the column. I've tried wrapping them in NLITERAL() but haven't had any luck.
Thanks!
Of course the macro variable values are strings. Macro variable values are ALWAYS strings. The problem is that your strings are not valid names of variables. Variable names cannot have parentheses or quotes in them. If you want to call a function in macro code you need to nest the call inside of the %SYSFUNC() macro function.
%let first = %sysfunc(intnx(month,%sysfunc(today()),-7,begin), MONYY7.);
Related
I would like to use a macro in SAS to calculate the last day of the current month when executed.
As i'm quite new to the SAS macro's i've tried to create on based on the information i've found on the internet.
%let last_day = %sysfunc(putn(%sysfunc(intnx(month,%sysfunc(today()),e), date9.));
However it does not seem to work when i execute it.
You left out the number of intervals in the INTNX() function call.
To create a macro variable with the string that looks like the last day of the current month in the style produced by the DATE9. format just use:
%let last_day = %sysfunc(intnx(month,%sysfunc(today()),0,e), date9.);
You could then use that macro variable to generate strings. Such as in a TITLE statement.
TITLE "End of the month is &last_day";
If you want to use it as an actual date you will need to convert it to a date literal by adding quotes and the letter d.
...
where date <= "&last_day"d ;
And if so it is probably simpler to not use the DATE9. format at all and just store the raw number of days since 1960 in the macro variable.
%let last_day = %sysfunc(intnx(month,%sysfunc(today()),0,e));
...
where date <= &last_day ;
I'm new to programming in SAS and I would like to do 2 macros, the first one I have done and it consists of giving 3 parameters: name of the input table, name of the column, name of the output table. What this macro does is translate the rare or accented characters, passing it a table and specifying in which column you want the rare characters to be translated:
The code to do this macro is this:
%macro translate_column(table,column,name_output);
*%LET table = TEST_MACRO_TRNSLT;
*%let column = marca;
*%let name_output = COSAS;
PROC SQL;
CREATE TABLE TEST AS
SELECT *
FROM &table.;
QUIT;
data &NAME_OUTPUT;
set TEST;
&column.=tranwrd(&column., "Á", "A");
run;
%mend;
%translate_column(TEST_MACRO_TRNSLT,marca,COSAS);
The problem comes when I try to do the second macro, that I want to replicate what I do in the first one but instead of having the columns that I can introduce to 1, let it be infinite, that is, if in a data set I have 4 columns with characters rare, can you translate the rare characters of those 4 columns. I don't know if I have to put a previously made macro in a parameter and then make a kind of loop or something in the macro.
The same by creating a kind of array (I have no experience with this) and putting those values in a list (these would be the different columns you want to iterate over) or in a macrovariable, it may be that passing this list as a function parameter works.
Could someone give me a hand on this? I would be very grateful
Either use an ARRAY or a %DO loop.
In either case use a space delimited list of variable names as the value of the COLUMN input parameter to your macro.
%translate_column
(table=TEST_MACRO_TRNSLT
,column=var1 varA var2 varB
,name_output=COSAS
);
So here is ARRAY based version:
%macro translate_column(table,column,name_output);
data &NAME_OUTPUT;
set &table.;
array __column &column ;
do over __column;
__column=ktranslate(__column, "A", "Á");
end;
run;
%mend;
Here is %DO loop based version
%macro translate_column(table,column,name_output);
%local index name ;
data &NAME_OUTPUT;
set &table.;
%do index=1 %to %sysfunc(countw(&column,%str( )));
%let name=%scan(&column,&index,%str( ));
&name = ktranslate(&name, "A", "Á");
%end;
run;
%mend;
Notice I switched to using KTRANSLATE() instead of TRANWRD. That means you could adjust the macro to handle multiple character replacements at once
&name = ktranslate(&name,'AO','ÁÓ');
The advantage of the ARRAY version is you could do it without having to create a macro. The advantage of the %DO loop version is that it does not require that you find a name to use for the array that does not conflict with any existing variable name in the dataset.
I want to insert values into a new table, but I keep getting the same error: VALUES clause 1 attempts to insert more columns than specified after the INSERT table name. This is if I don't put apostrophes around my date. If I do put apostrophes then I get told that the data types do not correspond for the second value.
proc sql;
create table date_table
(cvmo char(6), next_beg_dt DATE);
quit;
proc sql;
insert into date_table
values ('201501', 2015-02-01)
values ('201502', 2015-03-01)
values ('201503', 2015-04-01)
values ('201504', 2015-05-01);
quit;
The second value has to remain as a date because it used with > and < symbols later on. I think the problem may be that 2015-02-01 just isn't a valid date format since I couldn't find it on the SAS website, but I would rather not change my whole table.
Date literals (constants) are quoted strings with the letter d immediately after the close quote. The string needs to be in a format that is valid for the DATE informat.
'01FEB2015'd
"01-feb-2015"d
'1feb15'd
If you really want to insert a series of dates then just use a data step with a DO loop. Also make sure to attach one of the many date formats to your date values so that they will print as human understandable text.
data data_table ;
length cvmo $6 next_beg_dt 8;
format next_beg_dt yymmdd10.;
do _n_=1 to 4;
cvmo=put(intnx('month','01JAN2015'd,_n_-1,'b'),yymmn6.);
next_beg_dt=intnx('month','01JAN2015'd,_n_,'b');
output;
end;
run;
#tom suggest you in comments how to use date and gives very good answer how to it efficently, which is less error prone than typing values. I am just putting the same into the insert statement.
proc sql;
create table date_table
(cvmo char(6), next_beg_dt DATE);
quit;
proc sql;
insert into date_table
values ('201501', "01FEB2015"D)
;
UPDATE I've been told this isn't possible using arrays because of they way they are stored. This changes my question a bit, but the gist is still the same. How can I most efficiently generate the tables I need from a given vector of values (ex: day, week, month, year) without just repeating the code multiple times? Is there any way to simply substitute the given date value into INTX in a loop?
Ok, this is my last question on this subject, I promise. After some good advice, I'm using the INTX function. However, I'd like to just loop through the different categories I select and create tables. I tried this, but to no avail.
data;
array period [*] $ day week month year;
run;
%MACRO sqlloop;
proc sql;
%DO k = 1 %TO dim(&period); /* in case i decide to drop/add from array later */
%LET bucket = &period[&k];
CREATE TABLE output.t_&bucket AS (
SELECT INTX( "&bucket.", date_field, O, 'E') AS test FROM table);
%END
quit;
%MEND
%sqlloop
Sadly this doesn't work because I'm fouling up the array reference somehow. If I can get this step I'll be in good shape.
You could replace your array with a macro variable string:
%let period=day week month year;
In your macro then, you loop over the words in the macro variable:
%MACRO sqlloop;
proc sql;
%DO k = 1 %TO %sysfunc(countw(&period.)); /*fixed extra s*/
%LET bucket = %scan(&period.,&k.);
CREATE TABLE output.t_&bucket AS (
SELECT INTNX( "&bucket.", date_field, 0, 'E') AS test FROM table);
%END;
quit;
%MEND;
%sqlloop
edit you forgot some semicolons apparently. :p
In SAS, is it possible to refer a %let statement to a value located in a database?
For instance, the value of my n in %let n=50 depends on some value calculated in one of my databases, e.g., first row plus first column. And since that value gets modified 100 times in my loop I don't want to manually enter that value.
There's several ways to do this. Here's two:
proc sql;
select a+b into :n
from your_table
where some_condition;
quit;
This populations a macro variable, &n, with the sum of the variables a and b. The condition you specify should be true only for one row of your table.
Another approach:
data tmp;
set your_table;
if _n_=1 then do;
call symputn('n',a+b);
end;
run;