SQL
案分
支払手数料は、支払いごとに一括で計上するのが普通だと思いますが、プロジェクトごとに分割することもあるようです。ただ、浮動小数点を扱う場合と違い、金額は整数なので、丸めによって誤差が生じます。その誤差をどう処理するか考えてみました。
3つのプロジェクトにかかわる支払いがあり、その支払手数料が525円の場合を例にあげます。
プロジェクト |
支払額(税別) |
A |
4,315円 |
B |
1,777円 |
C |
2,155円 |
合計 |
8,247円 |
支払手数料(税込) |
525円 |
→ |
支払手数料(税別) |
500円 |
|
|
|
仮払消費税 |
25円 |
まず、支払手数料(税別)を支払額で重みづけして案分します。
プロジェクト |
計算 |
支払手数料 |
A |
500円×4,315円÷8,247円 |
261.6102円 |
B |
500円×1,777円÷8,247円 |
107.7361円 |
C |
500円×2,155円÷8,247円 |
130.6535円 |
案分した支払手数料の小数点以下を四捨五入します。
プロジェクト |
支払手数料 |
四捨五入 |
A |
261.6102円 |
262円 |
B |
107.7361円 |
108円 |
C |
130.6535円 |
131円 |
合計 |
|
501円 |
※例では、合計で1円の誤差が生じています。
ケース1 誤差が0より大きい場合
支払手数料とその小数点以下を四捨五入したものとの差で大きいほうから並べ替えます。
プロジェクト |
支払手数料 |
四捨五入 |
差 |
A |
261.6102円 |
262円 |
0.3898円 |
C |
130.6535円 |
131円 |
0.3465円 |
B |
107.7361円 |
108円 |
0.2639円 |
合計 |
|
501円 |
|
上から誤差の値の行数だけ1を引いて補正します。
プロジェクト |
支払手数料 |
四捨五入 |
補正後 |
A |
261.6102円 |
262円 |
261円 |
← |
C |
130.6535円 |
131円 |
131円 |
B |
107.7361円 |
108円 |
108円 |
合計 |
|
501円 |
500円 |
※この場合、誤差は1なので、上から1行だけ1を引きます。例えば誤差が3であったなら(例の場合、四捨五入した値の合計が503円であったなら)、上から3行だけ四捨五入した値から1を引いて補正します。
ケース2 誤差が0より小さい場合
支払手数料とそれを四捨五入したものとの差で小さいほうから並べ替えます。
上から誤差の値の行数だけ1を足して補正します。
ケース3 誤差がない場合
OKです。
SQLだとこんな感じです。
declare @data table (
idx int identity (0, 1),
project nvarchar(32),
payment money, -- 支払金額(税別)
charge1 money, -- 支払手数料(小数点以下あり)
charge2 money, -- 支払手数料(小数点以下なし)
error money) -- 小数点以下の誤差
declare @renum table (
idx int,
rowno int identity (0, 1))
declare @charge1 money -- 支払手数料
declare @charge2 money -- 合計 支払手数料
declare @amount money -- 合計 支払金額
declare @error money -- 支払手数料の誤差
set @charge1 = 500
insert into @data (project, payment) values (N'A', \4315)
insert into @data (project, payment) values (N'B', \1777)
insert into @data (project, payment) values (N'C', \2155)
--
-- 案分
--
select @amount = sum(payment) from @data
if @amount is not null and @amount != 0
begin
update @data set charge1 = (@charge1 * payment) / @amount -- 案分
update @data set charge2 = round(charge1, 0) -- 四捨五入
update @data set error = charge2 - charge1 -- 誤差
select @charge2 = sum(charge2) from @data -- 四捨五入したものを合計
set @error = @charge2 - @charge1 -- 全体の誤差
if @error > 0
begin
-- 誤差の大きい順に並べ替え
insert into @renum (idx) select idx from @data order by error desc
-- 上から誤差の値の行数だけ1を引く
update l set charge2 -= 1 from @data as l
join @renum as r on r.idx = l.idx where r.rowno < @error
end
else if @error < 0
begin
-- 誤差の小さい順に並べ替え
insert into @renum (idx) select idx from @data order by error
-- 上から誤差の値の行数だけ1を足す
update l set charge2 += 1 from @data as l
join @renum as r on r.idx = l.idx where r.rowno < -@error
end
end
--
-- 結果
--
select idx, project, payment, charge2 from @data
union all select max(idx) + 1, N'合計', sum(payment), sum(charge2)
from @data order by idx
idx |
project |
payment |
charge2 |
0 |
A |
4315.00 |
261.00 |
1 |
B |
1777.00 |
108.00 |
2 |
C |
2155.00 |
131.00 |
3 |
合計 |
8247.00 |
500.00 |
支払いと支払手数料なので正の整数で考えていますが、その他の正負の整数を扱う場面でも応用できます。また、小数点以下の丸めは切り捨て(round(*,0,1))でも同じ結果になると思います。