PowerBuilderからWinSCPを使う

 こんにちは、石川さんです。開発中のPowerBuilderのアプリケーションからWinSCPを使って通信する方法を調べたのですが、ちょっと難しかったので記録しておきます。誰かのお役に立ちますように。実行環境は64ビット版のWindows 10で、PowerBuilder 2017 R3の32ビットアプリケーションを開発しています。

 階層化したフォルダーへファイルを展開する必要があったので、複雑なことができるよう.NetアセンブリをOLEオブジェクトとして利用できる方法についてのお話になります。単なるEXEとして実行する方法ではありません。

セットアップについて(基本的にはWinSCPのホームページに書いてありました)

 英語が読める方は、こちらを読むようにお願いします。手順としては、

  • ダウンロードして
  • 解凍して
  • OLEオブジェクトとして利用できるように登録します

です。

 まずは、ダウンロードします。こちらのページにありました。PowerBuilderからの利用には、「.NET assembly / COM library」を選択すれば使えるようです。以下のリンクをクリックしてファイルをダウンロードします。

 ダウンロードされるファイルは「WinSCP-X.X.X-Automation.zip」のような名前になっています。ぼくがダウンロードしたときは、「WinSCP-5.21.7-Automation.zip」でした。

 zipファイルを解凍した中にある、「WinSCPnet.dll」が本体、「WinSCP.exe」が実行形式のファイルだそうです。dllを確認したところ、32ビット版でした。開発中のアプリケーションが32ビット版なので、ここは一致している必要があるような気がします。ぼくの環境ではこの二つのファイルを展開するだけで使えるようになりました。

 次にOLEオブジェクトとして利用できるようにする手順ですが、以下のコマンドを実行します。WinSCPnet.dllのあるフォルダで実行ですよ!実行するとレジストリに登録されるようです。レジストリの中まではざっとしか確認していません。

%WINDIR%\Microsoft.NET\Framework\<version>\RegAsm.exe WinSCPnet.dll /codebase /tlb

REM ぼくの環境では以下のとおりでした。
C:\Windows\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe WinSCPnet.dll /codebase /tlb

 これで、使えるようになりました。あとは、PowerBuilderでコーディングするだけですね。ちなみに、実行すると「WinSCPnet.tlb」というファイルができました。登録を解除するには「/tlb」ではなく「/unregister」を指定して実行すればオッケーのようです。このあたり、あまり詳しくありません。試しに「/unregister」を指定したコマンドを実行してから開発中のプログラムを実行したらエラーがでていました。

PowerBuilderのソースコード

 ここのソースコードをコピペして動かしてみました。ただし、そのままだと動かなかったので、少し変更しました。変更部分を含めて再登録しておきます。ボタンをつくって、クリックしたときのイベント(clicked)に記載して確認しています。

oleobject s_ftp // for WinSCP.Session
oleobject s_opt // for WinSCP.SessionOptions
oleobject s_trans // for WinSCP.TransferOptions, i just used default values (Binary transfer and overwrite options)
int return_code

s_ftp = CREATE oleobject

return_code = s_ftp.connectToNewObject("WinSCP.Session")

if return_code <> 0 then
   messagebox("Error", "S_FTP Component installation error")
   return - 1
end if   

s_opt = CREATE oleobject
return_code = s_opt.connectToNewObject("WinSCP.SessionOptions")

if return_code <> 0 then
   messagebox("Error", "Seasion Options  Component installation error")
   return - 1
end if      

s_trans = CREATE oleobject
return_code = s_trans.connectToNewObject("WinSCP.TransferOptions")

if return_code <> 0 then
   messagebox("Error", "Transfer Options Component installation error")
   return - 1
end if      

s_opt.protocol = 2 // 0:SFTP 1:SCP 2:FTP WEBDAV:3
s_opt.hostname = "CHANGE YOUR IP" // server IP
s_opt.UserName = "CHANGE YOUR ID" // user id
s_opt.Password = "CHANGE YOUR PASSWORD" // user pass
//s_opt.GiveUpSecurityAndAcceptAnySshHostKey = true  // this is not save, instead server key should be used
//s_opt.SshHostKeyFingerprint = "ssh-rsa 2048 xxxxxxxxxxx..."

try
   any result
   result = s_ftp.open(s_opt) 
//   return  integer(result) ←これがあって、下のputfilesが実行できなかったのでコメントアウト
catch (runtimeerror  e)
  messagebox("Error",e.getMessage())
return -1
end try

Ll_rtn = integer (s_ftp.putfiles("C:\poi.txt", "/FTP/poi.txt", false,s_trans ) ) // ローカルからリモートへコピー

IF Ll_rtn < 0 THEN
   as_msg   =   "File Upload Error(FTP)!"
END IF

s_ftp.close()

以上です!このあとに続く、これ以上の開発にはこちらのライブラリの情報が必要です。

皆さんの環境でもうまいこと動きますように。

追記

 実際に開発を進めていって、s_ftp.putfiles()の戻り値がうまく取得できていないことが発覚しました。これではエラーハンドリングできません。と、いうことで、後半をちょっと追記しますと、、、こんな感じに仕上がりました。

	OLEObject s_result	// for WinSCP.TransferOperationResult
	//ファイル送信
	s_result	=	s_ftp.putfiles(as_fileName, ls_folder, false, s_trans )
	
	if	s_result.isSuccess	then
		s_ftp.close()
		return	""
	else
		s_result.Check()
	end	if

catch (oleRuntimeError oe)
	destroy	s_trans
	destroy	s_opt
	destroy	s_ftp
	return	"WinSCPの処理に失敗しました。~n~n【詳細】~n"+oe.Description+"~n~n"+oe.getMessage()+")"
catch (runtimeerror  e)
	destroy	s_trans
	destroy	s_opt
	destroy	s_ftp
	return	"WinSCPの処理に失敗しました。(エラーメッセージ:"+e.getMessage()+")"
end try

 戻り値を取得するためのOLEObject変数s_resultを設定し、s_result.isSuccessをチェックすることで成功か失敗か、ということが判断できます。失敗したときは、s_result.Check()を呼び出すことでOLEランタイムエラーを発生してくれます。このs_result.isSuccessだけでも充分だと思いますが、一箇所でハンドリングしたいときなどは、いきなりs_result.Check()を呼び出すとよいと思います。

