cswork / libraryOpen practice workspace

Coding questions · Meta

Write SQL for engagement and attribution KPIs

This question evaluates SQL data-manipulation skills for engagement and attribution KPIs, including joins, time-window (24-hour) analysis, deduplication of event data, conditional aggregation, and correct denominator construction for rate metrics.

Using the schema and sample data below, answer the SQL tasks. Assume timestamps are UTC and comments with is_deleted=1 do not count. Schema:

users(user_id INT, created_at TIMESTAMP, locale STRING)
posts(post_id INT, user_id INT, created_at TIMESTAMP)
comments(comment_id INT, post_id INT, user_id INT, created_at TIMESTAMP, is_deleted TINYINT)
email_sends(send_id INT, user_id INT, campaign STRING, send_ts TIMESTAMP)
email_events(event_id INT, send_id INT, event_type STRING /* delivered|bounce|open|click */, event_ts TIMESTAMP)

Sample tables (tiny subset):

users
user_id | created_at | locale
1 | 2025-07-01 00:00:00 | en_US
2 | 2025-07-10 00:00:00 | en_US
3 | 2025-08-02 00:00:00 | fr_FR
4 | 2025-08-15 00:00:00 | en_US
posts
post_id | user_id | created_at
101 | 1 | 2025-08-31 10:00:00
102 | 1 | 2025-09-01 12:00:00
103 | 2 | 2025-09-01 13:30:00
104 | 3 | 2025-09-02 09:00:00
comments
comment_id | post_id | user_id | created_at | is_deleted
1001 | 101 | 2 | 2025-09-01 11:00:00 | 0
1002 | 101 | 3 | 2025-09-03 08:00:00 | 0
1003 | 104 | 4 | 2025-09-02 10:00:00 | 1
1004 | 104 | 2 | 2025-09-03 09:00:00 | 0
email_sends
send_id | user_id | campaign | send_ts
5001 | 2 | reengage_v1 | 2025-09-01 09:00:00
5002 | 3 | reengage_v1 | 2025-09-01 09:05:00
email_events
event_id | send_id | event_type | event_ts
6001 | 5001 | delivered | 2025-09-01 09:00:30
6002 | 5001 | open | 2025-09-01 09:10:00
6003 | 5001 | click | 2025-09-01 09:12:00
6004 | 5002 | bounce | 2025-09-01 09:05:30

Tasks:

  1. For each calendar day, compute total posts created and the percentage that received ≥1 non‑deleted comment within 24 hours of post creation.
  2. For the last 30 days, list the top 3 creators (user_id) by that 24‑hour commented‑post rate, requiring at least 5 posts per creator; break ties by higher post count, then lower user_id.
  3. For campaign reengage_v1, compute daily unique open rate and click‑through rate on delivered emails: denominator = delivered sends (exclude bounces), numerator = at most one open or one click per send_id; return campaign, date, delivered_sends, unique_opens, unique_clicks, open_rate, ctr.

Daily 24-hour commented-post rate

Using the PostgreSQL posts and comments tables defined below, compute daily post engagement.

For each calendar day on which at least one post was created, return:

  1. post_date: DATE of posts.created_at
  2. total_posts: number of posts created on that date
  3. commented_posts_24h: number of posts that received at least one non-deleted comment within 24 hours after the post's created_at timestamp
  4. commented_rate_24h: commented_posts_24h / total_posts as a decimal

Rules:

  • comments.is_deleted is an integer flag where 0 means the comment counts and 1 means it is deleted.
  • Treat timestamps as UTC.
  • Count each qualifying post once even if it has multiple qualifying comments.

Return rows ordered by post_date.

Table posts

columntypeconstraints
post_idINTPRIMARY KEY, NOT NULL
user_idINTNOT NULL
created_atTIMESTAMPNOT NULL

Sample data:

