SASクエリウインドウの便利さについて

SASデータセットを開いて、ぱっと条件かけて抽出したい時に右クリック→[WHERE]
























WHERE式ビルダを立ち上げて確認のための一時的な抽出をしたりすることがあると思います。 特に<定数値の参照>でその変数のバリエーションを全てリストしてくれたりして
とても便利ですが、以下の点で個人的に不満でした。

・抽出結果について変数選択ができないため全変数がでてきて、横に長いデータセットを見る場合はスクロールが面倒。行をハイライトしていても、ちょっとずれて、どのSUBJIDかわからなくなることがある
・抽出結果をそのままデータセットにできない。WHERE式で確認した後、データセットとして欲しくなっても結局コード書いて作らなければいけない
・一つのデータセットに対してしか見れない。複数のデータセットを繋いで抽出したい場合にコードを書いて予めマージしたデータセットを作らなければならない。

面倒がらずにコード書いてちゃんと導出しなさいという話で終わりですが、便利機能としてSASクエリウインドウを紹介します。正式名はSQLクエリウインドウだったんですね、最近知りました。 



SASの「ツール」→「クエリ」をクリックするか








コマンドウインドウという部分に「query」と打ってEnterします。
 クエリウインドウが立ち上がるので













抽出元のデータセットを好きなだけ選択します。テーブルソースというところは
参照するライブラリを選択する箇所になります。

OKを押すと、次は出力変数の選択画面になります
















ここで、もし複数データセットを選択していた場合、
先に右クリック→「テーブルの結合」で繋ぎ方と繋ぐ変数を設定しておかないと
勝手に直積結合しやがるので注意してください。

















あとは、出力する変数を選択して、抽出条件を追加したいなら右クリックメニューから
whereやhavingを設定したり、変数選択画面で、変数名をリネームしたりラベルつけたり、
またSQLでできることはほぼ何でも設定できるので、とりあえず色々いじって試してみてください。

















設定が完了後、右クリック→「クエリの実行」をすると結果がアウトプットウインドウに出力されます。









「クエリの表示」をクリックすると、設定した内容がSQL文に変換されて表示されます。










しかも、その画面の右には













こういうボタンがついていて、例えばテーブルの作成を選ぶと
現在の抽出結果でデータセットをこさえてくれます。

どうですか?ちょっとSQLかじってないとわかりにくいかもしれませんが、
凄くないですか?

まあ、GUIで楽にできるっていっても、結構手数かかってるやん、SAS書きならコードを早く書けるように練習しとけって感じもしますが、けど実際
使いなれている人がこれで、どんどんデータを確認しているところを横で見ていたことが
あるんですが、ボタンの配置を多分全部覚えていて、凄まじいスピードで、抽出を繰り返して、必要なデータをデータセット化してて、これはどれだけタイピング早くても、コードじゃおっつかないなと思ったことあります。


ちなみに日本語版SASオンラインヘルプに

SAS 9.3 SQLクエリウィンドウ ユーザーガイドが公開されていて、この機能だけについて100ページぐらい詳細な使い方が書いてあるので、極めんとするかたは読んで、面白いことがあれば教えてください。僕も、読もうと思ったのですが、途中で面倒になってやめてしまいました。








SASのWORKの実際の場所に、異常終了した時の残り滓ファイルがたまるので大掃除する話

WORKライブラリに割り当てられた場所が実際PC上で、どこに存在するかについて、普段あまり意識しません。

ライブラリのパスを返すpathname関数や

data _NULL_;
X=pathname("WORK");
put X;
run;

optionss プロシジャを使って、WORKの実際の場所を割り出すことができます

proc options option=work;
run; 

環境によるかもですが、「SAS Temporary Files」というフォルダの下に
「_TD○○」といったフォルダがあり、そこがWORKに割り当てられていると思います
この「_TD○○」はSASを閉じると、フォルダごと消滅します。
またSASを立ち上げると、新たに「_TD○○」(○の部分は変わる)が作成されそこが
現セッションのWORKの実存パスになります。

普通にSASを立ち上げて、閉じていれば「SAS Temporary Files」の下には何もないのですが
異常終了するなどしてWORKの除去機能が正常に作動しなかった場合、「_TD○○」という過去の
一時フォルダが残ってしまうことがあります。

こいつらは只のゴミなのでばっさり消してしまって容量をあけた方がよいです。

以下はSASのFAQにあった同様の内容です。
http://www.sas.com/offices/asiapacific/japan/service/technical/faq/list/body/pc055.html



