%macro varexist(data=_LAST_, varname=, alias=);
%local rc temp_ds temp_varnames varexist;
%if %upcase(&data) = _LAST_ %then %let data = &SYSLAST;
/* Use a unique temp dataset name to avoid collisions */
%let temp_ds = __temp_%sysfunc(monotonic());
/* DOSUBL block to check variable existence and generate varlist */
%let rc = %sysfunc(dosubl(%nrstr(
/* Check if dataset exists */
%if %sysfunc(exist(&data)) %then %do;
/* Get list of variables */
data &temp_ds (keep=name);
set &data (obs=0);
array _num[*] _numeric_;
do i = 1 to dim(_num);
name = vname(_num[i]);
output;
end;
stop;
run;
/* Check if &varname exists */
data _null_;
set &temp_ds end=eof;
if upcase(name) = upcase("&varname") then do;
call symputx('varexist', 1, 'G');
stop;
end;
if eof and not symgetn('varexist') then call symputx('varexist', 0, 'G');
run;
/* Generate comma-separated varlist with alias */
proc sql noprint;
select
%if &alias ne %then %do;
cats("&alias..", name)
%end;
%else %do;
name
%end;
into :temp_varnames separated by ","
from &temp_ds;
drop table &temp_ds;
quit;
%end;
%else %do;
%put ERROR: Dataset &data does not exist.;
%abort;
%end;
)));
/* Append missing variable if needed */
%if &varexist = 0 %then %do;
%if &alias ne %then %let temp_varnames = &temp_varnames, . as &varname;
%else %let temp_varnames = &temp_varnames, . as &varname;
%end;
/* Return the final varlist */
&temp_varnames
%mend varexist;
Avoid Global Scope Pollution
Uses %local for macro variables to prevent unintended interactions with the global environment.
Generates a unique temp dataset name (__temp_<monotonic()>) to avoid collisions in concurrent runs.
Efficiency
Replaces PROC TRANSPOSE with a lightweight data step using vname() to extract variable names. This is faster and avoids unnecessary transposition.
Uses array _num[*] _numeric_ to focus on numeric variables (aligns with your original problem statement). For character variables, add _char_ array logic.
Robustness
Explicitly checks if the dataset exists using %sysfunc(exist(&data)) before proceeding.
Uses call symputx with 'G' scope to ensure &varexist and &temp_varnames are available globally after DOSUBL.
Alias Handling
Properly constructs aliases using cats("&alias..", name) to avoid syntax errors like a.name vs. a..name.
Case Insensitivity
Uses upcase(name) = upcase("&varname") to handle case mismatches between dataset variables and user input.
/* Usage 1: Variable exists (no alias) */
proc sql;
create table want as
select %varexist(data=sashelp.class, varname=height)
from sashelp.class;
quit;
/* Usage 2: Variable does not exist (with alias) */
proc sql;
create table want as
select %varexist(data=sashelp.class, varname=bloodpressure, alias=a)
from sashelp.class as a;
quit;
/* Usage 3: Join with dynamic variable handling */
proc sql;
create table want as
select %varexist(data=sashelp.class, varname=bloodpressure, alias=a), b.iq
from sashelp.class as a
left join iq as b
on a.name = b.name;
quit;
I made some adjustments and hope that someone finds them helpful.
Here's my take.
A more descriptive macro name , much less code (easier to understand and maintain) and more checks (input vetting, data set contains variables).
I don't care about unique temp dataset name or global macro variable as they are short-lived and not useful to me.
Macro:
/* Macro used in a SQL select statement that returns the variable names from a data set. */
/* If column &varname doesn't exist, it's added to the list with a missing numeric value (dot). */
/* If column &varname already exists in the data set, simply list the variable names. */
%macro varaddsql(data=_LAST_, varname=, alias=);
%*** INIT ***;
%local rc dsvars addalias addvar;
%let data = %upcase(%sysfunc(compress(%superq(data ),.,kn))); %* clean input ;
%let varname= %upcase(%sysfunc(compress(%superq(varname), ,kn))); %* clean input ;
%let alias = %upcase(%sysfunc(compress(%superq(alias ), ,kn))); %* clean input ;
%if ^%sysfunc(exist(&data)) %then %do; %* vet input ;
%put ERROR: Dataset &data does not exist. Macro VARADDSQL will stop executing. ;
%return;
%end;
%*** MAIN ***;
%if &data = _LAST_ %then %let data = &syslast. ; %* fetch _last_ data set name ;
%if %length(&alias.) %then %let addalias = "&alias.." || ; %* prep table alias to add to variable name ;
%let rc = %sysfunc(dosubl(%nrstr( ;
proc contents data=&data noprint out=ZZ_VARADDSQL(keep=NAME); %* get variable names ;
run;
proc sql noprint;
select &addalias. upcase(NAME)
into :dsvars separated by ',' from ZZ_VARADDSQL; %* create variable list ;
drop table ZZ_VARADDSQL; %* drop temp table ;
quit;
)));
%if ^%length(&dsvars.) %then %do; %* check var list ;
%put ERROR: Dataset &data contains no variables. Macro VARADDSQL will stop executing. ;
%return;
%end;
%if ^%sysfunc(findw( %quote(&dsvars), &varname, %str(,.) ))
%then %let addvar= , . as &varname.; %* prep missing variable to add to var list ;
%*** RETURN COMMA-DELIMITED LIST OF VARIABLES, INCLUDING MISSING VARIABLE IF NEEDED ***;
&dsvars. &addvar.
%mend varaddsql;
Unit Test:
/*****************************************************************************
Unit test for program varaddsql.sas
******************************************************************************
Who When What
******************************************************************************
---
******************************************************************************/
%*************************************************************************;
%*** FAILURE expected ****************************************************;
%*************************************************************************;
* Invalid dataset name = > Message is displayed and error triggered.;
proc sql;
create table WANT as
select %varaddsql(data=SASHELP.CLASSX, varname=HEIGHT)
from SASHELP.CLASS;
quit;
* No variables in data set = > Message is displayed and error triggered.;
data BAD;
run;
proc sql;
create table WANT as
select %varaddsql(data=BAD, varname=HEIGHT)
from BAD;
quit;
%*************************************************************************;
%*** SUCCESS expected ****************************************************;
%*************************************************************************;
* Variable exists = > List of variables returned.;
proc sql;
create table WANT1 as
select %varaddsql(data=SASHELP.CLASS, varname=HEIGHT)
from SASHELP.CLASS;
quit;
* Variable exists, data set name stripped of invalid characters = > List of variables returned.;
proc sql;
create table WANT1 as
select %varaddsql(data=SASHELP.CLASS@, varname=HEIGHT)
from SASHELP.CLASS;
quit;
* Variable exists and _LAST_ data set used = > List of variables returned.;
proc sql;
create table WANT2 as
select %varaddsql(data=_LAST_, varname=HEIGHT)
from SASHELP.CLASS;
quit;
* Variable does not exist = > List of variables returned with added variable.;
proc sql;
create table WANT3 as
select %varaddsql(data=SASHELP.CLASS, varname=BLOODPRESSURE)
from SASHELP.CLASS;
quit;
* Variable does not exist and alias used = > List of variables returned with alias and with added variable.;
data IQ;
NAME='Jane '; IQ=150; output;
NAME='Janet'; IQ=144; output;
run;
proc sql;
create table WANT4 as
select %varaddsql(data=SASHELP.CLASS, varname=BLOODPRESSURE, alias=a)
,b.IQ
from SASHELP.CLASS as a
left join
IQ as b
on a.NAME=b.NAME;
quit;
April 27 – 30 | Gaylord Texan | Grapevine, Texas
Walk in ready to learn. Walk out ready to deliver. This is the data and AI conference you can't afford to miss.
Register now and lock in 2025 pricing—just $495!
Still thinking about your presentation idea? The submission deadline has been extended to Friday, Nov. 14, at 11:59 p.m. ET.
Learn how use the CAT functions in SAS to join values from multiple variables into a single value.
Find more tutorials on the SAS Users YouTube channel.
Ready to level-up your skills? Choose your own adventure.