You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MySQL WEEK()函数转PostgreSQL后留存率计算结果不一致求助

适配MySQL留存率计算代码到PostgreSQL后结果与预期不符,请求排查原因

我参考某博客的MySQL用户留存率计算方法开展个人项目,将代码适配为PostgreSQL(使用pgAdmin v6.19)后,复现的结果与原文预期不一致:

以下是我修改后的PostgreSQL代码:

create table login(login_date date,user_id int, id bigserial);
insert into login(login_date,user_id)
values  (TO_DATE('2020-01-01', 'YYYY-MM-DD'), 10),
(TO_DATE('2020-01-02', 'YYYY-MM-DD'), 12),
(TO_DATE('2020-01-03', 'YYYY-MM-DD'), 15),
(TO_DATE('2020-01-04', 'YYYY-MM-DD'), 11),
(TO_DATE('2020-01-05', 'YYYY-MM-DD'), 13),
(TO_DATE('2020-01-06', 'YYYY-MM-DD'), 9),
(TO_DATE('2020-01-07', 'YYYY-MM-DD'), 21),
(TO_DATE('2020-01-08', 'YYYY-MM-DD'), 10),
(TO_DATE('2020-01-09', 'YYYY-MM-DD'), 10),
(TO_DATE('2020-01-10', 'YYYY-MM-DD'), 2),
(TO_DATE('2020-01-11', 'YYYY-MM-DD'), 16),
(TO_DATE('2020-01-12', 'YYYY-MM-DD'), 12),
(TO_DATE('2020-01-13', 'YYYY-MM-DD'), 10),
(TO_DATE('2020-01-14', 'YYYY-MM-DD'), 18),
(TO_DATE('2020-01-15', 'YYYY-MM-DD'), 15),
(TO_DATE('2020-01-16', 'YYYY-MM-DD'), 12),
(TO_DATE('2020-01-17', 'YYYY-MM-DD'), 10),
(TO_DATE('2020-01-18', 'YYYY-MM-DD'), 18),
(TO_DATE('2020-01-19', 'YYYY-MM-DD'), 14),
(TO_DATE('2020-01-20', 'YYYY-MM-DD'), 16),
(TO_DATE('2020-01-21', 'YYYY-MM-DD'), 12),
(TO_DATE('2020-01-22', 'YYYY-MM-DD'), 21),
(TO_DATE('2020-01-23', 'YYYY-MM-DD'), 13),
(TO_DATE('2020-01-24', 'YYYY-MM-DD'), 15),
(TO_DATE('2020-01-25', 'YYYY-MM-DD'), 20),
(TO_DATE('2020-01-26', 'YYYY-MM-DD'), 14),
(TO_DATE('2020-01-27', 'YYYY-MM-DD'), 16),
(TO_DATE('2020-01-28', 'YYYY-MM-DD'), 15),
(TO_DATE('2020-01-29', 'YYYY-MM-DD'), 10),
(TO_DATE('2020-01-30', 'YYYY-MM-DD'), 18);

SELECT * from Login
ORDER BY login_Date;

SELECT
user_id,
extract(week FROM login_date)-1 AS login_week
FROM
login
GROUP BY
user_id, extract(week FROM login_date)
ORDER by user_id asc;

SELECT user_id, 
min(extract(week FROM login_date)-1) AS first_week
FROM login
GROUP BY user_id
ORDER BY user_id;

