更新回数をカウントしながらデータをupdateする機能を、ハッシュオブジェクトで実現する話

UPDATEステートメントはかなり応用の効くステートメントです。
UPDATEステートメントの基本についてはSAS忘備録の
「UPDATEステートメント入門」
http://sas-boubi.blogspot.jp/2014/06/update.html
が素晴らしいので、まず読んでください。

その上で、

マスターとなるデータセット

data MAS;
 X=1;Y='あ';output;
 X=2;Y='ろ';output;
 X=3;Y='ほ';output;
 X=4;Y='に';output;
run;









トランザクションデータセット

data TRA;
 X=1;Y='ほ';output;
 X=1;Y='ろ';output;
 X=1;Y='い';output;
 X=2;Y='ろ';output;
 X=3;Y='ひ';output;
 X=3;Y='は';output;
 X=5;Y='と';output;
 X=5;Y='ほ';output;
run;












を用意して、

data A0;
update MAS TRA;
by X;
run;

とすると結果は









となります。

トランザクションデータセットに同一キーのものが複数存在する場合、
結果は最終的に出現したデータになります。

例えばX=1でいうと、Yは「ほ」、「ろ」、「い」という順にトランザクションから読み込まれるので、
最終的にマスターデータセットのX=1に対応するYの値は「い」です。

ただ、これを、「ほ」で更新され、「ろ」で更新され、最後に「い」で更新され、
結局値が確定するまで3回上書き更新されたと捉え、この3回というカウントを変数として持たせたいとします。

昔、仕事で、最終的な更新結果を出すと同時に各オブザベーションが何回更新されたか。
つまりトランザクションデータに同一キーが結局いくつ出現したかを出さなければいけない状況がありました。

その時はまだ初心者でハッシュオブジェクト??だったので、普通にupdateしたあとで、別ステップでトランザクションデータのキーをfreqかなんかで集計したデータセットを作って、update後のデータセットとマージして切り抜けたと思いますが。

ひねくれた現在の僕なら以下のようにかけます。

あ、ちなみに今回は、問題を単純化にするためにトランザクションデータにnullは含まれないという前提条件をつけましょう。
updateステートメントにはnullの場合、更新を行わないという機能がありますが、ハッシュオブジェクトでやる場合、それで一つ条件分岐を増やさなければいけないので。
あとマスターの値とトラの値が同一であったとしても、同値で更新を行ったと考えます。

data _NULL_;
  if _n_ = 1 then do;
   declare hash hx(suminc:'UP_COUNT', ordered: 'yes');
    hx.defineKey('X');
    hx.definedata('X','Y','UP_COUNT');
    hx.definedone();
     end;
 do while (^FL);
   set MAS end=FL;
   UP_COUNT=0;
   hx.add();
  end;

 set TRA end=eof;
 UP_COUNT=1;
 rc=hx.check();
 if rc=0 then do;
  hx.sum(sum:up_count);
  hx.replace();
 end;
 if rc^=0 then do;
  UP_COUNT=0;
  hx.add();
 end;
 if eof then hx.output(dataset:'A1');
run;

で、結果は









です。

マスターに存在しなかった値が追加された時にカウントを1とするかどうかについて、今回は、追加は更新カウントに含めないとしています。

以前、紹介したsumincを使用して、checkメソッドによるキー参照をカウントしています。
checkメソッドにより、キーがハッシュオブジェクトに見つかればreplaceで更新、ないならaddメソッドで追加という、極めて単純な構造です。

このコードは、思いつきで、えいやっと書いたやつなんですが、なかなか美しくないでしょうか?
反復子オブジェクトを使わずに表現できていますし、自分的に結構好きですね。


4 件のコメント:

  1. こんにちは。
    面白いアイディアですね。
    ちょっと分からない部分があるんですが、

    「suminc:'UP_COUNT'」ってデータセット変数UP_COUNTを参照してプラスする数字を決めるわけですよね?

    ということは
    declare hash hx(suminc:'UP_COUNT', ordered: 'yes');

    set TRA end=eof;
    UP_COUNT=1;
    の部分だけUP_COUNTを、例えばCOUNTERとかって変数名に変えて実行しても同じ結果が得られるはずと思ったら、正しく出ませんでした。

    何か思い違いをしてると思うんですが、これってなんでか分かりますか??

    返信削除
    返信
    1. ありがとうございます!
      以下、独自解釈なんで、かなり嘘かもしれませんが

      >「suminc:'UP_COUNT'」ってデータセット変数UP_COUNTを参照してプラスする数字を決めるわけですよね?
      →そうだと考えてます。該当するキーが参照されたときにそのハッシュオブジェクトのレコードにフラグがたつイメージで、メソッド実行時にその変数に保持されている値で、ハッシュの中に格納されます。

      ただ格納するのに、sumメソッドのアクションが必要なんではないかな?と僕は解釈しました。hx.sum(sum:up_count);の部分で、現在アクティブな状態のPDV上でのup_countがハッシュの中のretain状態のup_countに加算されるのでは?と。

      つまり、僕の個人的理解では、

      retain X;
      X=X+Y;

      みたいなデータステップに対応させて考えると
      sumincはretainステートメントみたいなノリで
      X=X+Yの、実際に加算処理に働いている部分がsumメソッドで、加算分のYに該当するのがPDV上で、実行時点のsumincで指定した変数に格納されている値なのでは?

      という推理に基づいて、コードを書いて、動かしてみたら、期待通りの結果だったので、内部の正確な挙動は詳しくわからないけど、まあ良し!って感じです。

      ちょっと怪しいですね

      削除
    2. あ、すみません!
      だから、COUNTERって変数に変えるなら、sumメソッドの指定も変えなければいけないかもです

      削除
  2. こんにちは、ありがとうございます!
    理解することができました!

    hashオブジェクトもds2プロシジャも、なんか今までのデータステップの考えでは読めない部分があって、そこがまた面白いですね。

    返信削除