CMD Guide
HomeDatabasesSQL Practice Problems

Active Businesses

A window AVG(occurrences) OVER (PARTITION BY event_type) hangs the per-event-type average onto every row of that event type without collapsing the rows, so a single pass over Events lets you compare each occurrence against its own event's mean and then count, per business, how many events cleared the bar.

The problem, precisely

Table Events(business_id, event_type, occurrences) with primary key (business_id, event_type) — one row per business per event type. The average activity of an event type is the mean of occurrences across all businesses that have that event. A business is active if it has more than one event type whose occurrences is strictly greater than that event type's average. Find every active business_id.

The whole task is two reductions stacked: first reduce within each event type (compute its average), then reduce within each business (count how many of its events beat their average). The window function does the first reduction; GROUP BY business_id ... HAVING COUNT(*) > 1 does the second.

The query

SELECT business_id
FROM (
    SELECT business_id,
           occurrences,
           AVG(occurrences) OVER (PARTITION BY event_type) AS avgo
    FROM   Events
) x
WHERE  occurrences > avgo
GROUP  BY business_id
HAVING COUNT(business_id) > 1;

Read it inside-out. The subquery x labels each row with avgo = the average occurrences of its event type, computed across every business sharing that event type. The outer query keeps only rows that strictly beat their avgo, then groups by business and demands at least two surviving rows. COUNT(business_id) here counts the rows that passed the WHERE filter — i.e. the number of above-average events for that business — not the total events it has.

Worked example, traced

Start from this Events table:

business_idevent_typeoccurrences
1reviews7
3reviews3
1ads11
2ads7
3ads6
1page views3
2page views12

Step 1 — the window adds avgo. Each partition is one event type; the average is over all rows in that partition:

That value is broadcast back onto every row, and rows are not collapsed:

business_idevent_typeoccurrencesavgooccurrences > avgo?
1reviews75.0yes (7 > 5)
3reviews35.0no
1ads118.0yes (11 > 8)
2ads78.0no
3ads68.0no
1page views37.5no
2page views127.5yes (12 > 7.5)

Step 2 — filter, group, count. Keep only the yes rows, group by business, count survivors:

business_idabove-avg events keptCOUNTactive? (> 1)
1reviews, ads2yes
2page views1no
30no

Final result: business_id = 1.

The subtlety the average hides

The window average for an event type is computed over all rows in that partition — including the very row being labelled. Business 1's ads occurrence of 11 is part of the (11 + 7 + 6)/3 = 8.0 it is then compared against. This is exactly the intended semantics here ("average across all companies that have this event"), but it is worth seeing the mechanics: a large outlier inflates its own bar and makes it slightly harder to clear. With one extreme value, that bar can rise above every member of the partition, so a row can lose to an average it dominated. The diagram below makes this self-inclusion concrete.

diagram
diagram

Why a window beats the self-aggregate join

The pre-window way to write this needs a derived table of averages and a join back:

-- equivalent, but heavier
SELECT e.business_id
FROM   Events e
JOIN  (SELECT event_type, AVG(occurrences) AS avgo
       FROM   Events
       GROUP  BY event_type) a
  ON   a.event_type = e.event_type
WHERE  e.occurrences > a.avgo
GROUP  BY e.business_id
HAVING COUNT(*) > 1;

It gives the same answer, but it reads Events twice and joins them. The window version reads the table once: the engine partitions the already-loaded rows by event_type, computes one average per partition, and writes it back onto each row in place — no second scan, no join, no chance of the join silently dropping rows. When occurrences can be NULL or an event type can have a single business, the GROUP BY + join requires care that the equality join joins on exactly the right key; the window keeps the row-to-average correspondence automatic because the average travels with the row. For a self-comparison-to-group-average pattern, that is the canonical reason to reach for a window function instead of a self-join.

Pitfalls

Takeaways


Based on LeetCode 1126 "Active Businesses" (the standard Events dataset and expected output). Window-function semantics per the MySQL 8.0 Reference Manual ("Window Function Concepts and Syntax") and the SQL:2003 windowing model; the rule that window functions are evaluated after the WHERE/GROUP BY/HAVING phases follows the SQL logical processing order. Re-authored and deepened for this guide: added the self-inclusion subtlety of the partition average, a window-vs-self-join comparison with the equivalent join query, the strict-vs-non-strict and integer-truncation pitfalls, and a step-by-step traced diagram.

🤖 Don't fully get this? Learn it with Claude

Stuck on Active Businesses? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **Active Businesses** (Databases) and want to truly understand it. Explain Active Businesses from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **Active Businesses** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **Active Businesses** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **Active Businesses** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes