在数字列表之间拆分浮点数

时间:2015-08-04 13:29:29

标签: sql oracle

我在数字之间拆分0.00xxx浮点值时遇到问题。 以下是输入data的示例 0是1-3个浮点数的总和。 结果我希望看到四舍五入的数字而不会丢失1-3的总和:

在:

0 313.726
1 216.412
2 48.659
3 48.655

OUT:

0 313.73
1 216.41
2 48.66
3 48.66

它应该如何运作: 想法是将最低的休息(在我们的例子中,它是从值216.412的0.002)分成最高值。 0.001到48.659 = 48.66和0.001到48.655 = 48.656之后我们可以在不丢失数据的情况下舍入数字。

昨天坐在这个问题后,我找到了解决方案。我认为的查询应该是这样的。

 select test.*, 
 sum(value - trunc(value, 2)) over (partition by case when id = 0 then 0 else 1 end) part, 
 row_number() over(partition by case when id = 0 then 0 else 1 end order by value - trunc(value, 2) desc) rn,
 case when row_number() over(partition by case when id = 0 then 0 else 1 end order by value - trunc(value, 2) desc) / 100 <= 
 round(sum(value - trunc(value, 2)) over (partition by case when id = 0 then 0 else 1 end), 2) then trunc(value, 2) + 0.01 else trunc(value, 2) end result
 from test;

但是对我来说,在获得结果时添加const值“0.01”是很奇怪的。

有任何改进此查询的建议吗?

4 个答案:

答案 0 :(得分:1)

在呈现结果时,您可以使用round()sql函数。 Round()的第二个参数是您想要将数字四舍五入的有效位数。在测试表上发出此选择:

select id, round(value, 2) from test;

为您提供以下结果

0   313.73
1   216.41
2   48.66
3   48.65

通常,您可以使用存储的数字进行求和,然后使用round()函数来显示结果:这是一种使用完整有效数字进行求和然后使用round()函数进行呈现的方法最终结果:

select sum(value) from test where id != 0  

给出结果:313.726

select round(sum(value), 2) from test where id != 0  

给出结果:313.73

顺便说一句,请允许我两点观察:

1)你给id = 3的舍入让我感到困惑:48.654轮到48.65而不是48.66两位有效数字。我错过了什么吗?

2)严格来说,这个问题不是标签上的pl / sql问题。它完全属于sql领域。但是,pl / sql中也有一个round()函数,并且适用相同的原则。

答案 1 :(得分:1)

如果我告诉你,想要使用舍入,因为舍入部分数字与舍入总数不匹配。

在这种情况下,应用简单的技巧。你使用round除了最后一个数字。最后一个分数计算为舍入和和到目前为止的舍入部分之间的差异(除了最后一个之外)。

您可以用以下分析功能表达这一点

 WITH total AS
   (SELECT id, value, ROUND(value,2) value_rounded FROM test WHERE id = 0
   ),
   rounded AS
   ( SELECT id, value, ROUND(value,2) value_rounded FROM test WHERE id != 0
   )
 SELECT id, value_rounded FROM total
 UNION ALL
 SELECT id,
   CASE
     WHEN row_number() over (order by id) != COUNT(*) over ()
     THEN
       /* not the last row - regular result */
       value_rounded
     ELSE
       /* last row - corrected result */
       (select value_rounded from total) - SUM(value_rounded) over (order by id ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING)
   END AS value
 FROM rounded
 ORDER BY id;

请注意,这是对最后一个数字的测试

 row_number() over (order by id) != COUNT(*) over ()

