モデル通貨データ
Overview
通貨データを扱うアプリケーションでは、多くの場合、通貨の端数単位をキャプチャする能力が必要であり、算術演算を実行する際には正確な精度で10進数の丸めをエミュレートする必要があります。最近の多くのシステムで使用されているバイナリベースの浮動小数点演算(つまり、float、double)は、正確な10進数を表すことができず、ある程度の近似が必要なため、金銭計算には適していません。この制約は、通貨データをモデル化する際に重要な考慮事項となります。
MongoDB で数値モデルと非数値モデルを使用して通貨データをモデル化するには、いくつかのアプローチがあります。
数値モデル
数値モデルは、データベースに対して数学的に有効な正確な一致を照会する必要がある場合や、サーバー側の演算(例: $inc
、 $mul
)や集計パイプライン演算を実行する必要がある場合に適しています。
次のアプローチは、数値モデルに従います。
正確な精度を提供できる10進ベースの浮動小数点形式であるDecimal BSON 型を使用します。
スケール係数を使用して、10 スケール係数の累乗を乗算することで、金銭の値を 64 ビットの整数(
long
BSON 型)に変換します。
Non-Numeric Model
金融データに対してサーバー側で算術演算を実行する必要がない場合、またはサーバー側の近似で十分な場合は、非数値モデルを使用して金銭データをモデル化するのが適切な場合があります。
次のアプローチは、数値以外のモデルに従います。
通貨値に 2 つのフィールドを使用します。1 つのフィールドには正確な金銭の値が非数値
string
としてストアされ、もう 1 つのフィールドには値のバイナリベースの浮動小数点(double
BSON 型)の近似値がストアされます。
数値モデル
10進数 BSON 型の使用
decimal128
BSON 型は、IEEE 754 decimal128
10進ベースの浮動小数点数形式を使用します。double
BSON 型などのバイナリベースの浮動小数点形式とは異なり、 decimal128
10 進数値を近似せず、通貨データの処理に必要な正確な精度を提供できます。
mongosh
では、 decimal
値は割り当てられ、 Decimal128()
コンストラクターを使用してクエリされます。 次の例では、ノード価格を含むドキュメントをgasprices
コレクションに追加します。
db.gasprices.insertOne( { "date" : ISODate(), "price" : Decimal128("2.099"), "station" : "Quikstop", "grade" : "regular" } )
次のクエリは、上記のドキュメントと一致します。
db.gasprices.find( { price: Decimal128("2.099") } )
decimal
タイプの詳細については、「Decimal 128」を参照してください。
値を10進数に変換する
コレクションの値は、1 回限りの変換を実行するか、レコードにアクセスするときに変換を実行するようにアプリケーション ロジックを変更することで decimal
型に変換できます。
Tip
以下に概説する手順の代わりに、バージョン 4.0 以降では、 $convert
とそのヘルパー$toDecimal
演算子を使用して値をDecimal128()
に変換できます。
ワンタイム コレクション変換
コレクションは、コレクション内のすべてのドキュメントを繰り返し処理し、金額を decimal
型に変換し、ドキュメントをコレクションに書き戻すことで変換されます。
注意
decimal
値を新しいフィールドとしてドキュメントに追加し、新しいフィールドの値が検証されたら古いフィールドを削除することを強くお勧めします。
警告
必ず分離されたテスト環境でdecimal
変換をテストしてください。データファイルが作成または変更されると、以前のバージョンとの互換性がなくなり、小数点を含むデータファイルのダウングレードはサポートされません。
スケールファクター変換
スケール係数アプローチを使用し、金銭の値をセント数を表す64ビットの整数として保存した次のコレクションを考慮してください。
{ "_id" : 1, "description" : "T-Shirt", "size" : "M", "price" : NumberLong("1999") }, { "_id" : 2, "description" : "Jeans", "size" : "36", "price" : NumberLong("3999") }, { "_id" : 3, "description" : "Shorts", "size" : "32", "price" : NumberLong("2999") }, { "_id" : 4, "description" : "Cool T-Shirt", "size" : "L", "price" : NumberLong("2495") }, { "_id" : 5, "description" : "Designer Jeans", "size" : "30", "price" : NumberLong("8000") }
long
値は、 $multiply
演算子を使用してprice
とNumberDecimal("0.01")
を乗算することで、適切に形式されたdecimal
値に変換できます。次の集計パイプラインは、変換された値を$addFields
ステージの新しいpriceDec
フィールドに割り当てます。
db.clothes.aggregate( [ { $match: { price: { $type: "long" }, priceDec: { $exists: 0 } } }, { $addFields: { priceDec: { $multiply: [ "$price", NumberDecimal( "0.01" ) ] } } } ] ).forEach( ( function( doc ) { db.clothes.save( doc ); } ) )
集計パイプラインの結果は、 db.clothes.find()
クエリを使用して確認できます。
{ "_id" : 1, "description" : "T-Shirt", "size" : "M", "price" : NumberLong(1999), "priceDec" : NumberDecimal("19.99") } { "_id" : 2, "description" : "Jeans", "size" : "36", "price" : NumberLong(3999), "priceDec" : NumberDecimal("39.99") } { "_id" : 3, "description" : "Shorts", "size" : "32", "price" : NumberLong(2999), "priceDec" : NumberDecimal("29.99") } { "_id" : 4, "description" : "Cool T-Shirt", "size" : "L", "price" : NumberLong(2495), "priceDec" : NumberDecimal("24.95") } { "_id" : 5, "description" : "Designer Jeans", "size" : "30", "price" : NumberLong(8000), "priceDec" : NumberDecimal("80.00") }
decimal
値を持つ新しいフィールドを追加しない場合は、元のフィールドを上書きできます。次のupdateMany()
メソッドは、まずprice
が存在し、それがlong
であることを確認し、次にlong
の値をdecimal
に変換してprice
フィールドに格納します。
db.clothes.updateMany( { price: { $type: "long" } }, { $mul: { price: NumberDecimal( "0.01" ) } } )
結果は、 db.clothes.find()
クエリを使用して確認できます。
{ "_id" : 1, "description" : "T-Shirt", "size" : "M", "price" : NumberDecimal("19.99") } { "_id" : 2, "description" : "Jeans", "size" : "36", "price" : NumberDecimal("39.99") } { "_id" : 3, "description" : "Shorts", "size" : "32", "price" : NumberDecimal("29.99") } { "_id" : 4, "description" : "Cool T-Shirt", "size" : "L", "price" : NumberDecimal("24.95") } { "_id" : 5, "description" : "Designer Jeans", "size" : "30", "price" : NumberDecimal("80.00") }
Non-Numeric Transformation:
非数値モデルを使用し、金銭の値を正確に表現する string
として保存した次のコレクションを考慮してください。
{ "_id" : 1, "description" : "T-Shirt", "size" : "M", "price" : "19.99" } { "_id" : 2, "description" : "Jeans", "size" : "36", "price" : "39.99" } { "_id" : 3, "description" : "Shorts", "size" : "32", "price" : "29.99" } { "_id" : 4, "description" : "Cool T-Shirt", "size" : "L", "price" : "24.95" } { "_id" : 5, "description" : "Designer Jeans", "size" : "30", "price" : "80.00" }
次の関数は、最初に price
が存在することと、それが string
であることを確認し、次に string
値を decimal
値に変換して priceDec
フィールドに格納します。
db.clothes.find( { $and : [ { price: { $exists: true } }, { price: { $type: "string" } } ] } ).forEach( function( doc ) { doc.priceDec = NumberDecimal( doc.price ); db.clothes.save( doc ); } );
この関数は、コマンドラインには何も出力しません。結果は、 db.clothes.find()
クエリを使用して確認できます。
{ "_id" : 1, "description" : "T-Shirt", "size" : "M", "price" : "19.99", "priceDec" : NumberDecimal("19.99") } { "_id" : 2, "description" : "Jeans", "size" : "36", "price" : "39.99", "priceDec" : NumberDecimal("39.99") } { "_id" : 3, "description" : "Shorts", "size" : "32", "price" : "29.99", "priceDec" : NumberDecimal("29.99") } { "_id" : 4, "description" : "Cool T-Shirt", "size" : "L", "price" : "24.95", "priceDec" : NumberDecimal("24.95") } { "_id" : 5, "description" : "Designer Jeans", "size" : "30", "price" : "80.00", "priceDec" : NumberDecimal("80.00") }
アプリケーション ロジックの変換
アプリケーション ロジック内からdecimal
型への変換を実行できます。このシナリオでは、アプリケーションはレコードにアクセスする際に変換を実行するように修正されます。
一般的なアプリケーション ロジックは次のとおりです。
新しいフィールドが存在し、それが
decimal
型であることをテストします。新しい
decimal
フィールドが存在しない場合は、古いフィールド値を適切に変換して作成します
古いフィールドを削除
変換されたレコードを保持
スケール係数の使用
スケール係数アプローチを使用して通貨データをモデル化するには、
金額に必要な最大精度を決定します。たとえば、アプリケーションでは、
USD
通貨の金銭値に対して1セントの10分の1までの精度が必要になる場合があります。値に10の累乗を掛けて金額値を整数に変換します。これにより、必要な最大精度が整数の最下位桁になります。たとえば、必要な最大精度が1セントの10分の1である場合は、金額に1000を掛けます。
変換された金銭的価値を保存します。
たとえば、次の例では 9.99 USD
を 1000単位でスケーリングして、精度を最大10分の1セントまで維持しています。
{ price: 9990, currency: "USD" }
このモデルでは、特定の通貨価値に対して次のことを前提としています。
スケール係数は通貨に対して一貫しています。つまり、特定の通貨に対して一貫したスケール係数です。
スケール係数は通貨の一定かつ既知の特性です。つまり、アプリケーションは通貨からスケール係数を決定できます。
このモデルを使用する場合、アプリケーションは一貫して値の適切なスケーリングを実行する必要があります。
このモデルのユースケースについては、「数値モデル 」を参照してください。
Non-Numeric Model
非数値モデルを使用して通貨データをモデル化するには、次の2つのフィールドに値を保存します。
1つのフィールドに、正確な金額を数値以外のデータ型(
BinData
やstring
など)としてエンコードします。2番目のフィールドには、正確な値の倍精度浮動小数点近似値を保存します。
次の例えでは、非数値モデルを使用して、価格に9.99 USD
を、手数料に0.25 USD
格納します。
{ price: { display: "9.99", approx: 9.9900000000000002, currency: "USD" }, fee: { display: "0.25", approx: 0.2499999999999999, currency: "USD" } }
ある程度手をかければ、アプリケーションは数値近似を使用してフィールドに対して範囲クエリとソートクエリを実行できます。ただし、クエリ操作とソート操作に近似フィールドを使用するには、アプリケーションがクライアント側で後処理を実行して正確な値の非数値表現をデコードし、返されたドキュメントを正確な金額に基づいてフィルタリングする必要があります。
このモデルのユースケースについては、「非数値モデル 」を参照してください。