10. CSVフォーマットの定義

このチュートリアルでは DMDL を利用してAsakusa Frameworkで利用する データモデル を定義する方法を説明していきます。

結合モデルと集計モデルの定義 の続きで、ここではバッチアプリケーションからCSVフォーマットのファイルを読み書きするための定義を追加していきます。

10.1. 外部入出力に利用するデータモデル

これまでのチュートリアルで6つのデータモデルを作成してきましたが、これらのデータモデルをその用途によって分類してみます。 サンプルアプリケーションの説明 のデータフロー図も合わせて参考にしてください。

入力データとなるファイルに対応するデータモデル
出力データとなるファイルに対応するデータモデル
バッチ処理の処理途中のデータを格納するデータモデル

このうち「入力データとなるファイルに対応するデータモデル」と「出力データとなるファイルに対応するデータモデル」を外部入出力に利用するデータモデルとして利用します。

_images/example-app-dataflow-dmdl-3.png

Asakusa Frameworkには、分散ファイルシステム上に配置したデータに対して Direct I/O というデータ入出力機能を利用してデータを読み書きすることができます。 このチュートリアルではDirect I/Oを使って上記のデータモデルに対応するCSVファイルを読み書きできるように定義していきます。

なお「バッチ処理の処理途中のデータを格納するデータモデル」はデータフロー内の中間データを保持するために利用するデータモデルです。 外部入出力に利用するデータモデルと区別するためにこれを「中間モデル」や「中間データモデル」などと呼ぶことがあります。

上図が示す通り、 売上明細 ( sales_detail ) は外部入力用のデータモデルと中間データモデルの2つの用途で使用しています。 また、 結合モデル ( joined_sales_info ) は中間データモデルとしてのみ使用するため、外部入出力用の定義は不要です。

10.2. Direct I/O CSVのフォーマット情報を定義する

ここでは 売上明細 ( sales_detail ) のデータモデルに対して、Direct I/O CSVのフォーマット情報定義を追加する過程を説明していきます。

データモデルをDirect I/O CSVと連携させるには対象となるデータモデル定義に @directio.csv 属性を指定します。

models.dmdl
"売上明細"
@directio.csv
sales_detail = {
    ...
};

@directio.csv 属性には、CSVフォーマットに関する様々な設定を定義することができます。

前項の サンプルアプリケーションの説明 - データフォーマット では、CSVフォーマットについて以下のような仕様が定義されていました。

  • 各CSVファイルの文字エンコーディングはUTF-8として扱う。
  • 各CSVファイルの1行目は各項目の内容を示すヘッダとして扱い、実データとしては扱わない。
  • 各CSVファイルの日付項目のフォーマットは yyyy-MM-dd、日時項目のフォーマットは yyyy-MM-dd HH:mm:ss とする。

この仕様に沿って @directio.csv 属性にフォーマットに関する指定を追加します。

models.dmdl
"売上明細"
@directio.csv (
    charset = "UTF-8",
    has_header = TRUE,
    date = "yyyy-MM-dd",
    datetime = "yyyy-MM-dd HH:mm:ss"
)
sales_detail = {
    ...
};

@directio.csv 後ろに (...) で囲ったブロックを追加し、この中に設定項目の要素を定義します。要素間は , で区切ります。

charset はファイルの文字エンコーディングを指定します。既定値は UTF-8 であるため設定しなくても同じ動作になります。

has_header を有効にすることで、1行目をヘッダとして扱います。 このオプションを有効にしている場合にデータモデルの各プロパティに対して @directio.csv.field 属性を指定することで以下のように動作します。

  • ファイル入力時:指定したヘッダ名と入力ファイルが持つヘッダ名が一致するかをチェックする
  • ファイル出力時:指定したヘッダ名で出力ファイルのヘッダを生成する

date , datetime はそれぞれ DATE 型、 DATETIME 型の表現形式を指定します。

続けてデータモデルの各プロパティに対して @directio.csv.field 属性を追加して、ヘッダ名を指定します。

models.dmdl
"売上明細"
@directio.csv (
    charset = "UTF-8",
    has_header = TRUE,
    date = "yyyy-MM-dd",
    datetime = "yyyy-MM-dd HH:mm:ss"
)
sales_detail = {

  "売上日時"
  @directio.csv.field(name = "日時")
  sales_date_time : DATETIME;

  "店舗コード"
  @directio.csv.field(name = "店舗コード")
  store_code : TEXT;

  ...
};

また @directio.csv.file_name 属性を指定したプロパティは、ファイル入力時のファイル名を格納します。

models.dmdl
sales_detail = {
  ...

  "ファイル名"
  @directio.csv.file_name
  file_name : TEXT;
};

今回のアプリケーションでは、入力となる売上明細のファイル名をデータモデルとして保持しておき、 売上明細のエラーチェック時に、該当したレコードが含まれるファイル名をエラー情報に受け渡します。

なお、この属性を指定したプロパティはCSVフィールドとしては扱われないため、 CSVファイルフォーマットに関係なく入力ファイル名を保持することができます。

