佐々木屋

技術的なことから趣味まで色々書きます

NULLを考える②(COALESCE関数)

前回NULLの扱いで、NULLIF関数の話をしました。
これを使うと簡単に0除算エラーの対策が可能になります。

SELECT col_a / NULLIF(col_b, 0) 
FROM SomeTable

演算でNULLが来た場合はNULLを返す仕様をそのまま利用する形です。

基本形はこれだけど、これだと結果が NULLが返ります。C#であればそのまま結果NULLを自作関数に入れて数値等へ変換しちゃう場合が多いですが、NULL許容ではない場合だと少し不便ですよね。そうすると、NULLの場合の条件分岐が必要なので、以下のように長くなってしまいます。

SELECT CASE col_b WHEN 0 THEN 0 ELSE col_a / col_b END
FROM SomeTable

この場合、COALESCE 関数とNULLIF関数を併用すると簡単に解決します。
COALESCE 関数は、与えられた引数で最初に見つけたNULL以外の引数を返します。つまり、以下の場合は「3」が返ります。

SELECT COALESCE(NULL,NULL,3)

つまり、以下のように簡単になるわけです。

    -- col_bが0の場合、0になる
    -- COALESCE関数の第二引数を変更することで、-1とかにもできる
SELECT COALESCE(col_a / NULLIF(col_b, 0), 0)
FROM SomeTable


何かの比率を求める、ってのはよくあることなので、この手法は結構使う形ですので覚えておくと良いでしょう。

型変換は何を使う?(値型⇒String型)

今回は値型⇔string型です。共通認識は前回と一緒です。

  • VB.NETのCIntやCDecなどのC●●●は、CTypeと等価なので全てCTypeで検証
  • 時間計測はSystem.Diagnostics名前空間のStopwatchクラスを利用
  • 繰り返し回数maxは10,000,000回とする
  • それぞれを3回ずつ実行する

今回考えられる候補は、ToString、Convert、CType(VB.NETのみ)です。

int a = 123456789;

sw.Start();
for (int i = 1; i <= max; ++i) {
    string res = a.ToString();
}
sw.Stop();
Console.WriteLine(sw.Elapsed.ToString() + " ToString");
sw.Reset();

sw.Start();
for (int i = 1; i <= max; ++i) {
    string res = Convert.ToString(a);
}
sw.Stop();
Console.WriteLine(sw.Elapsed.ToString() + " Convert");
sw.Reset();
Dim a As Integer = 123456789

sw.Start()
For i As Integer = 1 To max
    Dim res As String = a.ToString()
Next
sw.Stop()
Console.WriteLine(sw.Elapsed.ToString() & " ToString")
sw.Reset()

sw.Start()
For i As Integer = 1 To max
    Dim res As String = Convert.ToString(a)
Next
sw.Stop()
Console.WriteLine(sw.Elapsed.ToString() & " Convert")
sw.Reset()

sw.Start()
For i As Integer = 1 To max
    Dim res As String = CType(a, String)
Next
sw.Stop()
Console.WriteLine(sw.Elapsed.ToString() & " CType")
sw.Reset()

結果は以下の通りです。

項目 C#
VB.NET
ToString 00:00:02.7788599
00:00:01.9934980
00:00:02.7895854
00:00:02.7907917
00:00:02.6248466
00:00:02.7211354
Convert 00:00:02.1907958
00:00:01.9914505
00:00:02.0879492
00:00:02.0197473
00:00:02.1154300
00:00:02.1438101
CType × 00:00:01.5729566
00:00:02.0180006
00:00:02.0006762


以外な結果です。最速はVB.NETのCType(CStr)でした。それでもToString、Convertと大差ありません。ToStringは書式指定が可能ですね。あとはnullの処理をどうするかだけかと思います。

結論、値型⇒String型はToStringで良いでしょう。

管理者権限で自動実行されない・・・

Windows7と8(8.1)まではスタートアップから問題なく管理者権限で実行できていましたが、Windows10からは実行不可となっています。

UACを解除したり、アプリケーションマニフェストを変更したりしてみましたが効果ナシ・・・。ということで、別の方法を模索していましたが、サービスにするほどでもない(起動時一発で済むような)ので、最終的にタスクスケジューラにする方法で落着。

タスクスケジューラを作成します。
f:id:sasaki816:20191222221610j:plain:w600

作成画面の「トリガー」で、「ログオン時」を選択します。
f:id:sasaki816:20191222221615j:plain

あとはお好みで設定します。
マニフェストを変更しないのであれば、管理者権限で実行するにチェックするのをお忘れなく。


UACについてはこちら。
sasaki816.hatenablog.com


マニフェスト変更についてはこちら。
sasaki816.hatenablog.com

トリガーの登録

SQLServerのトリガ登録の覚えです。

INSERTやUPDATEなどでテーブルに変更があった場合に処理を呼ぶ場合に使用するのが「トリガ」です。
トリガーはテーブルごとの設定になります。

CREATE TRIGGER [ トリガー名 ] ON [ 対象テーブル名 ] 
AFTER { [ INSERT] , [ UPDATE ] , [ DELETE ] } 
AS BEGIN
[ 実行したいスクリプト ]
END



例えば、テーブル更新時に登録日時を自動で入れたい場合などは以下のようにします。

CREATE TRIGGER [dbo].[trig_取引明細TEST] ON  [dbo].[取引明細TEST] 
AFTER INSERT
AS BEGIN
UPDATE 取引明細EST SET 売上日 = getdate() WHERE 支店CD = (SELECT 支店CD FROM inserted)
END



insertedテーブルにはINSERT、UPDATEステートメント実行で影響を受けた行がコピーされます。そこのキーを指定すれば実テーブルを更新できる、というわけです。

従って、insertedテーブルにキーが無く、更新テーブルにもキーが無い場合は、実テーブルの意図しない行が更新されてしまう可能性がありますので注意が必要ということになります。


なお、設定はテーブルの「トリガー」に格納されます。
f:id:sasaki816:20191002131330j:plain

iPhoneのSafari対応 Javascriptからクリップボードを設定

会社の携帯が変更になるようで、それに伴い色々設定作業が入ります。
設定作業自体は部下にやらせるとして、やはり不用意にパスワードなどは見せたくないわけで。

そうした時にパスワードをクリップボード経由で扱えばいいのですが、iPhoneクリップボードはちょっと特殊のようで。


仕組みとして直接クリップボードへ値を入れることは出来ません。但し、テキストボックスの値を疑似的に選択状態にすればクリップボードへの代入が可能になります。

手順としては、ダミーのテキストボックス(txtPassword )を1個用意してそこに値を設定し、その値を選択状態にしてクリップボードへコピーします。

var txtPassword = document.getElementById("txtPassword ");
var range = document.createRange();
range.selectNode(txtPassword);
window.getSelection().addRange(range);
document.execCommand('copy');
alert("コピーしました。");

後は空文字をテキストボックスに代入し、それを同様の処理でクリップボードへコピーすればクリップボード内を削除出来ます。ダミーのテキストボックスの値は文字色を白にしたり、コピー直後にリダイレクトして値を削除したり、色々工夫すれば担当者レベルであれば目くらましになるでしょう。

MIMETypeの取得

ファイルのMIMETypeを取得する方法です。
.NET Frameworkによって違いますので、お好みの方をどうぞ。

.NET Framework4.5以上

System.Web.MimeMappingクラスのGetMimeMappingメソッドへファイル名を渡すことで取得可能です。

System.Web.MimeMapping.GetMimeMapping("ファイル名");
System.Web.MimeMapping.GetMimeMapping("ファイル名")

GetMimeMappingメソッドはフルパスを渡しても結果的には一緒です。恐らく内部的にファイル拡張子から判断しているものと思われます。

ですので、拡張子が認識できない場合はnullを返さずoctet-streamを返します。ファイル有無も関係ありません。

.NET Framework4.0まで

System.Web.MimeMappingクラスでは取得できませんので、ClassRootレジストリからContent Typeを取得する方法となります。

使い方によりけりですが、GetMimeMappingメソッドと合わせるならデフォルトでoctet-streamを返す仕様が好ましいです。あえてnullを返すという手もありますが、臨機応変にどうぞ。

private string GetMimeTypeByExtension(string ext) {
    string res = "application/octet-stream";

    try {
        var key = Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(ext);
        if (key != null) {
            var mimetype = key.GetValue("Content Type");
            if (mimetype != null) res = (string)mimetype;
        }
    }
    catch { }

    return res;
}
Private Function GetMimeTypeByExtension(ByVal ext As String) As String
    Dim res As String = "application/octet-stream"

    Try
        Dim key = Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(ext)
        If key IsNot Nothing Then
            Dim mimetype = key.GetValue("Content Type")
            If mimetype IsNot Nothing Then res = CType(mimetype, String)
        End If
    Catch ex As Exception
    End Try

    Return res
End Function



if文のところはnull条件演算子を使えばもっと簡潔に書けますが、.NET Framework4.0では使用できないので少し読みにくいですが仕方ありません。

IIS管理外の画像ファイルを表示させる

ASP.NETIIS管理内のディレクトリの画像ファイルをImageコントロールへ動的に表示させるためには以下で簡単にできますが、

img.ImageUrl = ”相対パス”

これを別のディレクトリに置いた画像を表示させたい場合は、System.Drawing名前空間のBitmapクラスへインスタンス展開して、それをResponse.OutputStreamに向けて保存すれば表示可能です。

protected void Page_Load(object sender, EventArgs e) {
    Bitmap bmp = new Bitmap(@"pngファイル絶対パス");
    Response.ContentType = "image/png";
    Response.Flush();
    bmp.Save(Response.OutputStream, ImageFormat.Png);
    Response.End();
}
Protected Sub Page_Load(sender As Object, e As EventArgs) Handles Me.Load
    Dim bmp As New Bitmap("pngファイル絶対パス")
    Response.ContentType = "image/png"
    Response.Flush()
    bmp.Save(Response.OutputStream, ImageFormat.Png)
    Response.End()
End Sub



で、ここまでは簡単な話ですが、ここで終わってはツマランわけで。
折角なので、MIMEや保存時のImageFormatを表示させるファイルの拡張子によって動的に設定できれば汎用性はぐっとあがります。

最初の1行目のビットマップクラスへの展開によって、ContentTypeとImageFormatを動的に取得するようなクラスを考えればよいことになります。

オブジェクト指向の観点を考慮しつつ構築する演習としてみましょう。
ということで、このまま演習へ引き継ぎます。