2015年4月28日火曜日

IFステートメントとDOループは繋げて書ける。


タイトルの通りですが、つい最近まで繋げて書けないと思ってました。

* Sample Data ;
data DT1;
   do A=1 to 3;
      output;
   end;
run;

 A 
  1 
  2  
  3  

* IF THEN DOループ ;
data DT2;
   set DT1;

   if A=2 then do B=1 to 3;
      output;
   end;

   else output;
run;

 A 
  B  
  1 
  . 
  2  
  1 
  2  
  2 
  2  
  3 
  3  
  . 

「IF文による条件に当てはまった時だけ、DOループをまわす」って感じになります。

これ知るまでは以下みたいに書いてました。

data DT2;
   set DT1;

   if A=2 then do;
      do B=1 to 3;
         output;
      end;
   end;

   else output;
run;


SAS覚え中の人に言ったら、「知ってた」って言われたんで多分知らなかったの私だけっぽい。

2015年4月24日金曜日

DS2プロシジャのSQLEXEC関数でSQLを実行する。


SAS9.4で追加されたDS2プロシジャを勉強中です。

データステップがパワーアップした感じのやつなんですが、勝手が違うので、なかなかすんなりと動いてくれないです。。(そこがまた楽しいですが)

今回は、DS2プロシジャらしいダイナミックな関数、SQLEXEC関数を使ってみたいと思います(注意点もあるので最後までご覧ください)



*** サンプルデータ作成 *************;
data DT1;
   A=1;
   output;
   output;
run;

 A 
  1 
  1  

*** DS2でSQL (FedSQLステートメント) を実行する ***;
proc ds2;
   data _NULL_;

      method run();
          dcl double rc;
          rc = sqlexec( 'update DT1 set A=2' );
          if rc^=0 then put '更新失敗しました';  
      end;
       
   enddata;
   run;
quit;

 A 
  2  
  2 


構文
  SQLEXEC( 'FedSQLステートメント' )



ポイントと注意点
  • SQLEXEC関数に記載するSQL文は「PROC SQL」ではなく「FedSQLステートメント」である点に注意(FedSQLの詳細についてはSAS社のリファレンスを参照下さい)
  • FedSQLステートメントはシングルクオーテーションで囲む
  • UPDATEやINSERTなど更新・追加・削除・作成をするFedSQLを指定します(SQLEXEC関数自体はFedSQLを実行するだけで、実行結果のテーブルを取得できるわけではないので、単純なSELECT文を指定しても意味ない)
  • 色々試してみた感じでは実行が成功すればリターンコードとして「0」が返される?(SAS社のリファレンスにこの辺の挙動の記載が見つけられなかったので、プログラムに組み込む場合は要確認)


今回の例では、データセットDT1の変数Aの値を更新するSQLを実行しています。

  rc = sqlexec( 'update DT1 set A=2' );

DS2プロシジャの中で、FedSQLの文を文字列として生成できるところがポイントですね。



自分自身の備忘録も兼ねて、いつかDS2プロシジャ入門の記事を書きたいなと思ってます。
需要ないだろって感じですが。


2015年4月22日水曜日

SQLプロシジャ入門15:NULLの取扱い




SASのSQLプロシジャと、他のデータベースで用いられるSQLとで大きく異なるのが、NULLの取り扱いです。

同じデータに対して、SASのSQLプロシジャで実行した結果と、MSAccessのSQLで実行した結果を比較してみます。


SASのSQLプロシジャの場合

*** サンプルデータ作成 *************;
data DT1;
input A @@;
cards;
1 2 . 3
;

  1 
  2  
  .  
  3

*** A < 2 のレコードを抽出 **********;
proc sql;
   select A
   from DT1
   where A < 2;
quit;

  1 
  .  


*** Aが1以外のレコードを抽出 **********;
proc sql;
   select A
   from DT1
   where A ^= 1;
quit;

  2  
  .  
  3


MSAccessのSQLの場合

*** サンプルデータ ******************;


*** A < 2 のレコードを抽出 ************;


結果


*** Aが1以外のレコードを抽出 ***********;


結果


比較してみると、SASでは結果にNULLのレコードが含まれているのに対し、MSAccessの結果にはNULLが含まれていません。


この違いの原因は、聞きかじり程度の知識なので、あまりあてにならないかもですが、

データベースでのNULLは「未知」とか「適用不能」といった意味合いがあるようです。
この考えからすると、今回の例のような「2より小さい」とか「1以外である」といった事を、NULLに対して語ることができないので、抽出結果からAがNULLのレコードが出てこなかったわけです。


