相似子查询除法的简化解决方案
Streamlined solution for division on similar subqueries
我在 PostgreSQL 13 中有一个 table 看起来像这样(为此问题进行了修改):
SELECT * FROM visits.visitors_log;
visitor_id | day | source
--------------+------------------------+----------
9 | 2019-12-30 12:10:10-05 | Twitter
7 | 2019-12-14 22:10:26-04 | Netflix
5 | 2019-12-13 15:21:04-05 | Netflix
9 | 2019-12-22 23:34:47-05 | Twitter
7 | 2019-12-22 00:10:26-04 | Netflix
9 | 2019-12-22 13:20:42-04 | Twitter
将时间转换为另一个时区后,我想计算 2019 年 12 月 22 日来自特定来源的访问百分比。
涉及 4 个步骤:
- 转换时区
- 计算当天的总访问量
- 计算当天来自 Netflix 的总访问量
- 将这 2 个数字相除得到百分比。
我写了这段代码,它可以工作,但看起来重复而且不是很干净:
SELECT (SELECT COUNT(*) FROM (SELECT visitor_id, source, day AT TIME ZONE 'PST' FROM visits.visitors_log WHERE day::date = '2019-12-22') AS a
WHERE day::date = '2019-12-22' AND source = 'Netflix') * 100.0
/
(SELECT COUNT(*) FROM (SELECT visitor_id, source, day AT TIME ZONE 'PST' FROM visits.visitors_log WHERE day::date = '2019-12-22') AS b
WHERE day::date = '2019-12-22')
AS visitors_percentage;
谁能提出一个更简洁的方法来回答这个问题?
嗯。 . .您可以使用 window 函数来计算总数:
SELECT source, COUNT(*) / SUM(COUNT(*)) OVER () as visitors_percentage
FROM visits.visitors_log
WHERE (day AT TIME ZONE 'PST')::date = '2019-12-22'
GROUP BY SOURCE
使用聚合 FILTER
子句:
SELECT count(*) FILTER (WHERE source = 'Netflix') * 100.0
/ count(*) AS visitors_percentage
FROM visits.visitors_log
WHERE day >= timestamp '2019-12-22' AT TIME ZONE 'PST'
AND day < timestamp '2019-12-23' AT TIME ZONE 'PST';
参见:
- Aggregate columns with additional (distinct) filters
我改写了 WHERE
条件,因此它是“可搜索的”并且可以在 (day)
上使用索引。列上带有表达式的谓词不能使用普通索引。因此,我将包含下限和独占上限(给定时区的日期边界)的计算移动到 WHERE
子句中表达式的右侧。
对大表的性能产生 巨大 差异。
如果您经常使用该查询,请考虑为其创建一个函数:
CREATE OR REPLACE FUNCTION my_func(_source text, _day date, _tz text)
RETURNS numeric
LANGUAGE sql IMMUTABLE PARALLEL SAFE AS
$func$
SELECT round(count(*) FILTER (WHERE source = _source) * 100.0 / count(*), 2) AS visitors_percentage
FROM visits.visitors_log
WHERE day >= _day::timestamp AT TIME ZONE _tz
AND day < (_day + 1)::timestamp AT TIME ZONE _tz;
$func$;
致电:
SELECT my_func('Netflix', '2019-12-22', 'PST');
我加入了 round()
,这是一个完全可选的添加。
db<>fiddle here
旁白:“day”对于 timestamp with time zone
列来说是一个相当具有误导性的名称。
我在 PostgreSQL 13 中有一个 table 看起来像这样(为此问题进行了修改):
SELECT * FROM visits.visitors_log;
visitor_id | day | source
--------------+------------------------+----------
9 | 2019-12-30 12:10:10-05 | Twitter
7 | 2019-12-14 22:10:26-04 | Netflix
5 | 2019-12-13 15:21:04-05 | Netflix
9 | 2019-12-22 23:34:47-05 | Twitter
7 | 2019-12-22 00:10:26-04 | Netflix
9 | 2019-12-22 13:20:42-04 | Twitter
将时间转换为另一个时区后,我想计算 2019 年 12 月 22 日来自特定来源的访问百分比。
涉及 4 个步骤:
- 转换时区
- 计算当天的总访问量
- 计算当天来自 Netflix 的总访问量
- 将这 2 个数字相除得到百分比。
我写了这段代码,它可以工作,但看起来重复而且不是很干净:
SELECT (SELECT COUNT(*) FROM (SELECT visitor_id, source, day AT TIME ZONE 'PST' FROM visits.visitors_log WHERE day::date = '2019-12-22') AS a
WHERE day::date = '2019-12-22' AND source = 'Netflix') * 100.0
/
(SELECT COUNT(*) FROM (SELECT visitor_id, source, day AT TIME ZONE 'PST' FROM visits.visitors_log WHERE day::date = '2019-12-22') AS b
WHERE day::date = '2019-12-22')
AS visitors_percentage;
谁能提出一个更简洁的方法来回答这个问题?
嗯。 . .您可以使用 window 函数来计算总数:
SELECT source, COUNT(*) / SUM(COUNT(*)) OVER () as visitors_percentage
FROM visits.visitors_log
WHERE (day AT TIME ZONE 'PST')::date = '2019-12-22'
GROUP BY SOURCE
使用聚合 FILTER
子句:
SELECT count(*) FILTER (WHERE source = 'Netflix') * 100.0
/ count(*) AS visitors_percentage
FROM visits.visitors_log
WHERE day >= timestamp '2019-12-22' AT TIME ZONE 'PST'
AND day < timestamp '2019-12-23' AT TIME ZONE 'PST';
参见:
- Aggregate columns with additional (distinct) filters
我改写了 WHERE
条件,因此它是“可搜索的”并且可以在 (day)
上使用索引。列上带有表达式的谓词不能使用普通索引。因此,我将包含下限和独占上限(给定时区的日期边界)的计算移动到 WHERE
子句中表达式的右侧。
对大表的性能产生 巨大 差异。
如果您经常使用该查询,请考虑为其创建一个函数:
CREATE OR REPLACE FUNCTION my_func(_source text, _day date, _tz text)
RETURNS numeric
LANGUAGE sql IMMUTABLE PARALLEL SAFE AS
$func$
SELECT round(count(*) FILTER (WHERE source = _source) * 100.0 / count(*), 2) AS visitors_percentage
FROM visits.visitors_log
WHERE day >= _day::timestamp AT TIME ZONE _tz
AND day < (_day + 1)::timestamp AT TIME ZONE _tz;
$func$;
致电:
SELECT my_func('Netflix', '2019-12-22', 'PST');
我加入了 round()
,这是一个完全可选的添加。
db<>fiddle here
旁白:“day”对于 timestamp with time zone
列来说是一个相当具有误导性的名称。