sql—统计一年中每周活跃的不同客户数

hwazgwia  于 2021-07-26  发布在  Java
关注(0)|答案(2)|浏览(576)

我正在使用现有的电子商务数据库。实际上,这个过程通常是在excel中完成的,但是我们想直接用postgresql(版本10.6)中的查询来尝试。
我们将在一年内至少购买过一次的人定义为活跃客户。这意味着,如果我分析2020年第22周,一个活跃的客户将是自2019年第22周以来至少购买过一次的客户。
我要每年(2020年)每周的产量。基本上我需要的是。。。

select
    email,
    orderdate,
    id
from
    orders_table
where
    paid = true;

|---------------------|-------------------|-----------------|
|      email          |     orderdate     |        id       |
|---------------------|-------------------|-----------------|
|  email1@email.com   |2020-06-02 05:04:32|     Order-2736  |
|---------------------|-------------------|-----------------|

我无法创建新表。我想看到这样的输出:

Year| Week | Active customers
2020| 25   | 6978
2020| 24   | 3948
rmbxnbpk

rmbxnbpk1#

取决于是否有 year 以及 week 列可以使用 OVER (PARTITION BY ...)extract :

SELECT
    extract(year from orderdate),
    extract(week from orderdate),
    sum(1) as customer_count_in_week,
    OVER (PARTITION BY extract(YEAR FROM TIMESTAMP orderdate), 
                       extract(WEEK FROM TIMESTAMP orderdate))
FROM ordertable
WHERE paid=true;

它应该按年度和周存储所有订单,从而显示一年中每周的总计数,其中支付是真实的。
参考文献:
https://www.postgresql.org/docs/9.1/tutorial-window.html
https://www.postgresql.org/docs/8.1/functions-datetime.html

bxfogqkk

bxfogqkk2#

如果我分析2020年第22周,那么一个活跃的客户将是自2019年第22周以来至少购买过一次的客户。

你这边的问题

此方法存在一些不明确/问题:
是否包括或排除“2020年第22周”(我将其排除在下面,以便更接近“一年”。)
一年可以有52或53个完整的星期。根据当前日期,计算是以52或53周为基础的,可能会产生几乎2%的偏差!
如果从“去年的同一日期”开始计算时间范围,则由于闰年的原因,误差幅度仅为1/365或~0.3%。
一个固定的“365天”(或366天)将完全消除偏差。

sql方面的问题

不幸的是,窗口函数当前不允许 DISTINCT 关键词(有充分的理由)。所以某种形式:

SELECT count(DISTINCT email) OVER (ORDER BY year, week
                                   GROUPS BETWEEN 52 PRECEDING AND 1 PRECEDING)
FROM   ...

.. 触发器:

ERROR:  DISTINCT is not implemented for window functions

这个 GROUPS 关键字只被添加在postgres 10,否则就是我们需要的。
更重要的是,你的奇数帧定义甚至无法准确地工作,因为要考虑的周数并不总是52,如上所述。
所以我们必须自己动手。

解决方案

下面简单地生成所有感兴趣的周数,并计算每个周的不同客户数。很简单,只是日期数学从来都不简单。但是,根据设置的细节,可能会有更快的解决方案(我还有其他一些想法。)
要报告的时间范围可能会更改。下面是一个辅助函数,用于生成给定年份的周数:

CREATE OR REPLACE FUNCTION f_weeks_of_year(_year int)
  RETURNS TABLE(year int, week int, week_start timestamp)
  LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE
  ROWS 52 COST 10 AS
$func$
SELECT _year, d.week::int, d.week_start
FROM   generate_series(date_trunc('week', make_date(_year, 01, 04)::timestamp)  -- first day of first week
                     , LEAST(date_trunc('week', localtimestamp), make_date(_year, 12, 28)::timestamp) -- latest possible start of week
                     , interval '1 week')  WITH ORDINALITY d(week_start, week)
$func$;

电话:

SELECT * FROM f_weeks_of_year(2020);

它每周返回一行,但在当前年份的当前周停止(空置以备将来使用。)
计算基于以下事实:
一年中的第一个iso周总是包含1月4日。
最后一个iso周不能在12月28日之后开始。
实际周数是使用 WITH ORDINALITY . 请参见:
带有元素编号的postgresql unnest()
不管怎样,我坚持 timestamp 避免 timestamptz 为此目的。请参见:
在postgresql中生成两个日期之间的时间序列
函数还返回一周开始的时间戳( week_start )我们手头的问题不需要。但我保留了它,以使函数在总体上更有用。
使主查询更简单:

WITH weekly_customer AS (
   SELECT DISTINCT
          EXTRACT(YEAR FROM orderdate)::int AS year
        , EXTRACT(WEEK FROM orderdate)::int AS week
        , email
   FROM   orders_table
   WHERE  paid
   AND    orderdate >= date_trunc('week', timestamp '2019-01-04')  -- max range for 2020!
   ORDER  BY 1, 2, 3  -- optional, might improve performance
   )
SELECT d.year, d.week
     , (SELECT count(DISTINCT email)
        FROM   weekly_customer w
        WHERE  (w.year, w.week) >= (d.year - 1, d.week)  -- row values, see below
        AND    (w.year, w.week) <  (d.year    , d.week)  -- exclude current week
       ) AS active_customers
FROM   f_weeks_of_year(2020) d;  -- (year int, week int, week_start timestamp)

db<>在这里摆弄
cte weekly_customer 每个日历周向唯一的客户折叠一次,因为重复条目只是我们计算的噪音。它在主查询中被多次使用。截止条件再次基于1月4日。调整到你的实际报告期。
实际的计数是用一个低关联的子查询完成的。可能是一个 LEFT JOIN LATERAL ... ON true 相反。请参见:
在postgresql中,横向查询和子查询有什么区别?
使用行值比较简化范围定义。请参见:
“where(col1,col2)<(val1,val2)”的sql语法术语

相关问题