システム開発」カテゴリーアーカイブ

OracleからPostgreSQLへのデータ移行 – 2.バイナリデータ

OracleからPostgreSQLへのデータ移行 – 1.型指定 であらかたデータ移行はできましたが、次に問題になったのがバイナリデータでした。

最初にORA-24345エラーが発生。これについては ORA-24345 が発生するならこのディレクティブを1にしろ、と書かれているLONGTRUNCOK 1を設定することで解消しました。オンラインドキュメントではLONGTRUNKOK になっていますが、configファイルでは LONGTRUNCOK です。

そしてblobカラムに格納されていたデータが、一見移行できているように見えて途中で途切れているなどの問題が発生しました。

LONGREADLENに十分なサイズを指定することで解消しますが、余り大きくすると今度はOutOfMemoryが発生してしまいます。

OutOfMemoryを防ぐにはDATA_LIMITを小さくすれば良いのですが、そうするとデータ移行に時間がかかるようになります。

これらを踏まえて、1レコードあたりのデータサイズが大きなテーブルとそれ以外のテーブルについて出力を分けることにしました。

1レコードあたりのデータサイズの大きなテーブルをカンマ区切りでリストアップ。configファイルを二つ作り、一方には先のテーブル名をEXCLUDE に指定。もう一方には ALLOW に指定します。

ALLOW に指定した方のconfigではLONGREADLENを大きくとり、DATA_LIMITを小さくします。

これで通常のデータについては移行を手早く、サイズの大きなデータについてもエラーを回避し確実に移行できるようになりました。

OracleからPostgreSQLへのデータ移行 – 1.型指定

弊社でもOracleからPostgreSQLへの移行作業を行うことがあり、データ移行には定番でしょうが ora2pg を使用しています。

基本的にドキュメントのとおりに実行知ればデータ移行が完了してしまう優れものですが、いくつかハマったポイントもあるので書いてみたいと思います。

ちなみにLinuxで動作させる方が多いかと思いますが、ActivePerl をインストールすればWindowsでも問題もなく動作します。

ActivePerlのインストール後にパッケージマネージャでdmakeをインストールし、ora2pgのソースコードを適当なフォルダに展開、コマンドプロンプトからperl .\Makefile.pl して dmake installで終わりでした。

Oracleクライアントのインストールの手間を考えるとWindows環境で実行する方が簡単かもしれません。

さてora2pgでのデータ移行ですが、とりあえず接続先情報以外はconfigをそのまま使用して移行してみたのですが自動で行われた型指定とプログラムの動作に問題が生じました。

例えば、NUMBER(4,0)等がsmallintで移行されてしまいます。これが.NET Framework2の頃に作成された、SELECT結果を DataTable に格納、そのまま DataGridView にバインドしているようなプログラムで問題になりました。intを想定していたものが PostgreSQL+smallintだとshortになっており、DataErrorイベントが発生することに。

これをデータベース側で回避するとした場合、ora2pgのコンフィグファイルで変換する型を指定することになります。DATA_TYPE項目ですね。

Oracleの型:PostgreSQLの型, Oracleの型2:PostgreSQLの型2… のように指定するだけですが2つほどポイントが。

一つは、精度指定のカンマをバックスラッシュでエスケープすることです。これは公式ドキュメントのDATA_TYPEの項目にもしっかり書かれているのですが、読み飛ばしていたため、スクリプトで型変換リストを作成した際にハマってしまいました。

二つ目は、精度指定で小数点以下桁数が0の場合にはカンマ以降が不要という点です。公式ドキュメントの例に DATA_TYPE NUMBER(*\,0):bigint という記述があり、これを参考に NUMBER(4\, 0):integer と書いたのですが numeric(4) に変換されてしまいました。

最初、指定が反映されない理由分からず悩みましたが、実はNUMBER(4):integer のようにカンマ以降が不要でした。

数値データの取得後、プログラム上での扱いについては他にも小数点以下が全て0の場合に10 になるか 10.00 になるかといった問題も発生しました。これについては弊社ではプログラム側で対応しました。

MDBの簡易編集

弊社では、AccessのMDBを使用しているシステムがあります。

このシステムをメンテナンスするために、PCにAccessをインストールをしていました。

Accessも安くはありません。何か他に変わるソフトが無いのか探していたところに

同僚から

『MDB簡易編集』

というソフトを教えていただきました。

フリーソフトでインストールが不要で、MDBの作成や編集をすることができます。

今では、このソフトのみでシステムのメンテナンスができます。