ChatGPTすごいですね!間違えたら謝ってくれます。

 こんにちは。石川さんです。ChatGPT、何でも答えてくれるので、仕事のお悩みを聞いてもらいました。

CSVファイルはデータの中にカンマが含められません

 事の発端は、今開発中のシステムでのお話です。連携用のデータの中にカンマがありまして、そのままCSVデータとして送信すると、エラーになってしまいました。ま、CSVはカンマ区切りの値ですから、カンマは何らかの処置をしてあげなければいけません。ぼくとしてはデータを「”」ダブルクォーテーションでくくるのがお好みなのですが、要望は半角カンマは全角に置き換えてください、ということでした。ORACLEからSQLを使ってデータを取出していましたので、このSQLを変更するのが楽でよさそうですね。そこで、TRANSLATEを使って置き換えることに決めました。イメージはこんな感じになります。

-- 修正前
SELECT 商品コード||','||商品名||','||単価 FROM 商品マスタ;
-- 修正後(TRANSLATEを使って半角カンマ「,」を全角カンマ「,」に置き換える)
SELECT 商品コード||','||TRANSLATE(商品名,',',',')||','||単価 FROM 商品マスタ;

そう、色々と調べて、これでうまくいくね、というときに閃きました。ChatGPTに聞いてみたら、ちゃんと教えてくれるのでしょうか。気になって聞いてみました。

Oracleで半角カンマを全角カンマに置き換える方法を教えてください。

そうするとですねぇ、ぼくが調べたTRANSLATEではく、REPLACEをおすすめしてきました。なるほど。この場合は、書き方の違いは関数名だけですね。

-- おすすめ結果
SELECT REPLACE('1,2,3,4', ',', ',') FROM dual;

そこで、次の質問です。

translateではなくreplace関数を利用する理由は何でしょうか?

そうすると、関数の違いを説明してくれて、利点を述べてくれました。その上で何と、置換対象が一つしかない場合はREPLACEの方が単純で実行速度が速くなる、と、教えてくれました。ご存知だと思いますがTRANSLATEは指定された文字列中の文字を対応させて変換してくれます。REPLACEは指定された単語を指定した単語へ変換してくれます。なので、それは本当のような気がしましたが、置換する文字が増えるなどの拡張性を考えた場合、TRANSLATEの方がいいような気がしたので、更に質問を続けます。

translateよりreplaceの方が実行速度が速いのですね。知りませんでした。本当でしょうか。そして、今後の拡張性を考慮した場合はtranslateを利用しておいた方がよいと思いますがいかがでしょうか?

この質問に対する回答では、拡張があるならTRANSLATE使ってね、と言ってもらいました。ただ、先程の質問を繰り返したような感じだったせいか、同じような回答でした。さらに、具体的にどれくらい速いのか知りたくなって聞いてみました。

では、translateとreplaceの速度の違いはどれくらいあるのでしょうか?

こんな無茶ぶりでもちゃんと答えてくれました。テーブルを作ってランダムなデータを1万件投入してUPDATEで更新した様子と、その実測した結果を示してきました。ワオ!

  • TRANSLATE関数を使用した場合: 2.9秒
  • REPLACE関数を使用した場合: 0.6秒

いや~、これはすごいですね!ちょっと感動しました。お礼を述べて一旦終了したのですけど、、、そんなに違うかなぁ、、と思い、自分でも実測してみることにしました。1万件のデータを作って、半角の3を全角の3に置き換えて、3333を取り出す、というのを実験してみました!

-- まずはTRANSLATE
with s(n) as (
select 1 n from dual
union all
select n + 1 from s where s.n < 10000
)
select * from s
where translate(to_char(n),'3','3') = '3333';

-- 次にREPLACE
with s(n) as (
select 1 n from dual
union all
select n + 1 from s where s.n < 10000
)
select * from s
where replace(to_char(n),'3','3') = '3333';

--【実測します!】--
SQL> with s(n) as (
  2  select 1 n from dual
  3  union all
  4  select n + 1 from s where s.n < 10000
  5  )
  6  select * from s
  7  where translate(to_char(n),'3','3') = '3333';

         N
----------
      3333

経過: 00:00:00.15
SQL> with s(n) as (
  2  select 1 n from dual
  3  union all
  4  select n + 1 from s where s.n < 10000
  5  )
  6  select * from s
  7  where replace(to_char(n),'3','3') = '3333';

         N
----------
      3333

経過: 00:00:00.15
SQL>

結果が出ました。どちらも0.15秒です!やはり、2.9秒は怪しいと思いました。(笑)

1回目より2回目の方の実行速度が速くなるのはOracleがバッファキャッシュを利用しているのでよくある話なのかも、と、いうことで、再度質問してみました。

ちょっと気になったので再度質問します。先程の実行結果はtranslateよりreplaceを先に実行した場合、結果が変わってくるということはありますでしょうか?

そうすると、少々時間が経ってから「申し訳ありませんが、私の前回の回答に誤りがありました。実際には、TRANSLATE関数の方がREPLACE関数よりも実行速度が速いことが多いです。私の回答が混乱を招いてしまったこと、お詫び申し上げます。」と素直に間違いを認められました。で、ちょっと追加説明してから「再度、前回の回答が誤りであったことをお詫び申し上げます。」ですって。丁寧過ぎでしょ!

いや、感動しました。ChatGPT、すごいね!!!

初めてのJavaScript 読書中です

こんにちは、石川さんです。

最近、思うところがあって、JavaScriptの勉強を始めました。今読んでいるのは、そう「初めてのJavaScript 第3版」です。Angularを使い始めたので本来はTypeScriptの勉強、と言いたいところですが、一冊本を読んで、先にJavaScriptだな、という気持ちになったのでした。

いや~、JavaScript、思っていたよりも、色々とできるのですねぇ。テンプレートリテラルとか、クロージャとか、完全にPythonだけのものだと思っていたのですけど、いや、視野が狭かったですね!反省しました。

せっかく勉強しているので、色々と披露したいよねぇ、という気持ちになってきたところで気になりました。そういえばWordPressのこのページ、JavaScriptを使用できるのでしょうか。先日調べたところ、ブロックを選択すればできる、ということがどこかに書かれていましたので、ちょっと試してみましょう。

ブロックの追加

まずは、ブロックを追加します。「カスタムHTML」が選べるので、こちらを選択します。

