Set variable to macro variable with ampersand - sas

Not sure how to title this as the title is still pretty ambiguous but what I'm doing is.
PROC SQL NOPRINT;
SELECT LABEL INTO :head
FROM dictionary.columns
WHERE UPCASE(MEMNAME)='PROCSQLDATA' AND UPCASE(NAME)=%UPCASE("&var.");
QUIT;
DATA want;
SET have;
head="%SUPERQ(&head.)";
RUN;
So what I'm doing with the code is setting a macro variable "head" to the label of the variable "&var." within the data set "procsqldata". So let's say the label for one of the variables that I'm throwing into the proc sql is Adam&Steve. How do I set that to a variable within a data set without throwing an error. One way that I tried to cheat, which doesn't work because I may be doing it wrong is doing
%LET steve='&steve';
but that doesn't seem to work and it just does an infinite loop on the data step for some reason.

A few points.
First the %SUPERQ() function wants the NAME of the macro variable to quote. So if you write:
%superq(&head)
the macro processor will evaluate the macro variable HEAD and use the value as the name of the macro variable whose value you want it to quote. Instead write that as:
%superq(head)
Second macro triggers are not evaluated inside of strings that use single quotes on the outside. So this statement:
%let steve='&steve';
will set the macro variable Steve to single quote, ampersand, s, t, .... single quote.
But note that if you macro quote the single quotes then they do not have that property of hiding text from the macro processor. So something like:
%str(%')%superq(head)%str(%')
or
%bquote('%superq(head)')
Will generate the value of the macro variable HEAD surrounded with quotes.
So you might get away with:
head = %bquote('%superq(head)') ;
Although sometimes that macro quoting can confuse the SAS compiler (especially inside of a macro) so now that you have the single quotes protecting the ampersand you might need to remove the macro quoting.
head = %unquote(%bquote('%superq(head)')) ;
But the real solution is not to use macro quoting it at all.
Either pull the value using the SYMGET() function.
head = symget('head');
(make sure to set a length for the dataset variable HEAD or SAS will default it to $200 because of the function call).
Or better still just leave the label in a variable to begin with instead of trying to confuse yourself (and everyone else) by stuffing it into a macro variable just so you can then pull it back into a real variable.
PROC SQL NOPRINT;
create table label as SELECT LABEL
FROM dictionary.columns
WHERE LIBNAME='MYLIB' and MEMNAME='PROCSQLDATA' and UPCASE(NAME)=%UPCASE("&var.")
;
QUIT;
DATA want;
SET have;
if _n_=1 then set label;
head = label;
drop label;
run;

