화폐 데이터 모델링
이 페이지의 내용
개요
화폐 데이터를 처리하는 애플리케이션은 종종 화폐의 소수점 단위를 캡처하는 기능이 필요하며, 산술을 수행할 때 소수점 반올림을 정확하게 에뮬레이션해야 합니다. 많은 최신 시스템에서 사용하는 이진 기반 부동소수점 연산(예: float, double)은 정확한 소수점 분수를 표현할 수 없고 어느 정도의 근사치가 필요하므로 화폐 연산에 적합하지 않습니다. 이 제약 조건은 화폐 데이터를 모델링할 때 중요한 고려 사항입니다.
MongoDB에서 숫자 및 비숫자 모델을 사용하여 화폐 데이터를 모델링하는 방법에는 여러 가지가 있습니다.
숫자 모델
숫자 모델은 수학적으로 유효한 정확한 일치 항목을 데이터베이스에 쿼리해야 하거나 $inc
, $mul
, 집계 파이프라인 산술과 같은 서버 측 산술을 수행해야 하는 경우에 적합할 수 있습니다.
다음 접근 방식은 숫자 모델을 따릅니다:
정확한 정밀도를 제공할 수 있는 십진수 기반 부동소수점 형식인 십진수 BSON 유형을 사용합니다.
배율 10의 거듭제곱을 곱하여 화폐 가치를 64비트 정수(
long
BSON 유형)로 변환하기 위해 배율을 사용합니다.
Non-Numeric Model
화폐 데이터에 대해 서버측 산술을 수행할 필요가 없거나 서버측 근사치가 충분한 경우 비숫자 모델을 사용하여 화폐 데이터를 모델링하는 것이 적합할 수 있습니다.
다음 접근 방식은 숫자가 아닌 모델을 따릅니다:
화폐 가치에 두 필드 사용: 한 필드는 정확한 화폐 가치를 숫자가 아닌
string
(으)로 저장하고 다른 필드는 값의 이진 기반 부동 소수점(double
BSON 유형) 근사치를 저장합니다.
숫자 모델
십진수 BSON 유형 사용
The decimal128
BSON types uses the IEEE 754 decimal128
decimal-based floating-point numbering format. BSON 유형과 같은 이진 기반 부동 소수점 형식과 달리 는 소수점 double
decimal128
값을 근사화하지 않으며 화폐 데이터 작업에 필요한 정확한 정밀도를 제공할 수 있습니다.
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
유형에 관한 자세한 내용은 Decimal128을 참조하세요.
값을 십진수로 변환
컬렉션의 값은 일회성 변환을 수행하거나 레코드에 액세스할 때 변환을 수행하도록 애플리케이션 로직을 수정하여 decimal
유형으로 변환할 수 있습니다.
팁
아래에 설명된 절차 대신 버전 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
0} 값은 decimal
price
NumberDecimal("0.01")
연산자를 $multiply
사용하여 와 을 곱하여 적절한 형식의 값으로 변환할 수 있습니다. 다음 집계 파이프라인은 변환된 값을 $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
숫자가 아닌 모델을 사용하여 화폐 데이터를 모델링하려면 두 개의 필드에 값을 저장합니다:
한 필드에서 정확한 화폐 가치를 숫자가 아닌 데이터 유형(예:
BinData
또는string
)으로 인코딩합니다.두 번째 필드에는 정확한 값의 배정밀도 부동 소수점 근사치를 저장합니다.
다음 예에서는 숫자가 아닌 모델을 사용하여 가격에 대해 9.99 USD
, 수수료에 대해 0.25 USD
저장합니다.
{ price: { display: "9.99", approx: 9.9900000000000002, currency: "USD" }, fee: { display: "0.25", approx: 0.2499999999999999, currency: "USD" } }
애플리케이션은 주의를 기울이면 근사치 숫자로 필드에 대해 범위 및 정렬 쿼리를 수행할 수 있습니다. 그러나 쿼리 및 정렬 작업에 근사값 필드를 사용하려면 애플리케이션이 클라이언트 사이드 후처리를 수행하여 정확한 값의 숫자가 아닌 표현을 디코딩한 다음 정확한 화폐 가치에 따라 반환된 문서를 필터링해야 합니다.
이 모델의 사용 사례는 숫자가 아닌 모델을 참조하세요.