「ブロックを追加」から「カスタムHTML」を選択

すると、以下のように「HTMLを入力…」となりますので、ここへスクリプトを記載して行きます。「プレビュー」を押すと実行結果のプレビューが表示されます。

HTMLを入力

入力して保存したところ、簡単にできました!

【1つ目】

【2つ目】

できましたね!

簡単でした♪

今回入力したスクリプトは以下の通りです。

まずは【1つ目】です。ほぼこちらのスクリプトをコピペしたものです。Konva.jsという、HTML5 2d canvasのためのJavascriptライブラリを紹介しているホームページです。

次に【2つ目】のスクリプトです。

<style>
div#container {
  height: 200px;
  width: 116%;
  background-color: ivory;
}
</style>
<div id="container"></div>
<script src="https://unpkg.com/konva@8/konva.min.js"></script>
<script>
var height = 200;
var width = window.innerWidth;
      var resourceColor = 'rgb(185, 247, 247)';
      var eventColor = 'rgb(245, 200, 247)';
      var stage = new Konva.Stage({container: 'container', width: width, height: height, draggable:true});
      stage.on('contextmenu', function (e) {
        e.evt.preventDefault();
      });
      var layer = new Konva.Layer();
      stage.add(layer);
      function newBox(title, type) {
        return new Konva.Rect({
          cornerRadius: 10,
          fill: type === "resource" ? resourceColor : eventColor,
          stroke: 'black',
          strokeWidth: 1,
          name: 'rect',
          draggable: true,
          sceneFunc: function (context, shape) {
            shape._sceneFunc(context);
            context.beginPath();
            context.moveTo(0,20);
            context.lineTo(shape.getAttr('width'),20);
            context.moveTo(shape.getAttr('width')/2,20);
            context.lineTo(shape.getAttr('width')/2,shape.getAttr('height'));
            context.stroke();
            context.closePath();
            context.font = '12px sans-serif';
            context.fillStyle = "rgb(0, 0, 0)"
            context.fillText(title,25,17);
          }
        });
      };
      var rect1 = newBox("リソース", "resource");
      rect1.setAttrs({x: 60, y: 60, width: 100, height: 90});
      layer.add(rect1);
      var rect2 = newBox("イベント", "event");
      rect2.setAttrs({x: 260, y: 60, width: 100, height: 90});
      layer.add(rect2);
rect2.on('click', (event) => {console.log('clicked!', event);});
</script>

簡単に色々なことができるので、夢が広がりますね!

ORACLEのカーソルFORループと、CURRENT OF カーソルの使い方

こんにちは。石川さんです。

最近、PLSQLでストアドプロシージャを作っています。カーソルでSELECT文を定義して、データを一件ずつ取得して、正常に処理できたら処理済みの証として日付を更新する、という処理を書いているのですが、参考にしている元のプロシージャに色々といちゃもんを言いたくなって、書いちゃいました。

カーソルFORループを使おうよ!

SELECTで取得したデータをループで逐次処理するときは、カーソルFORループを使うようにしています。構文が記憶から抜けていたので色々と検索することになりました。ただ、色々と検索したときにあんまり登場してこなかったのですよね。ここにきて世の中的にはあまり認知されていないのかもという気持ちになってきたのですが、カーソルFORループのメリットをお伝えしておこうと思います。

カーソルの使い方は2種類

一つ目、カーソルFORループではないカーソルを使った繰り返し処理の例を示します。

DECLARE
  -- カーソルの定義
  CURSOR C IS SELECT ... ;
  -- FETCHしたときにデータを取得する変数
  C_DATA C%ROWTYPE; -- 受け取る変数は%ROWTYPEを使いましょう。取得カラムに対して一個ずつ変数定義しないように!
BEGIN
  OPEN C; -- カーソルをオープンします
  LOOP
    FETCH C INTO C_DATA; -- データを1件フェッチします
    EXIT WHEN C%NOTFOUND; -- データがなくなった時にループから抜け出します
    -- なにかの処理
  END LOOP;
  CLOSE C; -- カーソルをクローズします
EXCEPT
  WHEN OTHERS THEN -- 想定外のエラーが発生したとき
    IF C%ISOPEN THEN -- カーソルがオープンしていたら
        CLOSE C; -- カーソルを閉じます
    END IF;
    RAISE; -- 想定外のエラーを呼び出し元に渡します
END;
/

LOOPから、END LOOPまでの間を繰り返します。

二つ目、カーソルFORループを使用した場合の例を示します。

DECLARE
  -- カーソルの定義
  CURSOR C IS SELECT ... ;
BEGIN
  FOR i IN C LOOP
    -- なにかの処理
  END LOOP;
EXCEPT
  WHEN OTHERS THEN
    RAISE;
END;
/

FORから、END LOOPまでの間を繰り返します。そう、圧倒的に手順が少なくなります。メリットをまとめると以下の通り。

  • フェッチしたデータを受け取る変数の定義が不要
  • 明示的なオープン、クローズが不要
  • ループを抜けるための条件判定が不要、データがなくなればループは終了します
  • エラー発生したときに、カーソルのオープン判定とクローズ処理が不要

いいことずくめですねぇ。仮に難点があるとすれば、変数「i」がループ終了時には利用できなくなる、ということくらいですが、それは処理に応じて別途必要な変数を用意すればよいので、まったく問題になりませんよね。

個人的に、OPEN、FETCH、CLOSEを使うのは、データが絶対に1件しかないことが分かっている問い合わせのときに限られると思っています。というのもループ処理を終了する条件がNOTFOUNDになっているのですが、これは、2件目を探してデータの最後まで見にいかないとわからないことなのですよね。要は、無駄な検索をしてしまう、ということです。プログラム作成者が2件目がないことを知っているなら、パフォーマンスを改善するために、FETCH後すぐにCLOSEするようにしましょう。無駄な検索が発生しない分、はやくなります。

CURRENT OFカーソルを使って更新する

そう、そもそもなんでこの記事を書こうと思ったかを思い出しました。カーソルFORループ使って欲しいのはそうなのですけど、もうひとつ!
取り出した行を更新するときには、CURRENT OFカーソルを使ってほしいのです。これもパフォーマンスのためです。これを使わないということは、せっかく取得してきたのにそのことを忘れてもう一度検索し直している、ということとほぼ同じことですからね。
で、CURRENT OFの後に指定するのが、カーソルFORループのときは何でしたっけ、というのを調べてもすぐにわからなかったので、スクリプトつくって実験してみました。
知りたかったのは、カーソル名を指定するのか、カーソルの変数を指定するのかどちらでしょうか、ということです。

