以下宏在两个表之间进行内部连接,除了连接列之外,每个表包含一列:
%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;
我想找到一种在&xc
,&yc
和&by
中使用多个列的方法。
因为我认为我不能使用变量向量。
我的想法是将参数作为字符串向量而不是简单变量传递,例如xc = {"col1" "col2"}
并循环遍历它们
使用%let some_var= %sysfunc(dequote(&some_string));
将它们转换回变量。
仅在xc
上应用它会变成:
%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;
但是这个循环失败了。我怎么能让它发挥作用?
注意:这是一个简化的示例,我的最终目标是构建尽可能少的冗长的连接宏并集成数据质量检查。
答案 0 :(得分:1)
而不是矢量,想想简单的列表。
将变量列表作为不带引号,以空格分隔的值列表。值是SAS变量名称,可以作为标记扫描出来。
%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, ... )
请务必本地化所有宏变量,以防止宏外的不必要的副作用。
为了保持一致性,我尝试使用模拟SAS Procs的i / o相关宏参数 - data=
,out=
,file=
,...
有人会说命名参数很冗长!
如果你的原型代码是'期望xci
符号是某种连续编号的变量,但事实并非如此。您必须使用%local xc&i; %let xc&i=
进行分配,并使用&&xc&i
进行分配。此外,您的原始代码引用了未传递的&from
。
建筑很有趣。我还建议调查过去的会议论文和SAS文献,以了解可能已达到目标的类似作品。
答案 1 :(得分:1)
您可以从以空格分隔的列名列表开始,并避免完全循环:
/*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;
答案 2 :(得分:1)
实际上,编写使用SAS数据集选项而不是构建复杂的宏逻辑会更容易。
proc sql ;
create table want2 as
select *
from sashelp.class(keep=name age)
natural inner join sashelp.class(keep=name height weight)
;
quit;
我建议学习如何使用数据步骤代码而不是SQL代码。对于大多数正常的数据操作,它更清晰,更简单。假设您想要将IN1和IN2组合在变量ID上,并将变量A和B保持在IN1中,将变量X和Y保持在IN2中。
data out ;
merge in1 in2 ;
by id ;
keep id a b x y ;
run;
其次,我会抵制产生过于复杂的宏代码网络的冲动。这将使下一个程序员更难理解程序。两周后包括你自己。您的特定示例看起来不像是一个值得编码为宏的东西。您实际上并没有输入更少的信息,只需使用一些逗号代替您的SQL代码将具有FROM或JOIN等关键字的位置。
现在回答你的实际问题。要将值列表传递给宏,请使用分隔列表。当尽可能使用空格作为分隔符时,尤其要避免使用逗号作为分隔符。这将更容易键入,更容易传入宏并更容易使用,因为它与SAS语言匹配,如上面的数据步骤所示。如果你真的需要生成使用逗号的SQL语法之类的代码,那么让宏代码在需要的地方生成它们。
%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 ;
试一试:
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;
答案 3 :(得分:0)
最后,正如@Tom所说,SAS数据集选项更方便,使用它们不需要循环变量。
这是我带来的宏:
*--------------------------------------------------------------------------------------------- ;
* 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;
可重复的例子:
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)