一度、お試ししては、どうでしょうか。

Windows10の動作が遅いと感じたときにやるべきこと

必要のないサービスは停止すれば、驚異的に動作速度が向上します。

“SuperFetch” サービス

“Windows Search” サービス

この二つのサービスはCPUとHDDアクセスを沢山消費します。電気も消費します。

サービスの停止方法はネットをググってもらえれば簡単に探すことができます。

Windows10が遅いと感じた方はぜひお試しください。

アプリもOneDrive、Backup and Sync from Googleも ネット上のファイルバックアップソフトなんで必要なければアンインストールした方がよいでしょう。仕事で使うPCには必要のないアプリです。

読みづらいプロパティの読み方

これはプログラミング言語の世界の読み方なので正解は無いのでしょうが、一般的にはこんな感じで読むのでしょうか。

background-gradient:バックグランド グラディエント

border-collapse:ボーダー コラップス

border-radius:ボーダー レイディアス

char : キャラ

column-count:カラム カウント

height:ハイト(ヘイトじゃないよ)

horizontal-align:ホリゾンタル アライン

inherit:インヘリット

margin:マージン

NULL:ナル(ヌルじゃないよ)

opacity:オパシティ

ping:ピン

position:relative:ポジション レラティブ

text-align:テキスト アライン

text-overflow:ellipsis:テキスト エリプシス

theme:シーム・テーマ

vertical-align:バーティカル

visibility:hidden:ビジビリティ ヒドゥン

width:ワイズ・ウィッズ

word-wrap:ワード ラップ

WORDで表の中に図形を挿入すると表の中の文字が上にずれる現象

標準の設定でWORDは何故に思うように動作しないんだろうか?  もうWORD嫌いって方が実に多いですよね。 私もその一人です。 そこで前回のエクセルのTipsに続き、今回はWORDです。

Microsft WORDで表セル内の文字の上に図形オートシェイプを重ねるようにマウスのドラッグ操作で書き込むと文字が上にずれてしまい文字と図形がきれいに重なりません。 重なるように図形をマウスで微妙に動かしても、ちょうど良いと思うところに動いてくれません。(イライラです)

男女を◯で選択する時など、表形式の文書でよくあるパターンです。

以下の方法でこの現象を解消できます。

1)表の外に図形を挿入します。(ここ重要です)
2)作成した図形を選択して右クリックします。
3)ポップアップメニューから「オートシェイプの書式設定」を選択します。
4)「色と線」タブを選択します。
5)塗りつぶしの色は「色なし」を選択します。
6)「レイアウト」タブを選択します。
7)折り返しの種類と配置で「前面」を選択します。
8)「詳細設定」ボタンをクリックします。
9)「位置」タブの「アンカーを段落に固定する」にチェックマークを入れてオンにします。
10)「OK」ボタンをクリックして「レイアウト」ダイアログを閉じます。
11)「OK」ボタンをクリックして「オートシェイプの書式設定」ダイアログを閉じます。
12)作成した図形を表内の希望の位置へドラッグして移動させます

PS. ちなみに以上のことを「オートシェイプの規定値に設定」に出来ないのが悔しいです。

ユーザインターフェースの大切さ

『インターフェースの大切さ』というお話です。

先日の出来事ですが、80歳代であろうおばあちゃんが、ATMの操作で中々先に進められない。

街の中にあるATMコーナーだから後ろには長蛇の列。

後ろへは『金額をお確かめください』というメッセージが聞こえてくる。

何度入力しても同じメッセージが帰ってきて、何度か操作の回数を超えると『最初から操作をしてください。』のメッセージとなり、操作のやり直し。

何度も何度も延々と操作している。並んでる人を見たら、おばあちゃんだから仕方ないという顔もあれば『何やってんだ』という顔も見える。

振り込め詐欺なのかという心配もあり、思い切って近くによって『どうしましたか?大丈夫ですか?何かお手伝いすることはありますか?』と尋ねても、『自分でやらないと次来た時にできないと困るから結構です。』との返事。

悪いとは思いつつ、打たれた金額を見たら千円未満の数字を入力している。

最近では銀行内にあるATMは硬貨も引き出すことはできるのだが、街中にあるATMは概ねお札しか引き出せない。

これは明らかにメッセージが親切ではない。老若男女が使うATMなのに自身の操作の何が間違っているかが分からないメッセージが原因なのだ。

『おばあちゃん、ここはお札しか引き出せないですよ。100円玉とか硬貨は引き出せませんよ』と教えてあげた。