这是从开始(UNBOUNDED PRECEDING)到最后一个(最后一个)的所有部分的总和

  SUM(value_rounded) over (order by id ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING

我将您的数据分成两个来源总计 - 一行包含总数和舍入部分。

<强>更新

在某些情况下,最后更正的数字显示与原始值的“丑陋”大差异, 因为一个圆角方向的差异高于另一个圆角方向的差异。

以下选择考虑了这一点并分配了各部分之间的差异。

下面的示例在很多0.05s

的例子中说明了这一点
 WITH nums AS
   (SELECT  rownum id, 0.005 value  FROM dual  connect by level <= 5   
   ),
   rounded AS
   ( SELECT id, value, ROUND(value,2) value_rounded FROM nums
   ),
   with_diff as 
   (SELECT id, value, value_rounded,
   -- difference so far - between the exact SUM and SUM of rounded parts
   -- cut to two decimal points
   floor(100* (
   sum(value) over (order by id ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) -
   sum(value_rounded) over (order by id ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)))
   / 100  diff_so_far
   FROM rounded),
   delta_diff as 
   (select id, value, value_rounded,DIFF_SO_FAR, 
     DIFF_SO_FAR - LAG(DIFF_SO_FAR,1,0) over (order by ID) as diff_delta
   from with_diff)
 SELECT id, value,  
   CASE
     WHEN row_number() over (order by id) != COUNT(*) over ()
     THEN
       /* not the last row - take the rounded value and ... */
       value_rounded +
       /* ... add or subtract the delta difference */
       diff_delta           
     ELSE
       /* last row - corrected result */
       round(sum(value) over(),2) - SUM(value_rounded + diff_delta) over (order by id ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING)
   END AS value_rounded, diff_delta
 FROM delta_diff
 ORDER BY id;


         ID      VALUE VALUE_ROUNDED DIFF_DELTA
 ---------- ---------- ------------- ----------
          1       ,005             0      -0,01 
          2       ,005           ,01          0 
          3       ,005             0      -0,01 
          4       ,005           ,01          0 
          5       ,005           ,01      -0,01 

答案 2 :(得分:1)

select id, value, 
       case when id <> max(id) over () then round(value, 2) 
            else round(value, 2) - sum(round(value, 2)) over () +
                 round(first_value(value) over (order by id), 2) * 2 
       end val_rnd
  from test

输出:

    ID      VALUE    VAL_RND
------ ---------- ----------
     0    313.726     313.73 
     1    216.413     216.41 
     2     48.659      48.66 
     3     48.654      48.66 

以上查询有效,但它将所有差异移到最后一行。这不是“诚实的”,也许不是你追求的其他场景。 最“不诚实”的行为是可观察到的,具有大量值,均等于0.005

要进行全面分发,您需要:

  • 对子行中的所有原始值求和,并从ID为
  • 的行中减去舍入的舍入值
  • 使用row_number()按舍入值和原始值之间的差异顺序对子行进行排序(可能是降序,取决于差异的符号,使用sign()abs),
  • 指定每行增加.01(或差异<0时减少)直到达到差异/ .01(使用case when),
  • 具有id = 0的联合行,包含舍入的和
  • 可选择排序结果。

在一个查询中很难(但可以实现)。替代方案是一些PL / SQL过程或函数,它们可能更具可读性。

答案 3 :(得分:0)

基于以下规则的pragamtic解决方案:

1)检查舍入和与舍入部分之和之间的差异。

 select round(sum(value),2) - sum(round(value,2)) from test where id != 0;

2)应用这种差异

e.g。如果你得到0.01,这意味着一个四舍五入的部分必须增加0.01

如果你得到-.02,则意味着两个四舍五入的部分必须减少0.01

下面的查询简单地纠正了最后N个部分:

with diff as (
select round(sum(value),2) - sum(round(value,2)) diff from test where id != 0
), diff_values as
(select sign(diff)*.01 diff_value, abs(100*diff) corr_cnt
from diff)
select id, round(value,2)
  + case when row_number() over (order by id desc) <= corr_cnt then diff_value else 0 end result
from test, diff_values where id != 0
order by id;


        ID     RESULT
 ---------- ----------
         1     216,41 
         2      48,66 
         3      48,66 

如果校正记录的数量远远高于2,请检查数据和舍入精度。