类似子查询划分的简化解决方案

Posted

技术标签:

【中文标题】类似子查询划分的简化解决方案【英文标题】:Streamlined solution for division on similar subqueries 【发布时间】:2021-10-31 12:07:24 【问题描述】:

我在 PostgreSQL 13 中有一个看起来像这样的表(为了这个问题而修改):

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 的总访问次数 将这两个数字相除以获得百分比。

我编写了这段代码,它有效,但似乎重复且不是很干净:

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;

谁能提出一个更简洁的方法来回答这个问题?

【问题讨论】:

【参考方案1】:

使用聚合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 条件,因此它是“sargable”并且可以在(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小提琴here

除此之外:“day”对于timestamp with time zone 列来说是一个相当具有误导性的名称。

【讨论】:

【参考方案2】:

嗯。 . .您可以使用窗口函数来计算总数:

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

【讨论】:

以上是关于类似子查询划分的简化解决方案的主要内容,如果未能解决你的问题,请参考以下文章

等差数列划分--子序列问题DP解决

(My)SQL:如果子查询不是空集,则返回子查询

sql 查询的正确语法和 WHERE EXISTS 替代方案

归并排序-marge-sort

SQL over 子句 - 将分区划分为编号的子分区

2022-07-21:给定一个字符串str,和一个正数k, 你可以随意的划分str成多个子串, 目的是找到在某一种划分方案中,有尽可能多的回文子串,长度>=k,并且没有重合。 返回有几个回文子串。 来