【问题标题】:SQL Rounding Percentages to make the sum 100% - 1/3 as 0.34, 0.33, 0.33SQL 舍入百分比使总和 100% - 1/3 为 0.34、0.33、0.33
【发布时间】:2012-02-28 00:06:08
【问题描述】:

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

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 中完成这项工作,但有没有任何 simple 方法可以做到这一点。如果可能的话,我怎样才能在上述行之一的百分比列中获得 0.34? 0.01 是可以忽略不计的值,但是当值列是 1000000000 时,它是显着的。

【问题讨论】:

  • 百分比有 14 位小数,为什么不在percentage 列中输入0.33333333333333
  • 如果我说“你可以更新供应商 SET percent = 0.34 WHERE ...”那么你可能会明白通过阅读这个问题来理解你真正需要的东西是多么困难......
  • 看看 Martin Fowler 的“数量”模式。 martinfowler.com/eaaDev/quantity.html 特别看一下关于分钱的讨论。示例中的除法返回一个值数组,而不是单个值。值数组加起来就是总数。我发现这很简单。
  • 我知道我们可以在已经计算的百分比之上更新/使用第二组 sql,这就是我目前正在解决的问题。但是在同一个 sql 中没有一个简单的逻辑我们在哪里计算百分比?

标签: sql teradata rounding


【解决方案1】:

运行它,它会告诉你如何解决你的问题。 为了便于理解,我创建了一个名为 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 到百分比总和的差,并将其添加到具有最大 ID 的订单(您的随机订单)

select * from #tempOrders

1   33.33
2   33.33
3   33.34

【讨论】:

  • 两组sql可以轻松完成这个任务,这就是我目前正在做的。但我正在寻找单步过程,其中丢失的数据应该自动分配给任何随机供应商,对性能影响较小,因为它需要在 Teradata 上每小时运行数十亿行。
【解决方案2】:

听起来您在这里进行了某种“分配”。每当您尝试将某些东西从较高粒度分配到较低粒度时,这是一个常见问题,并且您需要能够正确地重新聚合到总值。

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

例如,如果我尝试将总价值(例如 55.30 美元)除以 8,则八个存储桶中的每一个的十进制值都是 6.9125 美元。我应该将 1 舍入到 6.92 美元,其余舍入到 6.91 美元吗?如果我这样做,我将失去一分钱。我必须将 1 舍入到 6.93 美元,将其他舍入到 6.91 美元。当您添加更多要除以的桶时,情况会变得更糟。

此外,当您开始舍入时,您会引入诸如“33.339 应该舍入为 33.34 还是 33.33?”之类的问题

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

在纯 SQL 中执行此操作有点困难。对于初学者,您的百分比不是 1/3,而是 0.33,这将产生 9.9,而不是 10 的总值。我会将其存储为比率或高精度十进制字段 (.33333333333333)。

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)

您的计算似乎是基于每个供应商的产品总数的平均分配。如果是这样,删除百分比可能是有利的,而只是将每个供应商的项目数存储在表中。

如果还可以存储一个标志,指示应该将余数应用于它的行,您可以基于该标志而不是随机分配。

【讨论】:

  • 这看起来几乎和我预期的一样。非常感谢您。现在我将不得不应用此逻辑并进行调整以使这项工作在 Teradata 上运行。非常感谢。
  • 如果您的主索引在产品密钥上,您应该会获得相当不错的性能,因为每个 AMP 都应该处理每个计算。
  • 是的。但是我在这里给出的例子并不是我的场景的精确复制品。我已经有一组 SQL 可以完成大部分拆分工作等,并带有更复杂的连接。上面提供的示例是为了使其易于理解。非常感谢 N West。
【解决方案3】:

使用窗口聚合函数应该是一件容易的事。你可能已经用它们来计算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

【讨论】:

    【解决方案4】:

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

    想法

    1. 根据您的条件获取全部计数(Count(*))
    2. 获取 Row_Number()
    3. 检查 (Row_Number() 值是否

    这是我的代码的一部分

    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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-29
      • 1970-01-01
      • 2021-02-18
      • 1970-01-01
      相关资源
      最近更新 更多