Coding questions · Meta
Write SQL to analyze shop visibility
This question evaluates proficiency in SQL-based time-series and event-data manipulation, including deduplication of consecutive states, window functions, aggregation, cohort analysis, and percentile/summary statistics.
You are given two tables. Use standard SQL (window functions allowed). Assume "today" is 2025-09-01 and that “currently visible” means a shop’s last state at or before 2025-09-01 23:59:59. Treat only changes where visibility flips (ignore consecutive duplicates).
Schema: shop_visibility(profile_id BIGINT, date DATE, ts TIMESTAMP, visibility INT) — 0=hidden, 1=visible shop_details(profile_id BIGINT, shop_category VARCHAR, visible_days BIGINT, creation_date DATE)
Small sample (illustrative only):
shop_visibility
+------------+------------+---------------------+------------+
| profile_id | date | ts | visibility |
+------------+------------+---------------------+------------+
| 101 | 2025-08-30 | 2025-08-30 09:00:00 | 1 |
| 101 | 2025-08-31 | 2025-08-31 11:00:00 | 0 |
| 101 | 2025-09-01 | 2025-09-01 10:15:00 | 1 |
| 102 | 2025-08-30 | 2025-08-30 08:00:00 | 1 |
| 102 | 2025-08-30 | 2025-08-30 12:00:00 | 1 |
| 102 | 2025-09-01 | 2025-09-01 18:00:00 | 0 |
| 103 | 2025-08-31 | 2025-08-31 09:00:00 | 0 |
| 103 | 2025-09-01 | 2025-09-01 20:00:00 | 0 |
| 104 | 2025-08-31 | 2025-08-31 05:00:00 | 0 |
| 104 | 2025-09-01 | 2025-09-01 09:00:00 | 1 |
| 104 | 2025-09-01 | 2025-09-01 12:30:00 | 0 |
| 104 | 2025-09-01 | 2025-09-01 16:45:00 | 1 |
| 105 | 2025-09-01 | 2025-09-01 07:00:00 | 1 |
| 106 | 2025-08-30 | 2025-08-30 06:00:00 | 0 |
| 106 | 2025-08-30 | 2025-08-30 14:00:00 | 1 |
| 106 | 2025-08-31 | 2025-08-31 14:05:00 | 0 |
| 106 | 2025-08-31 | 2025-08-31 14:10:00 | 1 |
| 106 | 2025-09-01 | 2025-09-01 02:00:00 | 0 |
| 106 | 2025-09-01 | 2025-09-01 22:00:00 | 1 |
shop_details
+------------+---------------+--------------+--------------+
| profile_id | shop_category | visible_days | creation_date|
+------------+---------------+--------------+--------------+
| 101 | Food | 180 | 2025-02-01 |
| 102 | Fashion | 120 | 2025-03-15 |
| 103 | Electronics | 30 | 2025-08-10 |
| 104 | Food | 75 | 2025-07-20 |
| 105 | Home | 200 | 2024-12-05 |
| 106 | Fashion | 10 | 2025-08-28 |
Write SQL for:
- Top 10 profiles by number of visibility flips (count transitions where visibility != LAG(visibility) over ts). Break ties by smaller profile_id first.
- Percentage of shops currently visible as of 2025-09-01 (numerator: profiles whose last state by ts on or before 2025-09-01 23:59:59 is 1; denominator: all profiles in shop_details). Return both percentage and counts.
- Distribution of currently visible shops across categories: for each shop_category, return visible_count and its share among all currently visible shops.
- Verify: “New users over time are visible far fewer days.” For each creation month, compute for every profile the fraction of days visible in its first 30 days since creation (using shop_visibility, deduping consecutive identical states, counting distinct dates with final visibility=1 per date), then return for each cohort: number of profiles, median fraction, and P75/P25. Include SQL that produces a cohort-level time series suitable for visualization. Briefly state how you would handle profiles created after 2025-08-02 (truncate the 30-day window) and how to test the trend statistically.
Top profiles by visibility flips
Using the tables below, write a SQL query to find the top 10 profiles by the number of visibility flips. A visibility flip is any row where the visibility value (0=hidden, 1=visible) is different from the previous row for that profile when ordered by ts; ignore consecutive duplicate states. Return profile_id and flip_count, sorted by flip_count in descending order and then by profile_id ascending, limited to at most 10 rows.
Table shop_visibility
| column | type | constraints |
|---|---|---|
| profile_id | BIGINT | NOT NULL |
| date | DATE | NOT NULL |
| ts | TIMESTAMP | NOT NULL |
| visibility | INT | NOT NULL |
Sample data:
| date | profile_id | ts | visibility |
|---|---|---|---|
| 2025-08-30 | 101 | 2025-08-30 09:00:00 | 1 |
| 2025-08-31 | 101 | 2025-08-31 11:00:00 | 0 |
| 2025-09-01 | 101 | 2025-09-01 10:15:00 | 1 |
| 2025-08-30 | 102 | 2025-08-30 08:00:00 | 1 |
| 2025-08-30 | 102 | 2025-08-30 12:00:00 | 1 |
| 2025-09-01 | 102 | 2025-09-01 18:00:00 | 0 |
| 2025-08-31 | 103 | 2025-08-31 09:00:00 | 0 |
| 2025-09-01 | 103 | 2025-09-01 20:00:00 | 0 |
| 2025-08-31 | 104 | 2025-08-31 05:00:00 | 0 |
| 2025-09-01 | 104 | 2025-09-01 09:00:00 | 1 |
| 2025-09-01 | 104 | 2025-09-01 12:30:00 | 0 |
| 2025-09-01 | 104 | 2025-09-01 16:45:00 | 1 |
| 2025-09-01 | 105 | 2025-09-01 07:00:00 | 1 |
| 2025-08-30 | 106 | 2025-08-30 06:00:00 | 0 |
| 2025-08-30 | 106 | 2025-08-30 14:00:00 | 1 |
| 2025-08-31 | 106 | 2025-08-31 14:05:00 | 0 |
| 2025-08-31 | 106 | 2025-08-31 14:10:00 | 1 |
| 2025-09-01 | 106 | 2025-09-01 02:00:00 | 0 |
| 2025-09-01 | 106 | 2025-09-01 22:00:00 | 1 |
Table shop_details
| column | type | constraints |
|---|---|---|
| profile_id | BIGINT | NOT NULL |
| shop_category | VARCHAR(50) | NOT NULL |
| visible_days | BIGINT | |
| creation_date | DATE | NOT NULL |
Sample data:
| creation_date | profile_id | shop_category | visible_days |
|---|---|---|---|
| 2025-02-01 | 101 | Food | 180 |
| 2025-03-15 | 102 | Fashion | 120 |
| 2025-08-10 | 103 | Electronics | 30 |
| 2025-07-20 | 104 | Food | 75 |
| 2024-12-05 | 105 | Home | 200 |
| 2025-08-28 | 106 | Fashion | 10 |
Expected output
Top profiles by number of visibility flips, ordered by flip_count descending and profile_id ascending. Profiles with at least one visibility flip are returned.
| flip_count | profile_id |
|---|---|
| 5 | 106 |
| 3 | 104 |
| 2 | 101 |
| 1 | 102 |
Hints
- Use LAG(visibility) OVER (PARTITION BY profile_id ORDER BY ts) to compare each row to the previous state.
- Count only rows where visibility differs from the previous visibility, then aggregate by profile_id and order by that count.
Percentage of shops currently visible as of 2025-09-01
You are analyzing shop visibility for Meta's commerce platform. Each shop profile logs visibility-state changes over time in shop_visibility (visibility = 1 means visible, 0 means hidden), and every profile has one descriptive row in shop_details.
A shop profile is considered currently visible as of 2025-09-01 23:59:59 if its last visibility record at or before that timestamp has visibility = 1. A profile that has no visibility records at or before the cutoff is treated as not visible.
Write a SQL query that returns a single row with these three columns, in this order:
visible_profiles— the number of profiles that are currently visible as of2025-09-01 23:59:59.total_profiles— the total number of profiles inshop_details.visible_percentage—visible_profiles / total_profiles * 100, rounded to two decimal places.
The denominator is the full set of profiles in shop_details, including any profile that has no visibility records.
Table shop_visibility
| column | type | constraints |
|---|---|---|
| profile_id | BIGINT | NOT NULL |
| date | DATE | NOT NULL |
| ts | TIMESTAMP | NOT NULL |
| visibility | INT | NOT NULL |
Sample data:
| date | profile_id | ts | visibility |
|---|---|---|---|
| 2025-08-30 | 101 | 2025-08-30 09:00:00 | 1 |
| 2025-08-31 | 101 | 2025-08-31 11:00:00 | 0 |
| 2025-09-01 | 101 | 2025-09-01 10:15:00 | 1 |
| 2025-08-30 | 102 | 2025-08-30 08:00:00 | 1 |
| 2025-08-30 | 102 | 2025-08-30 12:00:00 | 1 |
| 2025-09-01 | 102 | 2025-09-01 18:00:00 | 0 |
| 2025-08-31 | 103 | 2025-08-31 09:00:00 | 0 |
| 2025-09-01 | 103 | 2025-09-01 20:00:00 | 0 |
| 2025-08-31 | 104 | 2025-08-31 05:00:00 | 0 |
| 2025-09-01 | 104 | 2025-09-01 09:00:00 | 1 |
| 2025-09-01 | 104 | 2025-09-01 12:30:00 | 0 |
| 2025-09-01 | 104 | 2025-09-01 16:45:00 | 1 |
| 2025-09-01 | 105 | 2025-09-01 07:00:00 | 1 |
| 2025-08-30 | 106 | 2025-08-30 06:00:00 | 0 |
| 2025-08-30 | 106 | 2025-08-30 14:00:00 | 1 |
| 2025-08-31 | 106 | 2025-08-31 14:05:00 | 0 |
| 2025-08-31 | 106 | 2025-08-31 14:10:00 | 1 |
| 2025-09-01 | 106 | 2025-09-01 02:00:00 | 0 |
| 2025-09-01 | 106 | 2025-09-01 22:00:00 | 1 |
Table shop_details
| column | type | constraints |
|---|---|---|
| profile_id | BIGINT | NOT NULL |
| shop_category | VARCHAR(50) | NOT NULL |
| visible_days | BIGINT | |
| creation_date | DATE | NOT NULL |
Sample data:
| creation_date | profile_id | shop_category | visible_days |
|---|---|---|---|
| 2025-02-01 | 101 | Food | 180 |
| 2025-03-15 | 102 | Fashion | 120 |
| 2025-08-10 | 103 | Electronics | 30 |
| 2025-07-20 | 104 | Food | 75 |
| 2024-12-05 | 105 | Home | 200 |
| 2025-08-28 | 106 | Fashion | 10 |
Expected output
A single row giving the count of currently-visible profiles, the total number of profiles in shop_details, and the visible percentage rounded to two decimals.
| total_profiles | visible_percentage | visible_profiles |
|---|---|---|
| 6 | 66.67 | 4 |
Hints
- Use ROW_NUMBER() OVER (PARTITION BY profile_id ORDER BY ts DESC) to isolate each profile's most recent visibility record on or before the cutoff timestamp.
- Drive the query from shop_details with a LEFT JOIN so profiles with no visibility logs still count in the denominator; COALESCE the missing visibility to 0.
- Multiply by 100.0 (not 100) before ROUND(..., 2) so the division is numeric rather than integer.
Category distribution of currently visible shops
A shop profile is considered currently visible as of 2025-09-01 23:59:59 if its most recent visibility state with a timestamp on or before that moment has visibility = 1. A profile with no visibility events on or before the cutoff is treated as not currently visible.
You are given two tables:
shop_details— one row per shop profile (profile_id,shop_category).shop_visibility— a log of visibility state changes (profile_id,visibility,ts), wherevisibility = 1means the profile became visible andvisibility = 0means it became hidden.
Write a SQL query that returns, for each shop_category, the number of currently visible profiles and that category's percentage share among all currently visible profiles.
The result must contain exactly these columns:
shop_category— the category name.visible_count— the number of currently visible profiles in that category.visible_share_percentage—visible_countdivided by the total number of currently visible profiles across all categories, expressed as a percentage rounded to two decimal places (e.g.50.00).
Return only categories that have at least one currently visible profile. Order the result by visible_count descending, then by shop_category ascending.
Table shop_details
| column | type | constraints |
|---|---|---|
| profile_id | INTEGER | |
| shop_category | VARCHAR(50) |
Sample data:
| profile_id | shop_category |
|---|---|
| 1 | Food |
| 2 | Food |
| 3 | Fashion |
| 4 | Home |
| 5 | Food |
| 6 | Fashion |
| 7 | Home |
Table shop_visibility
| column | type | constraints |
|---|---|---|
| profile_id | INTEGER | |
| visibility | INTEGER | |
| ts | TIMESTAMP |
Sample data:
| profile_id | ts | visibility |
|---|---|---|
| 1 | 2025-08-01 09:00:00 | 0 |
| 1 | 2025-08-20 14:30:00 | 1 |
| 2 | 2025-08-10 08:15:00 | 1 |
| 3 | 2025-08-15 11:45:00 | 1 |
| 4 | 2025-07-01 10:00:00 | 0 |
| 4 | 2025-08-25 16:20:00 | 1 |
| 5 | 2025-08-05 07:30:00 | 1 |
| 5 | 2025-08-28 19:00:00 | 0 |
| 6 | 2025-09-05 12:00:00 | 1 |
Expected output
One row per category that has at least one currently-visible profile (as of 2025-09-01 23:59:59), with its visible profile count and percentage share of all currently-visible profiles, ordered by visible_count desc then shop_category asc.
| shop_category | visible_count | visible_share_percentage |
|---|---|---|
| Food | 2 | 50.00 |
| Fashion | 1 | 25.00 |
| Home | 1 | 25.00 |
Hints
- For each profile, find its latest visibility event with ts on or before the cutoff using ROW_NUMBER() OVER (PARTITION BY profile_id ORDER BY ts DESC), and keep only rn = 1.
- LEFT JOIN shop_details to that latest state and COALESCE the visibility to 0 so profiles with no qualifying events count as not visible; keep only the ones equal to 1.
- Get each category's share with SUM(COUNT()) OVER () as the grand total, then ROUND(100.0 * COUNT() / total, 2).