SAS proc sql inside %macro - sql

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;

Related

How to run a different query if table is empty one month earlier

How to run a different query if the output table is empty.
My current query is:
PROC SQL;
CREATE TABLE WORK.QUERY_FOR_A_KUNDESCORINGRATINGRE AS
SELECT t1.PD,
t1.DATO,
t1.KSRUID
FROM DLKAR.A_KUNDESCORINGRATINGRETRO t1
WHERE t1.KSRUID = 6 AND t1.DATO = '31Aug2022'd;
QUIT;
But I would like to make a conditional statement to run the query again if it is empty but with the filter t1.DATO set to '31Jul2022'd instead of august. So every time the query fails on a given date the query tries again one month earlier.
I hope you can point me in a direction.
Just loop until you get results. You should put an upper bound on the number of times it loops to make sure if will end.
This will require that you create a macro to allow the conditional code generation.
%macro loop(start,months);
%local offset;
PROC SQL;
%do offset=0 to -&months by -1;
CREATE TABLE WORK.QUERY_FOR_A_KUNDESCORINGRATINGRE AS
SELECT t1.PD
, t1.DATO
, t1.KSRUID
FROM DLKAR.A_KUNDESCORINGRATINGRETRO t1
WHERE t1.KSRUID = 6
AND t1.DATO = %sysfunc(intnx(month,&start,&offset,e))
;
%if &sqlobs %then %goto leave;
%end;
%leave:
QUIT;
%mend;
%loop('31AUG2022'd,6)
You could make SQL work a little harder to get what you want. Pull the data back as many months as you want, but only keep the observations that are for the latest month. Now you don't need any looping.
CREATE TABLE WORK.QUERY_FOR_A_KUNDESCORINGRATINGRE AS
SELECT t1.PD
, t1.DATO
, t1.KSRUID
FROM DLKAR.A_KUNDESCORINGRATINGRETRO t1
WHERE t1.KSRUID = 6
AND t1.DATO between %sysfunc(intnx(month,&start,-&offset,e)) and &start
having dato=max(dato)
;
Which method performs better will depend on the data and things like whether or not the data is sorted or indexed.
I assume you always want to query for DATO the last day of the month.
%macro QUERY_FOR_A_KUNDESCORINGRATINGRE(DATO);
** Try with the date given **;
PROC SQL;
CREATE TABLE WORK.QUERY_FOR_A_KUNDESCORINGRATINGRE AS
SELECT t1.PD,
t1.DATO,
t1.KSRUID
FROM DLKAR.A_KUNDESCORINGRATINGRETRO t1
WHERE t1.KSRUID = 6 AND t1.DATO = &DATO;
QUIT;
** Read the result AND any other dataset.
(SASHELP.CLASS is a small dtaset that always exists.) **;
data _null_;
set QUERY_FOR_A_KUNDESCORINGRATINGRE(in=real_result) SASHELP.CLASS;
** If there is any result, the first observation(=row) will be from
that result and real_result will be 1(the SAS coding of True)
otherwise real_result will be 0(the SAS coding of False) **;
if not real_result then do;
** find the last day of the previous month **;
month_earlier = intnx("month", -1, &DATO, 'end');
call execute('%QUERY_FOR_A_KUNDESCORINGRATINGRE('
|| put(month_earlier, 8.) ||');');
end;
** We only need one observation(=row), so stop now **;
stop;
run;
%mend;
%QUERY_FOR_A_KUNDESCORINGRATINGRE('31Aug2022'd);
Disclaimer: I did not test this. It might need some debugging.
Try to run this code, we need loop until you get records
%macro query;
%global DATO;
%let DAT0 = '31Aug2022'd;
%first: PROC SQL;
CREATE TABLE WORK.QUERY_FOR_A_KUNDESCORINGRATINGRE AS
SELECT t1.PD,
t1.DATO,
t1.KSRUID
FROM DLKAR.A_KUNDESCORINGRATINGRETRO t1
WHERE t1.KSRUID = 6 AND t1.DATO = &DATO;
QUIT;
%let dsid = %sysfunc(open (QUERY_FOR_A_KUNDESCORINGRATINGRE))
%let obs = %sysfunc(attrn(&dsid. nlobs));
%let dsid = %sysfunc(close(&dsid.));
%if &obs = 0 %then %do;
data _null_;
call symputx("dato",intnx('m',&dato.,-1));
run;
%goto first;
%end;
%mend;
%query;
Please note: I haven't tested this code would be great if this helps you
If your dataset only contains data for the last day of a month, this solves your problem without iterating at all:
PROC SQL;
CREATE TABLE WORK.QUERY_FOR_A_KUNDESCORINGRATINGRE AS
SELECT t1.PD,
t1.DATO,
t1.KSRUID
FROM DLKAR.A_KUNDESCORINGRATINGRETRO t1
WHERE t1.KSRUID = 6 AND t1.DATO = (
SELECT max(t2.DATO)
FROM DLKAR.A_KUNDESCORINGRATINGRETRO t2
WHERE t2.KSRUID = 6);
QUIT;

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

