---
title: FCMPにおけるDictionary/Hash Object．SQLとハッシュを繋ぐもの
tags:  #[第10回大阪sas勉強会]  
author: [森岡裕[SASユーザー総会世話人]](https://www.docswell.com/user/6484025)
site: [Docswell](https://www.docswell.com/)
thumbnail: https://bcdn.docswell.com/page/5EG3268Y7L.jpg?width=480
description: [第10回大阪sas勉強会]
published: October 21, 24
canonical: https://www.docswell.com/s/6484025/KYDEDN-2024-10-21-152222
---
# Page. 1

![Page Image](https://bcdn.docswell.com/page/5EG3268Y7L.jpg)

FCMPにおけるDictionary/Hash Object．
SQLとハッシュを繋ぐもの
イーピーエス株式会社
森岡 裕


# Page. 2

![Page Image](https://bcdn.docswell.com/page/4JQ5P4967P.jpg)

本発表は中級者向け以上です．
以下の前提知識ついては本発表では説明しません
初級者は一度に理解しようと聴くのではなく，イメージをつかむ感じで
【前提知識】
FCMPプロシジャ（ユーザー定義関数を作成するプロシジャ）
ハッシュオブジェクト
SQL


# Page. 3

![Page Image](https://bcdn.docswell.com/page/K749YQXME1.jpg)

%macro get(master=,key=,var=);
%let name = &amp;sysindex;
%let qkey = %sysfunc( tranwrd( %str(&quot;&amp;key&quot;) , %str( ) , %str(&quot;,&quot;) ) );
keyが一意になっていることを前提として、他の
データセットから値を取得するマクロ %get
if 0 then set &amp;master(keep= &amp;key &amp;var);
if _N_=1 then do;
declare hash h&amp;name.(dataset:&quot;&amp;master(keep= &amp;key &amp;var)&quot;, duplicate:&#039;E&#039;); keyが一意になってなくても動き、keyが存在す
h&amp;name..definekey(&amp;qkey);
るかをYNで返すマクロ %chk
h&amp;name..definedata(all:&#039;Y&#039;);
h&amp;name..definedone();
この2つのマクロがあれば、例えばADSLなどは
end;
結合と存在確認の2機能
ほぼ1ステップで作成可能。
if h&amp;name..find() ne 0 then do;
call missing(of &amp;var);
end;
%mend get;
%macro chk(master=,key=,flg=);
%let name = &amp;sysindex;
%let qkey = %sysfunc( tranwrd( %str(“&amp;key”) , %str( ) , %str(“,”) ) );
％Getマクロ
if 0 then set &amp;master(keep= &amp;key);
if _N_=1 then do;
declare hash h&amp;name.(dataset:”&amp;master(keep= &amp;key)”, multidata:’Y’);
h&amp;name..definekey(&amp;qkey);
h&amp;name..definedone();
％Chkマクロ
end;
&amp;flg = ifc(h&amp;name..check()=0,”Y”,”N”);
%mend chk;


# Page. 4

![Page Image](https://bcdn.docswell.com/page/LJ1K6PNY7G.jpg)

data adsl(label=&quot;&quot;);
set sdtm.dm;
findメソッドと
Checkメソッドをマクロ化してしまえばSDTM/ADaM
のデータハンドリングは，（乱暴に言って）ほぼそ
れで勝負がつく
VISIT=“Day 1”;
%get(master=EX,key=USUBJID VISIT,var=EXDAT)
TRTSDT=input(EXDAT,?? Yymmdd10.);
CMCAT=&quot;CONCOMITANT MEDICATION&quot;;
%chk(master=cm,key=usubjid cmcat,fl=CMFL);
・・・・・・・・・・・
run;


# Page. 5

![Page Image](https://bcdn.docswell.com/page/GJWDW24172.jpg)

ハッシュオブジェクトを綺麗にマクロ化すると
ハンドリング周りのだいたいの問題はクリアされるが全てがそれで解決するわけではない．
たとえば…
・ 通常のハッシュオブジェクトはデータステップ内の記述のため
SQLプロシジャの中で利用できないことの不便さ
・ whereステートメントメ内で作動させれない．
例えば1億オブザベーションのデータセットAについて，
数百obsのデータセットBにIDがある症例だけをwhereで絞りたいといった状況
・ マクロを減らしたい．あるいは複雑なネスト構造のマクロ内での使用時に，
見通しをよくするためにマクロに依らずにハッシュオブジェクトを
使いたいといった状況


# Page. 6

![Page Image](https://bcdn.docswell.com/page/4EZG5D2XJ3.jpg)

FCMPプロシジャでユーザー定義関数を作成できるが
その中で， ハッシュオブジェクトとディクショナリオブジェクトが利用できる
今日はその話をしてみたいです


# Page. 7

![Page Image](https://bcdn.docswell.com/page/Y76Y91VPEV.jpg)

data dm;
USUBJID=&quot;A-001&quot;;SUBJID=&quot;001&quot;;RFSTDTC=&quot;2024-09-01&quot;;RFENDTC=&quot;2024-10-01&quot;;output;
USUBJID=&quot;A-002&quot;;SUBJID=&quot;002&quot;;RFSTDTC=&quot;2024-09-02&quot;;RFENDTC=&quot;2024-10-02&quot;;output;
USUBJID=&quot;A-003&quot;;SUBJID=&quot;003&quot;;RFSTDTC=&quot;2024-09-03&quot;;RFENDTC=&quot;2024-10-03&quot;;output;
run;
data raw_ae;
SUBJID=&quot;001&quot;;AETERM=&quot;Vomit&quot;;output;
SUBJID=&quot;003&quot;;AETERM=&quot;Vomit&quot;;output;
SUBJID=&quot;005&quot;;AETERM=&quot;Vomit&quot;;output;
run;
DMドメインからSUBJIDをキーにして
USUBJIDを取得するような処理を考
えてみる


# Page. 8

![Page Image](https://bcdn.docswell.com/page/G75GN4DP74.jpg)

素直に平文で書くなら
data samp1;
set raw_ae;
if 0 then set dm(keep=SUBJID USUBJID);
if _N_ =1 then do;
declare hash h1(dataset:&quot;dm&quot;);
h1.definekey(&quot;SUBJID&quot;);
h1.definedata(&quot;USUBJID&quot;);
h1.definedone();
end;
if h1.find() ne 0 then call missing(of
USUBJID);
run;
先に紹介したマクロを使えば1行
data samp2;
set raw_ae;
%get(master=dm, key=SUBJID, var=USUBJID);
run;


# Page. 9

![Page Image](https://bcdn.docswell.com/page/9J2Y5V8ZJR.jpg)

これを関数で考える


# Page. 10

![Page Image](https://bcdn.docswell.com/page/DEYGK36NEM.jpg)

proc fcmp outlib=work.functions.common;
/*SUBJIDをいれるとDMドメインからUSUBJIDを返す関数*/
function usubjid(SUBJID $) $;
declare hash h1(dataset: &quot;DM&quot;);
rc=h1.definekey(&quot;SUBJID&quot;);
rc=h1.definedata(&quot;USUBJID&quot;);
rc=h1.definedone();
if h1.find() eq 0 then return(USUBJID);
else return(&quot;&quot;);
endsub;
quit;
え… 簡単すぎ．．．
/*使ってみる */
data wk1;
length SUBJID AETERM USUBJID $200.;
set raw_ae;
USUBJID=usubjid(SUBJID);
run;


# Page. 11

![Page Image](https://bcdn.docswell.com/page/VJNGRVZV78.jpg)

え，えすきゅーえるの中で ハッシュオブジェクトがうごいとるーーーーー
proc sql noprint;
create table wk2 as
select SUBJID,
usubjid(SUBJID) as USUBJID
,AETERM
from raw_ae;
quit;


# Page. 12

![Page Image](https://bcdn.docswell.com/page/YE9WM6ZVE3.jpg)

data wk3;
set raw_ae;
where usubjid(SUBJID) =&quot;A-001&quot;;
run;
データセットに存在しない，USUBJIDで絞り込みを行うという
whereステートメントにおいて，びっくりなことが起きている


# Page. 13

![Page Image](https://bcdn.docswell.com/page/GE8NLZ147D.jpg)

AEドメインの中にSUBJIDがあるかどうかでYNのフラグを返す処理を考えてみる
素直に平文で書くなら
先に紹介したマクロを使うなら
data samp3;
set dm;
data samp4;
if _N_=1 then do;
set dm;
declare hash
%chk(master=raw_ae,key=SUBJID,flg=AEFL)
h1(dataset:&quot;raw_ae&quot;,multidata:&quot;Y&quot;);
run;
h1.definekey(&quot;SUBJID&quot;);
h1.definedone();
end;
AEFL = ifc( h1.check() = 0 , &quot;Y&quot;, &quot;N&quot;);
run;


# Page. 14

![Page Image](https://bcdn.docswell.com/page/LELPLDQV7R.jpg)

proc fcmp outlib=work.functions.common;
/*SUBJIDをいれるとAEドメインにあるかどうかでYNが返る関数*/
function ae_chk(SUBJID $) $;
declare hash h1(dataset: &quot;raw_ae&quot;);
rc=h1.definekey(&quot;SUBJID&quot;);
rc=h1.definedone();
if h1.check() eq 0 then return(&quot;Y&quot;);
else return(&quot;N&quot;);
endsub;
quit;
これも当然，SQLでもwhere句でも動く
data wk5;
set dm;
AEFL= ae_chk(SUBJID) ;
run;


# Page. 15

![Page Image](https://bcdn.docswell.com/page/4JM4MP1N7W.jpg)

data wk6;
set dm;
where ae_chk(SUBJID) =&quot;Y&quot;;
run;
通常，データステップのwhereステートメントでは
SQLにおける in サブクエリや
Exists句をつかったような処理が書けないというのが
文法だったが
proc sql;
create table wk8 as
select *
from dm
where exists
(select 1 from raw_ae where
dm.SUBJID=raw_ae.SUBJID);
quit;
proc sql;
create table wk7 as
select *
from dm
FCMPのハッシュオブジェクトを使うことで可能となること
where SUBJID in
が
(select SUBJID from raw_ae);
わかった
quit;


# Page. 16

![Page Image](https://bcdn.docswell.com/page/PJRWV1L479.jpg)

どんな技術にも必ず弱点がある
ハッシュオブジェクトの関数化も


# Page. 17

![Page Image](https://bcdn.docswell.com/page/PEXPZYWZ7X.jpg)

関数定義時にハッシュオブジェクトに格納のデータセットが固定
引数にできない
proc fcmp outlib=work.functions.common;
/*SUBJIDをいれるとDMドメインからUSUBJIDを返す関数*/
function usubjid(SUBJID $) $;
declare hash h1(dataset: &quot;DM&quot;);
rc=h1.definekey(&quot;SUBJID&quot;);
rc=h1.definedata(&quot;USUBJID&quot;);
rc=h1.definedone();
if h1.find() eq 0 then return(USUBJID);
else return(&quot;&quot;);
endsub;
quit;
関数の場合は一つの戻り値しかとれない．
コールルーチンにするとSQLの中で動かない．


# Page. 18

![Page Image](https://bcdn.docswell.com/page/3EKD83VVJD.jpg)

サブルーチンもできる


# Page. 19

![Page Image](https://bcdn.docswell.com/page/L73R2LQQJ5.jpg)

proc fcmp outlib=work.functions.common;
/*USUBJIDをキーにRFSTDTCとRFENDTCを返すサブルーチン*/
subroutine rfdtc(USUBJID $ , RFSTDTC $, RFENDTC $) ;
outargs RFSTDTC, RFENDTC;
declare hash h1(dataset: &quot;DM&quot;);
rc=h1.definekey(&quot;USUBJID&quot;);
rc=h1.definedata(&quot;RFSTDTC&quot;);
rc=h1.definedata(&quot;RFENDTC&quot;);
rc=h1.definedone();
if h1.find() ne 0 then call missing(of RFSTDTC
RFENDTC);
endsub ;
quit;
data wk4;
length SUBJID USUBJID RFSTDTC RFENDTC $200.;
set raw_ae;
USUBJID=usubjid(SUBJID);
call missing(of RFSTDTC RFENDTC);
call rfdtc(USUBJID, RFSTDTC, RFENDTC);
run;


# Page. 20

![Page Image](https://bcdn.docswell.com/page/87DYZN9W7G.jpg)

FCMPの中のハッシュオブジェクトで使えるメソッドは以下の通り
DO_OVERメソッドやSUMメソッドなどは，使えない．通常のハッシュオブジェクトに比べて
制限がかかることをに注意


# Page. 21

![Page Image](https://bcdn.docswell.com/page/VJP6D5ZXE8.jpg)

Dictionaryオブジェクト


# Page. 22

![Page Image](https://bcdn.docswell.com/page/2EV31PW37Q.jpg)



# Page. 23

![Page Image](https://bcdn.docswell.com/page/57G32PDYJL.jpg)

proc fcmp;
declare dictionary dc;
length data2 data3 data5 $200;
dc[1] = 100;
dc[2] = 200;
dc[&quot;A&quot;]=&quot;AAA&quot;;
dc[&quot;B&quot;]=&quot;BBB&quot;;
dc[1,1] = 1000;
dc[&quot;A&quot;,&quot;B&quot;,1] = &quot;CCC&quot;;
key1 = 1;
key4_1 = 1;
data1 = dc[key1];
key4_2 = 1;
put key1= data1= ;
data4 = dc[key4_1,key4_2];
key2 = &quot;A&quot;;
put key4_1= key4_2= data4=;
data2 = dc[key2];
key5_1 = &quot;A&quot;;
put key2= data2=;
key5_2 = &quot;B&quot;;
dc[1] = &quot;ZZZ&quot;;
key5_3 = 1;
data3 = dc[key1];
data5 = dc[key5_1,key5_2,key5_3];
put key1= data3=;
put key5_1= key5_2= key5_3= data5=;
run;
quit;


# Page. 24

![Page Image](https://bcdn.docswell.com/page/4EQ5P216JP.jpg)

FCMPの中のDcitonaryオブジェクトで使えるメソッドは以下の通り
ハッシュオブジェクトに似ている．ただ，ハッシュオブジェクトと違って
単純な出し入れだけの場合は メソッドを必要とせず 辞書名[key] =値で操作ができ，それが一番多く使う
あと，上級者向けになりすぎるので，言及しませんが，実は辞書には，ハッシュオブジェクトや配列など
通常の単一値以外のものを格納することができる… なんと．．


# Page. 25

![Page Image](https://bcdn.docswell.com/page/KJ49YLRM71.jpg)

テストデータ
data a;
SEX=&quot;M&quot;;COUNTRY=&quot;JPN&quot;;output;
SEX=&quot;F&quot;;COUNTRY=&quot;CHN&quot;;output;
SEX=&quot;M&quot;;COUNTRY=&quot;JPN&quot;;output;
run;
値から，それぞれ 英語用・日本語用の表示が欲しい場合など，SASフォーマットではなく 今回の技を使う


# Page. 26

![Page Image](https://bcdn.docswell.com/page/LE1K6LQYJG.jpg)

proc fcmp outlib=work.functions.common;
function f1(key1 $,key2 $,key3 $) $ ;
length key1 key2 rvalue dummy $200 ;
_key1=strip(key1);
_key2=strip(key2);
_key3=strip(key3);
①英語or日本語
declare dictionary d1 ;
②カテゴリ名
d1[&#039;E&#039;,&#039;SEX&#039;,&#039;M&#039;]=&#039;Male&#039;;
d1[&#039;J&#039;,&#039;SEX&#039;,&#039;M&#039;]=&#039;男性&#039;;
③カテゴリ値
d1[&#039;E&#039;,&#039;SEX&#039;,&#039;F&#039;]=&#039;Female&#039;;
の 3つのキーで管理される辞書オブジェクトをつくる
d1[&#039;J&#039;,&#039;SEX&#039;,&#039;F&#039;]=&#039;女性&#039;;
d1[&#039;E&#039;,&#039;COUNTRY&#039;,&#039;JPN&#039;]=&#039;Japan&#039;;
d1[&#039;J&#039;,&#039;COUNTRY&#039;,&#039;JPN&#039;]=&#039;日本&#039;;
d1[&#039;E&#039;,&#039;COUNTRY&#039;,&#039;CHN&#039;]=&#039;China&#039;;
d1[&#039;J&#039;,&#039;COUNTRY&#039;,&#039;CHN&#039;]=&#039;中国&#039;;
rvalue=d1[_key1,_key2,_key3];
return(rvalue);
endsub;
run;
quit;


# Page. 27

![Page Image](https://bcdn.docswell.com/page/GEWDWVM1J2.jpg)

options cmplib=work.functions ;
data output1;
length out1 out2 $200.;
set a;
out1=f1(&quot;E&quot;,&quot;SEX&quot;,SEX);
out2=f1(&quot;E&quot;,&quot;COUNTRY&quot;,COUNTRY);
run;
proc sql;
select
f1(&quot;E&quot;,&quot;SEX&quot;,SEX) as out1,
f1(&quot;E&quot;,&quot;COUNTRY&quot;,COUNTRY) as out2
from output1;
quit;
proc sql;
select
f1(&quot;J&quot;,&quot;SEX&quot;,SEX) as out1,
f1(&quot;J&quot;,&quot;COUNTRY&quot;,COUNTRY) as out2
from output1;
quit;


# Page. 28

![Page Image](https://bcdn.docswell.com/page/47ZG54QXE3.jpg)

data b;
SEX=&quot;M&quot;;output;
SEX=&quot;F&quot;;output;
SEX=&quot;U&quot;;output;
run;
data c;
set b;
where f1(&quot;E&quot;,&quot;SEX&quot;,SEX) ne &quot;&quot;;
run;
これもまたwhereステートメントで作動するため，辞書にあるもの（ないもの）のみを抽出するといったことが
可能になる


# Page. 29

![Page Image](https://bcdn.docswell.com/page/YJ6Y936P7V.jpg)

余談


# Page. 30

![Page Image](https://bcdn.docswell.com/page/GJ5GNL6PJ4.jpg)

マニアックすぎて，過去の私の総会論文発表の中で，一番ウケが悪かったものとして
SQLの中で，LAG関数が使えない制限を， FCMPの ハッシュオブジェクトやDictionaryで無理やり突破するというの
があるので，気になる人はぜひ…


# Page. 31

![Page Image](https://bcdn.docswell.com/page/LE3R2LQ675.jpg)



# Page. 32

![Page Image](https://bcdn.docswell.com/page/8EDYZN9MEG.jpg)



# Page. 33

![Page Image](https://bcdn.docswell.com/page/V7P6D5ZQJ8.jpg)



# Page. 34

![Page Image](https://bcdn.docswell.com/page/2JV31PWPEQ.jpg)



# Page. 35

![Page Image](https://bcdn.docswell.com/page/5EG32PDQ7L.jpg)

まー そんなこんなで，自分はハッシュオブジェクトを通常のマクロ
ベースで使ってますが，毎度かき捨てで関数化したり
ADSLやADTTEのような構造固定のADaMの処理を関数にしたり，
色々有用かも．あと自分，SASフォーマット嫌いなので
それならむしろ
全部Dictionaryオブジェクトにしたい
ご清聴ありがとうございましたー


