金額計算などで利用する固定小数点数 decimal
をGlueで扱う場合の注意点を書きます。
GlueのGUIでそのまま設定すると、decimalにはなるのですが、 decimal(10,2)
という型に固定されてしまうといった落とし穴もありますので、そのあたりも。
例として以下のCSVをParquetに変換する方式で考えようと思います。
decimal_col,decimal_38_20_col 0.1,99999999999999999.99999999999999999999 0.2,99999999999999999.9999999999999999999
Crawlerによるスキーマの自動判別に注意
S3にCSVを置いてGlueのCrawlerを回してから、上記のCSVを読み込ませると、いきなり decimal_38_20_col
の値が丸められてしまいます。
+-----------+-----------------+ |decimal_col|decimal_38_20_col| +-----------+-----------------+ |0.1 |1.0E17 | |0.2 |1.0E17 | +-----------+-----------------+
この原因はCrawlerが自動で少数をDoubleとして判別してしまったことで、DynamicFrameで読み込む際に double
にCastされてしまったことが原因となります。
解決策としては以下のように手動やAPIでスキーマを double
から decimal
または string
に変更してあげることです。decimal
に設定してもDynamicFrame上は string
として認識されるので、機能的にはどちらでも同じなのでドキュメンテーション等の観点から選択で良いと思います。
root |-- decimal_col: string (nullable = true) |-- decimal_38_20_col: string (nullable = true) +-----------+--------------------------------------+ |decimal_col|decimal_38_20_col | +-----------+--------------------------------------+ |0.1 |99999999999999999.99999999999999999999| |0.2 |99999999999999999.99999999999999999999| +-----------+--------------------------------------+
Crawlerの設定によっては、Crawler回す度にスキーマ上書きされてしまうので注意してください。
高精度のdecimalへの変換はDataFrameを利用する
GlueのGUIから設定できるDynamicFrameによる変換の場合は、decimal
の精度(桁数)を決めるパラメータが、 precision=10
と scale=2
に固定されてしまいます。型の表現で言うと decimal(10,2)
です。
原因はDynamicFrameのコード見るとわかります。以下のようにハードコードされてしまっているためです。
class DecimalType(AtomicType): def __init__(self, precision=10, scale=2, properties={}): super(DecimalType, self).__init__(properties) self.precision = precision self.scale = scale
https://github.com/awslabs/aws-glue-libs/blob/master/awsglue/gluetypes.py
対応方法としてはDataFrameを利用してCASTする方法があります。
以下は一例ですが、以下のような関数を定義します。
def cast_to_decimal(data_frame,column_name,precision,scale): decimal_type = 'decimal({precision},{scale})'.format(precision=precision,scale=scale) casted_df = data_frame.withColumn(column_name,data_frame[column_name].cast(decimal_type)) return casted_df
DynamicFrameとして読み取った datasource0
をDataFrameに変換して上記の関数、decimalのパラメータと一緒に渡して変換をかけます。
from awsglue.dynamicframe import DynamicFrame casted1 = cast_to_decimal(datasource0.toDF(),'decimal_col',18,3) casted2 = cast_to_decimal(casted1,'decimal_38_20_col',38,20) decimalcasteddyf = DynamicFrame.fromDF(casted2,glueContext,'decimalcasteddf')
正しく decimal(10,2)
より精度上げたスキーマが反映されました。
Redshift Spectrumでクエリしたところです。
Doubleでは誤差が出てしまう、0.1と0.2のSUMを行っても正しく計算可能です。
select sum(decimal_col) from db.table;
- 0.1 + 0.2 はdoubleでは誤差がでる
- decimalでは正しく計算可能
precision=38
にしているので、こちらも問題ありません。
select sum(decimal_38_20_col) from db.table;
今回関連したコンポーネントのdecimal実装
それぞれdecimal型の記載があるページへのリンクです。
Redshift
Glue(Spark)
DecimalType (Spark 2.2.1 JavaDoc)
Parquet
parquet-format/LogicalTypes.md at master · apache/parquet-format · GitHub