Pass a dynamic array to iterate over proc 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);

SAS PROC SQL NOT CONTAINS multiple values in one statement

In PROC SQL, I need to select all rows where a column called "NAME" does not contain multiple values "abc", "cde" and "fbv" regardless of what comes before or after these values. So I did it like this:
SELECT * FROM A WHERE
NAME NOT CONTAINS "abc"
AND
NAME NOT CONTAINS "cde"
AND
NAME NOT CONTAINS "fbv";
which works just fine, but I imagine it would be a headache if we had a hundred of conditions. So my question is - can we accomplish this in a single statement in PROC SQL?
I tried using this:
SELECT * FROM A WHERE
NOT CONTAINS(NAME, '"abc" AND "cde" AND "fbv"');
but this doesn't work in PROC SQL, I am getting the following error:
ERROR: Function CONTAINS could not be located.
I don't want to use LIKE.
You could use regular expressions, I suppose.
data a;
input name $;
datalines;
xyabcde
xyzxyz
xycdeyz
xyzxyzxyz
fbvxyz
;;;;
run;
proc sql;
SELECT * FROM A WHERE
NAME NOT CONTAINS "abc"
AND
NAME NOT CONTAINS "cde"
AND
NAME NOT CONTAINS "fbv";
SELECT * FROM A WHERE
NOT (PRXMATCH('~ABC|CDE|FBV~i',NAME));
quit;
You can't use CONTAINS that way, though.
You can use NOT IN:
SELECT * FROM A WHERE
NAME NOT IN ('abc','cde','fbv');
If the number of items is above reasonable number to build inside code, you can create a table (work.words below) to store the words and iterate over it to check occurrences:
data work.values;
input name $;
datalines;
xyabcde
xyzxyz
xycdeyz
xyzxyzxyz
fbvxyz
;
run;
data work.words;
length word $50;
input word $;
datalines;
abc
cde
fbv
;
run;
data output;
set values;
/* build a has of words */
length word $50;
if _n_ = 1 then do;
/* this runs once only */
call missing(word);
declare hash words (dataset: 'work.words');
words.defineKey('word');
words.defineData('word');
words.defineDone();
end;
/* iterate hash of words */
declare hiter iter('words');
rc = iter.first();
found = 0;
do while (rc=0);
if index(name, trim(word)) gt 0 then do; /* check if word present using INDEX function */
found= 1;
rc = 1;
end;
else rc = iter.next();
end;
if found = 0 then output; /* output only if no word found in name */
drop word rc found;
run;

simple SAS select into

I want to use "select into" to create a list of all IDs in SAS.
/* my state table try01 */
data try01;
input id state $;
cards;
1108 va
1102 dc
1101 md
1105 on
;
run;
/* select into */
proc sql noprint;
select id into: x from try01;
quit;
%put &x;
My question is why the log shows that macro x is only one value (1108) instead
of a list (1108,1102,1101,1105) ? So confused... thanks a lot.
If you want SQL to put multiple values into the macro variable then you need to include the SEPARATED BY clause.
select id into :x separated by ' ' from try01;
You could then use this list in, for example an IN operator call.
proc print data=have ;
where id in (&x);
run;