まずは、作ったスクリプトです。

-- ■CURRENT OF カーソルの使い方 スクリプト

SET NULL NL
CREATE TABLE POI (n NUMBER, v VARCHAR2(20));
INSERT INTO POI(N) SELECT N FROM (
  WITH S(N) AS (
    SELECT 1 N FROM DUAL
    UNION ALL
    SELECT N + 1 FROM S
     WHERE N < 10)
  SELECT N FROM S
); -- このINSERT SELECTで10行作成しています。数字を変えれば好きなだけデータが作れます。

SELECT * FROM POI;

DECLARE
  n NUMBER;
  CURSOR C IS SELECT N, V FROM POI FOR UPDATE;
BEGIN
  FOR i IN C LOOP
    UPDATE POI SET V = 'RECORD IS No.'||i.N
     WHERE CURRENT OF C;
  END LOOP;
END;
/

SELECT * FROM POI;

DROP TABLE POI PURGE;

実行結果です。できました!

-- ■実行結果

SQL> SET NULL NL
SQL> CREATE TABLE POI (n NUMBER, v VARCHAR2(20));

表が作成されました。

経過: 00:00:00.01
SQL> INSERT INTO POI(N) SELECT N FROM (
  2    WITH S(N) AS (
  3      SELECT 1 N FROM DUAL
  4      UNION ALL
  5      SELECT N + 1 FROM S
  6       WHERE N < 10)
  7    SELECT N FROM S
  8  );

10行が作成されました。

経過: 00:00:00.00
SQL>
SQL> SELECT * FROM POI;

         N V
---------- --------------------
         1 NL
         2 NL
         3 NL
         4 NL
         5 NL
         6 NL
         7 NL
         8 NL
         9 NL
        10 NL

10行が選択されました。

経過: 00:00:00.01
SQL>
SQL> DECLARE
  2    n NUMBER;
  3    CURSOR C IS SELECT N, V FROM POI FOR UPDATE;
  4  BEGIN
  5    FOR i IN C LOOP
  6      UPDATE POI SET V = 'RECORD IS No.'||i.N
  7       WHERE CURRENT OF C;
  8    END LOOP;
  9  END;
 10  /

PL/SQLプロシージャが正常に完了しました。

経過: 00:00:00.01
SQL>
SQL> SELECT * FROM POI;

         N V
---------- --------------------
         1 RECORD IS No.1
         2 RECORD IS No.2
         3 RECORD IS No.3
         4 RECORD IS No.4
         5 RECORD IS No.5
         6 RECORD IS No.6
         7 RECORD IS No.7
         8 RECORD IS No.8
         9 RECORD IS No.9
        10 RECORD IS No.10

10行が選択されました。

経過: 00:00:00.00
SQL>
SQL> DROP TABLE POI PURGE;

表が削除されました。

経過: 00:00:00.04
SQL>

ちなみに、カーソル変数名を指定すると以下の通り、エラーになりました。

-- ■カーソル名ではなく、変数名を指定した場合は、エラーです
SQL> DECLARE
  2    n NUMBER;
  3    CURSOR C IS SELECT N, V FROM POI FOR UPDATE;
  4  BEGIN
  5    FOR i IN C LOOP
  6      UPDATE POI SET V = 'RECORD IS No.'||i.N
  7       WHERE CURRENT OF i;
  8    END LOOP;
  9  END;
 10  /
     WHERE CURRENT OF i;
                      *
行7でエラーが発生しました。:
ORA-06550: 行7、列23:
PLS-00413: CURRENT OF句の識別子はカーソル名ではありません。
ORA-06550: 行7、列23:
PL/SQL: ORA-00904: : 無効な識別子です。
ORA-06550: 行6、列5:
PL/SQL: SQL Statement ignored


経過: 00:00:00.01
SQL>

結果としては、カーソル名を指定すればオッケーということですね!

まとめ

PL/SQLでカーソルをつくってデータを取得するときは、カーソルFORループを使いましょう。取得したデータを更新するときは、CURRENT OF カーソルを使いましょう。

ORACLE PL/SQL 変数の使い方の違いによるパフォーマンス確認

こんにちは、石川さんです。

最近ORACLEのPL/SQLでストアドプログラムを作っています。どっちのパフォーマンスが良いのか、ちょっと気になったので調べてみました。

作っていたのは、ファイルを1行ずつ読み込んで、諸々チェックして、問題なければテーブルにINSERTする、というよくある単純な処理です。このとき、呼び出し側からOUTパラメータを指定して、登録件数とチェックしたエラー件数をもどす、という要件があるときの変数の使い方として、以下の二通りを考えました。どっちが早いのでしょうね?

  1. OUTパラメータを直接インクリメントする
  2. 別途定義したローカル変数をインクリメントして、最後にOUTパラメータへ結果をセットする

実験

わからないことは、すぐに実験しましょう。と、いうことで以下のスクリプトを作ってみました。

set serveroutput on

DECLARE
  counter NUMBER := 0;
  s1 TIMESTAMP;
  s2 TIMESTAMP;
  e1 TIMESTAMP;
  e2 TIMESTAMP;
  PROCEDURE inner_procedure1(an_counter OUT NUMBER) IS
  BEGIN
    an_counter := 0;
    LOOP
      an_counter := an_counter + 1;
      EXIT WHEN an_counter > 10000000;
    END LOOP;
  END;
  PROCEDURE inner_procedure2(an_counter OUT NUMBER) IS
    counter NUMBER := 0;
  BEGIN
    an_counter := 0;
    LOOP
      counter := counter + 1;
      EXIT WHEN counter > 10000000;
    END LOOP;
    an_counter := counter;
  END;
