2017年8月28日月曜日

そのマクロ変数、ローカル?グローバル?⑤マクロ変数の作成場所を明示的に指定する




マクロ変数を作成する場所(ローカル or グローバル)を明示的に指定することが出来ます。




「%LOCAL」と「%GLOBAL」を使う


・「%LOCALステートメント」で「ローカルマクロ変数」を作成できます。
・「%GLOBALステートメント」で「グローバルマクロ変数」を作成できます。


構文

  %LOCAL    マクロ変数1  マクロ変数2 ・・・;
  %GLOBAL  マクロ変数1  マクロ変数2 ・・・;




使用例の前にまずは、今回つかうマクロ変数をリセット(削除)しときます。
%symdel  MAC1 MAC2 MAC3 MAC4 MAC5  /  nowarn;



%macro TEST;

    %local    MAC1  MAC2 ;
    %global  MAC3 ;

    %put _user_;

%mend;
%TEST;

ログ
TEST MAC1
TEST MAC2
GLOBAL MAC3





「CALL SYMPUTX」を使う


CALL SYMPUTXの第3引数で、ローカル・グローバルどちらに作成するか指定できます。


構文

  CALL SYMPUTX( "マクロ変数名", "格納する値" , "G") ;
  CALL SYMPUTX( "マクロ変数名", "格納する値" , "L") ;


"G": グローバルマクロ変数にする
"L": ローカルマクロ変数にする(マクロ外で実行するとグローバルマクロ変数になる)


%macro TEST;

    data _null_;
        call symputx("MAC4","abcd","L");
        call symputx("MAC5","1234","G");
    run;

    %put _user_;

%mend;
%TEST;

ログ
TEST MAC4 abcd
GLOBAL MAC5 1234


ローカルマクロ変数はマクロ終了とともに消えるので、無駄なマクロ変数が残りません。
なのでローカルマクロ変数を明示的に定義する方が内部的にはキレイなプログラムといえると思います。



記事一覧

1. 基本概念
2. %LET編
3. CALL SYMPUT編
4. マクロ変数の展開
5. マクロ変数の作成場所を明示的に指定する

2017年8月25日金曜日

「%IF条件」で「NOT IN」が使えない。




以下の記事を読んだ方から質問を頂きました。

「%IF条件」で「IN」を使う。




質問


「%IF」で「^IN」を使ったら、エラーでちゃったんだけど、どうすればいい?

options minoperator mindelimiter=',' ;

%macro TEST( X= );
   %if  &X ^in (1,2)  %then %put NOTE: マクロ変数 X は 1 と 2 じゃない;
%mend;

%TEST( X=3 );

ログ
 ERROR: 数値演算項が必要とされる%EVAL関数または%IF条件に、文字演算項が見つかりました。
        条件: &X ^in (1,2) 
 ERROR: マクロTESTの実行を中止します。

上の例では「マクロ変数 X が 1, 2 以外」という条件を判定したかったわけです。
どうやら、「%IF」では「^IN」とか「NOT IN」みたいな書き方は現在(SAS9.4)対応していないようですね。




解決策


NOT演算子で囲ってあげるだけでOKです。

options minoperator mindelimiter=',' ;

%macro TEST( X= );
   %if  not ( &X in (1,2) )  %then %put NOTE: マクロ変数 X は 1 と 2 じゃない;
%mend;

%TEST( X=3 );

ログ
 NOTE: マクロ変数 X は 1 と 2 じゃない


2017年8月23日水曜日

【NVALID関数】指定した文字が変数名として有効か調べる


NVALID関数 を使うと、
指定した文字値が、SAS変数名として有効かどうかをチェックすることが出来ます。



構文


  NVALID( var , type )


① var   … 変数名として有効かチェックする文字値、またはチェックする文字値が格納された変数を指定。
② type … 変数名の規則タイプを指定。 "v7" を指定すると以下の規則(通常のSAS変数名の規則)でチェックが可能。

・先頭が半角の「英字」 または 「アンダースコア」
・後続の文字が半角の「英数字」または「アンダースコア」
・32文字以下

③変数名として有効であれば「1」、無効であれば「0」を返します。






サンプルデータ作成
data DT1;
input X & $10.;
cards;
myvar
my var
onevar
1var
;
run;
 X 
  myvar  
  my var
  onevar
  1var


変数 X に格納された値が変数名として有効かどうか判定
data DT2;
   set DT1;
   Y = nvalid( X , "v7" );