一方、SASのSQLプロシジャでは、SAS内での互換性を保つために、上記のようなNULLの特別扱いはしていません (データステップ等での欠損値の取り扱いと一緒)

ただし、SAS/ACCESSの機能を使って、DBMS等の外部データにアクセスする場合は、NULLの扱いが異なるので注意!
例えば、LIBNAMEステートメントを使って外部データにアクセスする場合や、パススルー機能によって外部データに送信するクエリとかでは、NULLの取り扱いが異なります
(「外部データに対応するエンジン」や「記述方法」などの要素によってそれぞれNULLの挙動が異なる)

SASやDBMS等のNULLの取り扱いとも違う場合があって、私もよく分かりません。。というわけで、ご注意ください。




ちなみに、SASとは関係ない話になりますが、このNULLに関するお話が、SASYAMAさんに教えて頂いた「達人に学ぶSQL徹底指南書」というのに詳しく書かれていて、読んでて面白かったのでおススメしたいです。↓↓


以上、今回でSQLプロシジャ入門は終わり(の予定)です。

14.データセットを縦結合する【UNION】
15.NULLの扱いに関する注意点

2015年4月20日月曜日

変数名は「A001 - A100」や「A0000001 - A100」という指定も出来る。


変数名をいっきに指定する方法 [まとめ]の番外編的な内容になります。


さっそくですが、以下のプログラムを実行すると、どんな変数が作られるでしょうか?
data DT2;
   length  A001 - A100 8. ;
run;


正解は、「A001, A002, A003 ・・・ A100」といった変数が作られます。


では、次の例はどうでしょう?
data DT3;
   length  A0000001 - A100 8.;
run;


実はこれも「A001, A002, A003 ・・・ A100」という変数ができます。
「A0000001」とゼロを沢山指定しても、余分なゼロは取り除かれ、最後に指定した変数「A100」のケタ数に揃えてくれます。

役に立ちそうな一例として、以下のようなマクロも簡単につくれてしまいます。

%macro MAC( DS, NUMBER );

   data &DS;
      length A0000001 - A&NUMBER 8.;
   run;

%mend;

%MAC( DT4, 12 );
%MAC( DT5, 123 );


マクロ実行文「%MAC( DT4, 12 )」では、変数A01~A12が作成されます。
マクロ実行文「%MAC( DT5, 123 )」では、変数A001~A123が作成されます。

このように変数の数によって、臨機応変にゼロを挿入した変数名を作ることが出来ます。


ひとつ気をつけるところが、次のように書くと、
data DT1;
   length  A01 - A123 8.;
run;


出来る変数は「A01・・・ A99, A100 ・・・ A123」となります。

最初の指定変数「A01」が最後の指定変数「A123」よりもゼロの数が少ないような場合は、変数名のケタがそろわない状態でつくられてしまうので、ケタをそろえたいのであれば、最初の指定変数を「A0000001」とか長めに指定しといた方がいいかもです。

2015年4月15日水曜日

VARINITCHKオプションで、「変数○○は初期化されていません」をWARNINGやERRORにする。

前回の続き。

SAS9.4から、VARINITCHKというオプションが追加されたので、使ってみました。


以下のプログラムを実行すると、、
data DT1;
   length A 8.;
run;

ログ



変数○○は初期化されていません」というログメッセージが、上記のようにNOTEとして出力されます。

ここで、「VARINITCHK=WARN」を指定すると、、
options varinitchk=warn;

data DT1;
   length A 8.;
run;

ログ






初期化メッセージがWARNINGとして出るようになりました。

また「VARINITCHK=ERROR」と指定すると、
options varinitchk=error;

data DT1;
   length A 8.;
run;

ログ







ERRORになり、データステップが中止されるようになります。

設定を最初の状態に戻すには、「VARINITCHK=NOTE」を指定します。
options varinitchk=note;


また、初期化メッセージ自体をログから消すこともできますが、それはしない方がいいので紹介はしないでおきます。
(前回記事で解説した通り、初期化メッセージがプログラムのミスを見つけるのに役立つ事があるので)


会社や人によっては、初期化メッセージをWARNINGやERRORと同じくらいのメッセージとして扱いたいという場合があるので、そういう時にはこのオプションが使えそうです。



2015年4月14日火曜日

「変数○○は初期化されていません」のメッセージは侮れない。



以下をご覧ください。

data DT1;
   length A 8.;
run;

ログ


ログにNOTEとして 「変数Aは初期化されていません」 と出ます。

