Knowledge Guide
HomeDatabasesSQL Practice Problems

easy Find Popular Posts

Each row in Posts is one like-event — a single user liking a single post — so the number of likes a post has is simply the number of rows that carry its post_id, which GROUP BY post_id + COUNT(...) computes by bucketing rows and counting each bucket.

Problem Statement

Table: Posts

Each row records that one user liked one post. There is no precomputed likes column — the likes are the rows. The schema is exactly two columns:

+-------------+------+
| Column Name | Type |
+-------------+------+
| post_id     | int  |
| user_id     | int  |
+-------------+------+

Task: return the number of likes for each post, as a column named post_likes, ordered by post_id ascending.

(Note: an earlier version of this page listed a post_likes column in the schema and said the table "contains the number of likes received." That is wrong and self-contradictory — if a stored count existed there would be nothing to COUNT. The whole point of the problem is that likes are stored one-row-per-like and must be aggregated. The schema above is the real one the example data matches.)

Worked example

Input — 8 like-events:

Posts:
+---------+---------+
| post_id | user_id |
+---------+---------+
|    1    |    2    |
|    2    |    1    |
|    1    |    3    |
|    3    |    1    |
|    1    |    4    |
|    4    |    2    |
|    2    |    2    |
|    4    |    3    |
+---------+---------+

Trace. The engine scans the 8 rows and drops each into a bucket keyed by post_id, accumulating a running count:

Row scannedpost 1post 2post 3post 4
(1, 2)1···
(2, 1)11··
(1, 3)21··
(3, 1)211·
(1, 4)311·
(4, 2)3111
(2, 2)3211
(4, 3)3212

After the scan, each bucket emits one output row: post 1 → 3, post 2 → 2, post 3 → 1, post 4 → 2. ORDER BY post_id ASC then sorts those four rows.

Output:

+---------+------------+
| post_id | post_likes |
+---------+------------+
|    1    |     3      |
|    2    |     2      |
|    3    |     1      |
|    4    |     2      |
+---------+------------+
diagram
diagram

The query

SELECT
    post_id,
    COUNT(*) AS post_likes
FROM Posts
GROUP BY post_id
ORDER BY post_id ASC;

Reading it the way the engine evaluates it (not the way it is written): FROM Posts produces the rows → GROUP BY post_id partitions them into one group per distinct post_idCOUNT(*) collapses each group to a single number → SELECT shapes the output columns → ORDER BY sorts the result. This is why you may put post_id in SELECT without an aggregate: it is the grouping key, so it is constant within each group.

COUNT(*) vs COUNT(user_id) — the distinction that actually bites

The original solution used COUNT(user_id). On this exact data it gives the right answer, but it is the wrong instinct, and knowing why is the real lesson here:

Concretely, add one row (5, NULL) — a post that exists but whose liker wasn't recorded:

GroupCOUNT(*)COUNT(user_id)
post 5 = { (5, NULL) }10

Now the two diverge. If "likes" means "like-events," COUNT(*) is correct (1). If it means "users who liked," COUNT(user_id) (0, here) or COUNT(DISTINCT user_id) is what you want. Because (post_id, user_id) is the primary key, user_id can never be NULL and can never repeat within a post, so all three agree on this table — which is exactly why the bug is invisible until the schema relaxes a constraint.

Why the naive version is not wrong, but is fragile

COUNT(user_id) here is correct only because the PK guarantees user_id is non-null and unique per post. Prefer COUNT(*) when you mean "how many rows"; it states the intent, costs no NULL check, and doesn't silently change answers if someone later drops the NOT NULL or uniqueness guarantee.

Pitfalls

Takeaways


Based on the LeetCode-style "Find Popular Posts" practice problem and the SQL semantics of GROUP BY and the COUNT aggregate as specified in the SQL standard and documented in the PostgreSQL and MySQL manuals (aggregate functions, NULL handling, ONLY_FULL_GROUP_BY). Re-authored and deepened for this guide: corrected the self-contradictory schema (removed the phantom post_likes column and the conflicting primary-key bullets), fixed the "Step 3" mislabel on the GROUP BY intermediate output, switched the solution to COUNT(*), and added the COUNT(*) vs COUNT(col) NULL distinction, a row-by-row trace, the bucketing diagram, and the zero-like-posts / LEFT JOIN pitfall.

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

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

🪜 Hint ladder (no spoilers)

Progressively stronger hints — you still solve it.

I'm working on the problem **Find Popular Posts** (Databases). Give me a HINT LADDER: start with the tiniest nudge, then wait. Only reveal the next, stronger hint when I ask. Do NOT show the full solution unless I type 'show solution'. Keep me doing the thinking. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🎨 Explain the approach visually

See the technique, not just code.

Explain the optimal approach to **Find Popular Posts** with a VISUAL walkthrough: trace it on a small concrete example using ASCII art / a step-by-step diagram, narrate what changes each step, then give time & space complexity with a one-line derivation. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🔍 Review my solution

Catch bugs, edge cases, sub-optimality.

I'll paste my solution to **Find Popular Posts**. Review it for correctness, missed edge cases, and time/space complexity, then coach me toward the optimal — don't just rewrite it. Ask me to paste my code now. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🔁 Drill the pattern

Lock in recognition with look-alikes.

Give me 2 problems that use the SAME underlying pattern as **Find Popular Posts**. For each, let me attempt first, then review my answer and name the trigger signal that reveals the pattern. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes