chakokuのブログ(rev4)

テック・コミック・DTM・・・ごくまれにチャリ

メモ :SASのHashの資料類

背景:家族メンバからSASのHashの公式ドキュメントについて質問を受ける
取り組み:Caludeにまるなげ 「SASでHASH Object を理解したいのですが、SAS の公式ドキュメントはどこに置かれていますか?」
結果:

SAS Hash Objectの公式ドキュメントは主に以下の場所にあります。

SAS公式ドキュメント
SAS公式チートシート(Tip Sheet)
  • Hash Objectの基本概念(簡単なまとめ)
    • Hash ObjectはDATA stepのメモリ上に完全に存在する高性能なルックアップテーブルです。Data Step Component Interface(DSCI)を通じて実装されており、O(1)の時間でデータの検索・追加・削除が行えます。
    • DATAステップの構成要素として利用でき、SAS PROCEDUREからは使えません。メソッドを呼び出して操作し、主なメソッドには DefineKey(キー定義)、DefineData(データ定義)、Find(検索)、Add(追加)などがあります。

もし具体的なコード例や特定の用途(テーブル結合、重複除去など)についての説明が必要でしたら、お気軽にどうぞ!

Claudeが提示した、Hashオブジェクトを使った事例

data result;
    if _N_ = 1 then do;
        /* Hash1: 社員ID → 部署コード */
        declare hash h1(dataset: 'work.employee');
        h1.definekey('emp_id');
        h1.definedata('dept_code');
        h1.definedone();

        /* Hash2: 部署コード → 部署名 */
        declare hash h2(dataset: 'work.department');
        h2.definekey('dept_code');
        h2.definedata('dept_name');
        h2.definedone();
    end;

    set work.main;

    /* まずh1で部署コードを取得 */
    if h1.find() = 0 then do;
        /* 取得したdept_codeをキーにh2を検索 */
        if h2.find() = 0 then output;
    end;
run;

どのように動くのかメモ

  • set work.main; の行があるので、work.mainの各行を取り出しながらループする(PDV)
  • if _N_ = 1 then do; により、ループの初回時のみ以下を実行
    • Hashオブジェクトを生成する。
    • 読み込んだテーブルの列名に対応する変数をPDVに生成
    • keyとdataを宣言
  • set work.main; の行実行時以下を実行
    • work.mainから一行取り出す。work.mainの列名の値を対応するPDVの変数に設定
    • if h1.find() = 0 then do; を実行する、h1のkeyがemp_idなので、PDVの変数よりemp_idの値を取り出しh1を引く。見つかればh1のvalueのdept_codeをPDVの変数dept_codeに設定し、次の行を実行に移る
    • 上記ifが見つかれば次の if h2.find()=0 then output;を実行する。h2のkeyがdept_codeなので、PDVの変数dept_codeの値を使ってh2を引く。見つかればPDVの変数dept_nameにh2のdept_nameの値を入れる。 outputにより、PDV上の変数(すなわちwork.mainの列とh1から取得したdept_codeとh2から取得したdept_name)、3つのテーブルを)を出力

補足:
work.employee, work.departmentにおいて、key, data宣言した列以外の列が含まれる場合、output では空欄になる。値を操作しなかった列のdataについて値を出力したい場合は、以下のコードを使う

h1.definedata('dept_code', 'name', 'salary', 'hire_date');

find()では該当行の存在と、存在した場合に、definedata()で宣言した列名の変数に値を自動で入れてくれる(コードに書かなくてもいいのは楽だが仕様を知っていないと挙動が分からない)。一方、hashにおいて、definedataでdataを宣言した場合を除き、SQLやgrepの様にkeyで一致した一行を取り出すようなことはできない。

上記Claudeが生成したコードでは問題があり、少しやり取りをして修正版は以下

data result;
    /* コンパイル時にwork.employeeとwork.departmentの
       全列をPDVに確実に生成するためのイディオム */
    if 0 then set work.employee;
    if 0 then set work.department;

    set work.main;

    if _N_ = 1 then do;
        /* Hash1: 社員ID → 部署コード */
        declare hash h1(dataset: 'work.employee');
        h1.definekey('emp_id');
        h1.definedata('dept_code');
        h1.definedone();

        /* Hash2: 部署コード → 部署名 */
        declare hash h2(dataset: 'work.department');
        h2.definekey('dept_code');
        h2.definedata('dept_name');
        h2.definedone();
    end;

    /* 毎ループ必ずリセット */
    call missing(dept_code, dept_name);

    /* まずh1で部署コードを取得 */
    if h1.find() = 0 then do;
        /* 取得したdept_codeをキーにh2を検索 */
        if h2.find() = 0 then output;
    end;

run;