created_atpost_iduser_id
2025-04-30 09:00:001203
2025-05-20 10:00:001011
2025-05-21 12:00:001021
2025-05-21 13:30:001032
2025-05-22 09:00:001043
2025-05-25 15:00:001053
2025-06-01 08:00:001064
2025-05-23 11:00:001071
2025-05-24 11:00:001081
2025-05-28 11:00:001091
2025-05-30 11:00:001101
2025-05-26 10:00:001113
2025-05-27 10:00:001123
2025-05-28 10:00:001133
2025-05-30 10:00:001143
2025-05-29 14:00:001152
2025-05-31 14:00:001162
2025-05-18 14:00:001172

Table comments

columntypeconstraints
comment_idINTPRIMARY KEY, NOT NULL
post_idINTNOT NULL
user_idINTNOT NULL
created_atTIMESTAMPNOT NULL
is_deletedSMALLINTNOT NULL

Sample data:

comment_idcreated_atis_deletedpost_iduser_id
10012025-05-20 15:00:0001012
10022025-05-22 11:00:0001013
10032025-05-22 10:00:0011044
10042025-05-23 09:30:0001042
10052025-05-25 16:00:0001051
10062025-06-02 07:00:0001062
10072025-05-01 08:00:0001202
10082025-05-24 10:00:0001073
10092025-05-26 12:00:0001082
10102025-05-28 12:00:0001094
10112025-05-31 12:00:0001103
10122025-05-26 11:00:0001111
10132025-05-27 18:00:0001122
10142025-05-29 10:30:0001134
10152025-05-30 11:30:0011141
10162025-05-30 13:00:0001151
10172025-06-02 15:00:0001163
10182025-05-19 12:00:0001174

Expected output

One row per calendar day with posts, showing total posts created, how many received at least one non-deleted comment within 24 hours, and the corresponding rate.

commented_posts_24hcommented_rate_24hpost_datetotal_posts
112025-04-301
112025-05-181
112025-05-201
002025-05-212
002025-05-221
112025-05-231
002025-05-241
112025-05-251
112025-05-261
112025-05-271
10.52025-05-282
112025-05-291
002025-05-302
002025-05-311
112025-06-011

Hints

  1. Use a DISTINCT post_id CTE to avoid double-counting posts with multiple qualifying comments.
  2. Use a half-open interval: comment created_at >= post created_at and < post created_at + INTERVAL '24 hours'.
  3. Aggregate from posts with a left join so posts without qualifying comments remain in the denominator.

Top creators by 24-hour commented-post rate over a fixed 30-day window

Using the same PostgreSQL posts and comments tables, consider only posts created from 2025-05-03 through 2025-06-01, inclusive.

For each creator (posts.user_id), compute:

  1. total_posts in this date window
  2. commented_posts_24h: posts with at least one non-deleted comment within 24 hours after creation
  3. commented_rate_24h: commented_posts_24h / total_posts as a decimal

Return the top 3 creators by commented_rate_24h, but only include creators with at least 5 posts in the window. Break ties by higher total_posts, then lower user_id.

Rules:

  • comments.is_deleted is an integer flag where 0 means the comment counts and 1 means it is deleted.
  • Count each qualifying post once even if it has multiple qualifying comments.

Table posts

columntypeconstraints
post_idINTPRIMARY KEY, NOT NULL
user_idINTNOT NULL
created_atTIMESTAMPNOT NULL

Sample data:

created_atpost_iduser_id
2025-04-30 09:00:001203
2025-05-20 10:00:001011
2025-05-21 12:00:001021
2025-05-21 13:30:001032
2025-05-22 09:00:001043
2025-05-25 15:00:001053
2025-06-01 08:00:001064
2025-05-23 11:00:001071
2025-05-24 11:00:001081
2025-05-28 11:00:001091
2025-05-30 11:00:001101
2025-05-26 10:00:001113
2025-05-27 10:00:001123
2025-05-28 10:00:001133
2025-05-30 10:00:001143
2025-05-29 14:00:001152
2025-05-31 14:00:001162
2025-05-18 14:00:001172

Table comments

columntypeconstraints
comment_idINTPRIMARY KEY, NOT NULL
post_idINTNOT NULL
user_idINTNOT NULL
created_atTIMESTAMPNOT NULL
is_deletedSMALLINTNOT NULL