%SUPERQ takes the name of the argument, retrieves the value of it in a macro quoted context.
You might have better long term understanding if the symbol name used (head) is a name with more contextual meaning (such as var_label). Also, your DICIONARY.COLUMNS query should include a criteria for libname=. Note: LIBNAME and MEMNAME values are always uppercase in DICTIONARY.COLUMNS. Name, which is the column name, can be mixed case and needs the upcase to compare for name equality.
PROC SQL NOPRINT;
SELECT LABEL INTO :var_label
FROM dictionary.columns
WHERE
LIBNAME = 'WORK' and
MEMNAME = 'PROCSQLDATA' and
UPCASE(NAME)=%UPCASE("&var.")
;
QUIT;
data labels;
set have;
head_label = "%superq(var_label)";
run;
An & in SUPERQ argument means the name of the macro variable, whose value is to be retrieved, is found as the value of a different macro variable.
%let p = %nrstr(Some value & That%'s that);
%let q = p;
%let v = %superq(&q);
%put &=v;
-------- LOG -------
V=Some value & That's that
&q become p and %superq retrieved value of p for assignment to v
Note: In some situations, you can retrieved the label of a variable in a running data step using the VLABEL or VLABELX functions.
data have;
label weight = 'Weight (kg)';
retain weight .;
weight_label_way1 = vlabel ( weight );
weight_label_way2 = vlabelx('weight');
run;

There will be a quoting function that can solve this, but I can never remember what they all do, and I find it best to avoid them, for my own sanity and that of my coworkers.
In this case, you don't need to resolve the macro variable into a string literal (ie head = "&head";) at all; you can use SYMGET:
DATA want;
SET have;
head = SYMGET('head');
RUN;
See the docs for the SYMGET function here:
https://documentation.sas.com/?docsetId=mcrolref&docsetTarget=n00cqfgax81a11n1oww7hwno4aae.htm&docsetVersion=9.4&locale=en
On an unrelated note, you should also read the 'DICTIONARY Tables and Performance' section at the end of this page:
https://documentation.sas.com/?cdcId=pgmsascdc&cdcVersion=9.4_3.5&docsetId=sqlproc&docsetTarget=n02s19q65mw08gn140bwfdh7spx7.htm&locale=en
You might be surprised how much faster your first query will run if you removed the UPCASE functions from the WHERE clause.

Related

Create a macro that applies translate on multiple columns that you define in a dataset

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.

set a dataset by dereferencing a variable

I would like to set a dataset by using a reference to dataset name however Iam getting error message: ERROR: File dataset_name123 does not exist(work.dataset123 does exist) What is wrong?
data _null_;
%let product = 'dataset_name123';
set work.&product nobs = row_no;
put row_no;
put &product;
run;
Member names are not quoted. Remove the quotes from your macro variable. In macro code everything is character so there is no need to add quotes around string literals. The quotes become part of the value of the macro variable.
%let product = dataset_name123;
%put &=product;
data _null_;
set work.&product nobs = row_no;
put row_no;
put "&product";
stop;
run;
If you do include quotes in a dataset reference then SAS will interpret it as the physical name of the dataset file itself. So code like:
data want;
set 'dataset_name123';
run;
would look for a filename 'dataset_name123.sas7bdat' in the current working directory.
It is not a great idea to do a %let statement in a data step. Macrovariables and SAS variables are created differently.
There are two problems in this code. First one is quotes around macrovariable, which after resolution will be used for table name and hence your query fails as table names cannot be in quotes .
second one is put statement for macro variable for macro variable to resolve you need %put.
below is modified code.
data class;
set sashelp.class;
run;
data _null_;
%let product = class;
set work.&product nobs = row_no;
put row_no;
%put &product;
run;

How to access nth element of array within loop in macro SAS?

I'm trying to get the ith item of an array within %do loop in macro definition and create a dataset with the name of the element, but all I can get is something like "z1" etc. This is what I got so far
%macro print(set,groupvar);
proc sql ;
select put(count(distinct &groupvar),1.)
into :hm
from &set
;
select distinct set
into :z1-:z&sysmaxlong
from &set
;
quit;
data %do i =1 %to &hm;
%scan(&z, &i);
%end;
;
%mend;
I also tried z[&i] instead of %scan(&z,&i) but still no luck
Issues:
You have a macro variable SET but you have the distinct SET in the select query, is that what you intended?
select distinct set
You don't need to specify the end of a series when creating macro variables.
select distinct &set into :z1- from &set; quit;
You created a series of macro variables Z1-Zn but try and use SCAN to retrieve the values? They're not stored in an array, its stored in a series of macro variables such as Z1, Z2 etc.
An output statement can not specify a dynamic destination table. You will need to create wallpaper code to output to the appropriate table based on splitting criteria.
Your macro will need to create macro variables to support this code template
data &out1 &out2 … &outN;
set input_data;
select;
when (&case1) output &out1;
when (&case2) output &out2;
…
when (&caseN) output &outN;
otherwise;
end;
run;
Some slick SQL could support the template
data &outlist;
set input_data;
select;
&whenStatements;
otherwise;
end;
run;
The hash object .output() method can specify a dynamic destination for saving hash contents. Consider the case of "Split a table into subset tables whose names are based on a variables values" aka "Split one data set into several data sets named according to a group variable". Some methods require a pre-parse of the data (as in your posted code), others do not.
There is some hash based splitter code at https://www.devenezia.com/downloads/sas/samples/hash-6.sas
You can find other data splitters on sas community.
If you want to use %scan() then just create one macro variable.
select distinct set
into :z separated by ' '
from &set
;
%let hm=&sqlobs;
The DATA statement will end at the first semi-colon. But you are generating &hm+1 semi-colons instead of just one. Remove the spurious semi-colon from inside the %do loop.
data
%do i =1 %to &hm;
%scan(&z, &i)
%end;
;

SAS Rename variables using a list of variables in a macro

A novice in SAS here. I am trying to rename variables in a data set by using the new values I have in a list. Since I have multiple files with over 100 variables that need to be renamed, I created the following macro and I am trying to pass the list with the new names. However, I am not sure how to pass the list of variables and loop through it properly in the macro. Right now I am getting an error in the %do loop that says: "ERROR: The %TO value of the %DO I loop is invalid."
Any guidance will be greatly appreciated.
The list of new variables comes from another macro and it is saved in &newvars.
The number of variables in the files are the same number in the list, and the order they should be replaced is the same.
%macro rename(lib,dsn,newname);
proc sql noprint;
select nvar into :num_vars from dictionary.tables
where libname="&LIB" and memname="&DSN";
select distinct(nliteral(name)) into:vars
from dictionary.columns
where libname="&LIB" and memname="&DSN";
quit;
run;
proc datasets library = &LIB;
modify &DSN;
rename
%do i = 1 %to &num_vars.;
&&vars&i == &&newname&i.
%end;
;
quit;
run;
%mend rename;
%rename(pga3,selRound,&newvars);
Thank you in advance.
You are getting that error message because the macro variable NUM_VARS is not being set because no observations met your first where condition.
The LIBNAME and MEMNAME fields in the metadata tables are always uppercase and you called the macro with lowercase names.
You can use the %upcase() macro function to fix that. While you are at it you can eliminate the first query as SQL will count the number of variables for you in the second query. Also if you want that query to generate multiple macro variables with numeric suffixes you need to modify the into clause to say that. The DISTINCT keyword is not needed as a dataset cannot have two variables with the same name.
select nliteral(name)
into :vars1 -
from dictionary.columns
where libname=%upcase("&LIB") and memname=%upcase("&DSN")
;
%let num_vars=&sqlobs;
You also should tell it what order to generate the names. Were the new names generated in expectation that the list would be in the order the variables exist in the dataset? If so use the VARNUM variable in the ORDER BY clause. If in alphabetical order then use NAME in the ORDER BY clause.
How are you passing in the new names?
Is it a space delimited list? If so your final step should look more like this:
proc datasets library = &LIB;
modify &DSN;
rename
%do i = 1 %to &num_vars.;
&&vars&i = %scan(&newname,&i,%str( ))
%end;
;
run; quit;
If NEWNAME has the base name to use for a series of variable names with numeric suffixes then you would want this:
&&vars&i = &newname&i
If instead you are passing into NEWNAME a base string to use to locate a series of macro variables with numeric suffixes then the syntax would be more like this.
&&vars&i = &&&newname&i
So if NEWNAME=XXX and I=1 then on the first pass of the macro processor that line will transform into
&vars1 = &XXX1
And on the second pass the values of VARS1 and XXX1 would be substituted.

macro variable is uninitialized after %let statement in sas

I want to create something in SAS that works like an Excel lookup function. Basically, I set the values for macro variables var1, var2, ... and I want to find their index number according to the ref table. But I get the following messages in the data step.
NOTE: Variable A is uninitialized.
NOTE: Variable B is uninitialized.
NOTE: Variable NULL is uninitialized.
When I print the variables &num1,&num2, I get nothing. Here is my code.
data ref;
input index varname $;
datalines;
0 NULL
1 A
2 B
3 C
;
run;
%let var1=A;
%let var2=B;
%let var3=NULL;
data temp;
set ref;
if varname=&var1 then call symput('num1',trim(left(index)));
if varname=&var2 then call symput('num2',trim(left(index)));
if varname=&var3 then call symput('num3',trim(left(index)));
run;
%put &num1;
%put &num2;
%put &num3;
I can get the correct values for &num1,&num2,.. if I type varname='A' in the if-then statement. And if I subsequently change the statement back to varname=&var1, I can still get the required output. But why is it so? I don't want to input the actual string value and then change it back to macro variable to get the result everytime.
Solution to immediate problem
You need to wrap your macro variables in double quotes if you want SAS to treat them as string constants. Otherwise, it will treat them the same way as any other random bits of text it finds in your data step.
Alternatively, you could re-define the macro vars to include the quotes.
As a further option, you could use the symget or resolve functions, but these are not usually needed unless you want to create a macro variable and use it again within the same data step. If you use them as a replacement for double quotes they tend to use a lot more CPU as they will evaluate the macro vars once per row by default - normally, macro vars are evaluated just once, at compile time, before your code executes.
A better approach?
For the sort of lookup you're doing, you actually don't need to use a dataset at all - you can instead define a custom format, which gives you much more flexibility in how you can use it. E.g. this creates a format called lookup:
proc format;
value lookup
1 = 'A'
2 = 'B'
3 = 'C'
other = '#N/A' /*Since this is what vlookup would do :) */
;
run;
Then you can use the format like so:
%let testvar = 1;
%let testvar_lookup = %sysfunc(putn(&testvar, lookup.));
Or in a data step:
data _null_;
var1 = 1;
format var1 lookup.;
put var1=;
run;