I have a control to verify.
I want to write a macro variable who includes an SQL proc in case the condition is verified.
Exemple:
%Macro Control1(List) / minoperator mindelimiter=' ';
%IF &Control1 in &List. %Then %do;
proc sql;
create table work.&Control1 as
Select id, count(id) as number
From data.&Produit.
group by id
having calculated number > 1
;
quit;
%end;
%mend;
%let list = &control1. &control2 &Control4 ;
%Control1(&List);
If we do the same process to control3 the proc sql doesn't run because control3 doesn't belong to the list.
I might have some mistakes in the syntax
/*CONTROL1 and PRODUIT ARE ALREADY DECLARED */
Your main problem is that you need to let SAS know that you want to use in as an operator in your macro logic.
To enable use of IN as an operator in macro language then you need set the MINOPERATOR option. To insure it always works for your macro independent of how the system option is set add it to the %macro statement.
%macro Control1(List) / minoperator mindelimiter=' ';
But you also have a number of other issues.
When defining a parameter to the macro you just want to type the name you want to parameter to have. Your example has an & before the name which would be invalid syntax and that is used to reference the value of macro variable and you cannot use the value to macro variable to name your parameter. So when defining the macro just list the name
%macro control1(list) .....
Note than when you CALL the macro is fine to reference a macro variable when passing in the parameter value to the macro, as you have done in your example call.
The %if statement has multiple problems. The first appears to just be a typo with an extra e added to the end of the list macro variable's name. The second is a logical mistake. The value the macro variable CONTROL1 is never going to be the same as that value with double quote characters around it. Also the () around the list of values for the in operator are not needed, but they won't cause any actual problems.
%if &control1 in &list %then %do;
Your SELECT statement is missing a comma between the two variables you are selecting.
select id,count(id) as number
The condition in your HAVING clause is not valid syntax. Are you trying to test if the count is larger than 1? Plus if you want to reference a variable you have calculated you need to use the CALCULATED keyword.
having calculated number > 1
Finally it is potentially confusing to reference two macro variables, CONTROL1 and PROUDUIT, in your macro that are not defined by the code (or even documented in a comment). They are not input parameters. You have not declared them as local macro variables. Or done anything to insure that they will exist.
Related
I am using the following code to change the length of all character variables in my dataset. I am not sure why this loop is not working.
data test ;
set my.data;
array chars[*] _character_;
%do i = 1 %to dim(chars);
length chars[i] $ 30 ;
%end;
run;
You're mixing data step and macro commands, for one. %do is macro only, but the rest of that is data step only. You also need the length statement to be the first time the variable is encountered, not the set statement, as character lengths are not changeable after first encounter.
You either need to do this in the macro language, or do this with some other data-driven programming technique (as user667489 refers to some of). Here are two ways.
Macro based, using the open group of functions, which opens the dataset, counts how many variables there are, then iterates through those variables and calls the length statement for each (you could identically have one length, iterate through the variables, and one number). This is appropriate for a generic macro, but is probably more difficult to maintain.
%macro make_class_longer(varlength=);
data class;
%let did=%sysfunc(open(sashelp.class,i));
%let varcount=%sysfunc(attrn(&did,nvars));
%do _i = 1 %to &varcount;
%if %sysfunc(vartype(&did., &_i.))=C %then %do;
length %sysfunc(varname(&did.,&_i)) $&varlength.;
%end;
%end;
%let qid=%sysfunc(close(&did));
set sashelp.class;
run;
%mend make_class_longer;
%make_class_longer(varlength=30);
Similarly, here is a dictionary.columns solution. That queries the metadata directly and builds a list of character variables in a macro variable which is then used in a normal length statement. Easier to maintain, probably slower (but mostly meaninglessly so).
proc sql;
select name into :charlist separated by ' '
from dictionary.columns
where libname='SASHELP' and memname='CLASS' and type='char'
;
quit;
data class;
length &charlist. $30;
set sashelp.class;
run;
Length of variables is determined when a data step is compiled, so the first statement that mentions a variable usually determines its length. In your example, this is the set statement. Once fixed, a variable's length cannot be changed, unless you rebuild the whole dataset.
To get the result you want here, you would need to move your length statement above your set statement, and consequently you would also need to explicitly specify all the names of the variables whose lengths you want to set, as they would not otherwise exist yet at that point during compilation. You can do this either by hard-coding them or by generating code from sashelp.vcolumn / dictionary.columns.
There are a number of logical and syntactical errors in that code.
The main logical error is that you cannot change the length of a character variable after SAS has already determined what it should be. In your code it is determined when the SET statement is compiled.
Another logical error is using macro %DO loop inside a data step. Why?
Your example LENGTH statement is syntactically wrong. You cannot have an array reference in the LENGTH statement. Just the actual variable names. You could set the length in the ARRAY statement, if it was the first place the variables were defined. But you can't use the _character_ variable list then since for the variable list to find the variables the variables would have to already be defined. Which means it would be too late to change.
You will probably need to revert to a little code generation.
Let's make a sample dataset by using PROC IMPORT. We can use the SASHELP.CLASS example data for this.
filename csv temp;
proc export data=sashelp.class outfile=csv dbms=csv ;run;
proc import datafile=csv out=sample replace dbms=csv ;run;
Resulting variable list:
This is also a useful case as it will demonstrate one issue with changing the length of character variables. If you have assigned a FORMAT to the variable you could end up with the variable length not matching the format width.
Here is one way to dynamically generate code to change the length of the character variables without changing their relative position in the dataset. Basically this will read the metadata for the table and use it to generate a series of name/type+length pairs for each variable.
proc sql noprint ;
select varnum
, catx(' ',name,case when type='num' then put(length,1.) else '$30' end)
into :varlist
, :varlist separated by ' '
from dictionary.columns
where libname='WORK' and memname='SAMPLE'
order by 1
;
quit;
You can then use the generated list in a LENGTH statement to define the variables' type and length. You can also add in FORMAT and INFORMAT statements to remove the $xx. formats and informats that PROC IMPORT (mistakenly) adds to character variables.
data want ;
length &varlist ;
set sample;
format _character_ ;
informat _character_;
run;
So I will briefly explain my code structure before I dive into the issue.
I've a macro
%Sales (Outdata= , dt =, Outdata2= , Outdata3= );
(
I create a table &outdata by (Select * from XYZ);
Proc SQL;
Create table &Outdata._1 as
(
)
%mend Sales
Now I call the macro
%Sales (Outdata = sales_final_Oct17, dt='2017-10-01');
Libname ABCDEFG
I Create a data set
Data ABCDEFG.all_sales_test;
Set ABCDEFG. all_Sales
sales_final_Oct17_1;
incur_month = month(rept_dt);
run;
Above (1 to 3) is the original code flow and it works fine.
My Problem:
I'm using a dynamic way of generating file name for each month (so that each month I do not manually enter file_name_month and date.
File name code
%let Last_Month = intnx('month', current_date,-1, "beginning");
Name = 'Sales_final';
Last_Month_Name = name|| put(&last_month, monyy7.);
Call SYMPUTX('Last_Month_Name_v', Last_Month_Name);
run;
Call Macro
%Sales(outdata=&Last_Month_Name, dt = 'Dynamic date');
Till this point everything works fine. The moment I create a data set similar to step 3 (above), the code breaks.
Libname ABCDEFG
Data ABCDEFG.all_sales_test;
Set ABCDEFG.all_Sales
Last_Month_Name_1;
incur_month = month(rept_dt);
run;
> Error Message: File ABCDEFG.LAST_MONTH_NAME_1.DATA does not exist.
What should I do to get rid of this error? It seems, if I pass a static name in the macro and then use the same name with "_1" it works fine but when I pass dynamic reference, then the data Set step fails with the above error message.
Any help is much appreciated. I'm new to SAS so excuse me if it's a silly question. Thanks.
In (1) the macro code is using the value of the macro parameter (or local macro variable) OUTDATA to create a dataset. In (2) you are supplying a value for OUTDATA in your call and in (3) you are using the same value again in the set statement.
One way to not have to type the value twice is to store the value into a macro variable then just reference that macro variable's value in steps (2) and (3).
So in (4) you did create a macro variable,Last_Month_Name_v , but you then used the value of a different macro variable, &Last_Month_Name in the macro call. But instead of using the macro variable in the set statement you just referenced some other dataset,Last_Month_Name_1 , that you never mentioned before at all.
Here are the simplified key steps in the process you want for how to create and use the macro variable. I have put in ... to show where I have left out parts of a statement or statements so we can concentrate on the flow of the macro variable and its value.
First you set the macro variable to some name that you want to use. Let's just use anything as the name for this example.
%let last_month_name= anything;
Then you use the value in the macro call to create the dataset. Notice the & before the name, that is what tells the macro processor to replace the name with the value. The period after the name tells the macro processor that is the end of the macro variable name.
%sales(outdata=&last_month_name. .... )
Then you can use the value again later when you want to tell the set statement which dataset to read.
set .... &last_month_name. ;
Now your posted macro %sales does not actually create the dataset named anything. Instead it appears to create a dataset named anything_1. Personally I don't know why that is there, but if you keep it that way then you need add the _1 back to the end of the macro variable's value in the set statement. Just do it in the same way that you did in the macro's code.
set .... &last_month_name._1 ;
Working with macros is one of the harder parts of SAS and can be very confusing to newcomers. Below is a simplified working example that demonstrates the approach I would take.
First we dynamically calculate the name of the table we want to save the results to:
%let current_date = %sysfunc(date());
%let last_month = Sales_final_%sysfunc(intnx(month, ¤t_date, -1, beginning), monyy7.);
%put &=last_month;
The output from the put statement in the above step is:
LAST_MONTH=Sales_final_OCT2017
Note that in the above code, I'm passing two parameters to %sysfunc(). The first parameter is the call to the intnx() function. The second parameter (monyy7.) is what format to apply to the result being returned from the function call.
Also note that there is no need to concatenate the prefix (Sales_final_) to the %sysfunc() result because when working with the macro language, the result of %sysfunc() is subsituted in place. There is no concatenation operator in the macro language at all - everything is based off of macro substitution.
Then it's a simple case of passing that value into the macro as shown below:
%macro sales(outdata=);
proc sql;
create table &outdata._1 as select * from sashelp.class;
quit;
%mend;
%sales(outdata=&last_month);
You should be able to modify the above into what you need.
I would like to return a value from a SAS macro I created but I'm not sure how. The macro computes the number of observations in a dataset. I want the number of observations to be returned.
%macro nobs(library_name, table_name);
proc sql noprint;
select nlobs into :nobs
from dictionary.tables
where libname = UPCASE(&library_name)
and memname = UPCASE(&table_name);
quit;
*return nobs macro variable;
&nobs
%mend;
%let num_of_observations = %nobs('work', 'patients');
Also, I would like the &nobs macro variable that is used within the macro to be local to that macro and not global. How can I do that?
I'll answer the core question Bambi asked in comments:
My main concern here is how to return a value from a macro.
I'm going to quibble with Dirk here in an important way. He says:
A SAS macro inserts code. It can never return a value, though in some cases you can mimic functions
I disagree. A SAS macro returns text that is inserted into the processing stream. Returns is absolutely an appropriate term for that. And when the text happens to be a single numeric, then it's fine to say that it returns a value.
However, the macro can only return a single value if it only has macro statements in addition to that value. Meaning, every line has to start with a %. Anything that doesn't start with % is going to be returned (and some things that do start with % might also be returned).
So the important question is, How do I return only a value from a macro.
In some cases, like this one, it's entirely possible with only macro code. In fact, in many cases this is technically possible - although in many cases it's more work than you should do.
Jack Hamilton's linked paper includes an example that's appropriate here. He dismisses this example, but that's largely because his paper is about counting observations in cases where NOBS is wrong - either with a WHERE clause, or in certain other cases where datasets have been modified without the NOBS metadata being updated.
In your case, you seem perfectly happy to trust NOBS - so this example will do.
A macro that returns a value must have exactly one statement that either is not a macro syntax statement, or is a macro syntax statement that returns a value into the processing stream. %sysfunc is an example of a statement that does so. Things like %let, %put, %if, etc. are syntax statements that don't return anything (by themselves); so you can have as many of those as you want.
You also have to have one statement that puts a value in the processing stream: otherwise you won't get anything out of your macro at all.
Here is a stripped down version of Jack's macro at the end of page 3, simplified to remove the nlobsf that he is showing is wrong:
%macro check;
%let dsid = %sysfunc(open(sashelp.class, IS));
%if &DSID = 0 %then
%put %sysfunc(sysmsg());
%let nlobs = %sysfunc(attrn(&dsid, NLOBS));
%put &nlobs;
%let rc = %sysfunc(close(&dsid));
%mend;
That macro is not a function style macro. It doesn't return anything to the processing stream! It's useful for looking at the log, but not useful for giving you a value you can program with. However, it's a good start for a function style macro, because what you really want is that &nlobs, right?
%macro check;
%let dsid = %sysfunc(open(sashelp.class, IS));
%if &DSID = 0 %then
%put %sysfunc(sysmsg());
%let nlobs = %sysfunc(attrn(&dsid, NLOBS));
&nlobs
%let rc = %sysfunc(close(&dsid));
%mend;
Now this is a function style macro: it has one statement that is not a macro syntax statement, &nlobs. on a plain line all by itself.
It's actually more than you need by one statement; remember how I said that %sysfunc returns a value to the processing stream? You could remove the %let part of that statement, leaving you with
%sysfunc(attrn(&dsid, NLOBS))
And then the value will be placed directly in the processing stream itself - allowing you to use it directly. Of course, it isn't as easy to debug if something goes wrong, but I'm sure you can work around that if you need to. Also note the absence of a semi-colon at the end of the statement - this is because semicolons aren't required for macro functions to execute, and we don't want to return any extraneous semicolons.
Let's be well behaved and add a few %locals to get this nice and safe, and make the name of the dataset a parameter, because nature abhors a macro without parameters:
%macro check(dsetname=);
%local dsid nlobs rc;
%let dsid = %sysfunc(open(&dsetname., IS));
%if &DSID = 0 %then
%put %sysfunc(sysmsg());
%let nlobs = %sysfunc(attrn(&dsid, NLOBS));
&nlobs
%let rc = %sysfunc(close(&dsid));
%mend;
%let classobs= %check(dsetname=sashelp.class);
%put &=classobs;
There you have it: a function style macro that uses the nlobs function to find out how many rows are in any particular dataset.
What is the Problem writing function-like macros?
i.e. macros you can use as%let myVar = %myMacro(myArgument)
You can use your user written macro as if it were a function if all you do is
calling some %doSomething(withSometing) like macro functions
assign values to macro variables with a %let someVar = statement
"return" your result, typically by writing &myResult. on the last line before your %mend
As soon as you include a proc or data step in your macro, this does not work any more
Luckily, %sysFunc() comes to the rescue, so we can use any data step function
This includes low level functions like open, fetch and close which can even access your data
nerdy people can do quite a lot with it, but even if you are nerdy, your boss will seldom give you the time to do so.
How do we solve this?, i.e. which building blocks do I use to solve this?
proc fcmp allows packaging some data step statements in a subroutine or function
This function, meant for use in a data step, can be used within %sysfunc()
Within this function you can call run_macro to execute any macro IN BACKGROUND IMMEDIATELY
Now we are ready for the practical solution
Step 1: write a helper macro
with no parameters,
using some global macro variables
"returning" its result in a global macro variable
I know that is bad coding habit, but to mitigate the risk, we qualify those variables with a prefix. Applied to the example in the question
** macro nobsHelper retrieves the number of observations in a dataset
Uses global macro variables:
nobsHelper_lib: the library in which the dataset resides, enclosed in quotes
nobsHelper_mem: the name of the dataset, enclosed in quotes
Writes global macro variable:
nobsHelper_obs: the number of observations in the dataset
Take care nobsHelper exists before calling this macro, or it will be ost
**;
%macro nobsHelper();
** Make sure nobsHelper_obs is a global macro variable**;
%global nobsHelper_obs;
proc sql noprint;
select nobs
into :nobsHelper_obs
from sashelp.vtable
where libname = %UPCASE(&nobsHelper_lib)
and memname = %UPCASE(&nobsHelper_mem);
quit;
%* uncomment these put statements to debug **;
%*put NOTE: inside nobsHelper, the following macro variables are known;
%*put _user_;
%mend;
Step 2: write a helper function;
**Functions need to be stored in a compilation library;
options cmplib=sasuser.funcs;
** function nobsHelper, retrieves the number of observations in a dataset
Writes global macro variables:
nobsHelper_lib: the library in which the dataset resides, enclosed in quotes
nobsHelper_mem: the name of the dataset, enclosed in quotes
Calls the macro nobsHelper
Uses macro variable:
nobsHelper_obs: the number of observations in the dataset
**;
proc fcmp outlib=sasuser.funcs.trial;
** Define the function and specity it should be called with two character vriables **;
function nobsHelper(nobsHelper_lib $, nobsHelper_mem $);
** Call the macro and pass the variables as global macro variables
** The macro variables will be magically qouted **;
rc = run_macro('nobsHelper', nobsHelper_lib, nobsHelper_mem);
if rc then put 'ERROR: calling nobsHelper gave ' rc=;
** Retreive the result and pass it on **;
return (symget('nobsHelper_obs'));
endsub;
quit;
Step 3: write a convenience macro to use the helpers;
** macro nobs retrieves the number of observations in a dataset
Parameters:
library_name: the library in which the dataset resides
member_name: the name of the dataset
Inserts in your code:
the number of observations in the dataset
Use as a function
**;
%macro nobs(library_name, member_name);
%sysfunc(nobsHelper(&library_name, &member_name));
%* Uncomment this to debug **;
%*put _user_;
%mend;
Finally use it;
%let num_carrs = %nobs(sasHelp, cars);
%put There are &num_carrs cars in sasHelp.Cars;
Data aboutClass;
libname = 'SASHELP';
memname = 'CLASS';
numerOfStudents = %nobs(sasHelp, class);
run;
I know this is complex but at least all the nerdy work is done.
You can copy, paste and modify this in a time your boss will accept.
;
A SAS macro inserts code. It can never return a value, though in some cases you can mimic functions, usually you need a work around like
%nobs(work, patients, toReturn=num_of_observations )
** To help you understand what happens, I advice printing the code inserted by the macro in your log: ;
options mprint;
We pass the name of the macro variable to fill in to the macro, I find it most practical to
not require the user of my macro to put quotes around the libary and member names
make the name of the variable a named macro variable, so we can give it a default;
%macro nobs(library_name, table_name, toReturn=nobs);
Make sure the variable to return exists
If it exists it is known outside of this macro.
Otherwisse if we create it here, it wil by default be local and lost when we leave the macro;
%if not %symexist(&toReturn.) %then %global &toReturn.;
In the SQL, I
use the SASHELP.VTABLE, a view provided by SAS on its meta data
add the quotes I omitted in the macro call ("", not '': macro variables are not substituted in single qoutes)
use the macro %upcase function instead of the SAS upcase function, as it sometimes improves performance;
proc sql noprint;
select nobs
into :&toReturn.
from sashelp.vtable
where libname = %UPCASE("&library_name.")
and memname = %UPCASE("&table_name.");
quit;
%mend;
Pay attention if you call a macro within a macro, Run this code and read the log to understand why;
%macro test_nobs();
%nobs(sashelp, class); ** will return the results in nobs **;
%nobs(sashelp, shoes, toReturn=num_of_shoes);
%let num_of_cars = ;
%nobs(sashelp, cars, toReturn=num_of_cars);
%put NOTE: inside test_nobs, the following macro variables are known;
%put _user_;
%mend;
%test_nobs;
%put NOTE: outside test_nobs, the following macro variables are known;
%put _user_;
You can't 'return' a value from a function-style macro unless you have written it using only macro statements. Quentin's link provides an example of how to do this.
For example, you cannot use your macro like so, because proc sql cannot execute in the middle of a %put statement (this is possible with other more complex workarounds, e.g. dosubl, but not the way you've written it).
%put %nobs(mylib,mydata);
The best you can do without significant changes is to create a global macro variable and use that in subsequent statements.
To create a macro variable that is local to the originating macro, you have to first declare it via a %local statement within the macro definition.
I know I am very late to this discussion, but thought of commenting since I came across this. This is another way of doing this I think:
%macro get_something_back(input1, input2, output);
&output = &input1 + &input2;
%mend;
data _test_;
invar1 = 1; invar2 = 2;
%get_something_back(invar1, invar2, outvar);
end;
This will also work outside a datastep.
%global sum;
%macro get_something_back(input1, input2, outvar);
%let &outvar = &sysevalf(&input1 + &input2);
%mend;
%get_something(1, 2, sum);
I posted a question a while back about trimming a macro variable down that I am using to download a CSV from Yahoo Finance that contains variable information on each pass to the site. The code that was suggested to me to achieve this was as follows:
data _null_;
a = "&testvar.";
call symputx('svar',trim(input(a,$8.)));
run;
That worked great, however I have since needed to redesign the code so that I am declaring multiple macro variables and submitting multiple ones at the same time.
To declare multiple macros at the same time I have used the following lines of code:
%let svar&e. = &svar.;
%put stock_ticker = &&svar&e.;
The varible &e. is an iterative variable that goes up by one everytime. This declares what looks to be an identical macro to the one called &svar. everytime they are put into the log, however the new dynamic macro is now throwing up the original warning message of:
WARNING: The quoted string currently being processed has become more than 262 characters long. You
may have unbalanced quotation marks.
That i was getting before i started using the symputx option suggested in my original problem.
The full code for this particular nested macro is listed below:
%macro symbol_var;
/*here the start row and end row created in the macro above are passed to this nested macro and then passed through the*/
/*source dataset. at the end of the loop each ticker macro variable is defined in turn for use in the following nested*/
/*macro, symbol by metric.*/
%do e = &beg_point. %to &end_point. %by 1;
%put stock row in dataset nasdaq ticker = &e.;
%global svar&e;
proc sql noprint;
select symbol
into :testvar
from nasdaq_ticker
where monotonic() = &e.;
quit;
/*convert value to string here*/
data _null_;
a = "&testvar.";
call symputx('svar',trim(input(a,$8.)));
run;
%let svar&e. = &svar.;
%put stock_ticker = &&svar&e.;
%end;
%mend;
%symbol_var;
Anyone have any suggestions how I could declare the macro &&svar&e. directly into the call synputx step? It currently throws up an error saying that the macro variable being created cannot contain any special characters. Ive tried using "E, %NRQUOTE and %NRBQUOTE but either I have used the function in an invalid context or I haven't got the syntax exactly right.
Thanks
Isn't this as simple as the following two line data step?
%macro symbol_var;
/*here the start row and end row created in the macro above are passed to this nested macro and then passed through the*/
/*source dataset. at the end of the loop each ticker macro variable is defined in turn for use in the following nested*/
/*macro, symbol by metric.*/
data _null_;
set nasdaq_ticker(firstobs=&beg_point. obs=&end_point.);
call symputx('svar' || strip(_n_), symbol);
run;
%mend;
%symbol_var;
Or the following (which includes debugging output)
%macro symbol_var;
/*here the start row and end row created in the macro above are passed to this nested macro and then passed through the*/
/*source dataset. at the end of the loop each ticker macro variable is defined in turn for use in the following nested*/
/*macro, symbol by metric.*/
data _null_;
set nasdaq_ticker(firstobs=&beg_point. obs=&end_point.);
length varname $ 32;
varname = 'svar' || strip(_n_);
call symputx(varname, symbol);
put varname '= ' symbol;
run;
%mend;
%symbol_var;
When manipulating macro variables and desiring bullet-proof code I often find myself reverting to using a data null step. The original post included the problem about a quoted string warning. This happens because the SAS macro parser does not hide the value of your macro variables from the syntax scanner. This means that your data (stored in macro vars) can create syntax errors in your program because SAS attempts to interpret it as code (shudder!). It really makes the hair on the back of my neck stand up to risk my program at the hands of what might be in the data. Using the data step and functions protects you from this completely. You will note that my code never uses an ampersand character other than the observation window points. This makes my code bullet proof regarding what dirty data there may be in the nasdaq_ticker data set.
Also, it is important to point out that both Dom and I wrote code that makes one pass over the nasdaq_ticker data set. Not to bash the original posted code, but looping in that way causes a proc sql invocation for every observation in the result set. This will create very poor performance for large result sets. I recommend developing an awareness of how many times a macro loop is going to cause you to read a data set. I have been bitten by this many times in my own code.
Try
call symputx("svar&e",trim(input(a,$8.)));
You need double quotes ("") to resolve the e macro.
As an aside, I am not sure you need the input statement if $testvar is a string and not a number.
I would have written this as
%macro whatever();
proc sql noprint;
select count(*)
into :n
from nasdaq_ticker;
select strip(symbol)
into :svar1 - :svar%left(&n)
from nasdaq_ticker;
quit;
%do i=1 %to &n;
%put stock_ticker = &&svar&i;
%end;
%mend;
Currently, in our DI studio job we have a couple of lookups. When one of these lookups for a certain record returns multiple values, the lookup will choose one of them (I think the first one) to select. I would actually prefer to receive an error because for me it means the lookup is not yet well defined and I probably need to add extra 'where' expressions.
Is there an option to set it this way? I can not find any at the moment.
If you are comfortable with macro, one option might be...
%macro get_lookup(&in_where);
proc sql noprint;
select
lookup_value,
count(lookup_value)
into
:l_lookup_result,
:l_count_results
from lookup table
where &in_where;
quit;
%if l_count_results = 1 %then %do;
%global g_lookup_result;
%let g_lookup_result = &l_lookup_result;
%end;
%else %do;
%put ERROR: Lookup did not return expected 1 result.;
%raise_error;
%end;
%mend get_lookup;
%get_lookup(&where_clause);
It's a bit cumbersome, but it'll get the job done. Unless it returns more than one value in which case it will fall over. Gracefully.
Notes: I've assumed you build up some lookup-related where clause in macro variable &where_clause, which is then used to populate the macro parameter &in_where. I've also assumed you have a macro defined called %raise_error - otherwise, if this lookup doesn't set the value of &g_lookup_result, any susbequent code that attempts to use the macro variable will fall over.