在同一个 SELECT sql 查询中从 SUM() 计算百分比
Posted
技术标签:
【中文标题】在同一个 SELECT sql 查询中从 SUM() 计算百分比【英文标题】:Compute percents from SUM() in the same SELECT sql query 【发布时间】:2013-03-16 16:47:24 【问题描述】:在表my_obj
中有两个整数字段:
(value_a integer, value_b integer);
我尝试计算value_a = value_b
的次数,我想用百分比来表示这个比率。
这是我尝试过的代码:
select sum(case when o.value_a = o.value_b then 1 else 0 end) as nb_ok,
sum(case when o.value_a != o.value_b then 1 else 0 end) as nb_not_ok,
compute_percent(nb_ok,nb_not_ok)
from my_obj as o
group by o.property_name;
compute_percent
是一个简单的存储过程 (a * 100) / (a + b)
但 PostgreSQL 抱怨 nb_ok
列不存在。
你会如何正确地做到这一点?
我在 Ubuntu 12.04 中使用 PostgreSQL 9.1。
【问题讨论】:
But postgresql complains that the column nook doesn't exist.
?请解决您的问题,任何地方都没有nook
。在谈论错误消息时,请按原样将其放在您的问题中。复制/粘贴。
考虑我回答的最后一段,并点击手册的链接,了解为什么nbok
以小写形式出现。
SQL 关键字的大写完全是可选的,只是个人喜好问题。但标识符的小写字母不是。
【参考方案1】:
这个问题比看起来的要多。
简单版
这更加更快更简单:
SELECT property_name
,(count(value_a = value_b OR NULL) * 100) / count(*) AS pct
FROM my_obj
GROUP BY 1;
结果:
property_name | pct
--------------+----
prop_1 | 17
prop_2 | 43
怎么做?
您根本不需要函数。
不要计算value_b
(您不需要从它开始)并计算总数,而是使用count(*)
作为总数。更快、更简单。
这假设您没有 NULL
值。 IE。两列都定义为NOT NULL
。您的问题中缺少信息。
如果不是,您的原始查询可能没有按照您的想法进行。如果任何值为 NULL,则您的版本根本不计算该行。您甚至可以通过这种方式引发除零异常。
此版本也适用于 NULL。 count(*)
生成所有行的计数,无论值如何。
以下是计数的工作原理:
TRUE OR NULL = TRUE
FALSE OR NULL = NULL
count()
忽略 NULL 值。瞧。
Operator precedence 规定=
在OR
之前绑定。您可以添加括号以使其更清晰:
count ((value_a = value_b) OR FALSE)
你也可以这样做
count NULLIF(<expression>, FALSE)
count()
的结果类型默认为bigint
。
除法bigint / bigint
,截断小数位。
包括小数位
使用 100.0
(带小数位)强制计算为 numeric
,从而保留小数位。
你可能想用round()
这个:
SELECT property_name
,round((count(value_a = value_b OR NULL) * 100.0) / count(*), 2) AS pct
FROM my_obj
GROUP BY 1;
结果:
property_name | pct
--------------+-------
prop_1 | 17.23
prop_2 | 43.09
顺便说一句:
我使用value_a
而不是valueA
。不要在 PostgreSQL 中使用不带引号的混合大小写标识符。我已经看到太多来自这种愚蠢的绝望问题。如果您想知道我在说什么,请阅读手册中的 Identifiers and Key Words 章节。
【讨论】:
这个COUNT
是否比类似的 SUM((value_a=value_b)::integer) 或类似的 SUM(CASE...)
运行得更快?
@AndrewLazarus:差异通常很小,因为与从磁盘读取数据相比,这三种形式中的每一种都非常便宜。在我上次的测试中,OR
出现在 CASE
和 ::int
之前。但是OR
、CASE
和NULLIF
是“接近通话”,真的。您可以轻松地对此进行测试。这是recent similar test on dba.SE(但没有OR
版本)。【参考方案2】:
可能最简单的方法就是使用 with 子句
WITH data
AS (SELECT Sum(CASE WHEN o.valuea = o.valueb THEN 1 ELSE 0 END) AS nbOk,
Sum(CASE WHEN o.valuea != o.valueb THEN 1 ELSE 0 END) AS nbNotOk,
FROM my_obj AS o
GROUP BY o.property_name)
SELECT nbok,
nbnotok,
Compute_percent(nbok, nbnotok)
FROM data
【讨论】:
假设一个/两个列上有一个覆盖索引,优化器是否能够将它们用于此查询(以及以何种容量),还是严格的表扫描? @Clockwork-Muse 它就像一个在线查询一样工作。它将尽其所能构建data
,但之后一切都在内存中完成。
@ConradFrix 我的问题有误,我要编辑,group by
是group by o.property_name
。否则就没有意义了。【参考方案3】:
您可能还想试试这个版本:
WITH all(count) as (SELECT COUNT(*)
FROM my_obj),
matching(count) as (SELECT COUNT(*)
FROM my_obj
WHERE valueA = valueB)
SELECT nbOk, nbNotOk, Compute_percent(nbOk, nbNotOk)
FROM (SELECT matching.count as nbOk, all.count - matching.count as nbNotOk
FROM all
CROSS JOIN matching) data
【讨论】:
以上是关于在同一个 SELECT sql 查询中从 SUM() 计算百分比的主要内容,如果未能解决你的问题,请参考以下文章
在 ORACLE 的 select 语句中从 PL/SQL 调用函数
在 Spark (v.1.5.2) 中从 SQL 查询创建表