CMD Guide
HomeDatabasesSQL Practice Problems

Generate the Invoice

Mechanism

Roll each purchase line up to a per-line subtotal (quantity * price), sum those subtotals per invoice_id, sort the invoices by total descending with invoice_id ascending as the deterministic tie-breaker, keep the single top row with LIMIT 1, then re-scan the line-level rows and emit only the lines whose invoice_id matches that winner. The trick is that you compute the winner on aggregated rows but you must return un-aggregated line detail, so the query splits into two passes over the same join: one to pick the invoice, one to project its lines.

The problem

Two tables. Products(product_id, price) gives the unit price; Purchases(invoice_id, product_id, quantity) lists how many units of each product an invoice ordered. Return the line detail (product_id, quantity, price, where price is the line subtotal, not the unit price) of the single invoice with the highest total. If two invoices tie on total, return the one with the smaller invoice_id. Result order is unconstrained.

Concrete inputs we will trace end to end:

Products
product_idprice
1100
2200
Purchases
invoice_idproduct_idquantity
112
214
223
321
4110

This is built so invoice 2 (400 + 600 = 1000) and invoice 4 (10 × 100 = 1000) tie at 1000 — the tie-break must pick invoice 2.

diagram
diagram

The query

WITH details_by_invoices AS (
    SELECT
        pu.invoice_id,
        pu.product_id,
        pu.quantity,
        pu.quantity * pr.price AS price   -- line subtotal
    FROM Purchases AS pu
    INNER JOIN Products AS pr
        ON pu.product_id = pr.product_id
),
invoice_with_max_total AS (
    SELECT invoice_id
    FROM details_by_invoices
    GROUP BY invoice_id
    ORDER BY SUM(price) DESC,   -- highest total first
             invoice_id ASC     -- tie-break: smallest id
    LIMIT 1
)
SELECT product_id, quantity, price
FROM details_by_invoices
WHERE invoice_id IN (SELECT invoice_id FROM invoice_with_max_total);

Two CTEs: the first materializes every purchase line with its subtotal; the second collapses that to one row per invoice, orders, and slices off the single winner. The outer SELECT then reuses the first CTE — not the second — to recover the full line detail.

Worked trace

Stage 1 — details_by_invoices (join each purchase to its unit price, multiply by quantity):

invoice_idproduct_idquantityprice = qty × unit
1122 × 100 = 200
2144 × 100 = 400
2233 × 200 = 600
3211 × 200 = 200
411010 × 100 = 1000

Stage 2 — group and total (SUM(price) per invoice):

invoice_idSUM(price)
1200
2400 + 600 = 1000
3200
41000

Stage 3 — order and slice. ORDER BY SUM(price) DESC floats invoices 2 and 4 (both 1000) to the top, tied. invoice_id ASC breaks the tie by putting 2 before 4. LIMIT 1 keeps invoice 2. Without the second sort key the tie would resolve arbitrarily and could return invoice 4 on some runs.

Stage 4 — project the winner's lines. Re-scan Stage 1, keep rows where invoice_id = 2:

product_idquantityprice
14400
23600

Note the output price column is the line subtotal (400, 600), not the unit price (100, 200). The problem's column name price is deliberately overloaded.

Why the naive version is wrong

The tempting shortcut is to compute the max total once and filter invoices equal to it:

-- BUGGY when totals tie
SELECT product_id, quantity, price
FROM details_by_invoices
WHERE invoice_id IN (
    SELECT invoice_id FROM details_by_invoices
    GROUP BY invoice_id
    HAVING SUM(price) = (
        SELECT MAX(t) FROM (
            SELECT SUM(price) AS t FROM details_by_invoices GROUP BY invoice_id
        ) x
    )
);

On the trace data this returns both invoice 2 and invoice 4's lines, because both totals equal the max of 1000 — four rows instead of two, with no way to honor "smallest invoice_id". The ORDER BY ... DESC, invoice_id ASC LIMIT 1 idiom is what makes the choice singular and deterministic: it folds the "highest total" rule and the "smallest id on a tie" rule into one sort, then takes exactly one row.

The dialect-friendly choice: LIMIT 1 vs window RANK

The same winner can be selected with a window function, which some engineers reach for first:

-- window alternative
SELECT invoice_id FROM (
    SELECT invoice_id,
           RANK() OVER (ORDER BY SUM(price) DESC) AS rk
    FROM details_by_invoices
    GROUP BY invoice_id
) r
WHERE rk = 1
ORDER BY invoice_id
LIMIT 1;

This is correct but heavier, and the choice of ranking function matters: RANK() assigns rank 1 to both tied invoices, so you still need the outer ORDER BY invoice_id LIMIT 1 to single out invoice 2 — the window did not actually break the tie. ROW_NUMBER() OVER (ORDER BY SUM(price) DESC, invoice_id ASC) would break it inside the window, but at that point you have just re-implemented the simpler ORDER BY ... LIMIT 1 with extra machinery. The LIMIT 1 form is also the most portable: it runs unchanged on MySQL, PostgreSQL, and SQLite. SQL Server has no LIMIT — there you write SELECT TOP 1 ... ORDER BY SUM(price) DESC, invoice_id ASC, or OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY. Window functions also have a portability cliff: they are unavailable in MySQL before 8.0.

Pitfalls

Takeaways


Based on the LeetCode Database problem "Generate the Invoice." Cross-checked against the PostgreSQL documentation (LIMIT and window functions), the MySQL 8.0 reference manual (window functions, available from 8.0), and the SQL Server documentation (TOP / OFFSET-FETCH). Re-authored and deepened for this guide: added the mechanism statement, the four-stage worked trace on tied invoices 2 and 4, a hand-authored data-flow diagram, the buggy HAVING = MAX counter-example, the LIMIT-1-vs-window-RANK dialect comparison, and the integer-vs-decimal tie-break nuance.

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

Stuck on Generate the Invoice? 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 **Generate the Invoice** (Databases) and want to truly understand it. Explain Generate the Invoice 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 **Generate the Invoice** 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 **Generate the Invoice** 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 **Generate the Invoice** 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