Sample data:

comment_idcreated_atis_deletedpost_iduser_id
10012025-05-20 15:00:0001012
10022025-05-22 11:00:0001013
10032025-05-22 10:00:0011044
10042025-05-23 09:30:0001042
10052025-05-25 16:00:0001051
10062025-06-02 07:00:0001062
10072025-05-01 08:00:0001202
10082025-05-24 10:00:0001073
10092025-05-26 12:00:0001082
10102025-05-28 12:00:0001094
10112025-05-31 12:00:0001103
10122025-05-26 11:00:0001111
10132025-05-27 18:00:0001122
10142025-05-29 10:30:0001134
10152025-05-30 11:30:0011141
10162025-05-30 13:00:0001151
10172025-06-02 15:00:0001163
10182025-05-19 12:00:0001174

Expected output

Top creators (by user_id) over the fixed window 2025-05-03 to 2025-06-01, ordered by 24-hour commented-post rate, then total_posts, then user_id. Only creators with at least 5 posts are included. With the sample data, two creators qualify.

commented_posts_24hcommented_rate_24htotal_postsuser_id
30.561
30.563

Hints

  1. Reuse a DISTINCT post_id marker CTE for qualifying non-deleted comments within 24 hours.
  2. Filter posts by DATE(created_at) between the two inclusive DATE literals before aggregating per user.
  3. Apply the minimum-post threshold before ranking, then rank by rate descending, total_posts descending, and user_id ascending.

Daily unique open rate and click-through rate for an email campaign

Using the email_sends and email_events tables, compute daily engagement metrics for campaign 'reengage_v1'. For each calendar day (based on email_sends.send_ts) and this campaign, compute: delivered_sends = number of send_ids that have a 'delivered' event (exclude bounced sends), unique_opens = number of delivered send_ids that have at least one 'open' event, unique_clicks = number of delivered send_ids that have at least one 'click' event, open_rate = unique_opens / delivered_sends, and ctr = unique_clicks / delivered_sends. Count at most one open and one click per send_id in the numerators. Return campaign, date, delivered_sends, unique_opens, unique_clicks, open_rate, ctr.

Table email_sends

columntypeconstraints
send_idINTPRIMARY KEY, NOT NULL
user_idINTNOT NULL
campaignVARCHAR(50)NOT NULL
send_tsTIMESTAMPNOT NULL

Sample data:

campaignsend_idsend_tsuser_id
reengage_v150012025-05-20 09:00:002
reengage_v150022025-05-20 09:05:003
reengage_v150032025-05-21 10:00:001
welcome50042025-05-21 11:00:004

Table email_events

columntypeconstraints
event_idINTPRIMARY KEY, NOT NULL
send_idINTNOT NULL
event_typeVARCHAR(20)NOT NULL
event_tsTIMESTAMPNOT NULL

Sample data:

event_idevent_tsevent_typesend_id
60012025-05-20 09:00:30delivered5001
60022025-05-20 09:10:00open5001
60032025-05-20 09:12:00click5001
60042025-05-20 09:05:30bounce5002
60052025-05-21 10:00:20delivered5003
60062025-05-21 10:05:00open5003
60072025-05-21 10:15:00open5003
60082025-05-21 11:00:10delivered5004
60092025-05-21 11:05:00open5004
60102025-05-21 11:06:00click5004
60112025-05-20 09:20:00open5001
60122025-05-20 09:25:00click5001

Expected output

Per day metrics for campaign 'reengage_v1'. For each send date, counts of delivered sends (with a delivered event), unique opens, unique clicks, and the corresponding open rate and click-through rate.

campaignctrdatedelivered_sendsopen_rateunique_clicksunique_opens
reengage_v112025-05-201111
reengage_v102025-05-211101

Hints

  1. Start by identifying delivered sends for the target campaign using a CTE that joins email_sends and email_events on event_type = 'delivered'.
  2. From that delivered set, derive distinct send_ids with opens and clicks, then aggregate per (campaign, send_date) to compute counts and rates.