run;
 X 
   Y   
  myvar  
   1 
  my var
   0 
  onevar
   1
  1var
   0 




実践例

この関数は、マクロで使うと便利です。

%macro TEST( VAR= );

     %if "%sysfunc(nvalid(&VAR,v7))" = "0" %then %do;
          %put %str(E)RROR: マクロ変数 VAR の値が有効な変数名ではありません;
          %put %str(E)RROR: マクロ TEST を停止します;
          %return;
     %end;

     data DT1;
         &VAR = 1;
     run;

%mend;

%TEST( VAR=MY VAR );

ログ
ERROR: マクロ変数 VAR の値が有効な変数名ではありません
ERROR: マクロ TEST を停止します

上の例では、
マクロ「TEST」の引数「VAR」には有効なSAS変数名を指定しなきゃいけないようにし、無効なSAS変数名が指定されたら、ERRORをログに出して、マクロを停止しています。



後、別の実践例として以前書いた変数定義書のチェックにもNVALIDが使えます。
http://sas-boubi.blogspot.jp/2014/09/blog-post.html

2017年8月21日月曜日

「PUT」や「%PUT」でログに独自のエラーメッセージを表示するときの定石



海外のプログラムみてるとよくこの書き方してるなーっていう定石を紹介。
まずは以下の記事で、ログに独自のERROR, WARNING, NOTEメッセージを表示する方法を紹介済みなのでご覧下さい。

ログに独自のエラーメッセージを表示する。



たとえば以下のように、
データステップの場合はPUTを使い、マクロの場合は%PUTを使って独自のログメッセージを表示できます。


データステップの場合
data _null_;
     A = 1;
     if A = 1 then put "ERROR: 変数 A が 1 じゃダメ!!";
run;

ログ
35  data _null_;
36       A = 1;
37       if A = 1 then put "ERROR: 変数 A が 1 じゃダメ!!";
38  run;

ERROR: 変数 A が 1 じゃダメ!!
NOTE: DATAステートメント処理(合計処理時間):
      処理時間           0.00 秒

マクロの場合
%macro TEST(A=);
     %if "&A" = "1" %then %put ERROR: マクロ変数 A が 1 じゃダメ!!;
%mend;

%TEST(A=1)


ログ
34  %macro TEST(A=);
35       %if "&A" = "1" %then %put ERROR: マクロ変数 A が 1 じゃダメ!!;
36  %mend;
37
38 %TEST(A=1)
ERROR: マクロ 変数 A が 1 じゃダメ!!


ただし、1つ問題があります。

みなさん、プログラム実行後のログに対して 「ERROR」 や 「WARNING」などのキーワードで検索かけて問題となるメッセージがないかを確認しますよね。
その際、上のログ2行目の「35       %if "&A" = "1" %then %put ERROR: マクロ変数 A が 1 じゃダメ!!;」には「ERROR」という文字を含んでいるため検索にひっかかってしまいます。
検索かけたいのは 実際に発生したエラーだけなので、これは無視したいですよね。


そこで以下のように "E" "RROR" という感じで文字を区切って検索にひっかからないようにします。

data _null_;
     A = 1;
     if A = 1 then put "E" "RROR: 変数 A が 1 じゃダメ!!";
run;

ログ
35  data _null_;
36       A = 1;
37       if A = 1 then put "E" "RROR: 変数 A が 1 じゃダメ!!";
38  run;

ERROR: 変数 A が 1 じゃダメ!!
NOTE: DATAステートメント処理(合計処理時間):
      処理時間           0.00 秒




また%PUTの場合も、 %str(E)RROR と書いて検索にひっかからないようにします。

%macro TEST(A=);
     %if "&A" = "1" %then %put %str(E)RROR: マクロ変数 A が 1 じゃダメ!!;
%mend;

%TEST(A=1)


ログ
34  %macro TEST(A=);
35       %if "&A" = "1" %then %put %str(E)RROR: マクロ変数 A が 1 じゃダメ!!;
36  %mend;
37
38 %TEST(A=1)
ERROR: マクロ 変数 A が 1 じゃダメ!!

%strをダミーで使ってる感じですね。

2017年8月17日木曜日

そのマクロ変数、ローカル?グローバル?④マクロ変数の展開



「ローカル」 と 「グローバル」 に同名のマクロ変数が存在する場合、どちらを優先して展開するのか? という問題です。




フロー図


