SQL Server/SQL Database再入門 第1回 Decimal型とMoney型の違い


小数点を含む計算は難しい

プログラミングの世界の小数点を含む計算というのは難しいと私は思っています。注意しているつもりなのですが、過去に何度も何度も小数点が絡む部分で不具合を作りこんできました。なので、小数点を含む計算をするときには以下を確認するようにしています。

  • 10進数なのか2進数なのか
  • リテラル(ソースコードに数値や文字列を直接に記述した定数)に割り当てられる型は何か
  • 異なる型同士の計算をした場合、結果の型は何になるのか
  • 有効桁を超えた場合はどのような挙動になるのか

小数点をSQL Serverで扱う際に特に紛らわしいと感じるのがDecimal型とMoney型の二つの型があることです。この二つはまったく異なる性質を持っているのですが、似たようなものととしてなんとなく扱われている開発現場もあるのではないかと思います。

Money型とDECIMAL(19,4)型の違い

以下をみてください。

DECLARE @money1 MONEY
DECLARE @money2 MONEY
DECLARE @money4 MONEY

DECLARE @decimal1 DECIMAL(19,4)
DECLARE @decimal2 DECIMAL(19,4)
DECLARE @decimal4 DECIMAL(19,4)
SET @money1 = 10
SET @money2 = 3
SET @money4 = @money1 / @money2

SET @decimal1 = 10
SET @decimal2 = 3
SET @decimal4 = @decimal1 / @decimal2

SELECT @money4 AS [money], @decimal4 AS [decimal]

こうしてみると同じ結果になります。

今度は以下を実行してみましょう。

SET @money1 = 10
SET @money2 = 3
SET @money3 = 10000
SET @money4 = @money1 / @money2 * @money3

SET @decimal1 = 10
SET @decimal2 = 3
SET @decimal3 = 10000
SET @decimal4 = @decimal1 / @decimal2 * @decimal3

SELECT @money4 AS [money], @decimal4 AS [decimal]

今度はさきほどとは異なり、違う結果になってしまいました。

どちらも同じ数値「3.3333」に「10000」を掛けただけです。それなのにMONEY型の結果は「33333.00」になり、DECIMAL(19,4)は「33333.3333」になったのです。

違いを調べる

実際にそれぞれの型を指定してテーブルを作って確認しています。結果をみるとLength(長さ、バイト数)の部分に違いがありますが、それ以外は何も違わないように見えます。

CREATE TABLE MoneyDecimal (
money1 MONEY,
decimal1 DECIMAL(19,4)
);
GO

EXEC sp_help MoneyDecimal;
GO

計算列を定義して、計算後の型がどうなっているのか確認してみると、明確な違いがでてきます。Money型同志の掛け算の結果は長さ(バイト数)も有効桁も小数点の位置も変わりませんが、Decimal(19, 4)型の同志の掛け算の結果は長さ(バイト数)も有効桁も小数点の位置も変わってしまっていることが分かると思います。

CREATE TABLE MoneyDecimal (
money1 MONEY,
money2 AS money1 * money1,
decimal1 DECIMAL(19,4),
decimal2 AS decimal1 * decimal1
);
GO

EXEC sp_help MoneyDecimal;
GO

つまり、Decimalは計算内容によって型が変わる場合があるということです。もう少しいろんなパターンを試してみましょう。

CREATE TABLE MoneyDecimal (
money1 MONEY,
decimal1 DECIMAL(19,4),
[decimal + decimal] AS decimal1 + decimal1,
[decimal - decimal] AS decimal1 - decimal1,
[decimal / decimal] AS decimal1 / decimal1,
[decimal * decimal] AS decimal1 * decimal1,
[money + decimal] AS money1 + decimal1,
[money - decimal] AS money1 - decimal1,
[money / decimal] AS money1 / decimal1,
[money * decimal] AS money1 * decimal1,
[decimal + money] AS decimal1 + money1,
[decimal - money] AS decimal1 - money1,
[decimal / money] AS decimal1 / money1,
[decimal * money] AS decimal1 * money1
);
GO

要するに、Decimal型の場合、演算をすると演算結果はなるべ丸めないように有効桁や小数点が調整されるということです。
一方でMoney型の場合は小数点の位置は固定されており、切り捨てが発生します。内部的に整数として値を保持して演算していて、最後に小数点を調整するような挙動になっているのかなと思っています。

参考情報

シリーズ SQL Server/SQL Database再入門

なんとなくならSQL Serverを使えている人のためのSQL Server/SQL DatabaseのTips集です。

  1. Decimal型とMoney型の違い
  2. Index SeekとIndex Scan
  3. クエリ毎の性能指標の確認方法を知る
  4. SQL Databaseでパーティション テーブルとパーティション インデックス
  5. 統計とクエリの関係
  6. 結合方法:NESTED LOOP、MERGE、HASH