Calculate Percentage of Players Returning the Day After First Login

To determine the fraction of players who log in on the day immediately following their first login, we analyze the Activity table, which records player events with timestamps.

First, identify each player’s earliest login date and compute the expected return date — that is, the day after their first activity:

SELECT player_id, DATE_ADD(MIN(event_date), INTERVAL 1 DAY) AS next_day
FROM Activity
GROUP BY player_id;

This subquery produces a result set where each row corresponds to a player and the date they should return to qualify as a returning user.

Next, find which players actually logged in on that computed next day. This requires joining the computed next-day dates with the original Activity table, matching both player ID and event date:

SELECT DISTINCT a.player_id
FROM (
    SELECT player_id, DATE_ADD(MIN(event_date), INTERVAL 1 DAY) AS next_day
    FROM Activity
    GROUP BY player_id
) AS first_login
JOIN Activity a
  ON a.player_id = first_login.player_id
 AND a.event_date = first_login.next_day;

This query uses an explicit JOIN with an ON condition, ensuring only matching records from both sides are included — an inner join. It avoids the pitfalls of implicit joins and Cartesian products.

Finally, compute the fraction by dividing the count of returning players by the total number of unique players in the dataset:

SELECT ROUND(
    COUNT(DISTINCT returning_players.player_id) * 1.0 / COUNT(DISTINCT all_players.player_id),
    2
) AS fraction
FROM (
    SELECT DISTINCT a.player_id
    FROM (
        SELECT player_id, DATE_ADD(MIN(event_date), INTERVAL 1 DAY) AS next_day
        FROM Activity
        GROUP BY player_id
    ) AS first_login
    JOIN Activity a
      ON a.player_id = first_login.player_id
     AND a.event_date = first_login.next_day
) AS returning_players,
Activity AS all_players;

Note the use of * 1.0 to enforce floating-point division in SQL dialects that default to integer arithmetic. The DISTINCT ensures we count each player only once, evenif they have multiple entries on the target date.

While the original approach used an implicit cross join between the result set and the full Activity table, this can lead to inflated counts due to Cartesian multiplication. For example, if 10 players returned and the full table has 100 rows, an implicit join without proper filtering would produce 10 × 100 = 1000 rows, skewing the denominator. Using explicit JOINN with conditions avoids this entirely.

Implicit joins (comma-separated tables in the FROM clause) are deprecated in modern SQL for clarity and safety. Always prefer explicit JOIN syntax with ON conditions to ensure predictable behavior and maintainability.

Tags: sql aggregate-functions DATE_ADD INNER JOIN player-retention

Posted on Sat, 01 Aug 2026 16:01:13 +0000 by koughman