Pass a dynamic array to iterate over proc sql - sql

In this question
Simple iteration through array with proc sql in SAS
%macro doit(list);
proc sql noprint;
%let n=%sysfunc(countw(&list));
%do i=1 %to &n;
%let val = %scan(&list,&i);
create table somlib._&val as
select * from somlib.somtable
where item=&val;
%end;
quit;
%mend;
%doit(100 101 102);
I want to pass a list through macro doit which we can extract from a dataset.
For eg.: list contains the distinct values of variable 'age' present in dataset 'agegroups'.
data agegroups;
input age;
datalines;
1
2
4
5
8
18
16
19
23;
I looked upon %macro array for it but it didnt help me out(http://www2.sas.com/proceedings/sugi31/040-31.pdf)
Any help will be highly appreciated. Thanks !

As stated in the comments, BY group processing might be a better option.
However, you can use PROC SQL to create your list:
proc sql noprint;
select distinct age
into :ageList separated by ' '
from agegroups;
quit;
%put Age List: &ageList;
%doit(&ageList);

Related

Get Data from SAS Macro (list of values) to SAS table (column)

I am trying to create a SAS table from Macro variable using PROC SQL:
I have a list of value saved in a macro variable :
%let l=1,2,3;
I want to create a SAS table with a column containing the values of the macro variable :
1
2
3
Thank you very much for your help.
Sincerely,
Abdeljalil
you should so some effort to solving this yourself.
Put the values into a string, parse the string and output the values you would like.
%let l=1,2,3;
data want;
str = "&l";
do i=1 to countw(str,',');
value = input(scan(str,i,","),best.);
output;
end;
/*drop other variables if you want*/
drop str i;
run;
Something like this?
%let age=%str(12,13,15);
proc sql;
select * from sashelp.class where age in (&age);
quit;
You have a data set that contains a list of names and you want to place these names into a macro variable for later use. That will work as long as the macro variable does not go beyond the 64K limit.
If the value hits this limit, then you can use macro processing to retrieve the names from the data set. Since a macro definition does not have the 64K restriction, it can be used to create the list for you.
In the sample code on the Full Code tab, we have a list of names that we want to use on an INPUT statement along with a given informat. This sample demonstrates how to create the list without having to use a macro variable.
data one;
input name $;
datalines;
abc
def
ghi
;
run;
%macro test;
%let dsid=%sysfunc(open(one));
%let cnt=%sysfunc(attrn(&dsid,nobs));
%do i=1 %to &cnt;
%let rc=%sysfunc(fetchobs(&dsid,&i));
%cmpres(%sysfunc(getvarc(&dsid,%sysfunc(varnum(&dsid,name))))) $4.
%end;
%let rc=%sysfunc(close(&dsid));
%mend test;
/** Using %PUT to see outcome **/
/** %test could be used on an INPUT statement **/
%put %test;
source: http://support.sas.com/kb/39/605.html

How to randomly select variables in SAS?

I can find all sorts of information on how to randomly select observations in SAS which is a fairly easy task. This is not what I need though. I need to randomly select variables. What I want to do specifically is randomly choose 20 variables from my list of 159 variables and do this 50 times. I want to ensure diversity too. I have been spending about two days on this and am having no luck.
I'm glad that you asked this question, because I just developed a solution for that! Let's break down exactly what needs to be done, step-by-step.
Step 0: What do we need to do?
We need a way to take all of our variables and randomly select 20 of them while keeping them within the bounds of the SAS language rules.
We'll require:
All variables in the dataset
A way to re-sort them randomly
A limit of 20 variables
A way to loop this 50 times
Let's start with 1.
Step 1: Getting all the variables
sashelp.vcolumn provides a list of all variables within a dataset. Let's select them all.
proc sql noprint;
create table all_vars as
select name
where libname = 'LIBRARYHERE' AND memname = 'HAVE'
;
quit;
This gets us a list of all variables within our dataset. Now, we need to sort them randomly.
Step 2: Making them random
SAS provides the rand function that allows you to pull from any distribution that you'd like. You can use call streaminit(seedhere) prior to the rand function to set a specific seed, creating reproducable results.
We'll simply modify our original SQL statement and order the dataset with the rand() function.
data _null_;
call streaminit(1234);
run;
proc sql noprint;
create table all_vars as
select name
from sashelp.vcolumn
where libname = 'LIBRARYHERE' AND memname = 'HAVE'
order by rand('uniform');
quit;
Now we've got all of our variables in a random order, distributed evenly by the uniform distribution.
Step 3: Limit to 20 variables
You can do this a few ways. One way is the obs= dataset option in separate procedures, another is the outobs= proc sql option. Personally, I like the obs= dataset option since it doesn't generate a warning in the log, and can be used in other procedures.
data _null_;
call streaminit(1234);
run;
proc sql noprint outobs=20;
create table all_vars as
select name
from sashelp.vcolumn
where libname = 'LIBRARYHERE' AND memname = 'HAVE'
order by rand('uniform');
quit;
Step 4: Loop it 50 times
We'll use SAS Macro Language to do this part. We can create 50 individual datasets this way, or switch the code up slightly and read them into macro variables.
%macro selectVars(loop=50, seed=1234);
data _null_;
call streaminit(&seed);
run;
%do i = 1 %to &loop;
proc sql noprint outobs=20;
create table all_vars&i as
select name
from sashelp.vcolumn
where libname = 'LIBRARYHERE' AND memname = 'HAVE'
order by rand('uniform')
;
quit;
%end;
%mend;
%selectVars;
Or, option 2:
%macro selectVars(loop=50, seed=1234);
data _null_;
call streaminit(&seed);
run;
%do i = 1 %to &loop;
proc sql noprint outobs=20;
select name
into :varlist separated by ' '
from sashelp.vcolumn
where libname = 'LIBRARYHERE' AND memname = 'HAVE'
order by rand('uniform')
;
quit;
%end;
%mend;
%selectVars;
The 2nd option will create a local macro variable called &varlist that will have the random 20 variables separated by spaces. This can be convenient for various modeling procs, and is preferable since it does not create a separate dataset each time.
Hope this helps!
You will need to treat your meta data as data and use SURVEYSELECT to select observations. Then perhaps put these names into macro variables but you did not mention the exact output you want.
data v;
array rvars[159];
run;
proc transpose data=v(obs=0) out=vars name=name;
var rvars:;
run;
proc surveyselect reps=4 sampsize=20 data=vars out=selection;
run;
proc transpose data=selection out=lists(drop=_:);
by replicate;
var name;
run;
proc print;
run;
data _null_;
set lists;
by replicate;
call symputx(cats('VLIST',_n_),catx(' ',of col:));
run;
%put _global_;

Two variables in a macro [SAS]

So, i want to have a macro that has others macros inside.
Here is the code: `
proc sql NOPRINT ;
select id into :l_id separated by ' ' from work.AMOSTRACHU;
select count(*) into :nr_reg separated by ' ' from tdata.work.AMOSTRACHU;
quit;
* check;
%put l_id=&l_id nr_reg=&nr_reg;
%macro ciclo_first();
%do n=1 %to &nr_reg;
%let ref=%scan(&l_id,&n);
%put ref=&ref;
proc sql;
select recetor into : lsus&ref separated by ' ' from tdata.5pct_&ref;
select count(*) into :nrsus&ref separated by ' ' from tdata.5pct_&ref;
quit;
%put lsus&ref=&lsus&ref;
%put nrsus&ref=&nrsus&ref;
%MACRO CICLO_PF_SUSref();
%do n=1 %to &nrsus&ref %by 1;
%let sus=%scan(&lsus&ref,&n);
%put sus=&sus;
%LET I = %EVAL(14);
%DO %WHILE (&I<=24);
*my code (depends on &i and &sus)* (works fine alone)
%LET I = %EVAL(&I+1);
%END;
%END;
%MEND;
%CICLO_PF_SUSref;
%MACRO CICLO_PF_SUS_CSRANK();
%do n=1 %to &nrsus&refm %by 1;
%let sus=%scan(&lsus&ref,&n);
%put sus=&sus;
%CICLO_PF_SUSPEITOSrefmsisdn;
%CICLO_PF_SUS_CSRANK;
my code ( just depends on &sus)/
%END;
%MEND;
%CICLO_PF_SUS_CSRANK;
%end;
%mend;
%ciclo_first;`
I think the major problem is in this part:
%put lsus&ref=&lsus&ref;
%put nrsus&ref=&nrsus&ref;
And the error about that is:
A character operand was found in the %EVAL function or %IF condition
where a numeric operand is required. The condition was:
&nrsus&ref
How can i change this in order to work? I understand that it doesn't make all the sense to have something depending on two, like &nrsus&ref.
the first warnings and errors appears here:
ref=15
WARNING: Apparent symbolic reference LSUS not resolved.
lsus15=&lsus15 WARNING: Apparent symbolic
reference NRSUS not resolved.
nrsus15=&nrsus15 ERROR: Expected semicolon not
found. The macro will not be compiled.
How can i solve this? Have no ideas and it would be really useful to make this macro functional in order to avoid to run this 100 times.
UPDATE [06.08.2015]
I have a table with 100 numbers, that's in
'work.amostrachu'.
I created the macro ciclo_first in order to run the other 2 macros for this list. because, if i replace manually the &ref by the number i want it works fine.
Let's suppose 'work.amostrachu' has:
ID 1 2 3 (...) till n=100
Then, with this part:
proc sql;
select recetor into : lsus&ref separated by ' ' from work.5pct_&ref;
select count(*) into :nrsus&ref separated by ' ' from work.5pct_&ref;
quit;
I want to get the elements that are on the column 'recetor' of work.5pct_&ref.
For ID=1 i would obtain lsus1 composed by, for example, 3 numbers (124,564,859)
And, then, the %MACRO CICLO_PF_SUSref(); will have as input these 3 numbers (that could be 4 or 5 or sometingh else).
(here, i might be calling badly the list of elements i want from 'work.5pct_&ref).
Then, the output of the previous macro would be the input of this one: %MACRO CICLO_PF_SUS_CSRANK.
And that would be all.
The %MACRO CICLO_PF_SUSref() and %MACRO CICLO_PF_SUS_CSRANK works ok if i just replace the &ref by the id. that's why i tried to create a macro that would run these 2 macros for the initial list. if you have best ideas, i would be thankful.
So, i want something that allows me to run this two macros (%MACRO CICLO_PF_SUSref() and `%MACRO CICLO_PF_SUS_CSRANK) for the list i get in the beginning:
proc sql NOPRINT ;
select id into :l_id separated by ' ' from work.AMOSTRACHU;
select count(*) into :nr_reg separated by ' ' from tdata.work.AMOSTRACHU;
quit;
[UPDATE 10.08.2015]
Ok, just read the suggested answers and worked on it.
I have a list, with the identification(numerical) of 100 clients, let's call each client : ref. That's on WORK.AMOSTRACHU.
I wroted the following code and it worked, and will help me explain you what i want:
proc sql NOPRINT ;
select id into :l_id separated by ' ' from work.AMOSTRACHU;
select count(*) into :nr_reg separated by ' ' from work.AMOSTRACHU;
quit;
* check;
%put l_id=&l_id nr_reg=&nr_reg;
%macro lista_ent();
%do n=1 %to &nr_reg;
%put n=&n;
%let ref=%scan(&l_id,&n);
%put ref=&ref;
proc sql;
select recetor into :listae&ref SEPARATED BY ' ' from work.e5pct_id&ref;
select count(*) into :nre&ref separated by ' ' from work.e5pct_id&ref;
quit;
%end;
%mend;
%lista_ent;
Will show you the output for the first 3 cases (of 100, the beggining list in work.amostrachu), it's the results part in SAS:
Recetor
507
723
955
-page break-
3
-page break-
380
500
675
977
984
-page break-
5
-page break-
200
225
351
488
698
781
927
-page break-
7
So, i have the 'values' of the column 'recetor' of the data work.e5pct_id&ref and how many values i have for each ref. (i've showed you results for the first 3 refs, but i have it for the 100).
Now, the first macro:
%MACRO CICLO_M_PF_ref();
%local me n i;
%do n=1 %to nre&ref %by 1;
%let me=%scan(listae&ref,&n);
%put me=&me;
%LET I = %EVAL(14);
%DO %WHILE (&I<=24);
proc sql;
create table work.smthng_&I as
select * from
work.wtv&I
WHERE A=&me OR B=&me;RUN;
PROC APPEND
DATA=work.smthng_&I
BASE=work.pf_&me
FORCE;
RUN;
%LET I = %EVAL(&I+1);
%END;
%END;
%MEND;
%CICLO_M_PF_ref;
My all doubts in the & and && are around here.
So, with the data: I have my first ref whose results of column 'recetor' are
Recetor
507
723
955
-page break-
3
So, i want to run that code for each one of this values. First for '507', then for '723' and then for '955', and i want to do it for all the refs.
So, when the macro finishes to run my code for this 3, i want the macro to skip to the second ref and then run my code for the values of the column 'recetor' for the second ref: 380,500,675,977 and 984.
i used this code:
proc sql;
select recetor into :listae&ref SEPARATED BY ' ' from work.e5pct_id&ref;
select count(*) into :nre&ref separated by ' ' from work.e5pct_id&ref;
quit;
because each one of the refs have different values and the number of them could be different, just as i showed you. so, this whas to tell the macro to run it nre&ref times and for all values in the list listae&ref.
the error is the following:
ERROR: A character operand was found in the %EVAL function or %IF
condition where a numeric operand is required. The condition was:
nre&ref ERROR: The %TO value of the %DO T loop is invalid. ERROR: The macro CICLO_M_PF_REF will stop executing.
I can't quite follow your desired output and macro but here are some things I noticed.
None of your macros take parameters. If you change your macro to take parameters you can call them individually which may help to stream line your process.
I think you want something like this:
%macro def1(param1);
...
%mend;
%macro def2(param2);
...
%mend;
%macro execute();
%do i=1 to 100;
%def1(param1);
%def2(param2);
%end;
%mend;
This still seems a bit awkward, so if you can explain your process with your data there may be a better way overall.
I see a number of issues you could address, but without test data it is hard to evaluate.
When trying to show the value for macro variable x&i you need to double up on the prefix &. So if I=1 and X1 = FRED then &&x&i = FRED.
When pushing values into macro variables from SQL use the automatic macro variable SQLOBS to get the record count. No need to run the query again to get the count.
You cannot select COUNT(*) into multiple macro variables. SQL will just return one count.
SAS dataset or variable names cannot start with a digit (tdata.5pct_&ref) or contain periods (tdata.work.AMOSTRACHU).
Do NOT nest macro definitions. You can nest the calls, but nesting
the definitions is just going to lead to confusion.
Your actual nested macros do not make much sense. What is this variable I that is introduced? It appears to be a constant.
Why not just code them as part of the outer macro? Not much need to make them separate macros if they are only called at one place.
If you do nest them then make sure to define your local macro variables as local to prevent overwriting the values of macro variables with the same name that might exist in an outer macro scope. The N looping variable for your %DO loops for example.
First define your subroutine macros.
%MACRO CICLO_PF_SUSref(ref_list);
* CICLO_PF_SUSref ;
%local n sus;
%do n=1 %to %sysfunc(countw(&ref_list,%str( )));
%let sus=%scan(&ref_list,&n);
%put NOTE: &sysmacroname N=&n SUS=&sus;
%end;
%MEND CICLO_PF_SUSref;
%MACRO CICLO_PF_SUS_CSRANK(ref_list);
* CICLO_PF_SUS_CSRANK ;
%local n sus ;
%do n=1 %to %sysfunc(countw(&ref_list,%str( )));
%let sus=%scan(&ref_list,&n);
%put NOTE: &sysmacroname N=&n SUS=&sus;
%put NOTE: Call macro named: CICLO_PF_SUSPEITOSrefmsisdn;
%end;
%MEND CICLO_PF_SUS_CSRANK;
Then your main macro.
%macro ciclo_first(id_list);
* Start ciclo_first ;
%local n id ;
%do n=1 %to %sysfunc(countw(&id_list,%str( )));
%let id=%scan(&id_list,&n);
proc sql noprint;
select recetor into : lsus&id separated by ' ' from pct_&id;
%let nrsus&id = &sqlobs ;
quit;
%put NOTE: Current ID=&id ;
%put NOTE: &&nrsus&id records read from PCT_&ID ;
%put NOTE: Value List LSUS&id = &&LSUS&id ;
%CICLO_PF_SUSref(&&lsus&id);
%CICLO_PF_SUS_CSRANK(&&lsus&id);
%end;
* End ciclo_first ;
%mend ciclo_first;
Then setup some data and call the main macro.
* Setup test data ;
data AMOSTRACHU;
do id=1 to 2; output; end;
run;
data PCT_1 ;
do recetor='A','B';
output;
end;
run;
data PCT_2 ;
do recetor='C','D';
output;
end;
run;
options mprint;
%ciclo_first(1 2);

SAS MACRO Looping

I am working on a dataset that has the following structure:
Color Apple Orange Grape Avocado Blueberry
Yellow 1 . . . .
Orange . 1 . . .
Purple . . 1 . 1
I want to write a macro that creates table for each fruit type, selecting all the colors (rows) that have a value of 1. For example, the table for apple TBL_APPLE will have 4 rows like the following
Currently I am thinking looping through both the rows and columns. As a frist step, I turned all the row and column variables into macros:
/*rows*/
proc sql noprint;
select count(*) into :Nobs
from work.fruit;
select Color into :Attr1-:Attr%left(&Nobs)
from work.fruit;quit;
/*columns*/
proc contents data=work.fruit out=contents noprint; run;
%let n=&sqlobs;
proc sql; select name into :fruit1-fruit%left(&n) from contents; quit;
%macro fruit;
%do i=1 %to &NObs;
%do j=1 %to &n;
proc sql;
create table tlb_&&fruit&j as
select *
from work.fruit
where &n = &n;
quit;
%end;
%end;
%mend fruit;
%fruit;
Not sure if it is something you want, but by my understanding the problem could
be simplify as below:
%macro fruit( type);
data &type ;
set dataset;
where &type = 1;
run;
%mend fruit;
I would first define a macro that performs the simple task you want to repeat:
%macro fruitds(fruit);
data &fruit.(keep=color);
set fruit;
where &fruit eq 1;
run;
%mend fruitds;
Then use a data step reading the column names from sashelp.vcolumns and call execute the macro for each column name that is not Color
data _null;
set sashelp.vcolumn;
call execute(cats('%fruitds(',name,')'));
where libname eq 'WORK'
and memname eq 'FRUIT'
and name ne 'Color';
run;
%color_fruit;
proc sql;
select name into:fruit_name separated by ' ' from dictionary.columns
where libname='WORK' and memname='FRUIT' and upcase(name)^='COLOR';
quit;
%let nums_fruit=%sysfunc(countw(&fruit_name));
%do i=1 %to &nums_fruit;
%let fruit=%scan(&fruit_name,&i,%str( ));
data tab_&fruit;
set fruit(keep=color &fruit);
if &fruit=1 then output;
run;
%end;
run;
%mend;

SAS proc sql inside %macro

Firstly I have the following table:
data dataset;
input id $ value;
datalines;
A 1
A 2
A 3
A 4
B 2
B 3
B 4
B 5
C 2
C 4
C 6
C 8
;
run;
I would like to write a macro so that the user can subset the data by giving the id value. I do proc sql inside the macro as follows:
%macro sqlgrp(id=,);
proc sql;
create table output_&id. as
select *
from dataset
where id = '&id.'
;
quit;
%mend;
%sqlgrp(id=A); /*select id=A only*/
I am able to generate the output_A table in the WORK library, however it has zero (0) observations.
Why is this not working?
You need to use double quotes when referring to macro variables.
Current Code
%macro sqlgrp(id=,);
proc sql;
create table output_&id. as
select *
from dataset
where id = '&id.'
;
quit;
%mend;
%sqlgrp(id=A); /*select id=A only*/
Looks for values of id that are literally '&id.'. You can test this by creating this dataset:
data dataset;
input id $ value;
datalines;
&id. 2
A 2
;
run;
Now, use %let to set the value of the macro variable id:
%let id=A;
Run a quick test of the functionality difference between single and double quotes. Notice the titles also contain single and double quotes, so we can see exactly what has happened in the output:
proc sql;
title 'Single Quotes - where id=&id.';
select *
from dataset
where id='&id.';
title "Double Quotes - where id=&id.";
select *
from dataset
where id="&id.";
title;
quit;
Correct Code
%macro sqlgrp(id=,);
proc sql;
create table output_&id. as
select *
from dataset
where id = "&id."
;
quit;
%mend;
%sqlgrp(id=A); /*select id=A only*/
The double quotes allow the macro variable &id to resolve to 'A', which will return results based on your input.
Just a simple rewrite of the previous answer which passes 'in' and 'out' through a signature of the macros
%macro sqlgrp(in=, id=, out=);
proc sql noprint;
create table &out. as select * from &in. where id = "&id.";
quit;
%mend sqlgrp;