SQL舍入百分比使总和100% - 1/3为0.34,0.33,0.33

时间:2012-02-06 14:13:25

标签: sql teradata rounding

我目前正在尝试使用百分比列拆分一个值。但由于大多数百分比值是1/3,我无法在值中得到两个小数点100%的aboslute。例如:

Product    Supplier      percentage         totalvalue        customer_split
                         decimal(15,14)   (decimal(18,2)       decimal(18,2)
--------   --------     ------------     ---------------  ---------------
Product1    Supplier1    0.33            10.00                3.33
Product1    Supplier2    0.33            10.00                3.33
Product1    Supplier3    0.33            10.00                3.33

所以,这里我们在值列中缺少0.01,供应商希望随机将任何一个供应商的这个缺失值0.01。我一直试图用两组带有临时表的SQL来完成这项工作,但是有没有简单这样做的方法。如果可能的话,如何在上述行之一的百分比列中获得0.34? 0.01是可忽略的值,但是当值列为1000000000时,它是重要的。

4 个答案:

答案 0 :(得分:3)

运行它,它会给出如何解决问题的想法。 我创建了一个名为orders的表,其ID很容易理解:

create table orders(
customerID int)

insert into orders values(1)
go 3

insert into orders values(2)
go 3

insert into orders values(3)
go 3

这些值代表您拥有的33%

1   33.33
2   33.33
3   33.33

现在:

create table #tempOrders(
customerID int,
percentage numeric(10,2))

declare @maxOrder int
declare @maxOrderID int
select @maxOrderID = max(customerID) from orders
declare @total numeric(10,2)
select @total =count(*) from orders
insert into #tempOrders
    select customerID, cast(100*count(*)/@total as numeric(10,2)) as Percentage
    from orders
    group by customerID

update #tempOrders set percentage = percentage + (select 100-sum(Percentage) from #tempOrders)
where customerID =@maxOrderID

此代码将基本上计算百分比和具有最大ID的顺序,然后它得到100与百分比之和的差异,并将其添加到具有maxID(您的随机顺序)的订单

select * from #tempOrders

1   33.33
2   33.33
3   33.34

答案 1 :(得分:3)

听起来你在这里正在做某种类型的“分配”。这是一个常见的问题,只要您尝试从较高的粒度分配到较低的粒度,并且您需要能够正确地重新聚合到总值。

当处理较大的分数时,这会成为一个更大的问题。

例如,如果我尝试将总值(例如$ 55.30除以8)除以八个桶中的每一个得到$ 6.9125的十进制值。我应该把一个回合到6.92美元,剩下的到6.91美元吗?如果我这样做,我将失去一分钱。我不得不将第一轮赔率为6.93美元,其他赔率为6.91美元。当你添加更多的桶来划分时,这会变得更糟。

此外,当你开始回合时,你会引入诸如“33.339应该舍入到33.34还是33.33?”这样的问题。

如果您的业务逻辑是这样的,您只想要超过2个有效余数可能存在,并将其“随机”添加到其中一个美元值,这样您就不会丢失任何美分,@ Diego在右侧跟踪这个。

在纯SQL中执行它有点困难。首先,你的百分比不是1/3,它是.33,它将产生9.9的总值,而不是10.我会将其存储为比率或高精度十进制字段(.33333333333333)。 / p>

P    S    PCT           Total  
--   --   ------------  ------  
P1   S1   .33333333333  10.00   
P2   S2   .33333333333  10.00   
P3   S3   .33333333333  10.00   


SELECT 
   BaseTable.P, BaseTable.S, 
   CASE WHEN BaseTable.S = TotalTable.MinS 
      THEN BaseTable.BaseAllocatedValue + TotalTable.Remainder
      ELSE BaseTable.BaseAllocatedValue
   END As AllocatedValue
FROM
(SELECT
   P, S, FLOOR((PCT * Total * 100)) / 100 as BaseAllocatedValue,
   FROM dataTable) BaseTable
INNER JOIN
(SELECT
   P, MIN(S) AS MinS,
   SUM((PCT * Total) - FLOOR((PCT * Total * 100)) / 100) as Remainder,
FROM dataTable
GROUP BY P) as TotalTable
ON (BaseTable.P = TotalTable.P)

根据每个供应商的产品总数,您的计算结果似乎相等。如果是,则删除百分比可能是有利的,而只是将每个供应商的项目数存储在表格中。

如果还可以存储一个标志,指示应该为其应用余数值的行,则可以根据该标志而不是随机分配。

答案 2 :(得分:1)

使用窗口聚合函数这应该是一项简单的任务。您可能已经将它们用于计算customer_split

totalvalue  / COUNT(*) OVER (PARTITION BY Product) as customer_split

现在总结一下customer_splits,如果总值有差异,则将其添加(或减去)到一个随机行。

SELECT 
   Product                       
   ,Supplier                      
   ,totalvalue                    
   ,customer_split 
    + CASE
         WHEN COUNT(*) 
              OVER (PARTITION BY Product
                    ROWS UNBOUNDED PRECEDING) = 1 -- get a random row, using row_number/order you might define a specific row
         THEN totalvalue - SUM(customer_split)
                           OVER (PARTITION BY Product)
         ELSE 0
      END
FROM 
 (
   SELECT
      Product                       
      ,Supplier                      
      ,totalvalue                    
      ,totalvalue / COUNT(*) OVER (PARTITION BY Product) AS customer_split
   FROM dropme
 ) AS dt

答案 3 :(得分:0)

经过多次试验和测试,我认为我找到了更好的解决方案

<强>观

  1. 根据您的条件获取所有(计数(*))
  2. 获取Row_Number()
  3. 检查是否(Row_Number()值&lt; Count(*)) 然后选择round(curr_percentage,2) 其他     获取所有其他百分比(带圆形)的总和并从100减去它 此步骤将在每次超出最后一次时选择当前百分比 100 - 所有其他百分比的总和
  4. 这是我的代码的一部分

    Select your_cols
          ,(Select count(*) from [tbl_Partner_Entity] pa_et where [E_ID] =@E_ID) 
           AS cnt_all
         ,(ROW_NUMBER() over ( order by pe.p_id)) as row_num
         ,Case when (
            (ROW_NUMBER() over ( order by pe.p_id)) < 
            (Select count(*)   from [tbl_Partner_Entity] pa_et where [E_ID] =@E_ID))
          then round(([partnership_partners_perc]*100),2)
          else 
             100-
        ((select sum(round(([partnership_partners_perc]*100),2))  FROM [dbo].
         [tbl_Partner_Entity] PEE where [E_ID] =@E_ID and pee.P_ID != pe.P_ID))
          end AS [partnership_partners_perc_Last]
    
    FROM [dbo].[tbl_Partner_Entity] PE
    where [E_ID] =@E_ID