WITH with_week_number AS (
SELECT
a.user_id,
EXTRACT(WEEK FROM a.login_date) - MIN(EXTRACT(WEEK FROM a.login_date)) AS login_week,
b.first_week,
EXTRACT(WEEK FROM a.login_date) - b.first_week AS week_number
FROM
(
  SELECT
    user_id,
    login_date
  FROM
    login
  GROUP BY
    user_id,
    login_date
) a
JOIN
(
  SELECT
    user_id,
    MIN(EXTRACT(WEEK FROM login_date)) AS first_week
  FROM
    login
  GROUP BY
    user_id
) b ON a.user_id = b.user_id
GROUP BY
a.user_id, a.login_date, b.first_week
)
SELECT
first_week,
SUM(CASE WHEN week_number = 0 THEN 1 ELSE 0 END) AS week_0,
SUM(CASE WHEN week_number = 1 THEN 1 ELSE 0 END) AS week_1,
SUM(CASE WHEN week_number = 2 THEN 1 ELSE 0 END) AS week_2,
SUM(CASE WHEN week_number = 3 THEN 1 ELSE 0 END) AS week_3,
SUM(CASE WHEN week_number = 4 THEN 1 ELSE 0 END) AS week_4,
SUM(CASE WHEN week_number = 5 THEN 1 ELSE 0 END) AS week_5,
SUM(CASE WHEN week_number = 6 THEN 1 ELSE 0 END) AS week_6,
SUM(CASE WHEN week_number = 7 THEN 1 ELSE 0 END) AS week_7,
SUM(CASE WHEN week_number = 8 THEN 1 ELSE 0 END) AS week_8,
SUM(CASE WHEN week_number = 9 THEN 1 ELSE 0 END) AS week_9
FROM with_week_number
GROUP BY first_week
ORDER BY first_week;

问题排查与修正

核心问题1:周数计算基准不一致

PostgreSQL的EXTRACT(WEEK FROM date)遵循ISO 8601标准,每周从周一开始,2020年1月1日属于第1周;而MySQL默认WEEK()函数以周日为一周起始,2020年1月1日会被算作第0周。你的代码中,部分查询用extract(week FROM login_date)-1对齐MySQL逻辑,但CTE子查询b中直接用MIN(EXTRACT(WEEK FROM login_date)),未做减1处理,导致周数基准混乱,最终week_number计算全部偏移。

核心问题2:CTE中冗余且错误的计算逻辑

CTE里的EXTRACT(WEEK FROM a.login_date) - MIN(EXTRACT(WEEK FROM a.login_date))完全无意义:在GROUP BY a.user_id, a.login_date的前提下,每个分组只有一个周数值,MIN结果等于自身。这部分逻辑属于冗余,且进一步干扰了周数计算。

修正后的代码

WITH user_first_week AS (
  -- 统一减1,对齐MySQL的周起始逻辑
  SELECT
    user_id,
    MIN(EXTRACT(WEEK FROM login_date) - 1) AS first_week
  FROM login
  GROUP BY user_id
),
user_login_weeks AS (
  SELECT
    a.user_id,
    EXTRACT(WEEK FROM a.login_date) - 1 AS login_week,
    b.first_week,
    -- 计算与首次登录周的差值
    (EXTRACT(WEEK FROM a.login_date) - 1) - b.first_week AS week_number
  FROM (
    -- 用DISTINCT去重,替代冗余的GROUP BY
    SELECT DISTINCT user_id, login_date
    FROM login
  ) a
  JOIN user_first_week b ON a.user_id = b.user_id
)
SELECT
  first_week,
  SUM(CASE WHEN week_number = 0 THEN 1 ELSE 0 END) AS week_0,
  SUM(CASE WHEN week_number = 1 THEN 1 ELSE 0 END) AS week_1,
  SUM(CASE WHEN week_number = 2 THEN 1 ELSE 0 END) AS week_2,
  SUM(CASE WHEN week_number = 3 THEN 1 ELSE 0 END) AS week_3,
  SUM(CASE WHEN week_number = 4 THEN 1 ELSE 0 END) AS week_4,
  SUM(CASE WHEN week_number = 5 THEN 1 ELSE 0 END) AS week_5,
  SUM(CASE WHEN week_number = 6 THEN 1 ELSE 0 END) AS week_6,
  SUM(CASE WHEN week_number = 7 THEN 1 ELSE 0 END) AS week_7,
  SUM(CASE WHEN week_number = 8 THEN 1 ELSE 0 END) AS week_8,
  SUM(CASE WHEN week_number = 9 THEN 1 ELSE 0 END) AS week_9
FROM user_login_weeks
GROUP BY first_week
ORDER BY first_week;

修正说明

  1. 统一周数基准:所有周数计算都执行-1操作,对齐MySQL的周起始逻辑,确保和原文的周数定义一致。
  2. 拆分CTE逻辑:将用户首次周计算与登录周差值计算拆分为两个独立CTE,逻辑更清晰易维护。
  3. 简化去重逻辑:用DISTINCT替代GROUP BY user_id, login_date,消除冗余代码。

内容的提问来源于stack exchange,提问作者Karina

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 16:37:01