【IRIS/Cache】CSVファイルからレコードを生成する(別手段)

CSVファイルを読み取り、テーブルのレコードを生成する方法をご紹介します。

※この記事は下記の方向けになります。
  • CSVファイルから直接テーブルのレコードを生成したい
  • 生成AIからテストデータを簡易に作成したい
  • 独自のCSVパーサーがない

はじめに

CSVファイルから直接テーブルのレコードを生成する方法のご紹介になります。

今回の方法は、「SQL.Import.Mgr.cls」を利用して、CSVをインポートします。
細かい制御はできませんが、CSVファイルの取り込みが簡単に行えるので利便性は高いです。

また、データクラスに余計な関数を増やす必要がないのもメリットです。

同じく簡易な方法でのCSVインポート処理については、下記を参照してください。

CSVファイルを読み込む処理に関しては、下記記事を参考にしてください。

CSVファイルをインポートする

前述した通り、CSVファイルをインポートするには「SQL.Import.Mgr.cls」を使用します。

サンプルは下記になります。
関数の解説は後程行います。

ClassMethod importCSV(fileName As %String, clsNm As %String, rowName As %String, rowType As %String = "", dlm As %String = ",", charSet As %String = "UTF8")
{
	s (makeRflg,openFlg) = 0
	try {
		s mgr = ##class(%SQL.Import.Mgr).%New()
		
		s mgr.FileName = fileName
				
		s sNm = $p(clsNm, ".", 1, *-1)
		, tNm = $p(clsNm, ".", *)
		
		s mobj = ##class(%SQL.Manager.API).%New()
		$$$ThrowOnError(mobj.CheckIdentifier(.sNm))
		$$$ThrowOnError(mobj.CheckIdentifier(.tNm))
				
		s mgr.ClassName = clsNm
		s mgr.TableName = $tr(sNm,".","_")_"."_tNm
		s mgr.IQN=$$$BuildIQN(sNm, tNm)

    // CSVファイルの設定
		s mgr.Delimiter = dlm
		//s mgr.StringQuote = """"
		s mgr.Charset = charSet
		
        // 日時の設定
		s mgr.DateFormat = 10  // 10:YYYY-MM-DD, YYYY/MM/DD
		, mgr.TimeFormat = 1
		, mgr.TimeStampFormat = 8 // YYYY-MM-DD hh:mm:ss
		
        // 取り込み設定
		s mgr.NoCheck = 0      // 1:validationを行わない
		s mgr.HasHeaders = 1   // 1:ヘッダを含む(複数行ヘッダはNG)
		s mgr.DeferIndices = 0 // 1:データインポート後にインデックス作成
		
		// CSVカラムと型を設定
		f pos=1:1:$l(rowName,",") d mgr.ColumnNames.Insert($p(rowName,",",pos))
		i (rowType'="") f pos=1:1:$l(rowType,",") d mgr.ColumnTypes.Insert($p(rowType,",",pos))
		
		
		$$$ThrowOnError(mgr.GenerateImportRoutine()) // 取り込みルーチン生成
		s makeRflg = 1
		
		$$$ThrowOnError(mgr.OpenImport()) // ファイルを開く
        s openFlg = 1
		i (mgr.HasHeaders) $$$ThrowOnError(mgr.ReadHeader(.header)) // ヘッダを飛ばす
	
		s (total,rows,okCnt,ngCnt) = 0
		d {
			d mgr.ImportRows(.rows, .ok,, .done,, total)
			s total = total + rows
			, okCnt = okCnt + ok
		} while('done)
		zw ^IRIS.TempSQLImp($job)
		
		w !!,$$$FormatText("総数:%1, 成功:%2, 失敗:%3 at %4", total, okCnt, mgr.ErrorCount(), $zdt($h,3,1))
		
		$$$ThrowOnError(mgr.BuildIndices())

        // 後処理
		$$$ThrowOnError(mgr.CloseImport())
        s openFlg = 0
		$$$ThrowOnError(mgr.DeleteImportRoutine()) // 取り込みルーチン削除
	}catch e {
		w !,$System.Status.DisplayError(e.AsStatus())
		
		d:(openFlg) mgr.CloseImport() // ファイルを閉じる
		d:(makeRflg) mgr.DeleteImportRoutine() // ルーチン削除
	}
}

【コマンド実行】
※CSVファイルは、「CSVファイルからレコードを生成する」でも使用した、ChatGPTの生成物を利用しています。

s fileName = “D:\Temp\csv\読み込み\patient_data_sample_bom.csv”
s clsNm = “developer.csv.Patient”
s rowName = “xActive,zUpdateCount,dele,storeCd,patientId,漢字氏名,カナ氏名,ローマ字氏名,漢字旧姓,カナ旧姓,性別,生年月日,死亡日時,コメント,新患登録日,ABO血液型,RH血液型,未使用区分”
s rowType = “N,N,N,S,S,S,S,S,S,S,S,D,TS,S,D,S,S,N”

d ##class(developer.csv.Sample).importCSV(fileName,clsNm,rowName,rowType)

解説

関数の引数を解説

引数名説明
fileNameCSVファイルのフルパス
clsNm取り込み対象テーブルの名称
rowNameCSVファイルの列名を指定する
 ※データクラスのプロパティ名と一致させる必要があります
rowTypeCSVファイルの列の型を指定する
 ・D – Date
 ・TS – TimeStamp
 ・N – Numeric
 ・S – String、Stream
 ・T – Time
dlmデリミタ(初期値=”,”)
charSetファイルの文字コード(初期値=”UTF8″)
関数の引数を説明

データクラスの各プロパティの型が、全て文字列であればrowTypeの指定は不要です。

もし、プロパティの型に「%Date」や「%Time」等が存在して、CSVファイルに出力されている場合は、rowTypeの指定を行わないと正確に取り込む事ができません。

取り込み処理の解説

兎にも角にも、先ずは「%SQL.Import.Mgr」をインスタンス化します。

s mgr = ##class(%SQL.Import.Mgr).%New()

プロパティ「ClassName 」「TableName 」「IQN」は必須の設定になります。

s mgr.ClassName = clsNm // データクラス名
s mgr.TableName = $tr(sNm,".","_")_"."_tNm // SQLで使用するクラス名
s mgr.IQN=$$$BuildIQN(sNm, tNm) // 内部用

Delimiter」は、ファイルの区切り文字を指定します。
TSVファイル時は「$c(9)」を設定します。

各列の値が、何かしらの文字列で括られている場合、「StringQuote」に該当の文字を指定します。
 例) [テストの点数, 96, 合格] ←であれば「」をStringQuoteに指定する

ファイルの文字コードを「Charset」に指定します。

s mgr.Delimiter = dlm // デリミタ "," or $c(9)
s mgr.StringQuote = """" // 値を括っている文字 "123","455"の「"」等
s mgr.Charset = charSet // 文字コード

DateFormat」は「10」でOKです。
 ※日付のフォーマットが「YYYY-MM-DD or YYYY/MM/DD」のみで、「YYYYMMDD」は不可。
 ※「DD/MM/YYYY」であれば「9」です。

TimeFormat」も基本は「1」で良いです。※$zth(time,format)の「format」の値を指定します。

日時のフォーマットが「YYYY-MM-DD hh:mm:ss」であれば「TimeStampFormat」は「8」固定、「YYYY-MM-DDThh:mm:ss」であれば、「9」固定になります。

s mgr.DateFormat = 10  // 10:YYYY-MM-DD, YYYY/MM/DD
, mgr.TimeFormat = 1
, mgr.TimeStampFormat = 8

CSVファイルが信頼できる精度のデータであれば、「NoCheck」「DeferIndices」は、「1」を指定しても問題ありません。
こちらの方が処理が早くなります。

CSVファイルのデータに重複しているユニークキーが存在していたり、値の変換「日付の$horolog化」が必要であったり等の理由があれば、両プロパティは「0」を指定します。

ヘッダが設定されている場合は、「HasHeaders」に「1」を設定します。
※ヘッダは1行のみ有効です。複数行は未対応です。

s mgr.NoCheck = 0      // 1:validationを行わない
s mgr.HasHeaders = 1   // 1:ヘッダを含む(複数行ヘッダはNG)
s mgr.DeferIndices = 0 // 1:データインポート後にインデックス作成

カラム(列)の名称と型を設定します。
カラムの名称は、プロパティ名と完全一致である必要があります。

f pos=1:1:$l(rowName,",") d mgr.ColumnNames.Insert($p(rowName,",",pos))
i (rowType'="") f pos=1:1:$l(rowType,",") d mgr.ColumnTypes.Insert($p(rowType,",",pos))

取り込み処理のメイン部分になります。

エラーが発生したら、「^IRIS.TempSQLImp($job, [行数])」に書き込まれるので、zwriteで出力しています。

s (total,rows,okCnt,ngCnt) = 0
d {
	d mgr.ImportRows(.rows, .ok,, .done,, total)
	s total = total + rows
	, okCnt = okCnt + ok
} while('done)
zw ^IRIS.TempSQLImp($job)

引数「rows」を足していくと、読み込んだ総レコード数となります。
引数「ok」を足していくと、読み込み成功したレコード数となります。
引数「done=1」で、最終行の読み込み完了となります。

取り込み部のミソ

レコードが追加される内部処理が下記になります。

この処理は、「mgr.GenerateImportRoutine()」で生成されたルーチンの一部になります。

&sql(INSERT INTO "developer_csv".Patient (xActive,zUpdateCount,dele,storeCd,patientId,漢字氏名,カナ氏名,ローマ字氏名,漢字旧姓,カナ旧姓,性別,生年月日,死亡日時,コメント,新患登録日,ABO血液型,RH血液型,未使用区分)
    VALUES (:valray(1),:valray(2),:valray(3),:valray(4),:valray(5),:valray(6),:valray(7),:valray(8),:valray(9),:valray(10),:valray(11),:valray(12),:valray(13),:valray(14),:valray(15),:valray(16),:valray(17),:valray(18)))

これより、「%SQL.Import.Mgr」を利用してのCSVファイル取り込みは、下記のような特性があります。

%SQL.Import.Mgr.clsの特性
  • 「ColumnNames」に設定するカラム名は、プロパティ名でなければならない
  • 値はプロパティの型に沿って変換する必要がある
  • INSERT INTO節であるため、データの更新・削除は行えない
  • ユニークキーが被ると、漏れなくエラーとなる

おわり

今回はCSVファイルの取り込みをご紹介致しました。

「%SQL.Import.Mgr」を利用すれば、「取り込みPGを別途作成する必要がない」「データクラスに取り込み関数(import)を作成する必要もない」為、非常に使い勝手の良い方法だと思います。

ただし、データの更新(UPDATE)や削除(DELETE)等ができないため、データの無いテーブルを対象にするか、ユニークキーが重複しないデータのみを取り込む事になります。

業務の一環としてCSVファイルの取り込みを行い、データ更新が発生するのであれば、この手法は使えません。

システム開始時のマスタ作成やテストデータの生成等でご活用下さい。