BEGIN
  s1 := SYSTIMESTAMP;
  inner_procedure1(counter);
  e1 := SYSTIMESTAMP;
  s2 := SYSTIMESTAMP;
  inner_procedure2(counter);
  e2 := SYSTIMESTAMP;
  DBMS_OUTPUT.PUT_LINE('START:'||TO_CHAR(s1,'YYYY-MM-DD HH24:MI:SS.FF'));
  DBMS_OUTPUT.PUT_LINE('END  :'||TO_CHAR(e1,'YYYY-MM-DD HH24:MI:SS.FF'));
  DBMS_OUTPUT.PUT_LINE(e1 - s1);
  DBMS_OUTPUT.PUT_LINE('START:'||TO_CHAR(s2,'YYYY-MM-DD HH24:MI:SS.FF'));
  DBMS_OUTPUT.PUT_LINE('END  :'||TO_CHAR(e2,'YYYY-MM-DD HH24:MI:SS.FF'));
  DBMS_OUTPUT.PUT_LINE(e2 - s2);
END;
/

inner_procedure1が、OUTパラメータをインクリメントする「1.」のケースで、inner_procedure2が、ローカル変数をインクリメントする「2.」のケースになります。

では何回か実行します。そして、結果は、、、

(・・・略・・・)
12:41:11  39  /
START:2022-11-30 12:41:11.901000000
END  :2022-11-30 12:41:12.665000000
+000000000 00:00:00.764000000
START:2022-11-30 12:41:12.665000000
END  :2022-11-30 12:41:13.149000000
+000000000 00:00:00.484000000

PL/SQLプロシージャが正常に完了しました。

経過: 00:00:01.26
12:41:13 SQL> /
START:2022-11-30 12:41:18.841000000
END  :2022-11-30 12:41:19.492000000
+000000000 00:00:00.651000000
START:2022-11-30 12:41:19.492000000
END  :2022-11-30 12:41:19.872000000
+000000000 00:00:00.380000000

PL/SQLプロシージャが正常に完了しました。

経過: 00:00:01.03
12:41:19 SQL> /
START:2022-11-30 12:41:23.800000000
END  :2022-11-30 12:41:24.439000000
+000000000 00:00:00.639000000
START:2022-11-30 12:41:24.439000000
END  :2022-11-30 12:41:24.818000000
+000000000 00:00:00.379000000

PL/SQLプロシージャが正常に完了しました。

経過: 00:00:01.02
12:41:24 SQL>

「2.ローカル変数をインクリメントして、最後にOUTパラメータへ結果をセットする」方がおよそ1.5倍ほど、はやい!ということがわかりました。

と、いうことで今後はOUTパラメータへのアクセスは最後の一回のみ、ということにしたいと思います。

結論

ローカル変数をインクリメントして、最後にOUTパラメータへ結果をセットした方がはやい、というのは結果からみるとそのとおりなのですが、実は、10000000回(1千万回)ループしているのですよね。その上で、早くなったのは、0.3秒未満なので、大量データを何度も扱わない限りは気にしなくても良さそうです。億超えのデータをしょっちゅう扱うような人は、ちょっとずつ効いてきますので、気にしましょう!
ぼくはとっても気になります!!!

Oralce テーブルのカラムの順番を入れ替える

 今日も見に来てくださってありがとうございます。石川さんです。

 今日は、Oracleにおいてのテーブルのカラムの順番を入れ替える方法について書いておきます。昔だと、テーブル再作成してデータを入れ直す、という方法しかなかったのですが、別のやり方もあります、というお話です。Google先生に聞いたら、新しいやり方については簡単に出てこなかったので、周知しなければ、というモチベーションです。(笑)

Invisibleにして、Visibleにする

 結論を先に知りたい人のために、項目をInvisibleにして、visibleに設定し直すと、項目がテーブルの最後に移動します。この特性を使って順番を入れ替える、ということになります。

-- テーブルを作成します。POIに意味はありませんが、キーボード上に並んでいて、「ポイ」という感じが気に入っていてテストのときによく使っています。
SQL> create table poi (
  2    a number,
  3    b number,
  4    e number,
  5    d number
  6  );

表が作成されました。

-- 「C」列を追加し忘れました。よく見ると「E」「D」の列の順番も間違っていました。
SQL> desc poi
 名前                                      NULL?    型
 ----------------------------------------- -------- ----------------------------
 A                                                  NUMBER
 B                                                  NUMBER
 E                                                  NUMBER
 D                                                  NUMBER

-- 「C」列を追加します。
SQL> alter table poi add (c number);

表が変更されました。

-- 確認すると、最後に追加されています。
SQL> desc poi
 名前                                      NULL?    型
 ----------------------------------------- -------- ----------------------------
 A                                                  NUMBER
 B                                                  NUMBER
 E                                                  NUMBER
 D                                                  NUMBER
 C                                                  NUMBER

-- 「E」「D」列を「invisible」に設定します。
SQL> alter table poi modify ( e invisible, d invisible );

表が変更されました。

-- 確認すると、確かに「E」「D」列が見えなくなっています。ちなみにこの状態でINSERT INTO POI VALUES ( 1, 2, 3 );を実行すると、見えない列はNULLになりました。
SQL> desc poi
 名前                                      NULL?    型
 ----------------------------------------- -------- ----------------------------
 A                                                  NUMBER
 B                                                  NUMBER
 C                                                  NUMBER

-- 順番が大事ですので、まずは「D」列を見えるようにします。
SQL> alter table poi modify ( d visible );

表が変更されました。

-- そして、次に「E」列を見えるようにします。
SQL> alter table poi modify ( e visible );

表が変更されました。

-- 確認すると、順番が正しく入れ替わりました。
SQL> desc poi
 名前                                      NULL?    型
 ----------------------------------------- -------- ----------------------------
 A                                                  NUMBER
 B                                                  NUMBER
 C                                                  NUMBER
 D                                                  NUMBER
 E                                                  NUMBER

SQL> insert into poi values ( 1, 2, 3, 4, 5 );

1行が作成されました。

-- INSERT文を実行するときに項目を省略してみましたが、入れ替わった順番どおり値がセットされていました!
SQL> select * from poi;

         A          B          C          D          E
---------- ---------- ---------- ---------- ----------
         1          2          3          4          5

経過: 00:00:00.01
SQL>

 テーブルの項目をInvisibleに設定するオプションですが、Oracle 12cから登場したようです。これを実行すると文字通り見えなくなります。

追記:開発中のテーブルで実際に項目追加が必要になってやってみましたが、VISIBLEに設定するのは手抜きして以下のように一気にやっても順番通りに戻りました。

alter table poi modify ( e visible, d visible );