弊社のSEが設計したシステムならば、この場面では1000円未満の金額は入力できない画面仕様にするであろう。 たとえば ,000円 が最初から表示されていればどうだろうか。

さらにその上には『このATMでは1,000円未満の金額は引き出すことはできません。』と表示させればどうだろうか。

入力内容に対してエラーメッセージ表示させるというプログラマー的な発想より、操作するものがいかに簡単に操作方法が分かり間違えを最初から入力させないインターフェース。

操作者に寄り添ったインターフェースがシステムの完成度が高く長く使えるシステムなのである。

コンピュータシステムを守る

巷を賑やかしているランサムウェア「WannaCry」。

防御のためには、一般的に以下の作業を薦められています。

1.Windowsを最新の状態にする。

2.ウイルス対策ソフトのウィルス定義データベースを最新にする。

3.知らない相手のメールは開かないし、添付ファイルは絶対に開かない。

ここまでしたから絶対安全だと確信している方がほとんどではないでしょうか。

しかしながら、絶対はないのです。

日本人の性格は、安全のために何をするかを一生懸命考えます。

でも何かが起きたときのための準備をどうするかを考えるのは苦手なようです。

自分だけは何も起きない。起きてほしくない。起きないでしょう。起きるわけない。

ほとんどの方がそう考えます。

しかしながら、そのときは突然思いがけない方向から訪れるかもしれないのです。

ウイルスの感染からデータを守る時に一番先に考えないといけないことは、

OS・プログラム・データのバックアップを

ネットワークから切断された媒体に保存することなのです。

そしてそれを、できるだけ遠隔地に保存することなのです。

ClosedXMLでExcelファイルに画像を埋め込む

公式版が画像埋め込みをサポートしています。
以下の内容は不要な情報となりました。

Excelファイルの出力でClosedXMLを使うことがありましたが、どうも公式のものは画像埋め込みをサポートしていない模様。

「ClosedXML 画像」で検索すると上位に表示されるページC#でExcelに画像データを埋め込む を参考にさせていただきClosedXMLImageSupportを入手しましたが、本家の最新版と開きがあるようなので、ClosedXMLImageSupportから画像埋め込み関係のコードを本家最新版に取り込みます。

ClosedXMLImageSupportのソリューション内にあるClosedXML_Sandboxが動けば良しとして、ざくっと追加、追記します。

  • ClosedXML\Excel\Drawings\
    • 追加:IXLMarker.cs
    • 追加:IXLPicture.cs
    • 追加:XLMarker.cs
    • 追加:XLPicture.cs
  • ClosedXML\Excel\
    • IXLWorksheet.cs
      • 追加:System.Collections.Generic.List Pictures();
      • 追加:void AddPicture(Drawings.IXLPicture pic);
    • XLWorkbook_Save.cs
      • 追加:using A = DocumentFormat.OpenXml.Drawing;
      • 追加:using Xdr = DocumentFormat.OpenXml.Drawing.Spreadsheet;
      • 追加:private static void AddPictureAnchor(WorksheetPart worksheetPart, Drawings.IXLPicture picture)
      • 内部処理追加:private static void GenerateWorksheetPartContent(WorksheetPart worksheetPart, XLWorksheet xlWorksheet, SaveContext context)
    • XLWorksheet.cs
      • 追加:private List pictures
      • 追加:public List Pictures()
      • 追加:public void AddPicture(Drawings.IXLPicture pic)

これでSandboxを実行するとExcelファイルが出力されて画像も埋め込まれています。
が、しかし。

他サイトの記事にもあるように画像の縦横比を無視してセルにフィットしてしまいます。

ClosedXMLImageSupportから取り込んだXLPictureは、AddMarker()でXLMarkerを追加して、一つ目がfrom、二つ目がtoとしてセルの範囲を指定するようになっています。このtoにあたるMarkerのセル位置とオフセットを上手いこと指定してあげれば画像の縦横比を維持して画像を埋め込むことができるようです。

しかし画像はfromで指定したセルの左上から、toで指定したセルの左上までは拡張されてしまうため、画像が収まる縦横セル数を求めた上でオフセットを指定する必要があります。十分大きな範囲を指定してからどうにかする、という手は使えません。

画像のサイズはピクセルですが、Excelのセル高さ(行高さ)はポイント、セル幅(カラム幅)は標準フォントの数字が何文字表示できるか。なので換算が必要になります。

