计算两个总数的运行比率
Posted
技术标签:
【中文标题】计算两个总数的运行比率【英文标题】:Compute a running ratio of two totals 【发布时间】:2015-03-13 18:05:06 【问题描述】:我有一个 PostgreSQL 9.4.1 数据库(Retrosheet 数据),其中有一个表 events
,每个棒球比赛包含一行。我想计算给定球员的连续击球平均值:公式是(到目前为止的击球总数)/(到目前为止的有效击球总数)。
我可以使用窗口函数来获取 David Ortiz 的运行总命中数,其播放器代码为 ortid001
,使用以下查询:
SELECT count(*) OVER (ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
FROM events WHERE bat_id='ortid001' AND (event_cd='20' OR event_cd='21'
OR event_cd='22' OR event_cd='23');
(涉及event_cd
的子句仅标识哪些行被视为命中。)
使用相同的技术,我可以获得一个连续的 at-bats(event_cd
子句拒绝不计为 at-bat 的每一行。请注意,上面选择的命中是 at-蝙蝠):
SELECT count(*) OVER (ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
FROM events WHERE bat_id='ortid001' AND (event_cd != '11' AND
event_cd!='14' AND event_cd!='15' AND event_cd!='16' AND
event_cd!='17');
如何将这些结合起来?理想情况下,对于使用bat_id='some_player_id'
描述比赛的每一行,我将计算两个函数:描述击球的所有先前行的计数,以及描述命中的所有先前行的计数。将这些除以得出该行的连续击球率。
【问题讨论】:
【参考方案1】:假设(因为尚未声明)event_cd
是数据类型 integer
并且可以为 NULL。
SELECT *, round(hit::numeric / at_bat, 2) AS rate
FROM (
SELECT input_ts
, count(*) FILTER (WHERE event_cd = ANY ('20,21,22,23'::int[]))
OVER (ORDER BY input_ts) AS hit
, count(*) FILTER (WHERE NOT (event_cd = ANY ('11,14,15,16,17'::int[])))
OVER (ORDER BY input_ts) AS at_bat
FROM events
WHERE bat_id = 'ortid001'
) sub
ORDER BY input_ts;
由于您使用的是 pg 9.4,您可以使用新的聚合 FILTER
子句。相关答案:
框架定义ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
是默认的,所以你不必声明它。
但是数据库表中没有“自然顺序”。不要将此与电子表格混淆。您需要用ORDER BY
定义它。我正在使用虚构的列input_ts
,将其替换为定义您的排序顺序的(列表)列。更多:
我避免使用NOT IN
,因为它在使用 NULL 值时表现出棘手的行为。
转换为numeric
是为了避免整数除法,这会截断小数位并导致有用性问题。将结果四舍五入为两位小数。
【讨论】:
谢谢;有没有办法通过主键显式排序?或者这和依赖当前的默认排序一样糟糕? @MichaelCurry:当然,只需将 PK 列的名称放在那里。好/坏是您要求的函数。没有ORDER BY
的“默认”顺序只是一个可以随时更改的任意顺序。这是 Postgres 以最方便的方式呈现行的方式,通常是磁盘上行的当前物理顺序,这通常与插入行的顺序一致。但这可以随时改变,VACUUM
或 CLUSTER
或任何 UPDATE
或 DELETE
等。【参考方案2】:
使用条件聚合。您还没有指定 order by
子句,这是窗口函数真正需要的。您想要的查询类似于:
SELECT sum(case when event_cd in ('20', '21', '22', '23') then 1 else 0 end) OVER (ORDER BY ??),
sum(case when event_cd not in ('11', '14', '15', '16', '17') then 1 else 0 end) OVER (ORDER BY ??),
(sum(case when event_cd in ('20', '21', '22', '23') then 1.0 else 0 end) OVER (ORDER BY ??) /
sum(case when event_cd not in ('11', '14', '15', '16', '17') then 1 else 0 end) OVER (ORDER BY ??)
) as ratio
FROM events
WHERE bat_id = 'ortid001';
为??
放入适当的排序列。
【讨论】:
在这种情况下,数据库中的行恰好已经按正确的顺序排列。所以 OVER 子句就是OVER (ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
。这给出了正确的结果,但我不确定是否有更好的方法来做到这一点。
没有“按正确顺序”排列的表格。表格代表无序集。以上是关于计算两个总数的运行比率的主要内容,如果未能解决你的问题,请参考以下文章