Do you know how can I iterate on a list using SAS and a MACRO ?
%LET table = item1 item2;/*List of all input*/
/*I try to iterate on the list using a macro*/
%MACRO Main_Extract ;
array orig[*] &table;
do i=1 to dim(orig);
%put orig[i];
end;
%MEND;
%Main_Extract;
If table, the list of items, is of variable names for an array, then you do not need a macro. Just use plain data step code and use the macro variable to list the array elements.
array orig &table;
do I = 1 to dim(orig);
put orig[I]=
end;
When a macro variable contains a space separated list of items, the use of such inside of a macro is usually done by parsing out each item using %scan inside a %do loop. An example of when this is useful would be generating a series of select clauses for a Proc SQL statement.
One time use of parsing out each item
%macro special_sauce (items=);
%local i item;
%let i = 1;
%do %while (%length(%scan(&items,&i)));
%let item = %scan(&items,&i);
%put NOTE: code generated for &=item;
/* ... emit some SAS code or code-snippet involving &item ... */
&item._squared = &item ** 2; /* emit data step source statement that presumes item is a variable name that is being squared */
%let i = %eval(&i+1);
%end;
%mend;
options mprint;
data want;
set sashelp.class;
%special_sauce(items=age height)
run;
If the list of items is needed to be used more than once it is also helpful to store the individual items in local macro variables for easy re-use.
List of items used more than once, parse once and put items in a 'macro-array'. There is really no such thing as a macro-array, simply a convention of numerically suffixed symbol names that can be iterated over.
%macro special_sauce2 (items=);
%local i item itemCount;
%let i = 1;
%do %while (%length(%scan(&items,&i)));
%let item = %scan(&items,&i);
%let itemCount = &i; /* track number of items parsed */
%local item&i; /* local macro variable name with numeric suffix, sometimes called a macro array */
%let item&i = &item; /* save the extracted item */
%let i = %eval(&i+1);
%end;
/* use the items in the 'macro-array' */
%do i = 1 %to &itemCount;
%put NOTE: value of macro variable item&i is &&item&i;
&&item&i.._2 = &&item&i ** 2;
%end;
/* use the items in the 'macro-array' */
%do i = 1 %to &itemCount;
%put NOTE: Hi again, value of macro variable item&i is &&item&i;
&&item&i.._3 = &&item&i ** 3;
%end;
%mend;
options mprint;
data want;
set sashelp.class;
%special_sauce2(items=age height)
run;
Good rule of thumb, don't macro if you don't have to.
Related
I have 5 separate datasets(actually many more but i want to shorten the code) named dk33,dk34,dk35,dk51,dk63, each dataset contains a numeric field: surv_probs. I would like to load the values into 5 arrays and then use the arrays in a datastep(result), however, I need advice what is the best way to do it.
I am getting error when I use the macro: setarrays: (code below)
WARNING: The quoted string currently being processed has become more than 262 characters long. You might have unbalanced quotation
marks.
WARNING: The quoted string currently being processed has become more than 262 characters long. You might have unbalanced quotation
marks.
ERROR: Illegal reference to the array dk33_arr.
Here is the main code.
%let var1 = dk33;
%let var2 = dk34;
%let var3 = dk35;
%let var4 = dk51;
%let var5 = dk63;
%let varN = 5;
/*put length of each column into macro variables */
%macro getlength;
%do i=1 %to &varN;
proc sql noprint;
select count(surv_probs)
into : &&var&i.._rows
from work.&&var&i;
quit;
%end;
%mend;
/*load values of column:surv_probs into macro variables*/
%macro readin;
%do i=1 %to &varN;
proc sql noprint;
select surv_probs
into: &&var&i.._list separated by ","
from &&var&i;
quit;
%end;
%mend;
data _null_;
call execute('%readin');
call execute('%getlength');
run;
/* create arrays*/
%macro setarrays;
%do i=1 %to 1;
j=1;
array &&var&i.._arr{&&&&&&var&i.._rows};
do while(scan("&&&&&&var&i.._list",j,",") ne "");
&&var&i.._arr = scan("&&&&&&var&i.._list",j,",");
j=j+1;
end;
%end;
%mend;
data result;
%setarrays
put dk33_arr(1);
* some other statements where I use the arrays*
run;
Answer to toms question:
*macro getlength(when executed) creates 5 macro variables named: dk33_rows,dk34_rows,dk35_rows,dk51_rows,dk63_rows
*the macro readin(when executed):creates 5 macro variables dk33_list,dk34_list,dk35_list,dk51_list,dk63_list. Each containing a string which is comma separates the values from the column: eg.: 0.99994,0.1999,0.1111
*the macro setarrays creates 5 arrays,when executed, dk33_arr,dk34_arr,... holding the parsed values from the macro variables created by readin
I find that "macro arrays" like VAR1,VAR2,.... are generally more trouble than they are worth. Either keep your list of dataset names in an actual dataset and generate code from that. Or if the list is short enough put the list into a single macro variable and use %SCAN() to pull out the items as you need them.
But either way it is also better to avoid trying to write macro code that needs more than three &'s. Build up the reference in multiple steps. Build a macro variable that has the name of the macro you want to reference and then pull the value of that into another macro variable. It might take more lines of code, but you can more easily understand what is happening.
%let i=1 ;
%let mvarname=var&i;
%let dataset_name=&&&mvarname;
Before you begin using macro code (or other code generation techniques) make sure you know what code you are trying to generate. If you want to load a variable into a temporary array you can just use a DO loop. There is no need to macro code, or copying values, or even counts, into macro variables. For example instead of getting the count of the observations you could just make your temporary array larger than you expect to ever need.
data test1 ;
if _n_=1 then do;
do i=1 to nobs_dk33;
array dk33 (1000) _temporary_;
set dk33 nobs=nobs_dk33 ;
dk33(i)=surv_probs;
end;
do i=1 to nobs_dk34;
array dk34 (1000) _temporary_;
set dk34 nobs=nobs_dk34 ;
dk34(i)=surv_probs;
end;
end;
* What ever you are planning to do with the DK33 and DK34 arrays ;
run;
Or you could transpose the dataset first.
proc transpose data=dk33 out=dk33_t prefix=dk33_ ;
var surv_probs ;
run;
Then your later step is easier since you can just use a SET statement to read in the one observation that has all of the values.
data test;
if _n_=1 then do;
set dk33_t ;
array dk33 dk33_: ;
end;
....
run;
I'm trying to define a macro function that returns unique list items from a space separated list. This macro itself uses other macros that I tested and seem to work fine by themselves (see examples below), it's all very simple code.
However for some reason the code runs indefinitely and I don't know how to debug it properly. I usually debug using %put statements but they don't print here as there's no error and I stop the code manually.
Here is the main macro, followed by my other convenience macros that I use, you can execute the whole batch to load the macros and then check out given examples.
*---------------------------------------------------------------;
* LIST_UNIQUE ;
* Return only unique items from list, ;
* in order of first appearance ;
*---------------------------------------------------------------;
/* EXAMPLE
%put %list_unique(); ** (nothing)
%put %list_unique(a); ** a
%put %list_unique(a a); ** doesn't work (should be a)
%put %list_unique(a b); ** doesn't work (should be a b)
*/
%macro list_unique(data);
%local out curr_item;
%do i=1 %to %list_length(&data);
%let curr_item = %extract(&data,&i);
%if not %list_in(&curr_item,&out) %then %let out = &out &curr_item;
%end;
&out
%mend;
*---------------------------------------------------------------;
* LIST_LENGTH ;
* Length of space separated list ;
*---------------------------------------------------------------;
/* EXAMPLES :
%put %list_length(); ** 0
%put %list_length(item1 item2 item3); ** 3
*/
%macro list_length(data);
%sysfunc(countw(&data,%str( )))
%mend;
*---------------------------------------------------------------;
* LIST_IN ;
* check if item is in list ;
*---------------------------------------------------------------;
/* EXAMPLE
%put %list_in(,a); ** 0
%put %list_in(a,); ** 0
%put %list_in(a,a); ** 1
%put %list_in(a,a a); ** 1
%put %list_in(b,a b c d); ** 1
%put %list_in(e,a b c d); ** 0
*/
%macro list_in
(item /* item to search in list */
,list /* space separated list to quote */
);
/* exception when list has null length */
%if not %length(&list) %then 0%return;
/* general case */
%do i=1 %to %list_length(&list);
%if %extract_pos(&list,&i) = &item %then 1%return;
%end;
0
%mend;
*-------------------------------------------------------------------------------;
* EXTRACT_POS ;
* Extracts subset of values from space separated list ;
*-------------------------------------------------------------------------------;
/* EXAMPLES
%put %extract_pos(,1); ** (nothing)
%put %extract_pos(a b c d,); ** (nothing)
%put %extract_pos(a b c d,1); ** a
%put %extract_pos(a b c d,2 1:3 1); ** b a b c a
*/
%macro extract_pos
(data
,ind
);
%local i j token output new_ind;
%do i=1 %to %sysfunc(countw(&ind,%str( )));
%let token = %scan(&ind,&i,%str( ));
%if %index(&token,:) %then %do; /* if token with ':' */
%do j=%scan(&token,1,:) %to %scan(&token,2,:);
%let output = &output %scan(&data,&j,%str( ));
%end;
%end;
%else %do; /* if simple token */
%let output = &output %scan(&data,&token,%str( ));
%end;
%end;
&output
%mend;
You cannot protect macros you call from modifying your macro variables, but if the macros are designed properly they will NOT. Unless you are INTENDING to modify any existing macro variable you need to define your macro variables as local. One or more of your macros were using the macro variable I without defining it as local. So if there already existed a macro variable named I then the macro modified the existing variable's value.
Also one of your macros was calling %extract() instead of %extract_pos().
I also simplified your %list_in() macro to just be a call to an existing SAS function, like your %list_length() macro.
%macro list_unique
/*---------------------------------------------------------------
Return only unique items from list
---------------------------------------------------------------*/
(data /* Space delimited list of items */
);
%local i curr_item out ;
%do i=1 %to %list_length(&data);
%let curr_item = %extract_pos(&data,&i);
%if not %list_in(&curr_item,&out) %then %let out=&out &curr_item;
%end;
&out
%mend list_unique;
%macro list_length(data);
%sysfunc(countw(&data,%str( )))
%mend list_length;
%macro list_in
/*---------------------------------------------------------------
Check if item is in list
---------------------------------------------------------------*/
(item /* item to find in list */
,list /* space separated list to search */
);
%sysevalf(%sysfunc(indexw(&list,&item,%str( ))),boolean)
%mend list_in;
%macro extract_pos
/*-------------------------------------------------------------------------------
Extracts subset of values from space separated list
-------------------------------------------------------------------------------*/
(data /* Space delimited list of values */
,ind /* Space delimited list of positions or position ranges */
);
%local i j token output;
%do i=1 %to %sysfunc(countw(&ind,%str( )));
%let token = %scan(&ind,&i,%str( ));
%do j=%scan(&token,1,:) %to %scan(&token,-1,:);
/* Token is single position or range in format start:end */
%let output = &output %scan(&data,&j,%str( ));
%end;
%end;
&output
%mend extract_pos;
Test
831 %put %list_unique(); %** (nothing);
832 %put %list_unique(a); %** a ;
a
833 %put %list_unique(a a); %** doesnot work (should be a);
a
834 %put %list_unique(a b); %** doesnot work (should be a b);
a b
Enable Macro Debugging by adding this line to the beginning of your code, which will resolve the macro code and variables:
Options macrogen symbolgen mlogic mprint mfile;
Run your code and check your log for details,
When done Disable Macro Debugging by replacing the options in step 1 with the options below:
Options nomacrogen NoSymbolgen nomlogic nomprint nomfile;
For more details you can check the SAS Debugging documentation
http://support.sas.com/documentation/cdl/en/mcrolref/61885/HTML/default/viewer.htm#a001066200.htm
Variable I is shared down in nameSpace. An easy fix is to use different looping variable in each macro.
Found some documentation on SAS logic of sharing. SAS Blogs
The following macro makes an inner join between two tables containing one column from each table in addition to the joining column :
%macro ij(x=,y=,to=".default",xc=,yc=,by=);
%if &to = ".default" %then %let to = &from;
PROC SQL;
CREATE TABLE &to AS
SELECT t1.&xc, t2.&yc, t1.&by
FROM &x t1 INNER JOIN &y t2
ON t1.&by = t2.&by;
RUN;
%mend;
I want to find a way to use several columns in &xc, &yc and &by.
As I don't think I can use vectors of variables.
My idea is to pass parameters as vectors of strings instead of simple variables, for example xc = {"col1" "col2"} and loop through them
using %let some_var= %sysfunc(dequote(&some_string)); to convert them back to variables.
Applied on xc only it would become something like:
%macro ij(x=,y=,to=".default",xc=,yc=,by=);
%if &to = ".default" %then %let to = &from;
PROC SQL;
CREATE TABLE &to AS
SELECT
%do i = 1 %to %NCOL(&xc)
%let xci = %sysfunc(dequote(&xc[1]));
t1.&xci,
%end;
t2.&yc, t1.&by
FROM &x t1 INNER JOIN &y t2
ON t1.&by = t2.&by;
RUN;
%mend;
But this loop fails. How could I make it work ?
Note: this is a simplified example, my ultimate ambition is to build join macros that would be as little verbose as possible and integrate data quality checks.
Really this would be much easier to code use SAS dataset options instead of building complicated macro logic.
proc sql ;
create table want2 as
select *
from sashelp.class(keep=name age)
natural inner join sashelp.class(keep=name height weight)
;
quit;
I would suggest learning how to use data step code instead of SQL code. For most normal data manipulations it is clearer and simpler. Say you wanted to combine IN1 and IN2 on the variable ID and keep the variable A and B from IN1 and the variable X and Y from the IN2.
data out ;
merge in1 in2 ;
by id ;
keep id a b x y ;
run;
Second I would resist the urge to generate too complex a web of macro code. It will make the programs harder to understand for the next programmer. Including yourself two weeks later. Your particular example does not look like something that is worth coding as a macro. You are not really typing less information, just using a few commas in place of where your SQL code would have had keywords like FROM or JOIN.
Now to answer your actual question. To pass in a list of values to macro use a delimited list. When at all possible use space as the delimiter, but especially avoid using comma as the delimiter. This will be easier to type, easier to pass into the macro and easier to use since it matches the SAS language as you can see in the data step above. If you really need to generate code like SQL syntax that uses commas then have the macro code generate them where needed.
%macro ij
(x= /* First dataset name */
,y= /* Second dataset name */
,by= /* BY variable list */
,to= /* Output dataset name. If empty use data step to generate DATAn work name */
,xc= /* Variable list from first dataset */
,yc= /* Variable list from second dataset */
);
%if not %length(&to) %then %do;
* Let SAS generate a name for new dataset ;
data ; run;
%let to=&syslast ;
proc delete data=&to; run;
%end;
%if not %length(&xc) %then %let xc=*;
%if not %length(&yc) %then %let yx=*;
%local i sep ;
proc sql ;
create table &to as
select
%let sep= ;
%do i=1 %to %sysfunc(countw(&by)) ;
&sep.T1.%scan(&by,&i)
%let sep=,;
%end;
%do i=1 %to %sysfunc(countw(&xc)) ;
&sep.T1.%scan(&xc,&i)
%end;
%do i=1 %to %sysfunc(countw(&yc)) ;
&sep.T2.%scan(&yc,&i)
%end;
from &x T1 inner join &y T2 on
%let sep= ;
%do i=1 %to %sysfunc(countw(&by)) ;
&sep.T1.%scan(&by,&i)=T2.%scan(&by,&i)
%let sep=,;
%end;
;
quit;
%mend ij ;
Try it:
options mprint;
%ij(x=sashelp.class,y=sashelp.class,by=name,to=want,xc=age,yc=height weight);
SAS LOG:
MPRINT(IJ): proc sql ;
MPRINT(IJ): create table want as select T1.name ,T1.age ,T2.height ,T2.weight from sashelp.class
T1 inner join sashelp.class T2 on T1.name=T2.name ;
NOTE: Table WORK.WANT created, with 19 rows and 4 columns.
MPRINT(IJ): quit;
Instead of vectors, think simple lists.
Pass your variable lists as unquoted, space separated list of values. The values are SAS variable names that can be scanned out as tokens.
%macro ij (x=, ...);
...
%local i token;
%let i = 1;
%do %while (%length(%scan(&X,&i)));
%let token = %scan(&X,&i);
&token.,/* emit the token as source code */
%let i = %eval(&i+1);
%end;
...
%mend;
%ij ( x = one two three, ... )
Be sure to localize all your macro variables to prevent unwanted side effects outside the macro.
For consistency I try to use i/o related macro parameters that mimic SAS Procs -- data=, out=, file=, ...
Some would say named arguments are verbose!
If your 'proto-code' expects the xci symbol to be some sort of serially numbered variable, it is not. You would have to use %local xc&i; %let xc&i= for assignment, and &&xc&i for resolution. Also, your original code references &from which is not passed.
Building is fun. I would also recommend surveying past conference papers and SAS literature for similar works that may already meet your goal.
You could start with a space-separated list of column names and avoid looping entirely:
/*Define list of columns*/
%let COLS = A B C;
%put COLS = &COLS;
/*Add table alias prefix*/
%let REGEX = %sysfunc(prxparse(s/(\S+)/t1.$1/));
%let COLS = %sysfunc(prxchange(®EX,-1,&COLS));
%put COLS = &COLS;
%syscall prxfree(REGEX);
/*Condense multiple spaces to a single space*/
%let COLS = %sysfunc(compbl(&COLS));
%put COLS = &COLS;
/*Replace spaces with commas*/
%let COLS = %sysfunc(translate(&COLS,%str(,),%str( )));
%put COLS = &COLS;
In the end as #Tom noted, SAS dataset options are more convenient, and using them one doesn't need to loop over variables.
Here is the macro I came with :
*--------------------------------------------------------------------------------------------- ;
* JOIN ;
* Performs any join (defaults to inner join). ;
* By default left table is overwritten (convenient for successive left joins) ;
* Performs a natural join so columns should be renamed accordingly through 'rename' parameters ;
*----------------------------------------------------------------------------------------------;
%macro join
(data1= /* left table */
,data2= /* right table */
,keep1= /* columns to keep (default: keep all), don't use with drop */
,keep2=
,drop1= /* columns to drop (default: none), don't use with keep */
,drop2=
,rename1= /* rename statement, such as 'old1 = new1 old2 = new2 */
,rename2=
,j=ij /* join type, either ij lj or rj */
,out= /* created table, by default data1 (left table is overwritten)*/
);
%if not %length(&out) %then %let out = &data1;
%if %length(&keep1) %then %let keep1 = keep=&keep1;
%if %length(&keep2) %then %let keep2 = keep=&keep2;
%if %length(&drop1) %then %let drop1 = drop=&drop1;
%if %length(&drop2) %then %let drop2 = drop=&drop2;
%if %length(&rename1) %then %let rename1 = rename=(&rename1);
%if %length(&rename2) %then %let rename2 = rename=(&rename2);
%let kdr1 =;
%let kdr2 =;
%if (%length(&keep1) | %length(&drop1) | %length(&rename1)) %then %let kdr1 = (&keep1&drop1 &rename1);
%if (%length(&keep2) | %length(&drop2) | %length(&rename2)) %then %let kdr2 = (&keep2&drop2 &rename2);
%if &j=lj %then %let j = LEFT JOIN;
%if &j=ij %then %let j = INNER JOIN;
%if &j=rj %then %let j = RIGHT JOIN;
proc sql;
create table &out as select *
from &data1&kdr1 t1 natural &j &data2&kdr2 t2;
quit;
%mend;
Reproducible Examples:
data temp1;
input letter $ number1 $;
datalines;
a 1
a 2
a 3
b 4
c 8
;
data temp2;
input letter $ letter2 $ number2 $;
datalines;
a c 666
b d 0
;
* left join on common columns into new table temp3;
%join(data1=temp1,data2=temp2,j=lj,out=temp3)
* inner join by default, overwriting temp 1, after renaming to join on another column;
%join(data1=temp1,data2=temp2,drop2=letter,rename2= letter2=letter)
I have the following variables: A_Bldg B_Bldg C_Bldg D_Bldg. I want to multiply them by INTSF and store the result in a new variable, Sale_i. For example, A_Bldg * INTSF = Sale_A, B_Bldg * INTSF = Sale_B, and so on.
My code is:
%macro loopit(mylist);
%let n=%sysfunc(countw(&mylist));
%do J = 1 %to &n;
%let i = %scan(&mylist,&J);
data test;
set data;
sale_&i. = &i._Bldg * INTSF;
run;
%end;
%mend;
%let list = A B C D;
%loopit(&list);
This only produces Sale_D, which is the last letter in the list. How do I get Sales A-C to appear? The first four lines of code are so I can loop through the text A-D. I thought about doing it with arrays, but didn't know how to choose the variables based on the A-D indicators. Thanks for your help!
You're currently looping through your list and recreating the test dataset every time, so it only appears to have sale_d because you're only viewing the last iteration.
You can clean up your loop by scanning through your list in one data step to solve your problem:
%let list = A B C D;
%macro loopit;
data test;
set data;
%do i = 1 %to %sysfunc(countw(&list.));
%let this_letter = %scan(&list., &i.);
sale_&this_letter. = &this_letter._Bldg * INTSF;
%end;
run;
%mend loopit;
%loopit;
Your %DO loop is in the wrong place. But really you do not need to use macro code to do something that the native SAS code can already do.
data want;
set have ;
array in A_Bldg B_Bldg C_Bldg D_Bldg ;
array out sale_1-sale4 ;
do i=1 to dim(in);
out(i)=intsf*in(i);
end;
run;
I have a table like this:
Lista_ID 1 4 7 10 ...
in total there are 100 numbers.
I want to call each one of these numbers to a macro i created. I was trying to use 'scan' but read that it's just for character variables.
the error when i runned the following code was
there's the code:
proc sql;
select ID INTO: LISTA_ID SEPARATED BY '*' from
WORK.AMOSTRA;
run;
PROC SQL;
SELECT COUNT(*) INTO: NR SEPARATED BY '*' FROM
WORK.AMOSTRA;
RUN;
%MACRO CICLO_teste();
%LET LIM_MSISDN = %EVAL(NR);
%LET I = %EVAL(1);
%DO %WHILE (&I<= &LIM_MSISDN);
%LET REF = %SCAN(LISTA_ID,&I,,'*');
DATA WORK.UP&REF;
SET WORK.BASE&REF;
FORMAT PERC_ACUM 9.3;
IF FIRST.ID_CLIENTE THEN PERC_ACUM=0;
PERC_ACUM+PERC;
RUN;
%LET I = %EVAL(&I+1);
%END;
%MEND;
%CICLO_TESTE;
the error was that:
VARIABLE PERC IS UNITIALIZED and
VARIABLE FIRST.ID_CLIENTE IS UNITIALIZED.
What I want is to run this macro for each one of the Id's in the List I showed before, and that are referenced in work.base&ref and work.up&ref.
How can I do it? What I'm doing wrong?
thanks!
Here's the CALL EXECUTE version.
%MACRO CICLO_teste(REF);
DATA WORK.UP&REF;
SET WORK.BASE&REF;
BY ID_CLIENTE;
FORMAT PERC_ACUM 9.3;
IF FIRST.ID_CLIENTE THEN PERC_ACUM=0;
PERC_ACUM+PERC;
RUN;
%CICLO_TESTE;
DATA _NULL_;
SET amostra;
*CREATE YOUR MACRO CALL;
STR = CATT('%CLIO_TESTE(', ID, ')');
CALL EXECUTE(STR);
RUN;
First you should note that SAS macro variable resolve is intrinsically a "text-based" copy-paste action. That is, all the user-defined macro variables are texts. Therefore, %eval is unnecessary in this case.
Other miscellaneous corrections include:
Check the %scan() function for correct usage. The first argument should be a text string WITHOUT QUOTES.
run is redundant in proc sql since each sql statement is run as soon as they are sent. Use quit; to exit proc sql.
A semicolon is not required for macro call (causes unexpected problems sometimes).
use %do %to for loops
The code below should work.
data work.amostra;
input id;
cards;
1
4
7
10
;
run;
proc sql noprint;
select id into :lista_id separated by ' ' from work.amostra;
select count(*) into :nr separated by ' ' from work.amostra;
quit;
* check;
%put lista_id=&lista_id nr=&nr;
%macro ciclo_teste();
%local ref;
%do i = 1 %to &nr;
%let ref = %scan(&lista_id, &i);
%*check;
%put ref = &ref;
/* your task below */
/* data work.up&ref;*/
/* set work.base&ref;*/
/* format perc_acum 9.3;*/
/* if first.id_cliente then perc_acum=0;*/
/* perc_acum + perc;*/
/* run; */
%end;
%mend;
%ciclo_teste()
tested on SAS 9.4 win7 x64
Edited:
In fact I would recommend doing this to avoid scanning a long string which is inefficient.
%macro tester();
/* get the number of obs (a more efficient way) */
%local NN;
proc sql noprint;
select nobs into :NN
from dictionary.tables
where upcase(libname) = 'WORK'
and upcase(memname) = 'AMOSTRA';
quit;
/* assign &ref by random access */
%do i = 1 %to &NN;
data _null_;
a = &i;
set work.amostra point=a;
call symputx('ref',id,'L');
stop;
run;
%*check;
%put ref = &ref;
/* your task below */
%end;
%mend;
%tester()
Please let me know if you have further questions.
Wow that seems like a lot of work. Why not just do the following:
data work.amostra;
input id;
cards;
1
4
7
10
;
run;
%macro test001;
proc sql noprint;
select count(*) into: cnt
from amostra;
quit;
%let cnt = &cnt;
proc sql noprint;
select id into: x1 - :x&cnt
from amostra;
quit;
%do i = 1 %to &cnt;
%let x&i = &&x&i;
%put &&x&i;
%end;
%mend test001;
%test001;
now in variables &x1 - &&x&cnt you have your values and you can process them however you like.
In general if your list is small enough (macro variables are limited to 64K characters) then you are better off passing the list in a single delimited macro variable instead of multiple macro variables.Remember that PROC SQL will automatically set the count into the macro variable SQLOBS so there is no need to run the query twice. Or you can use %sysfunc(countw()) to count the number of entries in your delimited list.
proc sql noprint ;
select id into :idlist separated by '|' from .... ;
%let nr=&sqlobs;
quit;
...
%do i=1 %to &nr ;
%let id=%scan(&idlist,&i,|);
data up&id ;
...
%end;
If you do generate multiple macro variables there is no need to set the upper bound in advance as SAS will only create the number of macro variables it needs based on the number of observations returned by the query.
select id into :idval1 - from ... ;
%let nr=&sqlobs;
If you are using an older version of SAS the you need set an upper bound on the macro variable range.
select id into :idval1 - :idval99999 from ... ;