マクロ変数の展開を「マクロ内」で行っているのか、「マクロの外」で行っているのかによって、以下のとおり展開結果が変わります。





文章でまとめると、
・「マクロ外」では「グローバルマクロ変数」 が展開される。
・「マクロ内」では「ローカルマクロ変数」→「グローバルマクロ変数」 の優先順位で展開される。




具体例


たとえば以下では、同じ名前のローカル・グローバルマクロ変数を定義しています。

%macro TEST;

    data _null_;
          call symputx( "MAC1", "1234", "L" );
          call symputx( "MAC1", "abcd", "G" );
    run;

    %put _user_;

%mend;
%TEST;

ログ
TEST MAC1 1234
GLOBAL MAC1 abcd

(※ CALL SYMPUTXでローカル・グローバルマクロ変数を分けて作る方法は以下で紹介済み。また次回記事でも紹介予定。)





では以下のように、%PUTを使って、マクロ内とマクロ外でマクロ変数を展開してログに出力してみます。

%macro TEST;

    data _null_;
          call symputx("MAC1","1234","L");
          call symputx("MAC1","abcd","G");
    run;

    %put マクロ内 ---> &MAC1;

%mend;
%TEST;

%put マクロ外 ---> &MAC1;


ログ
マクロ内 ---> 1234
マクロ外 ---> abcd

ログを見て頂くと分かる通り、結果はフロー図で示した通りですね。


2017年8月15日火曜日

SASのブラックホール問題




SASはたまにブラックホールを生み出します。
やばいです、SASコードを全部吸い込んじゃいます。



、、何を言っているかというと、ほとんどのSASプログラマーが経験する問題のことです。
以下をご覧ください。

%macro TEST;
   data DT1;
      set SASHELP.CLASS;
   run;
%mend;
%TEST;

proc print data=DT1;
run;

proc means data=DT1;
run;


上のプログラムでもし「%mend;」を書き忘れた場合、どうなるでしょう?
まだマクロの定義が終わっていないとみなされて、その後に続く proc print や proc means もマクロに吸い込まれていってしまいます。

この現象のことを 「ブラックホール問題」 とか 「”ブラックホール”マクロ問題」 と呼びます。



よくマクロの中にマクロを書いてると、一方のマクロを「%mend;」で閉め忘れてしまってブラックホール作っちゃう事があるんですよね。

そうならないために以下のように「%mend TEST;」って感じで、マクロ名も書くとマクロの開始と終了が分かり易くなります。

%macro TEST;
   data DT1;
      set SASHELP.CLASS;
   run;
%mend TEST;


ほかにも文字列をクォーテーションで囲むときに閉め忘れてブラックホール状態になることありますね。



ブラックホールが出来てしまったら、色々対処法ありますが、SASコードを吸い込んで肥大化したブラックホールマクロは何しでかすか分からないので、SASを再起動しちゃった方が確実です。


もしこの問題に直面してる人を見かけたら「あ、それはいわゆるブラックホール問題ってやつですね」っていって、この呼び方を流行らせましょう。


2017年8月10日木曜日

そのマクロ変数、ローカル?グローバル?③CALL SYMPUT編



「CALL SYMPUT」 または 「CALL SYMPUTX」 で定義したマクロ変数が、ローカル・グローバルどちらになるのか解説します。


まずは前提知識。




シンボルテーブル


マクロ変数を入れる箱の事をシンボルテーブルといいます。

・ローカルシンボルテーブル ・・・ ローカルマクロ変数を入れる箱
・グローバルシンボルテーブル ・・・ グローバルマクロ変数を入れる箱

図1
つまりマクロ変数がローカルなのか?グローバルなのか?という問題は、どちらのシンボルテーブルに入るかを問題としているわけです。




CALL SYMPUT で定義したマクロ変数の、ローカル・グローバルの判定方法


📝 説明の前に・・
SAS社のリファレンスの説明と実際の動作に差異があったため、私の実験結果に基づく動作の説明も含みます。何か不足している点など、お気づきの点がありましたらコメントいただけると幸いです。



CALL SYMPUT が含まれるデータステップの「run;」がマクロの中にあるのか、マクロの外にあるのかで、ローカル・グローバルの判定結果が変わります。


マクロの中に「run;」がある
%macro TEST;
   data _null_;
      call symput("MAC1", "1234");
   run;
%mend;
%TEST;

マクロ外に「run;」がある
data _null_;
   call symput("MAC1", "1234");
run;



判定は以下のとおり。
図2