まとめ

 Oracleのテーブルのカラム定義の順番を入れ替えるのに、かつては再作成とデータ投入が必要でしたが、ALTER TABLE文だけで項目が入れ替えられるようになっていました!権限やインデックス、データ投入など、必要な手間を考えると圧倒的にこちらのほうが良い気がします。

Angular始めました – リアクティブフォームを使ってみる。そして、stackblitz.comのプログラムを組み込んでみる

 今日も見に来てくださってありがとうございます。石川さんです。
先日に引き続き、Angularを勉強しています。今回はリアクティブフォームを使って、サンプルプログラムを作ってみました。

出来上がりイメージ

  今回は、たまたまニュートン-ラフソン法について質問があったので、Angularで実装してみました。こんな感じになります。ルートを求めたい値「k」を入力して、Calcボタンをクリックすると、漸化式を繰り返します。二乗した結果が「k」との誤差0.01未満になったら終了します。

ニュートン-ラフソン法の実行結果

 今回も、statsblitz.comを利用してみました。作業中に、デフォルトで用意されているコンポーネントの「hello.component.ts」を削除してみたところ、以下のエラーがでました。しばらく解決できなくて、何かの設定があるのかと、ウロウロしてしまったので、回避策をメモしておきます。

Error in src/app/hello.component.ts (1:1)
File '/~/src/app/hello.component.ngtypecheck.ts' not found.

 何のことはありません、おかしな状態になっているだけ、ということのようでした。画面のプロジェクトエクスプローラーの左下の「DEPENDENCIES」をポイントすると、くるりとなった矢印が登場するので、そちらをクリックしたところ、しばらく待って、エラーが消えました。

DEPENDENCIESの解決

ソースコード

ソースコードは以下のとおりです。

まずは、コンポーネントのスクリプトです。リアクティブフォームのポイントは「FormGroup」ですね。TypeScript側で「myGroup」として定義しています。また、今回はFormBuilderを使って「k」を定義しました。また、Validatorsを使って、正の数をチェックするようにしました。
onSubmit()呼び出し時、漸化式で計算を実行して、二乗した結果がkに近づいて差が0.01を下回ったところで処理を完了します。

import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';

@Component({
  selector: 'app-x2',
  templateUrl: './x2.component.html',
  styleUrls: ['./x2.component.css'],
})
export class X2Component implements OnInit {
  myGroup: FormGroup;
  results: string[];
  guess: number;

  constructor(private fb: FormBuilder) {}

  ngOnInit() {
    this.myGroup = this.fb.group({
      k: [24, Validators.min(0)],
    });
  }

  get k() { return this.myGroup.get('k'); }

  onSubmit() {
    console.log('Executed!')
    this.results = [];
    let k: number = this.k.value;
    let epsilon: number = 0.01;
    let guess: number = k / 2;
    let newGuess: number;
    while (Math.abs(guess * guess - k) >= epsilon) {
      newGuess = guess - (guess ** 2 - k) / (2 * guess);
      if (this.results.length == 0) {
        this.results.push(guess.toString() + ' => ' + newGuess.toString());
      } else {
        this.results.push(' => ' + newGuess.toString());
      }
      guess = newGuess;
    }
    this.guess = guess;
  }
}

 そして、コンポーネントのHTMLファイルです。

<p>
ニュートン-ラフソン法でkの根を求めます。
</p>
<p>
f(x) = x<sup>2</sup> - k<br>
x<sub>n + 1</sub> = x<sub>n</sub> - f(x) / f'(x) = x<sub>n</sub> - (x<sub>n</sub><sup>2</sup> - k)/2x<sub>n</sub>
</p>
<form [formGroup]="myGroup" (submit)="onSubmit()">
  <table>
    <tr>
      <th>k</th>
      <td><input type="number" formControlName="k"></td>
    </tr>
    <tr>
      <th></th>
       <td *ngIf="k.invalid" [style.color]="'red'">正の数を入力してください。</td>
    </tr>
    <tr>
      <th></th>
      <td>
        <input type="submit" value="Calc" [disabled]="myGroup.invalid">
      </td>
    </tr>
  </table>
</form>
<ul>
  <li>まず、guess = k / 2 = {{k.value}} / 2 = {{k.value / 2}} とします。<br>guess - (guess<sup>2</sup> - {{k.value}})/ (2 × guess)がより近い値になるので、計算結果の誤差が0.01になるまで繰り返します。<br>
    <p *ngIf="!results">Calcを押して続行します。</p></li>
  <li *ngFor="let ans of results">{{ans}}</li>
</ul>
<p *ngIf="guess">{{guess}} * {{guess}} = {{guess * guess}}</p>

WordPressへの組み込み

 stackblitz.comを見ていると、「Share」メニューがあったので、クリックして見ると、「Embed」タブが出てきましたので、WordPressに組み込めるのではないか、と、調べて組み込んでみました。

 組み込みブロックはあるのですが、stackblitz.com用のものがなく、調べているとここに書いてありました。iframeタグを使って、ということだったので、実験してみましたところ、以下のとおり、実行できました!

 ただ、編集のプレビュー画面では「Calc」ボタンは動きませんでした。iframeではsubmitが無効になっているようです。ただ、確認画面では動作しました。こちらの原因はまた機会があったら調べてみます。公開された後に実行できるかどうかわかりませんので、stackblitz.comのソースコードはこちら、実行結果はこちらです。

まとめ

 Angularのリアクティブフォームを使って、ページを作ってみました。

Angular始めました – stackblitz.comを使ったAngularの簡単な紹介

 今日も見に来てくださって、ありがとうございます。石川さんです。

 先日「Angularはじめました。」ということで、「AngularによるモダンWeb開発 基礎編 第2版」を読み込んで簡単な仕組みを作ってみよう、と、意気込んでいましたがずいぶん経ってしまいました。まずは、この書籍のサンプルをダウンロードして動作確認しようと取り組んでみたところ、もろもろ躓きまして。この本、初学者にはおすすめできませんね。あ、ぼくはすぐに何とか解決しましたよ。プロですからね。エヘン!でも、ちょっと初心者には難しかったかも知れない、と、追加の書籍を二冊購入して色々と勉強していました。学習効果が高いのはアウトプット、ということは分かっているのですが、どうにも本が好きなのでしょうねぇ。すぐに次の本、と買ってしまい割とコレクターみたいなところがあります。で、今日は、Angularってどんなものなの、ということを簡単に紹介したいと思います。そうそう、アウトプットです!

