【问题标题】:Percentile_disc() for a non round valuesPercentile_disc() 用于非整数值
【发布时间】:2017-12-07 13:10:30
【问题描述】:

我正在努力寻找解决方案,但没有成功。

在我的查询中,我选择了 count(*)percentile_disc(.9) 来查找第 90 个位置。 情况是,当计数为 29 时,第 90 个百分位比 27 更接近数字 26,但仍返回第 27 个对象。

有什么办法可以说,如果 5

参考表

ID    Count    90th
-------------------
1     50       45
2     40       36
3     27       25     <-- Should be 24
4      9        9     <-- Should be  8

90% 的 9 是 0.9,它应该删除 1 并得到 8。

---直到这里是我对第N个百分位数的理解---

现在我有什么:

我的表有条目丢失(每天 + 100k),所以我想每天运行这个查询。

Service_id   start_time      end_time
-------------------------------------
Service1    1499025651614    1499025651648
Service2    1499025655145    1499025655434
Service3    1499025656029    1499025656112
Service2    1499025658755    1499025659135
Service3    1499025726862    1499025728346
Service1    1499025748782    1499025750032
Service3    1499025749277    1499025749900
Service3    1499025757681    1499025758517
Service2    1499025775000    1499025775101
Service1    1499025785556    1499025785633
...

我有一个查询来选择每个服务的最小值、最大值和平均值

 select mt.SERVICE_ID as SERVICE_ID,
           count(*) as COUNT,
           round(avg((mt.end_time - mt.start_time) / 1000), 2) as Avg,
           round(min((mt.end_time - mt.start_time) / 1000), 2) AS Min,
           round(max((mt.end_time - mt.start_time) / 1000), 2) AS Max
      from myTable mt
     group by mt.service_id

我想在使用连接之前合并讨论的第 90 个百分位。

select service_id, round(percentile_disc(.90) within group(order by elapsed), 2) as perc
from (select mt.service_id, ((mt.end_time - mt.start_time) / 1000) as elapsed
      from myTable mt)
group by service_id

当计数为 9 时出现问题,在这种情况下,MAXPerc 相同(由于百分位数没有删除任何内容)但在这种特殊情况下我需要删除最后一个,结果给我第 8 位的时间。

在这种情况下,有什么办法可以再删除一个位置?

【问题讨论】:

  • 请提供minimal reproducible example,包括您的表的 DDL 语句、DML 语句(例如说明您的问题和预期输出的数据)(详细说明为什么需要该输出的逻辑) .目前,您问题中的文字描述与“参考表”不匹配,也不匹配您的查询,因此不清楚您要达到的目标。
  • @MT0 添加了更多数据,希望足以澄清。谢谢

标签: oracle percentile


【解决方案1】:

PERCENTILE_DISC() 并没有完全按照你的想法去做。

Oracle Documentation:

目的

PERCENTILE_DISC 是一个逆分布函数,它采用离散分布模型。它接受一个百分位值和一个排序规范,并从集合中返回一个元素。计算中忽略空值。

...

对于给定的百分位值PPERCENTILE_DISC 对 ORDER BY 子句中表达式的值进行排序,并返回具有最小 CUME_DIST 值的值(相对于相同的排序规范)大于或等于P

分析示例

以下示例计算样本表 hr.employees 中每个员工工资的中位数离散百分位数:

SELECT last_name, salary, department_id,
   PERCENTILE_DISC(0.5) WITHIN GROUP (ORDER BY salary DESC)
      OVER (PARTITION BY department_id) "Percentile_Disc",
   CUME_DIST() OVER (PARTITION BY department_id 
      ORDER BY salary DESC) "Cume_Dist"
FROM employees where department_id in (30, 60);

LAST_NAME         SALARY DEPARTMENT_ID Percentile_Disc  Cume_Dist
------------- ---------- ------------- --------------- ----------
Raphaely           11000            30            2900 .166666667
Khoo                3100            30            2900 .333333333
Baida               2900            30            2900         .5
Tobias              2800            30            2900 .666666667
Himuro              2600            30            2900 .833333333
Colmenares          2500            30            2900          1
Hunold              9000            60            4800         .2
Ernst               6000            60            4800         .4
Austin              4800            60            4800         .8
Pataballa           4800            60            4800         .8
Lorentz             4200            60            4800          1

