Skip to main content
  1. Posts/

SQL 计算次日留存率

·1 min·

SQL 计算次日留存率 #

计算留存率是 SQL 的典型题目, 这里以牛客网的题目为例. 数据表如下:

{
  "question_practice_detail": [
    {
      "id": "编号",
      "device_id": "用户使用的设备编号",
      "quest_id": "题目编号",
      "result": "答题结果",
      "date": "日期"
    }
  ]
}

要求返回的结果为:

{
  "result": [
    {
      "avg_ret": "平均留存率"
    }
  ]
}

Solution 1 #

为了找出所有在 “第二天” 刷了题的用户, 我们可以将 question_practice_detail 表自连接, 连接规则为 device_id 相同且 date 相差一天. 为了统计方便, 我们使用 LEFT JOIN, 这样表中包含了有第二天记录的用户, 也包含了没有第二天记录的用户. 其中 date1 为第一天的日期, date2 为第二天的日期. 为了避免重复计算, 我们使用 DISTINCT 去重. 最后, 我们只需要计算 date2 的个数除以 date1 的个数即可.

SELECT COUNT(date2) / COUNT(date1) AS avg_ret
FROM (
    SELECT
        DISTINCT qpd.device_id,
        qpd.date AS date1,
        uniq_id_date.date AS date2
    FROM question_practice_detail AS qpd
    LEFT JOIN (
        SELECT DISTINCT device_id, date
        FROM question_practice_detail
    ) AS uniq_id_date
    ON qpd.device_id = uniq_id_date.device_id
        AND DATE_ADD(qpd.date, INTERVAL 1 DAY) = uniq_id_date.date
) AS id_last_next_date;

Solution 2 #

使用 LEAD 函数结合 PARTITION BY 找出每一个用户第二次刷题的数据记为 date2, 然后使用 DATEDIFF 函数计算 date2date1 的差值, 如果差值为 1, 则记为 1, 否则记为 0. 最后求平均值即可.

SELECT AVG(IF(DATEDIFF(date2, date1) = 1, 1, 0)) AS avg_ret
FROM (
    SELECT
        DISTINCT device_id,
        date AS date1,
        LEAD(date) OVER (PARTITION BY device_id ORDER BY date) AS date2
    FROM (
        SELECT DISTINCT device_id, date
        FROM question_practice_detail
    ) AS uniq_id_date
) AS id_last_next_date;