CMD Guide
HomeDatabasesSQL Practice Problems

Average Time of Process

Each process leaves two rows in one table — a start and an end sharing the same (machine_id, process_id) — so to compute a duration you must bring those two rows onto the same row, which is exactly what a self-join does: join Activity to itself, forcing the left copy to be the start and the right copy the matching end, then average end.timestamp − start.timestamp per machine.

Problem

Table Activity(machine_id, process_id, activity_type, timestamp), where activity_type is the enum 'start' | 'end' and timestamp is a float of seconds. The primary key is (machine_id, process_id, activity_type), so every process has exactly one start row and one end row, and the start always precedes the end. Every machine runs the same number of processes. Return each machine_id with its average process duration as processing_time, rounded to 3 decimal places, in any order.

The query

SELECT a1.machine_id,
       ROUND(AVG(a2.timestamp - a1.timestamp), 3) AS processing_time
FROM   Activity a1
JOIN   Activity a2
       ON  a1.machine_id    = a2.machine_id
       AND a1.process_id    = a2.process_id
       AND a1.activity_type = 'start'
       AND a2.activity_type = 'end'
GROUP BY a1.machine_id;

The two type predicates are the load-bearing part. a1 is pinned to the start row and a2 to the matching end row of the same process, so each surviving joined row carries both timestamps and the subtraction a2.timestamp − a1.timestamp is one process's duration. AVG then averages those durations within each machine_id group, and ROUND(…, 3) truncates the result to three decimals.

Worked example

Take the canonical LeetCode input — three machines, two processes each:

Activity
+------------+------------+---------------+-----------+
| machine_id | process_id | activity_type | timestamp |
+------------+------------+---------------+-----------+
|     0      |     0      |    start      |   0.712   |
|     0      |     0      |    end        |   1.520   |
|     0      |     1      |    start      |   3.140   |
|     0      |     1      |    end        |   4.120   |
|     1      |     0      |    start      |   0.550   |
|     1      |     0      |    end        |   1.550   |
|     1      |     1      |    start      |   0.430   |
|     1      |     1      |    end        |   1.420   |
|     2      |     0      |    start      |   4.100   |
|     2      |     0      |    end        |   4.512   |
|     2      |     1      |    start      |   2.500   |
|     2      |     1      |    end        |   5.000   |
+------------+------------+---------------+-----------+
diagram
diagram

Step 1 — the joined rows (one per process)

This is the row the original page hid behind placeholders. After the join, each process collapses to a single row carrying both timestamps and the duration:

+------------+------------+----------+--------+----------+
| machine_id | process_id | a1 start | a2 end | duration |
+------------+------------+----------+--------+----------+
|     0      |     0      |  0.712   | 1.520  |  0.808   |
|     0      |     1      |  3.140   | 4.120  |  0.980   |
|     1      |     0      |  0.550   | 1.550  |  1.000   |
|     1      |     1      |  0.430   | 1.420  |  0.990   |
|     2      |     0      |  4.100   | 4.512  |  0.412   |
|     2      |     1      |  2.500   | 5.000  |  2.500   |
+------------+------------+----------+--------+----------+

Step 2 — group, average, round

GROUP BY a1.machine_id partitions those six rows by machine; AVG averages each partition's duration; ROUND(…, 3) finishes:

m0: (0.808 + 0.980) / 2 = 1.788 / 2 = 0.894  -> 0.894
m1: (1.000 + 0.990) / 2 = 1.990 / 2 = 0.995  -> 0.995
m2: (0.412 + 2.500) / 2 = 2.912 / 2 = 1.456  -> 1.456
Result
+------------+-----------------+
| machine_id | processing_time |
+------------+-----------------+
|     0      |      0.894      |
|     1      |      0.995      |
|     2      |      1.456      |
+------------+-----------------+

The averages here land exactly on three decimals, so ROUND changes nothing — but it must stay in the query, because real data won't be so tidy.

Why the naive version is wrong

A common first attempt skips the self-join and tries to subtract within a single scan:

-- WRONG: there is no single row that has both timestamps
SELECT machine_id, AVG(end_ts - start_ts) ...   -- no such columns exist

The data is in long form — two rows per process — so the subtraction has no two columns to operate on until you pivot the pair onto one row. The self-join is that pivot. An alternative correct form makes the sign explicit and needs no join:

SELECT machine_id,
       ROUND(AVG(CASE WHEN activity_type = 'end'   THEN  timestamp
                      WHEN activity_type = 'start' THEN -timestamp END) * 2, 3) AS processing_time
FROM   Activity
GROUP BY machine_id;

This sums (+end) + (−start) across the group. The average of all 2N signed values is (Σend − Σstart) / 2N; multiplying by 2 gives (Σend − Σstart) / N, the same per-process average. It only works because each machine has the same count of starts and ends.

Pitfalls

Takeaways


Based on LeetCode 1661 "Average Time of Process per Machine" and its official example data, which yields exactly 0.894 / 0.995 / 1.456. SQL standard semantics for self-joins, AVG, and GROUP BY per the PostgreSQL and MySQL 8 reference manuals (including ONLY_FULL_GROUP_BY behavior). Re-authored and deepened for this guide: the original trace showed only placeholders for the joined rows and listed unrounded intermediates (0.89478, 0.99538, 1.45656) that did not round to the stated finals; replaced with the real per-process durations and arithmetic that round correctly, plus the join diagram, the signed-CASE alternative, and the pitfalls section.

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

Stuck on Average Time of Process? 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 **Average Time of Process** (Databases) and want to truly understand it. Explain Average Time of Process 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 **Average Time of Process** 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 **Average Time of Process** 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 **Average Time of Process** 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