Department 30 的中值为 2900,即 对应的百分位数 (Cume_Dist) 是更大的最小值 大于或等于 0.5。部门 60 的中值为 4800, 这是对应百分位数最小的值 值大于或等于 0.5。

在他们在文档中给出的示例中,如果百分位数设置为 0.9(而不是 0.5),那么您可以看到 CUME_DIST0.8 变为 1(对于部门 60 ) 所以PERCENTILE_DISC(0.9) ... 会给出4200,因为这是最小的CUME_DIST 大于或等于0.9 的值。在这种情况下,要获得倒数第二个值,您需要0.8 的百分位数。

当计数为 9 时出现问题,在这种情况下,MAX 和 Perc 相同(因为百分位数没有删除任何内容)但在这种特殊情况下我需要删除最后一个,结果给了我第 8 位的时间。

对于 9 个项目,每行的 CUME_DIST 值将是:

ROW_NUMBER CUME_DIST
---------- ---------
         1      .111
         2      .222
         3      .333
         4      .444
         5      .556
         6      .667
         7      .778
         8      .889
         9     1.000

如果您使用PERCENTILE_DISC( 0.9 ),那么它将寻找具有大于或等于该值的最低CUME_DIST 的值 - 只有一个值1.000,它也是最大值。

如果您想要不同的值,则需要使用较低的百分位数。

更新

你可以试试这样的:

select service_id, 
       elapsed as perc
from (
  select service_id,
         (end_time - start_time) / 1000 as elapsed,
         ROW_NUMBER() OVER ( PARTITION BY service_id ORDER BY (end_time - start_time) )
           AS rn,
         COUNT() OVER ( PARTITION BY service_id ) AS ct
  from   myTable
)
WHERE rn = ROUND( 0.9 * ct );

根据您的业务逻辑更改最后一行以使用ROUNDFLOORCEIL。如果我正确判断了逻辑,CEIL 将给出与使用PERCENTILE_DISC 相同的答案。

我需要的是计数为 7,删除最后一条记录并返回第 6 个值(7 的 90% 为 0.7,四舍五入为 1),计数为 21,删除最后 2 条记录并返回第 19 个位置-value(21 的 90% 是 2.1 轮到 2)等等。

使用rn = ROUND( 0.9 * ct )

  • 如果计数为 7,则 0.9 * 7 = 6.3 所以ROUND( 6.3 ) 将给出第 6 行
  • 如果计数为 21,则 0.9 * 21 = 18.9 所以ROUND( 18.9 ) 将给出第 19 行
  • 如果计数为 3,则 0.9 * 3 = 2.7 所以ROUND( 2.7 ) 将给出第 3 行(最大值)。

目前尚不清楚您希望为小集合返回什么 - 如果您从不想要最大行(除非只有一行),那么类似:

WHERE rn = GREATEST( 1, LEAST( ct - 1, ROUND( 0.9 * ct ) ) )

【讨论】:

  • 那么,在这种情况下,我应该使用什么?
  • 如果您的组中有 9 行并且您想要第 8 个值,则使用 8/9 = 0.888...(或任何大于 7/9 且小于或等于 8/9 的值)。
  • 有什么方法可以自动执行此操作? DB中有150多个不同的Service,有的调用2次,有的调用+2000次。我需要的是计数为 7,删除最后一条记录并返回第 6 个值(7 的 90% 为 0.7,四舍五入为 1),计数为 21,删除最后 2 条记录并返回第 19 个位置值( 21 的 90% 是 2.1 轮到 2) 等等。感谢您的宝贵时间
  • @AndresMtnez 更新
  • 这看起来很好,我会投入工作,我会回来找你的。谢谢@MT0
猜你喜欢
  • 1970-01-01
  • 2014-06-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-28
  • 2017-06-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多