cswork / libraryOpen practice workspace

Coding questions · Meta

Design SQL Query for Shop Visibility and User Activity Metrics

This question evaluates proficiency in SQL-based data manipulation—aggregation, JOINs, CASE expressions, window functions, ranking, and metric design for user tenure segmentation and temporal activity comparisons.

SHOP_VISIBILITY

+----------+---------+------------+------------+-------------+--------------+
| user_id  | shop_id | event_date | is_visible | signup_date | action_type  |
+----------+---------+------------+------------+-------------+--------------+
| 101      | 12      | 2023-06-01 | TRUE       | 2023-05-20  | view         |
| 102      | 18      | 2023-06-01 | FALSE      | 2021-11-02  | view         |
| 103      | 44      | 2023-06-02 | TRUE       | 2023-06-01  | click        |
| 104      | 12      | 2023-06-02 | TRUE       | 2020-01-15  | purchase     |
| 105      | 18      | 2023-06-02 | FALSE      | 2023-05-30  | view         |
+----------+---------+------------+------------+-------------+--------------+
Scenario

E-commerce ‘shop visibility’ dataset used to test SQL skills and metric design.

Question

Write a query that for each shop returns total visible days, ordered descending (GROUP BY, HAVING, ORDER BY). Extend it to include shop category with a JOIN and apply CASE logic for visibility buckets. Using window functions, rank shops by average daily visibility within their category. Define and implement a metric—using today’s snapshot—to compare activity of new versus old users by grouping users into tenure buckets and reporting activity rates.

Hints

Compute tenure as DATEDIFF(today, signup_date); activity = active_events / users.

Per-shop visible days

Compute the number of distinct days each shop was visible (is_visible = TRUE). Exclude shops with zero visible days and order results by visible_days descending, then shop_id ascending.

Table SHOP_VISIBILITY

columntypeconstraints
user_idINTEGERNOT NULL
shop_idINTEGERNOT NULL
event_dateDATENOT NULL
is_visibleBOOLEANNOT NULL
signup_dateDATENOT NULL
action_typeVARCHAR(20)NOT NULL

Sample data:

action_typeevent_dateis_visibleshop_idsignup_dateuser_id
view2023-06-01true122023-05-20101
view2023-06-01false182021-11-02102
click2023-06-02true442023-06-01103
purchase2023-06-02true122020-01-15104
view2023-06-02false182023-05-30105

Expected output

Per-shop count of distinct dates where the shop was visible, excluding shops with zero visible days, ordered by visible_days descending then shop_id ascending.

shop_idvisible_days
122
441

Hints

  1. Use COUNT(DISTINCT ...) on event_date conditioned by is_visible
  2. Filter out zero counts with HAVING
  3. Order by visible_days desc

Visibility by category rank

For each shop, join its category, compute visible_days across all dates, derive avg_daily_visibility = visible_days / total_distinct_event_days, bucket visibility (None/Low/High), and rank shops within their category by avg_daily_visibility (ties get the same rank).

Table SHOP_VISIBILITY

columntypeconstraints
user_idINTEGERNOT NULL
shop_idINTEGERNOT NULL
event_dateDATENOT NULL
is_visibleBOOLEANNOT NULL
signup_dateDATENOT NULL
action_typeVARCHAR(20)NOT NULL

Sample data:

action_typeevent_dateis_visibleshop_idsignup_dateuser_id
view2023-06-01true122023-05-20101
view2023-06-01false182021-11-02102
click2023-06-02true442023-06-01103
purchase2023-06-02true122020-01-15104
view2023-06-02false182023-05-30105

Table SHOP_DIM

columntypeconstraints
shop_idINTEGERPRIMARY KEY, NOT NULL
categoryVARCHAR(50)NOT NULL

Sample data:

categoryshop_idname
Electronics12NULL
Fashion18shop_id
Electronics44NULL

Expected output

Per-shop visibility enriched with category, average daily visibility over all dates, visibility bucket, and rank within the category by average daily visibility.

avg_daily_visibilitycategorycategory_rankshop_idvisibility_bucketvisible_days
1Electronics112High2
0.5Electronics244Low1
0Fashion118None0

Hints

  1. Aggregate to shop-day first to avoid double counting
  2. Use total distinct event days as the denominator
  3. Rank within category with DENSE_RANK

Snapshot activity by tenure

Snapshot activity by user tenure

Using the SHOP_VISIBILITY table and a fixed snapshot date of 2023-06-02, summarize that day's activity broken down by each user's tenure bucket.

Consider only the events that occurred on the snapshot date (event_date = DATE '2023-06-02'). For each such event, compute the user's tenure in days as snapshot_date - signup_date and assign a tenure bucket:

  • New — tenure < 30 days
  • Established — tenure 30 to 179 days (i.e. >= 30 and < 180)
  • Old — tenure >= 180 days

Return one row per tenure bucket that has at least one event on the snapshot date, with the following columns:

  • snapshot_date — the constant date 2023-06-02
  • tenure_bucket — 'New', 'Established', or 'Old'
  • active_users — count of distinct user_id values active in that bucket on the snapshot date
  • active_events — total number of events (rows) in that bucket on the snapshot date
  • activity_rate — active_events / active_users (events per active user), rounded to 4 decimal places

Order the result by tenure_bucket ascending (alphabetical).

Table SHOP_VISIBILITY

columntypeconstraints
user_idINTEGERNOT NULL
shop_idINTEGERNOT NULL
event_dateDATENOT NULL
is_visibleBOOLEANNOT NULL
signup_dateDATENOT NULL
action_typeVARCHAR(20)NOT NULL

Sample data:

action_typeevent_dateis_visibleshop_idsignup_dateuser_id
view2023-06-01true122023-05-20101
view2023-06-01false182021-11-02102
click2023-06-02true442023-06-01103
purchase2023-06-02true122020-01-15104
view2023-06-02false182023-05-30105
view2023-06-02true442023-04-10106
click2023-06-02true122023-04-10106

Table SHOP_DIM

columntypeconstraints
shop_idINTEGERPRIMARY KEY, NOT NULL
categoryVARCHAR(50)NOT NULL

Sample data:

categoryshop_id
Electronics12
Fashion18
Electronics44

Expected output

On 2023-06-02, one row per active tenure bucket with distinct active users, total events, and activity_rate (events per user), ordered by bucket.

active_eventsactive_usersactivity_ratesnapshot_datetenure_bucket
212.00002023-06-02Established
221.00002023-06-02New
111.00002023-06-02Old

Hints

  1. Pin the snapshot date in a CTE and join on event_date to keep only that day's events.
  2. In PostgreSQL, subtracting two DATEs (snapshot_date - signup_date) yields an integer day count - no DATEDIFF needed.
  3. Use COUNT(DISTINCT user_id) for active_users vs COUNT(*) for events, and cast to numeric before dividing.