これは「変数が作成されたのになにも値が与えられていない」ときに出てくるログです。
上の例では「変数Aを定義して作成したのに、なにも値が与えられていない」ということですね。


このように初期化されていない変数は、変数の枠だけ作られて、中身は欠損値になります。
ERRORとかWARNINGじゃないし放っとくか、となるかもしれませんが、このメッセージが役に立つ場合もあります。



たとえば、以下のプログラムを実行して、ログを見てみると、、

data DT2;
   length AAABBB 8.;
   AAABB = 1;
run;

ログ
NOTE: 変数AAABBBは初期化されていません。


初期化されていません」と出てしまいました。
あれ?「length AAABBB 8.」で属性定義した後、ちゃんと「AAABB = 1」で値をセットしてるのに、、、



しかしプログラムをよく見ると、

length AAABBB 8.;
AAABB = 1;


Bの数を1個間違えて書いてたことに気づきます。
このように変数名のタイプミスをしてしまった時などに、この初期化メッセージが気づかせてくれる事があるので、侮れません。
ていうか、私自身、結構やってしまうミスなので、いつも助けられてます。



ちなみに「属性定義だけして、中身は欠損値でいいんだけど、、初期化メッセージが出るのはヤダ。。」といった場合は、欠損値を与える事の出来る CALL MISSING が便利です。

data DT3;
   length A 8. B C $10.;
   call missing( A, B, C );
run;




続く(VARINITCHKオプションで、「変数○○は初期化されていません」をWARNINGやERRORにする。


2015年4月10日金曜日

「FIRST.BY変数」と「LAST.BY変数」で、グループ毎の最初と最後のオブザベーションを特定する。




SASの基本的な機能でよく使われる、一時変数 「FIRST.BY変数」「LAST.BY変数」 を解説したいと思います。


まずは例をご覧ください。

*** サンプルデータ作成 ;
data DT1;
  input A$ B C;
cards;
001 1 1
001 1 2
001 2 1
001 2 2
002 1 1
002 1 2
002 2 1
002 2 2
;











*** 変数Aの値毎に、最初と最後のオブザベーションにフラグを立てる ;
proc sort data=DT1;
   by A B C;
run;

data DT2;
   set DT1;
   by A B C;

   length FLG1 FLG2 $1.;
   if  first.A = 1 then FLG1 = "Y";
   if  last.A = 1 then FLG2 = "Y";
run;



解説

データステップ内にBYステートメントが書いてあると、「FIRST.BY変数」と「LAST.BY変数」という一時変数が利用可能になります。

今回の例では、BYステートメントで

   by A B C;

と書くと、BY変数A, B, C に対応する以下の一時変数が利用可能になります。

「first.A」, 「last.A」
「first.B」, 「last.B」
「first.C」, 「last.C」

これらの一時変数について、以下にイメージを示します。


①「first.A」 「last.A」のイメージ














②「first.B」 「last.B」のイメージ














③「first.C」 「last.C」のイメージ





📝注意

  • 一時変数なので、データステップ内でしか存在しません(データセット化した時にはなくなってます)
  • あと、みなまで言うなって感じかもですが、BYステートメントで指定した変数の順に、あらかじめソートされている必要があります。
  • 今回の機能は「サブセット化IF」と一緒に使用すると正しく動かなくなりやすいです(解説記事:「サブセット化IFでありがちな落とし穴」)


2015年4月1日水曜日

SQLプロシジャのINTO句で、前方に余計な半角スペースが入る場合がある。


まずは以下をご覧ください。

*** Sample Data ;
data DT1;
   do A=1 to 3;
        output;
   end;
run;

  A   
   1  
   2  
   3

*** カウントした結果をマクロ変数に格納 ;
proc sql noprint;
   select count(*) into: MAC
   from DT1;
quit;

*** マクロ変数をログに展開 ;
%put "&MAC";


ログ
"       3"

ログを見ると前方に半角スペースが入ってます。

マニュアルを確認してみたら、
今回のような書き方で、値を1個だけマクロ変数にいれようとすると、前方または後方に半角スペースが残ってしまうようです。

この前方または後方の半角スペースを取り除きたい場合、SAS9.3以降であれば、trimmed を加えます。

proc sql noprint;
   select count(*) into: MAC trimmed
   from DT1;
quit;

%put "&MAC";


ログ
"3"


もしくは、以下のように %letステートメント を挟んであげればokです。

proc sql noprint;
   select count(*) into: MAC
   from DT1;
quit;

%let MAC=&MAC;
%put "&MAC";


ログ
"3"


See also