SAS:自动化完整性约束创建

SAS: Automating integrity constraints creation

我正在尝试基于此数据集自动创建一些完整性约束:

ds_name | var_name | ic_clause                          | ic_msg
--------+----------+------------------------------------+-----------------------
tableA  | var1     | primary key($var$)                 | $var$ is a primary key
tableB  | var2     | check(where=($var$ in ('a', 'b'))) | invalid $var$ value

我们的想法是创建一个通用程序,循环遍历该数据集并相应地创建 IC。在这种特定情况下,等效的硬编码程序将是:

proc datasets nolist;
    modify tableA;
        ic create primary key(var1)
            message = "var1 is a primary key";
quit;

proc datasets nolist;
    modify tableB;
        ic create check(where=(var2 in ('a', 'b')))
            message = "invalid var2 value";
quit;

这些是我在程序中想象的步骤,但我需要帮助才能将它们转化为实际代码:

  1. 获取一行的值并将它们放入宏变量
  2. var_name
  3. 列中的实际变量名替换$var$子字符串
  4. 运行一个通用的proc数据集,例如:

        proc datasets nolist;
            modify &my_ds;
                ic create &my_clause
                    message = &my_msg;
        quit;
    
  5. 遍历所有行

任何人都可以帮我这个代码吗?我不知道我建议的步骤是否是实现我正在尝试做的事情的最佳方式。基本上,我正在尝试在 SAS 中模拟关系数据库,并尽可能地实现自动化。

谢谢!

您可以按照您的建议使用数据变量本身来编写您的宏语句。您基本上想要创建一个新的长变量,通过将所有变量串在一起,宏调用看起来就像在每一行的数据步骤中编写的那样。您可以使用 tranwrd 函数将占位符文本替换为实际的 VAR_NAME。以下应该有效:

data test;
    infile datalines dlm="|";
    length DS_NAME VAR_NAME IC_CLAUSE IC_MSG ;
    input DS_NAME $ VAR_NAME $ IC_CLAUSE $ IC_MSG $;
    datalines;
    tableA  | var1     | primary key($var$)                 | $var$ is a primary key
    tableB  | var2     | check(where=($var$ in ('a', 'b'))) | invalid $var$ value
    ;
run;

** write your master macro **;
%MACRO master_loop(DS_NAME=,IC_CLAUSE=,IC_MSG=);
proc datasets nolist;
    modify &DS_NAME.;
        ic create &IC_CLAUSE.
            message = "&IC_MSG.";
quit;
%MEND;

** create all your macro statements **;
data master_strings; 
    length STR 0;
    set test;
    IC_CLAUSE1 = tranwrd(IC_CLAUSE,"$var$",strip(VAR_NAME)); /* replace the placeholder with the actual VAR_NAME contents */
    IC_MSG1 = tranwrd(IC_MSG,"$var$",strip(VAR_NAME)); /* replace the placeholder with the actual VAR_NAME contents */
    STR = %nrstr("%master_loop("||"DS_NAME="||strip(DS_NAME)||",IC_CLAUSE="||strip(IC_CLAUSE1)||",IC_MSG="||strip(IC_MSG1)||");");
run;

** put all macro statements into a list**;
** this would look similar to writing out multiple %master_loop statements if hard-coded **;
proc sql noprint;
    select STR
    into: all_macro_calls separated by " "
    from master_strings;
quit;

** submit all your macro calls **;
%put &all_macro_calls.;

您可以使用 call execute 使您的 'hardcoded' 程序完全动态化(其中 IC 是具有约束的基础数据集):

data _null_;
set IC;
call execute("proc datasets nolist;modify "||strip(ds_name)
   ||";ic create "||tranwrd(strip(ic_clause),'$var$',strip(var_name))
   ||" message = '"||tranwrd(strip(ic_msg),'$var$',strip(var_name))
   ||"';quit;");
run;

基本上,对于数据集中的每个观察,call execute 将通过插入变量值(ds_namevar_name 等...来执行适当的 proc datasets ) 在正确的位置。 tranwrd 函数将负责用 var_name.

的实际值替换 $var$ 占位符

您可能会发现无法将 SAS 转换为 DBMS。最好使用元数据生成检查数据的程序,而不是尝试实施完整性约束。

但是数据驱动代码生成的概念很有趣,所以让我们看看我们是否可以使用您的示例来演示如何从元数据生成代码。我发现当您将元数据中的变量名称与需要生成的代码相匹配时效果会更好。因此,让我们调用用于在 IC 语句 MESSAGE 上创建 MESSAGE= 选项的变量。

现在我们可以使用一个简单的数据步骤来生成代码。不确定为什么在约束和消息字段中使用伪代码而不是对值进行硬编码,但我们可以使用 TRANWRD() 函数将 $varname$ 字符串替换为 VARNAME 的值变量。

让我们制作一个示例元数据文件。

data ic_metadata;
  infile datalines dlm="|";
  length libname  memname  varname  constraint message 0;
  input libname memname varname constraint message ;
datalines;
work|tableA|var1|primary key($varname$)                |$varname$ is a primary key
work|tableB|var2|check(where=($varname$ in ('a', 'b')))|invalid $varname$ value
;

还有一些要处理的示例数据。

data tablea tableb ;
 length var1 8 var2  ;
 var1+1;
 var2='a';
run;

现在让我们使用元数据生成代码并%INCLUDE到运行它。

 filename code temp;
 data _null_;
   file code ;
   set ic_metadata ;
   by libname memname ;
   if first.libname then put 'proc datasets lib=' libname 'nolist;' ;
   if first.memname then put '  modify ' memname ';' ;
   constraint=tranwrd(constraint,'$varname$',trim(varname));
   message=tranwrd(message,'$varname$',trim(varname));
   put 'ic create ' constraint message= :$quote. ';' ;
   if last.memname then put 'run;';
   if last.libname then put 'quit;' ;
 run;
 %include code / source2 ;

因此 运行在示例中我们得到了这样的 SAS 日志:

161  +proc datasets lib=work nolist;
162  +  modify tableA ;
163  +ic create primary key(var1) message="var1 is a primary key" ;
NOTE: Integrity constraint _PK0001_ defined.
164  +run;

NOTE: MODIFY was successful for WORK.TABLEA.DATA.
165  +  modify tableB ;
166  +ic create check(where=(var2 in ('a', 'b'))) message="invalid var2 value" ;
NOTE: Integrity constraint _CK0001_ defined.
167  +run;

NOTE: MODIFY was successful for WORK.TABLEB.DATA.
168  +quit;