売上明細 ( sales_detail ) の最終的なデータモデル定義は、以下のようになります。

models.dmdl
"売上明細"
@directio.csv(
    has_header = TRUE,
    datetime = "yyyy-MM-dd HH:mm:ss"
)
sales_detail = {

    "売上日時"
    @directio.csv.field(name = "日時")
    sales_date_time : DATETIME;

    "店舗コード"
    @directio.csv.field(name = "店舗コード")
    store_code : TEXT;

    "商品コード"
    @directio.csv.field(name = "商品コード")
    item_code : TEXT;

    "数量"
    @directio.csv.field(name = "数量")
    amount : INT;

    "販売単価"
    @directio.csv.field(name = "販売単価")
    unit_selling_price : INT;

    "販売金額"
    @directio.csv.field(name = "販売金額")
    selling_price : INT;

    "ファイル名"
    @directio.csv.file_name
    file_name : TEXT;
};

同様の手順で models.dmdl に含まれる以下のデータモデルにDirect I/O CSVのフォーマット情報を定義しましょう。

10.3. 終わりに

全てのデータモデルを定義した後にデータモデルクラスの生成を行い、6つのデータモデルの生成が成功していることをコンソールで確認してください。

このチュートリアル終了時点のDMDLスクリプト models.dmdl は、次のようになります。

models.dmdl
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
"売上明細"
@directio.csv(
    has_header = TRUE,
    datetime = "yyyy-MM-dd HH:mm:ss"
)
sales_detail = {

    "売上日時"
    @directio.csv.field(name = "日時")
    sales_date_time : DATETIME;

    "店舗コード"
    @directio.csv.field(name = "店舗コード")
    store_code : TEXT;

    "商品コード"
    @directio.csv.field(name = "商品コード")
    item_code : TEXT;

    "数量"
    @directio.csv.field(name = "数量")
    amount : INT;

    "販売単価"
    @directio.csv.field(name = "販売単価")
    unit_selling_price : INT;

    "販売金額"
    @directio.csv.field(name = "販売金額")
    selling_price : INT;

    "ファイル名"
    @directio.csv.file_name
    file_name : TEXT;
};

"店舗マスタ"
@directio.csv(
    has_header = TRUE
)
store_info = {

    "店舗コード"
    @directio.csv.field(name = "店舗コード")
    store_code : TEXT;

    "店舗名称"
    @directio.csv.field(name = "名称")
    store_name : TEXT;
};

"商品マスタ"
@directio.csv(
    has_header = TRUE,
    date = "yyyy-MM-dd"
)
item_info = {

    "商品コード"
    @directio.csv.field(name = "商品コード")
    item_code : TEXT;

    "商品名"
    @directio.csv.field(name = "商品名")
    item_name : TEXT;

    "商品部門コード"
    @directio.csv.field(name = "部門コード")
    department_code : TEXT;

    "商品部門名"
    @directio.csv.field(name = "部門名")
    department_name : TEXT;

    "商品カテゴリコード"
    @directio.csv.field(name = "カテゴリコード")
    category_code : TEXT;

    "商品カテゴリ名"
    @directio.csv.field(name = "カテゴリ名")
    category_name : TEXT;

    "商品単価"
    @directio.csv.field(name = "単価")
    unit_selling_price : INT;

    "マスタ登録日"
    @directio.csv.field(name = "登録日")
    registered_date : DATE;

    "マスタ適用開始日"
    @directio.csv.field(name = "適用開始日")
    begin_date : DATE;

    "マスタ適用終了日"
    @directio.csv.field(name = "適用終了日")
    end_date : DATE;
};

"売上明細+商品マスタ"
joined joined_sales_info
= sales_detail -> {

    "商品コード"
    item_code -> item_code;

    "販売数量"
    amount -> amount;

    "売上合計"
    selling_price -> selling_price;
} % item_code
+ item_info -> {

    "商品コード"
    item_code -> item_code;

    "カテゴリコード"
    category_code -> category_code;
} % item_code;

"カテゴリ別売上集計"
@directio.csv(
    has_header = TRUE
)
summarized category_summary = joined_sales_info => {

    "カテゴリコード"
    @directio.csv.field(name = "カテゴリコード")
    any category_code -> category_code;

    "販売数量"
    @directio.csv.field(name = "販売数量")
    sum amount -> amount_total;

    "売上合計"
    @directio.csv.field(name = "売上合計")
    sum selling_price -> selling_price_total;
} % category_code;

"エラー情報"
@directio.csv(
    has_header = TRUE,
    datetime = "yyyy-MM-dd HH:mm:ss"
)
error_record = {

    "ファイル名"
    @directio.csv.field(name = "ファイル名")
    file_name : TEXT;

    "売上日時"
    @directio.csv.field(name = "日時")
    sales_date_time : DATETIME;

    "店舗コード"
    @directio.csv.field(name = "店舗コード")
    store_code : TEXT;

    "商品コード"
    @directio.csv.field(name = "商品コード")
    item_code : TEXT;

    "エラーメッセージ"
    @directio.csv.field(name = "メッセージ")
    message : TEXT;
};