在 Oracle 中创建直方图/频率分布的最佳方法?
Posted
技术标签:
【中文标题】在 Oracle 中创建直方图/频率分布的最佳方法?【英文标题】:Optimal way to create a histogram/frequency distribution in Oracle? 【发布时间】:2011-09-06 07:36:07 【问题描述】:我有一个events
表,其中包含两列eventkey
(唯一的,主键)和createtime
,它将事件的创建时间存储为NUMBER
中自1970 年1 月1 日以来的毫秒数列。
我想创建一个“直方图”或频率分布,显示过去一周的每个小时内创建了多少事件。
这是在 Oracle 中使用width_bucket()
函数编写此类查询的最佳方式吗?是否可以使用其他 Oracle 分析函数之一而不是使用 width_bucket
来确定每行属于哪个存储桶编号并在此基础上执行 count(*)
来得出落入每个存储桶的行数?
-- 1305504000000 = 5/16/2011 12:00am GMT
-- 1306108800000 = 5/23/2011 12:00am GMT
select
timestamp '1970-01-01 00:00:00' + numtodsinterval((1305504000000/1000 + (bucket * 60 * 60)), 'second') period_start,
numevents
from (
select bucket, count(*) as events from (
select eventkey, createtime,
width_bucket(createtime, 1305504000000, 1306108800000, 24 * 7) bucket
from events
where createtime between 1305504000000 and 1306108800000
) group by bucket
)
order by period_start
【问题讨论】:
【参考方案1】:如果您的 createtime
是日期列,这将是微不足道的:
SELECT TO_CHAR(CREATE_TIME, 'DAY:HH24'), COUNT(*)
FROM EVENTS
GROUP BY TO_CHAR(CREATE_TIME, 'DAY:HH24');
事实上,投射createtime
列并不难:
select TO_CHAR(
TO_DATE('19700101', 'YYYYMMDD') + createtime / 86400000),
'DAY:HH24') AS BUCKET, COUNT(*)
FROM EVENTS
WHERE createtime between 1305504000000 and 1306108800000
group by TO_CHAR(
TO_DATE('19700101', 'YYYYMMDD') + createtime / 86400000),
'DAY:HH24')
order by 1
或者,如果您正在寻找栅栏值(例如,我从第一个十分位数 (0-10%) 到下一个十分位数 (11-20%) 的位置,您可以执行以下操作:
select min(createtime) over (partition by decile) as decile_start,
max(createtime) over (partition by decile) as decile_end,
decile
from (select createtime,
ntile (10) over (order by createtime asc) as decile
from events
where createtime between 1305504000000 and 1306108800000
)
【讨论】:
这很好用,谢谢。不知道为什么我一开始没有想到简单地截断日期,我想我很着迷于弄清楚如何解析和转换这种奇怪的“日期”格式 是否需要维护计数为零的 create_times 行?【参考方案2】:我不熟悉 Oracle 的日期函数,但我很确定有一种等效的方式来编写这个 Postgres 语句:
select date_trunc('hour', stamp), count(*)
from your_data
group by date_trunc('hour', stamp)
order by date_trunc('hour', stamp)
【讨论】:
在 PG 中完美运行!也很快。【参考方案3】:与 Adam 的响应几乎相同,但我更愿意将 period_start 保留为时间字段,以便在需要时更容易进一步过滤:
with
events as
(
select rownum eventkey, round(dbms_random.value(1305504000000, 1306108800000)) createtime
from dual
connect by level <= 1000
)
select
trunc(timestamp '1970-01-01 00:00:00' + numtodsinterval(createtime/1000, 'second'), 'HH') period_start,
count(*) numevents
from
events
where
createtime between 1305504000000 and 1306108800000
group by
trunc(timestamp '1970-01-01 00:00:00' + numtodsinterval(createtime/1000, 'second'), 'HH')
order by
period_start
【讨论】:
您能解释一下with events as ()
部分的用途,以及为什么要选择随机值吗?我对Oracle语法不是很熟悉
对不起...由于我没有您的数据表来运行查询,因此我正在生成随机数据来模拟您的表中可能存在的数据。 “with events”语句只允许我将该查询别名为“events”,因此查询的其余部分将匹配您可以直接针对事件表使用的内容,而无需进行任何更改。出于您的目的,只需删除“select trunc(....”上方的所有内容
啊,谢谢,我知道这在这种类型的答案中会有什么用 :)【参考方案4】:
使用 oracle 提供的函数“WIDTH_BUCKET”来累积连续或精细离散的数据。以下示例显示了一种创建具有 5 个存储桶的直方图并从 510 到 520 收集“COLUMN_VALUE”的方法(因此每个存储桶获取范围为 2 的值)。 WIDTH_BUCKET 将为低于最小值和高于最大值的值创建额外的 id=0 和 num_buckets+1 个存储桶。
SELECT "BUCKET_ID", count(*),
CASE
WHEN "BUCKET_ID"=0 THEN -1/0F
ELSE 510+(520-510)/5*("BUCKET_ID"-1)
END "BUCKET_MIN",
CASE
WHEN "BUCKET_ID"=5+1 THEN 1/0F
ELSE 510+(520-510)/5*("BUCKET_ID")
END "BUCKET_MAX"
FROM
(
SELECT "COLUMN_VALUE",
WIDTH_BUCKET("COLUMN_VALUE", 510, 520, 5) "BUCKET_ID"
FROM "MY_TABLE"
)
group by "BUCKET_ID"
ORDER BY "BUCKET_ID";
样本输出
BUCKET_ID COUNT(*) BUCKET_MIN BUCKET_MAX
---------- ---------- ---------- ----------
0 45 -Inf 5.1E+002
1 220 5.1E+002 5.12E+002
2 189 5.12E+002 5.14E+002
3 43 5.14E+002 5.16E+002
4 3 5.16E+002 5.18E+002
在我的表格中,没有 518-520,因此没有显示 id=5 的存储桶。另一方面,有低于 min (510) 的值,所以有一个 id=0 的桶,将 -inf 收集到 510 个值。
【讨论】:
以上是关于在 Oracle 中创建直方图/频率分布的最佳方法?的主要内容,如果未能解决你的问题,请参考以下文章