ポイントからピクセルは簡単ですがカラム幅からピクセルはかなり面倒です。

  1. そのExcelブックの標準フォントとサイズを調べ
  2. TextRenderer.MeasureText()にTextFormatFlags.NoPaddingを指定し、0~9について描画時の最大フォント幅を求め
  3. フォント幅からセルのパディングをもとめ
    (パディング = 切り上げ(フォント幅  / 4) * 2 + 1。*2は両サイドにパディングが入るため。+1はグリッドライン分)
  4. カラムのピクセル幅 = カラム幅 * フォント幅 + パディング

となるようです。
このあたりは
ttps://social.msdn.microsoft.com/Forums/en-US/9a6a9785-66ad-4b6b-bb9f-74429381bd72/margin-padding-in-cell-excel?forum=os_binaryfile

関連ドキュメント : MS-OI29500 の 2.1.595 Part 1 Section 18.3.1.13, col (Column Width & Formatting) f. を参考にしました。

これをカラム毎に計算しながら、fromで指定されたセルから画像が上手く収まるカラム数を求めるのは正直辛いです。

ということでClosedXMLImageSupportから取り込んだXLWorkbook_Save.csのAddPictureAnchor()を弄ることでtoを指定せずに縦横比を維持して画像を埋め込むことができるようにしてみました。

まず、XLPictureのフィールドとしてあって使われていないNoChangeAspect, NoMove, NoResizeをIXLPictureでプロパティとして定義しておきます。XLPicture側ももちろん変更。

その上で、XLWorkbook_Save.csに取り込んだAddPictureAnchor()

private static void AddPictureAnchor(WorksheetPart worksheetPart, Drawings.IXLPicture picture)
{
    /* 省略 */
    var markers = picture.GetMarkers();
    Xdr.TwoCellAnchor twoCellAnchor;
    Xdr.FromMarker fMark;
    Xdr.ToMarker tMark;
    if (markers.Count == 2)
    {
    fMark = new Xdr.FromMarker
    {
        ColumnId = new Xdr.ColumnId(markers[0].ColumnId.ToString()),
        RowId = new Xdr.RowId(markers[0].RowId.ToString()),
        ColumnOffset = new Xdr.ColumnOffset((markers[0].ColumnOffset + picture.PaddingX).ToString()),
        RowOffset = new Xdr.RowOffset((markers[0].RowOffset + picture.PaddingY).ToString())
    };
    tMark = new Xdr.ToMarker
    {
        ColumnId = new Xdr.ColumnId(markers[1].ColumnId.ToString()),
        RowId = new Xdr.RowId(markers[1].RowId.ToString()),
        ColumnOffset = new Xdr.ColumnOffset((markers[1].ColumnOffset + picture.PaddingX).ToString()),
        RowOffset = new Xdr.RowOffset((markers[1].RowOffset + picture.PaddingY).ToString())
    };
    }
    else
    {
    fMark = new Xdr.FromMarker
    {
        ColumnId = new Xdr.ColumnId(markers[0].ColumnId.ToString()),
        RowId = new Xdr.RowId(markers[0].RowId.ToString()),
        ColumnOffset = new Xdr.ColumnOffset((markers[0].ColumnOffset + picture.PaddingX).ToString()),
        RowOffset = new Xdr.RowOffset((markers[0].RowOffset + picture.PaddingY).ToString())
    };
    tMark = new Xdr.ToMarker
    {
        ColumnId = new Xdr.ColumnId(markers[0].ColumnId.ToString()),
        RowId = new Xdr.RowId(markers[0].RowId.ToString()),
        ColumnOffset = new Xdr.ColumnOffset((markers[0].ColumnOffset + extentsCx + picture.PaddingX).ToString()),
        RowOffset = new Xdr.RowOffset((markers[0].RowOffset + extentsCy + picture.PaddingY).ToString())
    };
    }
    twoCellAnchor = new Xdr.TwoCellAnchor(
    fMark, tMark,
        new Xdr.Picture(
            new Xdr.NonVisualPictureProperties(
                new Xdr.NonVisualDrawingProperties { Id = nvpId, Name = picture.Name },
                new Xdr.NonVisualPictureDrawingProperties(new A.PictureLocks { NoChangeAspect = true, NoMove = true, NoResize = true })
            ),
            new Xdr.BlipFill(
                new A.Blip { Embed = drawingsPart.GetIdOfPart(imagePart), CompressionState = A.BlipCompressionValues.Print },
                new A.Stretch(new A.FillRectangle())
            ),
            new Xdr.ShapeProperties(
                new A.Transform2D(
                    new A.Offset { X = 0, Y = 0 },
                    new A.Extents { Cx = extentsCx, Cy = extentsCy }
                ),
                new A.PresetGeometry { Preset = A.ShapeTypeValues.Rectangle }
            )
        ),
        new Xdr.ClientData()
    );

    worksheetDrawing.Append(twoCellAnchor);
}

これを↓こんな感じで。

private static void AddPictureAnchor(WorksheetPart worksheetPart, Drawings.IXLPicture picture)
{
    /* 省略 */
    var xdrPicture = new Xdr.Picture(
                new Xdr.NonVisualPictureProperties(
                    new Xdr.NonVisualDrawingProperties { Id = nvpId, Name = picture.Name },
                    // ここでIXLPictureに追加したプロパティを使用。
                    new Xdr.NonVisualPictureDrawingProperties(new A.PictureLocks { NoChangeAspect = picture.NoChangeAspect, NoMove = picture.NoMove, NoResize = picture.NoResize })
                ),
                new Xdr.BlipFill(
                    new A.Blip { Embed = drawingsPart.GetIdOfPart(imagePart), CompressionState = A.BlipCompressionValues.Print },
                    new A.Stretch(new A.FillRectangle())
                ),
                new Xdr.ShapeProperties(
                    new A.Transform2D(
                        new A.Offset { X = 0, Y = 0 },
                        new A.Extents { Cx = extentsCx, Cy = extentsCy }
                    ),
                    new A.PresetGeometry { Preset = A.ShapeTypeValues.Rectangle }
                )
            );

    var markers = picture.GetMarkers();

    var fMark = new Xdr.FromMarker
    {
        ColumnId = new Xdr.ColumnId(markers[0].ColumnId.ToString()),
        RowId = new Xdr.RowId(markers[0].RowId.ToString()),
        ColumnOffset = new Xdr.ColumnOffset((markers[0].ColumnOffset + picture.PaddingX).ToString()),
        RowOffset = new Xdr.RowOffset((markers[0].RowOffset + picture.PaddingY).ToString())
    };

    if (markers.Count == 2)
    {
        var tMark = new Xdr.ToMarker
        {
            ColumnId = new Xdr.ColumnId(markers[1].ColumnId.ToString()),
            RowId = new Xdr.RowId(markers[1].RowId.ToString()),
            ColumnOffset = new Xdr.ColumnOffset((markers[1].ColumnOffset + picture.PaddingX).ToString()),
            RowOffset = new Xdr.RowOffset((markers[1].RowOffset + picture.PaddingY).ToString())
        };

        var twoCellAnchor = new Xdr.TwoCellAnchor(fMark, tMark, xdrPicture, new Xdr.ClientData());

        worksheetDrawing.Append(twoCellAnchor);
    }
    else
    {
        var ext = new Xdr.Extent { Cx = extentsCx, Cy = extentsCy };
        var oneCellAnchor = new Xdr.OneCellAnchor(fMark, ext, xdrPicture, new Xdr.ClientData());

        worksheetDrawing.Append(oneCellAnchor);
    }
}

元のコードはMarker一つの場合にfromをtoとしても使い、常にTwoCellAnchorを使っていますが、これをOneCellAnchorを使うよう変更しています。あとnew PictureLocks()の箇所ですね。
これでExcelで普通に画像を挿入した場合と同じように埋め込まれるようになりました。

using (FileStream strm = new FileStream(@"sample.png", FileMode.Open))
{
    XLPicture pic = new XLPicture { ImageStream = strm, Name = "test image" };
    pic.AddMarker(new XLMarker { ColumnId = 2, RowId = 3 });
    worksheet.AddPicture(pic);
}

ついでにXLPictureのResize()も少し手を加えると拡大縮小して埋め込めるようになります。

IXLPictureのプロパティにしたNoMoveをtrueにすると埋め込んだ画像が移動できなくなります。NoResizeをtureにするとサイズが変更できなくなります。

XLPictureのPddingX, PddingY(またはEMUOffsetX, EMUOffsetY)にピクセル単位で値を指定するとXLMarkerで指定したセルからのオフセットとして画像が埋め込まれます。XLMarkerのColumnOffset、RowOffsetでもオフセットによる位置指定ができますが、こちらはEMUで指定する必要があるので面倒です。

EEMUOffsetX, EMUOffsetYは設定した値がPaddingX、PaddingYから取得される際にConvertToEmu()を通るので、「設定したピクセル値がEMUに変換されるオフセット」のようです。EMUのつもりで設定するとおかしなことになります。