で、関連する荒業として、例えばグラフ等の画像ファイルをgifやpngなどで作成して、それをEXCELやWORDにDDEで貼りつけるプログラムを書く場合、いったん画像ファイルを作成する必要があるのですが、貼り付けが終わった後に画像はまとめて全部消したいと言った場合に、テンポラリーな画像の置き場としてWORKに割り当てられたフォルダをファイルの保存場所に使うという技です。
正常に終了すれば、いちいち画像を消すプログラムを書かなくても、WORKもろともフォルダごと葬られるからです。
「_TD○○」の中のファイルはSASが作ったものでなくても、ロックしてない限りいっしょくたに消されます。
まあSASの機能を逆手にとった外法に近いので、お勧めはしません。




proc printはODS OUTPUTステートメントをサポートしていない話

今回は、思いつきが失敗した話になります。

以前、「Create tableだけがSQLプロシジャでデータセットを作る方法ではないという話」
http://sas-tumesas.blogspot.jp/2013/10/create-tablesql.html

でSQLプロシジャの出力をODS OUTPUTステートメントで捉えてデータセット化するという内容を紹介しました。
それで、最近ふと思ったのが、それってproc printでもできるのか?ということでした。

例えば以下のようなデータセットがあったとします。

data Q1;
do X=1 to 4;
 do Y=1 to 3;
  output;
  end;
 end;
run;

















そこに

proc print data=Q1 noobs;
 var Y;
 id X ;
 by X ;
run;

を実行すると
idステートメントとbyステートメントの同時指定の効果により


















のようなアウトプットがでます。
この形をデータセットで欲しいなと思って

ods output Print=OUT;
proc print data=Q1 noobs;
 var Y;
 id X ;
 by X ;
run;

として、実行しなおしてみると




ERROR: PROC PRINT はODS OUTPUT」ステートメントをサポートしていません
と見事に怒られてしましました。
悪くないアイデアだと思ったのですが、、。

ちなみに全然関係なく、id by sumbyの併用で、いかのような出力が得られ、たまに検算する時に
使います。

proc print data=Q1 noobs;
 var Y;
 id X;
 by X ;
 sumby X;
run;





























ARRAY配列の記述法

ARRAYの話題をもう一つ連投します。
といっても、こっちは他愛のない話です。

私は最近までしらなかったのですが、以下のコードの結果はどうなるでしょうか?

data A1;
array ANUM {5} (5*3);
run;





となるんですね、へー。5回、3を入れているってことですか。


同じ理屈で

data A2;
array ANUM {5} (2*3 3*4);
run;







です。

基礎知識なんでしょうか??
知らないと、コード見たとき、??ってなります。


ARRAY配列の要素番号は1から始めなくてもよく、任意の値を設定して意味を持たせられる。loopについてはdim関数の代わりにlbound,hbound関数を使える

ARRAYステートメントによる1次元配列の定義を行った場合、一つ目に配列に含まれる変数が要素番号1をもち、次は2、次は3と連番になっていきます。そこで
do i=1 to dim(配列名);処理;end;といった書き方をして、要素数分処理をループさせたりして利用することが多いと思います。

さて、話は変わって、今以下のようなデータセットがあったとします。

data Q1;
_95=1;_96=5;_97=8;_98=12;output;
run;




適当なデータですが、これは1995年から98年までのある商品の売り上げだとします。
_95は1995年の売上といったように、数字が意味を持っています。

そういった場合で、配列を定義する際、処理の内容によっては要素番号を1からの連番にして
意味を失わせるよりも、任意の数字を定義して、リンクさせた方が処理がわかりやすい場合もあります。

先にコードから

data A1;
set Q1;
 array SALES{95:98} _:;

 X=SALES(97);

run;





ここでSALESという配列を定義していますが{95:98}とすることで、この配列は
要素番号95から98までを持つ配列となります。

たとえば X=SALES(97);とするとxには_97の値、すなわち1997年の売上を取得できます。
X=SALES(1)などと書くと、当然エラーになります。


なるほど、要素番号に任意の数字を設定できるのはわかったとして、じゃあi=1 to dim(配列名)みたいな処理が書けなくなってるんじゃないかと思いますが、

そこはlbound関数やhbound関数を使用できます。こいつらは配列名を引数にとり、その配列の要素番号の最小値または最大値を返します。

つまり

data A2;
 set Q1;
 array SALES{95:98} _:;
 do YEAR=lbound(SALES) to hbound(SALES);
   X=SALES(YEAR);
  output;
 end;
run;

とすると95から98でループがかかります。







面白いです。





SQLでマクロ変数に値を格納する into: separated by

SQLでマクロ変数に値を格納する方法についてご質問いただきましたので、あまり詳しくなくて自信がないですが紹介します。

SELECT INTO:を利用します。

proc sql noprint;
select count(*) into:obs
from DS;
quit;

などでデータセットのオブザベーション数をobsというマクロ変数に格納できます。

他に例えば

data Q1;
X='い';Y='A';Z=1;output;
X='ろ';Y='B';Z=2;output;
X='は';Y='C';Z=3;output;
run;







のようなデータセットがあった場合


proc sql noprint;
 select X,Y,Z
 into  :MX1-:MX3
      ,:MY1-:MY3
      ,:MZ1-:MZ3
 from Q1
 ;
quit;

のようにかけば、マクロ変数MX1にXの一つ目の値、MX2に二つ目の値、以下、同Y同Zに同じように格納できます。

%put &MX1
     &MX2
     &MX3
     &MY1
     &MY2
     &MY3
     &MZ1
     &MZ2
     &MZ3
;

するとログにでるのは




です。


データセットのオブザベーション数が可変だけども、どれくらいになるかある程度わかっていれば

proc sql noprint;
 select X,Y,Z
 into  :MX1-:MX999
      ,:MY1-:MY999
      ,:MZ1-:MZ999
 from Q1
 ;
quit;

と大雑把な方法ですが、大きくとっておけば、オブザベーションがない場合も
よけいなマクロ変数は作成されないので、使えます。


あと、以下のように書くと

proc sql noprint;
 select X,Y,Z
 into  :MXALL separated by '、'
      ,:MYALL separated by '、'
      ,:MZALL separated by '、'
 from Q1
 ;
quit;


%put &MXALL
     &MYALL
     &MZALL
;

ログは





のように、データをseparated by で指定した区切り文字で連結したものを一つのマクロ変数に
格納できるので、マクロのパラメータや関数の引数に渡したい値を作る時などに重宝します。

他にselect into:について技をお持ちの方がいらっしゃれば、むしろ勉強させていただきたいです。
call symputはデータステップの中でやるので、可変的にマクロ変数を作るうえで凄いやりやすい
のですが、SQLでもできないでしょうか、、。

多分、色々できると思うのですが、、いっつも、とりあえずわかる方法でやってしまって、なかなか新手開拓ができてないです。









SASのテーブルビュー(view)について

ビュー(射影)は、データセットから作成される仮想的なデータセットです。
実際にデータセットとして実在しておらず、見方を定義したものだと考えてください
MS-ACCESSとかでいうと、クエリです。

ビューを開くと、定義されたコードが実行され、実行結果が普段のデータセットと同じようにみれます。複雑な抽出を経なければ作れないデータセットを、毎回、参照したいと思うたびに作成するのは時間とディスク容量の大きな無駄です。

一度組んだら後はダブルクリックで開くだけで常に最新のデータで反映された結果を見たいです。
そんな夢の技術がviewです。

ビューのメリットについてSAS Advanceの公式テキストでは以下のように挙げています。

・often save space(a view usually quite small compared with the data that it access)
・prevent users from continually submitting quaries to omit unwanted columns or rows
・ensure that input data sets are always current,because data is derived from tables at execution time
・shield sensitive or confidential columns from users while enabling the same users to view other columns  in the same table
・hide complex joins or queries from users

ですって。



今、以下の2つのデータセットがあったとします。
data Q1;
X=1;Y=1;Z=2;output;
X=2;Y=1;Z=2;output;
X=3;Y=1;Z=2;output;
X=4;Y=1;Z=2;output;
run;








data Q2;
X=3;A=5;
run;





Q1にQ2をXをキーにして結合したデータをみるためのVIEWをつくります


【SQLで作る方法】
proc sql noprint;
 create view V1 as
  select Q1.*,Q2.A
  from Q1 left outer join Q2
   on Q1.X=Q2.X;
quit;

です。単にcreate tableのtableをviewにかえるだけです。
すると、いかのようなのができて













こいつをクリックすると







です。

ちなみにふつうはWORKのデータセットを参照するようなビューは作りませんし、
ビュー自体もWORKにつくったりしません。
再利用するものなので普通は永久ライブラリを使います。

ちなみに作ったビューは

data A1;
 set V1;
run;



proc means data=V1;
run;

のように通常のデータセットと同じように扱えます。
(ただし、それ自体を普通のデータステップと同じように変更したりはできないなど
制限はあります。)

【データステップで作る方法】
data V2/view=V2;
 merge Q1(in=ina)
       Q2;
    by X;
    if ina;
run;

簡単ですね

SYMGET関数でマクロ変数の値を取得する

マクロがあまり得意ではないので、もし誤っている点があればご指摘ください。



%let MA=11;
%let MB=22;
%let MC=33;
%let X=44;

のようにマクロ変数に値が代入されているとします。

そして以下のようなデータセットがあったとします。

data Q1;
X='MA';output;
X='MB';output;
X='MC';output;
run;







そこでXの値に対応するマクロ変数の値を展開したい場合、次のように書けます。

data A1;
 set Q1;
  Y=symget(X);
run;








あるいは

data A1_;
 set Q1;
  Y=resolve('&'||X);
run;

でも可です。resolve関数はより広義的機能をもってたりするので興味のある方は
リファレンスやヘルプをひいてください。

でsymgetの戻りはlength$200がデフォで、つまり文字型ですが

data A2;
 set Q1;
  Y=symgetn(X);
run;

とsymgetn関数にすれば数値で得られます。


また

data A3;
 set Q1;
  Y=symget('X');
run;

とすると







単純にマクロ変数Xを展開するので、上記のようになります。


もう一点ポイントなのですが、

以下のプログラムをみてください

data A4;
 set Q1 end=eof;
 call symputx(cats('MV_',_N_),X);
 if eof then Y=cats("&MV_1","&MV_2","&MV_3");
run;









このプログラムでは、最後のY=のところでマクロ変数を展開することができません。
call symputはデータステップ中にマクロ変数に値を代入できますが、
それはそのデータステップが終了した時点で確定し、普通の方法では同データステップ内で
参照することはできません。

%symdel MV_1 MV_2 MV_3 MA MB MC X;
(※順に実行して確かめている方のために一度マクロ変数を消しています)

ところが

data A5;
 set Q1 end=eof;
 call symputx(cats('MV_',_N_),X);
 if eof then Y=cats(symget("MV_1"),symget("MV_2"),symget("MV_3"));
run;







のように書けば、symputで作成されたマクロ変数を同ステップで参照できます。



do until(end変数)set end=を利用して、複数のデータステップを1つにまとめて共存させる方法

例えばあるデータステップを実行して、その最終結果として得られる値を、
次のデータステップで使用したい場合、常識的に考えれば当然2ステップが必要になります。

しかしdo untilループとファイル終端の際に1がたつend変数を利用することで、
複数のステップを繋いだ、かなり柔軟なデータステップを書くことができます。

今回は例として、単純なものを紹介します。

たとえば以下のように2つのデータセットがあったとします。

data Q1;
do X=1 to 5;
 output;
end;
run;








data Q2;
do Y=1 to 10;
 output;
end;
run;













そこでQ2の全オブザベーションを足して求められる値(1から10までの足し算なので55です)を
Q1の各オブザベーションにそれぞれ足したいとします。

その場合、普通はまずQ2の合計を、何らかのプロシージャやあるいはデータステップで導出し
Q1の全obsにマージしたり、或いはマクロ変数を介して足し算したりすることが浮かぶと思います。
(SQLならサブクエリで1発ですが今回はおいといてください)

ところが、なんと以下のように1ステップで書くことができます。

data A1;

 do until(eofQ2);
  set Q2 end=eofQ2;
   Y_TOTAL+Y;
 end;

 do until(eofQ1);
 set Q1 end=eofQ1;
   TOTAL=X+Y_TOTAL;
   output;
 end;

run;

で結果は








となります。
do until(eofQ2)はdo until(eofQ2=1)の略で、set Q2の際にend=でeofQ2と変数をあてているので
Q2が全て読み込まれて処理されるまで、このループが続き、抜ける頃にはY_TOTALを含む最終obsのみが
残っています。

そこからQ1のループに入りますが、今回Q1をベースとして全obs残したいのでこちらにoutputをいれています。


このように書くことで、複数のデータステップを1つにまとめることができ、
前のステップで生成された値を、リレーのバトンタッチのように使用できます。

海外のコミュニティのやりとりなどで、何回も見たことがあるので、向こうでは定跡化されている
方法なのかもしれません。

あ、ちなみに多分今回の投稿で100回目です。
あと1万倍投稿すればデータステップ100万回になるので、がんばります。

もう多分10回目の投稿ぐらいから慢性的にネタ切れな感じなので、何か意見、要望、突っ込み、アイデア、質問、なんでもください、まっています。









指定した変数を除いて、それ以外の全ての変数を関数の対象にするマクロ


少し早いですが、PC内のファイルの年末大掃除をしていた際に
まだSASを覚えてそんなにたっていない頃に作ったマクロがでてきて、

僕自身、存在すら完全に忘れてたのですが
これが結構面白い発想のものだったので、自画自賛ながら紹介します。

例えば以下のように変数の多い、横長なデータセットがあった場合を考えます

data Q1;
A=1;B=2;C=5;D='あ';E=8;F=4;G='い';H='A';I=9;output;
A=4;B=3;C=2;D='い';E=6;F=2;G='ろ';H='A';I=5;output;
run;






AとBの合計を出してとかだったら sum(A,B)で余裕ですし、
AとB以外の合計を出してと言われたらsum(of C-numeric-I);でいけますよね。
(※変数名--変数名は変数の格納位置を指定する方法です。
間にnumericやcharacterを挟むことで、数値型か文字型で絞れます)

でも、きっついのが、変数の格納順が定まってなかったり、横に変数が増えていくような
ことが想定される、つまりケツにくる変数名が不明、特定できない場合に
sum(of C-numeric-I);の方法は使えません。

さらに痛いのがEとI以外の合計を出してみたいなケースで、
sum(A,B,C,F);みたいに全部列記して書くか、、それでも変数が可変的に追加される状況では
決め打ち対応できません。

そういった時に昔の僕が捻くり出したのが以下のマクロです。

%macro noarray(INDS,OUTDS,EXLIST,FUNC);
 data &OUTDS;
 informat &EXLIST DUMMYST;
 set &INDS;
  DUMMYST=.;
  DUMMYEND=.;
  RESULT=&FUNC(of DUMMYST-numeric-DUMMYEND);
  drop DUMMYST DUMMYEND;
 run;
%mend noarray;

INDSに流し込むデータセット名
OUTDSに作成するデータセット名
EXLISTに関数の対象から除外する変数リスト
FUNCにはSUMやMEANやMAXなど数値対象の関数を指定します。

=======================
以下、実行例
=======================
%noarray(Q1,A1,A B,sum)
これはAとB以外をsumしています 結果は




%noarray(Q1,A2,E I,sum)
これはEとI以外をsumしています 結果は




%noarray(Q1,A3,C I D,mean) 
これはCとIとD以外をmeanしています 結果は





なかなか面白いのが、関数の指定に変数順を使用して、
対象除外の変数を一番先頭にして、その後ダミーのスタート地点と
ダミーのゴールとなる変数を定義しているところですね。

numericのところを消せば、文字数値共通で引数にとれる関数に使えますし
characterにすれば文字型変数版マクロに早変わりですね。
てか、ここ自体、マクロパラメータにしちゃえばいいですね。

で、思い出深いのが、noarrayというマクロ名です。
array、配列がどうしてもわからなくて、使うのが凄い苦手だったんです。
で、マクロも苦手だったので、変数名をマクロにいれるとか、配列をつかうとかいう発想が
でなかったんです。それでこういう名前にしたんですね。





データステップで0,1のフラグ変数を簡単に作成する

問題です。次のプログラムを実行すると、どんなデータセットができるでしょうか?

data A;
 X=5=5;
run;

最近、こういう系の話ばっかりしていて、飽きているかもしれませんが。

エラーになってデータセットができない or X=5でデータセットができると考えた方は不正解です。

正解は






X=1でデータセットができるでした。

これは実は
X=(条件式);の形で、条件式の中が真である場合は、1、偽である場合は0を返します。
つまり先程のコードはX=(5=5);ということで、5と5は等しいので真でありX=1です。

要するに、これを利用することで簡単に0,1のフラグ変数を作れて、条件で集計する場合などに
役立ちますという話です。

今、以下のようなデータセットがあったとします。

data Q1;
 X=2;Y=1;output;
 X=3;Y=2;output;
 X=5;Y=2;output;
 X=4;Y=8;output;
run;








ここで、X>Y and X^=2の条件で0,1変数を作りたいなと思ったら

data A1;
 set Q1;
  FLAG=X>Y and X^=2;
run;








で、詰みなんです。簡単です。