具体例


では具体例をみていきましょう。
まず今回の解説で使うマクロ変数を予めリセット(削除)しときます。

%symdel MAC1 MAC2 MAC3 MAC4 MAC5 MAC6 MAC7 MAC8 MAC9 MAC10 / nowarn;



① マクロ外で定義したマクロ変数が、「グローバル」になる例。

data _null_;
   call symput("MAC1","abcd");
run;
%put _user_;

ログ
GLOBAL MAC1 abcd


② マクロ内で定義したマクロ変数が、「グローバル」になる例。

%macro TEST;
    data _null_;
       call symput("MAC2","abcd");
    run;

    %put _user_;
%mend;
%TEST;

ログ
GLOBAL MAC2 abcd


③ マクロ内で定義したマクロ変数が、「ローカル」になる例。

%macro TEST;
    %let MAC3 = abcd;

    data _null_;
       call symput("MAC4","1234");
    run;

    %put _user_;
%mend;
%TEST;

ログ
TEST MAC3 abcd
TEST MAC4 1234



例外

上のほうで示した「図2」のオレンジ色で示した「例外あり」のルートでは以下の例外が起きます。

① マクロ内で「SQLプロシジャ」の後に実行するCALL SYMPUTのマクロ変数は「ローカル」になる。

%macro TEST;
    proc sql ;
       select * from SASHELP.CLASS;
    quit;

    data _null_;
       call symput("MAC5","abcd");
    run;

    %put _user_;
%mend;
%TEST;

ログ
TEST MAC5 abcd


② マクロ内で「%GOTO &マクロ変数」や「%GOTO %マクロ名」みたいな記述があると、その後のCALL SYMPUTのマクロ変数は「ローカル」になる。

%let MAC6=GOGO;

%macro TEST;
    %goto &MAC6;
    %GOGO:

    data _null_;
       call symput("MAC7","1234");
    run;

    %put _user_;
%mend;
%TEST;

ログ
TEST MAC7 1234
GLOBAL MAC6 GOGO



③ マクロで「parmbuff」を使っていると、CALL SYMPUTのマクロ変数は「ローカル」になる。

%macro TEST / parmbuff;
    data _null_;
       call symput("MAC8","1234");
    run;

    %put _user_;
%mend;
%TEST;

ログ
TEST MAC8 1234



失敗例


① たまに質問を頂くので注意喚起。
CALL SYMPUTで定義したマクロ変数を同じデータステップ内で参照することは出来ません。

data _null_;
   call symput("MAC9","123");
   x = &MAC9;
run;

ログ
 WARNING: MAC9のシンボリック参照を解決できません。




② これは上級者が遭遇する失敗例。
「CALL SYMPUTでマクロ変数を定義」&「そのマクロ変数を使用(参照)する」みたいなマクロがあったとして、そのマクロをCALL EXECUTEで実行すると、うまくマクロ変数の参照が出来ないようです。

%macro TEST;
   data _null_;
      call symput("MAC10","123");
   run;

   %put &MAC10;
%mend;

data _null_;
   call execute('%TEST');
run;

ログ
 WARNING: MAC10のシンボリック参照を解決できません。

SAS社のUsage Noteにも載ってました。



てことで、どうでしょう、、正直最初はなんじゃこりゃーって感じでいろんなシチュエーションがあって覚えらんないですよね。慣れれば、なんとなくわかってきます。




記事一覧

1. 基本概念
2. %LET編
3. CALL SYMPUT編
4. マクロ変数の展開
5. マクロ変数の作成場所を明示的に指定する

2017年8月7日月曜日

「SASユーザー総会2017」に行ってきました。


8月3・4日に開催されたSASユーザー総会2017に行ってきました。



今年はSAS9で追加になった機能を使った面白い発表が多かったです。
PROC LUA とか PROC STREAM は数年後にはふつうに使ってるよって人が出てきそうですね。


それにしても、、発表が行われる部屋以外にイスとかがないので、やたら疲れた。。
ポスター貼るとこ付近に用意してほしい。。それかイスがある会場に変えてほしいな。。



そして帰りのアンケートに答えてもらえるお楽しみのやつですが、
今回はハサミとか爪切りが入ったやつがもらえました。
コンパクトでかわいいですね。


ということで、今回も色々勉強になりました。来年も楽しみです。

いずれ、海外の SAS Global Forum を超えて日本人すげーなって思われるようなユーザー総会になっていけばいいですね。