Angularとは何か、そのセットアップについて

 一言でAngularとは何か、というと、ステキなWebアプリを開発するためのフレームワーク、と言ってよいのかな、と、思います。ステキなWebアプリと書きましたが、流行の専門用語ではこれをPWA(Progressive Web Application)と呼んでいて、デスクトップアプリケーションのような体験ができるWebアプリ、ということのようです。これらはGoogleが開発を進めているオープンソースのフロントエンドフレームワークで、半年に一度はメジャーバージョンが更新される、という活発な開発状況です。書籍の数が比較的少ないのは、この頻繁なバージョンアップのせいではないか、ということをぼくは勝手に疑っています。

 ローカルで開発するためには、node.jsというjavascriptを実行するための環境を用意する必要があります。なので、まずはnode.jsをインストールします。node.jsのインストールが終わったら、そのnode.jsの中のインストールコマンドで、Angularをインストールするということになります。コマンドプロンプト、またはターミナルから以下のコマンドを実行してください。

npm install -g @angular/cli

これで、Angularがインストールされたことになります。その後、プロジェクトを作るために、任意のフォルダで以下のコマンドを実行することで、新しいプロジェクトのためのフォルダ(myProject)を作成します。

ng new myProject

開発は、ここから「ng serve」を実行して、作られた結果をブラウザで確認しながら、という感じで進められていくことになります。結構面倒ですよね。もっとお手軽にどんなものか知りたい、というAngular初心者の方々のために、stackblitz.comが便利なので紹介したいと思います。

stackblitz.comを使ってみましょう

 stackblitz.comはブラウザで利用できる統合開発環境(IDE)のクラウドサービスのひとつです。ホームページを見てみるとわかりますが、いくつかの開発に対応しています。今回はAngularですので、以下のアイコンを探してクリックしてみてください。

stackblitz.comのAngular開発環境入口

 すると、ブラウザの中に統合開発環境(IDE)がAngularの新規プロジェクトを開いた状態で開始します。左がフォルダとソースコードの一覧で、真ん中がソースコード、右がAngularを実行した結果、という感じです。

stackblits.comのAngular開発環境(初期画面)

 Angularでは基本的に、コンポーネントという部品の単位で開発することになります。初期表示では「app.component.ts」ファイルが開かれています。このファイルがコンポーネントの処理部分を担います。拡張子が「.html」のファイルが表示内容の骨格部分、「.css」が表示内容の飾りの部分、という感じで考えていただければ、と思います。

2022年2月3日 追記
 stackblitz.comにて本日再び新しいAngularプロジェクトを作成してみたところ、Angularのバージョンが13になっていました。保存してあるリポジトリもバージョンアップしましたので、以降の内容は12→13で読み替えるようお願いいたします。たしかこのバージョンは、半年に一回は更新されるんだよね。。。

 「Start editing to see some magic happen :)」と記載がありますので、ここの部分、さっそく直してみましょう。まずは、日本語使えるのかな、ということでこの部分を日本語にしてみます。左側の「app.component.html」ファイルをクリックして中央に開きます。英語部分を日本語にしてみましょう。

リアルタイムで修正が反映される

 HTMLファイルを編集すればわかりますが、修正と同時に右側のアプリケーション部分が更新されます。日本語もちゃんと表示されましたね。

画面遷移なしで使える機能を盛り込んでみた

 リアクティブフォーム以外で画面遷移しない範囲の簡単な項目をもろもろ盛り込んでみました。完全に自分用の備忘録です。忘れたときに参照しようと思って書いていますので、説明は省きたいと思います。出来上がりイメージは以下のとおりです。

できあがりイメージ

ソースコードは「app.components.ts」と「app.components.html」の二つだけ変更しました。以下の通りです。

import { Component, VERSION } from '@angular/core';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent {
  name = 'Angular ' + VERSION.major;
  today = new Date();
  clicked: boolean;
  message: string;
  count: number = 0;
 list: string[] = [
    "AngularによるモダンWeb開発",
    "Angular超入門",
    "Angularデベロッパーズガイド",
  ];
  ch1: boolean = false;
  ch2: boolean = false;
  ch3: boolean = true;
  opt: string;
  sel: string;
  mul: string;

  onClick() {
    this.clicked = !this.clicked;
  }

  keyup(val: string) {
    this.message = val;
  }

  countup() {
    this.count++;
  }

}
<hello name="{{ name }}"></hello>
<p>
  編集を開始して、魔法が起こるのを確認してください☺
</p>
<ol>
  <li>変数が利用できます。name = 「{{name}}」</li>
  <li>計算もできます。3 + 3 = {{3 + 3}}</li>
  <li>日時を表示できます。{{today | date: "YYYY-MM-dd hh:mm:ss"}}</li>
  <li *ngIf="clicked" (click)="onClick()" [style.background-color]="'cyan'">クリックに反応します。A</li>
  <li *ngIf="!clicked" (click)="onClick()" [style.background-color]="'lightblue'">クリックに反応します。B</li>
  <li><input type="text" #f1 (keyup)="keyup(f1.value)">入力を下に反映します。</li>
  <li>↑の入力をここ「{{message}}」に瞬時に反映させます。</li>
  <li>{{count}}回クリックしました。<button (click)="countup()">Click</button></li>
  <li>ngForで繰り返し処理ができます。</li>
  <ol>
    <li *ngFor="let item of list;let i = index">{{item + ":i = " + i}}</li>
  </ol>
  <li>チェックボックスが使えます。
    <input type="checkbox" [(ngModel)]="ch1">
    <input type="checkbox" [(ngModel)]="ch2">
    <input type="checkbox" [(ngModel)]="ch3"></li>
  <ul>
  <li *ngIf="ch1">一つ目がチェックされています。</li>
  <li *ngIf="ch2">二つ目がチェックされています。</li>
  <li *ngIf="ch3">三つ目がチェックされています。</li>
  </ul>
  <li>ラジオボタンが使えます。
    <label><input type="radio" [(ngModel)]="opt" value="男">男</label>
    <label><input type="radio" [(ngModel)]="opt" value="女">女</label>
    <label><input type="radio" [(ngModel)]="opt" value="不明">不明</label></li>
  <ul>選択値は「{{opt}}」です。</ul>
  <li>プルダウンが使えます。
  <select [(ngModel)]="sel">
    <option *ngFor="let item of list">{{item}}</option>
  </select></li>
  <ul>選択値は「{{sel}}」です。</ul>
  <li>複数選択リストが使えます。
    <select [(ngModel)]="mul" multiple size=3>
      <option *ngFor="let item of list">{{item}}</option>
    </select>
  </li>
  <ul>選択値は「{{mul}}」です。</ul>
</ol>

stackblitz.comとgithub.comで共有できます

  stackblitz.comは、github.comと連携することでソースコードを保存することができます。そして、保存すると他の人と共有することが可能になります。今回はこちらがソースコードの共有です。そして、こちらが実行結果の共有です。

まとめ

 Angularを使うと、これまで簡単にできなかったことが、そこそこ簡単にできるようになりました。

Angular始めました – モダンWeb開発 PWA(Progressive Web Application)始めますよ~!

 今日も見に来てくださって、ありがとうございます。石川さんです。

 ご縁があって、近々Webシステムの開発をすることになるかも知れない、ということになりましたので、諸々検討して、Angularの勉強を始めることにしました。

Angularによる モダンWeb開発

 Webシステムが出始めた当時、4GLの開発をしていたこともあって、あの複雑なだけで開発生産性が高くもなく、操作性の少しも良くない仕組みがどうしても好きになれず、ずっと避けてきていたのですよね。そう、もっさりとしていて遅いし、ユーザーインターフェースも雑で細やかさがなくて、嫌だったのですよね。アプリケーションのインストールがない、というメリット以外には良さを感じられなかったのです。様子が変わってきたのはGoogle Mapあたりからで、それまでのWebアプリケーションらしからぬ動きはかなりの衝撃でしたね。そして、GmailにGoogleドキュメントと、かつてのWebアプリケーションとは違う感じになってきているなぁ、というのは知っていましたが、とうとうWebアプリケーションに手を出す決意をしました。

 と、いうのも、Angularのホームページのチュートリアルに感動して、これは本格的に取り組まねば、という気持ちになりました。まず、実行できるチュートリアルがすべてWebで完結しているのですよね。Visual Studio CodeのようなIDEが自動的に開始します。先日gitPodでも体験していましたがブラウザだけで開発できるのは素晴らしいですね!そして、コンポーネント指向開発という新しい開発方法もなかなか面白いですね。部品が多くなり過ぎたら再利用が難しくなるかも知れないなぁ、という気持ちになりましたが、もうちょっとやれば、何かつかめるかも知れませんね。

 それと、Comppassの「Angular日本ユーザー会のYoutubeライブ!」を視聴しました。予想通りですが、まったくついていけませんでした!まだ開発もしてないですからねぇ。Angularのバージョン13リリースに関して、追加、更新、削除された機能について、語られていました。現状がまったくわからないので、まあついていけないのは当然ですね。参加して、引っ掛かりができるようになれば、という感じです。

 Angularのホームページと、画像の書籍「AngularによるモダンWeb開発 基礎編 第2版」を読み込んだら、簡単な仕組みを作ってみようと思います。

 さあ、がんばるぞー!

実行時エラー’5′:プロシージャの呼び出し、または引数が不正です。

 今日も見に来てくださって、ありがとうございます。石川さんです。
Excelの実行時エラー、1年ほど前に書いた記事のアクセスが一番多いので、みなさんのお役に立っているようです。と、いうことで先日職場にて発生した実行時エラー、今度は「5」です。こちらが発生しました。今回は全くつまづくこともなく、すぐに解決できたのですが、ひとによっては悩みに悩んで解決できない、ということもあるかも知れないと思って記事にすることにしました。(Excelはニーズが多いですからね。アクセスが増えるかもしれない、と調子に乗っております。)

実行時エラー ‘5’:プロシージャの呼び出し、または引数が不正です。

 急いでいる方に結論だけ、お知らせします。こちらは以下のスクリプトで再現できます。

Sub test()
    'B1セルの内容がA1セルより小さいとき、B1セルの背景をグレーにしてフォントを太字にする条件付き書式を設定します
    With Range("B1").FormatConditions.Add(Type:=xlExpression, Formula1:="=AND($B1<$A$1)")
        .Font.Bold = True
        .Interior.Color = RGB(166, 166, 166)
    End With
End Sub

 ちなみにこちらのスクリプト、以前は問題なく実行できていたのですけど、ある日突然「実行時エラー’5’」が発生するようになりました、というお問い合わせでした。ただ、こちらのスクリプト、メールで受信して、ダウンロードして実行しても、ぼくの環境ではエラーにならなかったのです。不思議でしょ。なので、上記のスクリプトを記述してもエラーにならない方もいるかも知れません。その場合は、違う原因かも知れません。

 それで、エラーの出ている人のエクセルとぼくのエクセルをよくよくを見てみますと、セルの列の表記が違うのですよね。

エラーが出ている方のエクセルの列の表記
エラーが発生していないエクセルの列の表記

 そう、列が「1、2、3、4、5、、、」となっているか「A、B、C、D、E、、、」となっているかの違いです。こちらは「ファイル(F)」の「オプション」を選択することで表示される「Excel のオプション」画面から「数式」を選択することで表示される「数式の処理」の「R1C1 参照形式を使用する(R)」をチェックすることで切り替えられます。チェック時は「1、2、3、4、5、、、」で、チェックオフ時は「 A、B、C、D、E、、、」と表示されるようになります。

 今回はこの設定が悪さをしていました。スクリプト作成時はR1C1参照形式を使わない設定になっていたのに、途中で切り替えて保存した、ということのようです。こちらの「R1C1 参照形式を使用する(R)」の設定は、Excel全体の設定ではなくて、ファイルごとに設定を保持するようです。ちょっと動作確認してみたところ、新規作成時は前回エクセル終了時の設定を利用して、既存ファイルを開いたときはそのファイルの設定を利用するようです。

まとめ

 マクロ実行時に今まで動作していたのにこのエラーが出るようになったら、「R1C1 参照形式を使用する(R)」の設定を疑ってみるといいかも知れませんね。マクロで条件付き書式の設定をするときには、参照形式をこの設定にそろえておかないといけない、ということですね。