ちなみに恥ずかしい話なんですが、僕はずっと上記のコードで済むところを
以下のように

data A2;
 set Q1;
  FLAG=ifn(X>Y and X^=2,1,0);
run;

ifn関数を使っていました。
条件によって戻り値を加工したい場合はifn(戻り値が文字型ならifc)ですが
0,1の戻り値なら普通に抽出式書くだけなんですね。


ちなみにSQLプロシジャにおいても、ほぼ同じ話があって、そちらの詳細は
ブログ「SAS忘備録」の「SQLプロシジャでフラグ変数を簡単に作成。 」という記事に書かれていて目から鱗ものですよ。

http://sas-boubi.blogspot.jp/2013/12/sql.html

ちなみSQLプロシジャでIFN IFC関数を利用して、0,1のフラグ変数ではなく、
条件式によって戻り値を変えるという方法については、本ブログの以下の記事で僕も書いています。
SQLのCASE式がどうしても苦手な場合、IFC、IFN関数で代用してみる
http://sas-tumesas.blogspot.jp/2013/11/sqlcaseifcifn.html



same演算子でwhere式の置き換えではなくwhereの追加を行う

前の投稿でwhere特有のcontainsやbetween等を紹介しましたが、今回はsameです。
一緒にするには少し毛色が違ったのでわけました。

さて今、以下のようなデータセットがあったとします。

data Q1;
do X=1 to 5;
 output;
end;
run;









そこに

data A1;
 set Q1;
 where X>2;
 where X<4;
run;

を実行すると、何がおきると思いますか?
whereステートメントが連続で、でてきます。

え、エラーじゃないの?と思われた方は不正解で、正しく実行されます。

結果は







となり、ログをみると













WHERE式を置き換えました」というNOTEがでています。
つまり、一つ目のWHEREは二つ目に上書きされて、無効化されてしまったのです。
最後にくるWHEREだけが効きます。

もし、今やりたいことが「X>2」「 X<4」の2条件で抽出をかけることであれば

当然

data A1;
 set Q1;
 where X>2 and X<4;
run;

でいいわけです。

が、しかし、次のようにも書くことができます。
それが

data A2;
 set Q1;
 where X>2;
 where same X<4;
run;


















ですsameをつなぐことによって
whereステートメント同士をandでつなぐのと同じ効果があります。
WHERE式を追加しました」というNOTEがでます。

で、どういう時に使うかというと

抽出式があまりに長くなって見通しが悪い時や、
元コードをできるだけ修正せずに、新しい条件を追加したい場合、
マクロ化して、抽出条件をオプションで追加できるように組む場合等でしょうか?

条件を色々追加しながら対話的に、データを段階的に絞りこんで探索する場合なんかも
いちいち and ○○とかいていくより、区切りがつくのでお勧めです。
















whereステートメントでのみ使用できる演算子_contains,between,like

データの抽出について、サブセット化IFステートメントでは使用できず、whereステートメントでのみ利用できる演算子がいくつかあります。それらの紹介です。

今、以下のようなデータセットがあったとします。

data Q1;
 X='ABCDE';output;
 X='FGHIJ';output;
 X='KLNMO';output;
 X='PQRST';output;
 X='UVWXY';output;
run;









①contains演算子

data A1;
 set Q1;
 where X contains 'K';
run;





これは○○を含むデータを抽出します。
上記の場合、Kという文字が、どこかに含まれて入れば抽出されます。
つまり

data A1_;
 set Q1;
 if index(X,'K')>0;
run;

と同じ意味になります。


②between演算子

data A2;
 set Q1;
 where X between 'K' and 'Z' ;
run;






通常、数値に対して使用することが多いですが、文字変数に対しても適用できます。
between ○ and ×; で○と×を含んでその間に存在する値を抽出します。
今回は辞書を想像していただいて、KとZの間にある文字列が抽出されます。


③like演算子

data A3;
 set Q1;
 where X like '%I%' ;
run;





data A4;
 set Q1;
 where X like '_B_D%' ;
run;





likeの後に文字パターンを指定し、それに合致するものを抽出します。

%は0から無制限の長さの文字列です。like '%I%' contains 'I'と同義です。
 '_' ;は任意の1文字を表します。
一文字何かの後、Bがきてその後またなんでもいいので一文字きて、Dがきて、その後は何もなくてもいいし、どんな文字がどれだけきてもいいですから、そのパターンに一致するのを抽出してというのがwhere X like '_B_D%' ;ですね。