HackerRank SQL (Intermediate) certification
Customer Spending
Customer Spending looks like the easiest question in the HackerRank SQL (Intermediate) test. The logic is short. What trips people up is the output format: the amount has to show 6 decimal places, and the usual tricks for that behave differently from what you expect.
What the question asks
Find the customers who spent very little. A customer qualifies if the total of all their invoices is 25% or less of the average invoice amount (the average over every invoice in the table).
For each such customer, show their name and their total spend with exactly 6 decimal places. Sort by the total spend, highest first.
Tables
| Table | Columns used |
|---|---|
customer |
id, customer_name |
invoice |
id, customer_id (points to customer.id), total_price (decimal(8,2)) |
Solution
SELECT
cu.customer_name,
CAST(SUM(i.total_price) AS DECIMAL(12, 6)) AS amount_spent
FROM customer AS cu
JOIN invoice AS i ON i.customer_id = cu.id
GROUP BY cu.id, cu.customer_name
HAVING SUM(i.total_price) <= 0.25 * (SELECT AVG(total_price) FROM invoice)
ORDER BY amount_spent DESC;How it works
- Total per customer. Join
customertoinvoice, group by customer andSUM(i.total_price). I group bycu.idas well as the name, so two different customers who happen to share a name are not merged. - The benchmark.
(SELECT AVG(total_price) FROM invoice)is the average of all invoices. Multiply by 0.25 to get the cut-off. - Filter the groups.
HAVING SUM(...) <= cut-offkeeps customers at or below it. The question says “25% or less”, so<=is the literal reading. - Six decimals.
CAST(... AS DECIMAL(12, 6))turns179.99into179.990000. 12 digits in total leaves room for 6 before the decimal point. - Sort.
amount_spentis still a number after the cast, soORDER BY amount_spent DESCsorts numerically.
Customers with no invoices drop out at the inner join. That is fine: the question is about how much customers spent, and most solutions people report passing with use an inner join.
Most public solutions use < instead of <=, and people report passing with both. That suggests the hidden data has no customer sitting exactly on the cut-off. I go with <= because it matches the wording of the task. If your version of the question says “less than 25%”, switch to <.
Mistakes that fail the hidden tests
ROUND(SUM(...), 6)on MySQL 8. This is the most copied answer, and it does not do what it looks like. On MySQL 8.0 I got179.99and2000.00, still 2 decimals, because rounding a 2-decimal value to 6 places changes nothing. On MySQL 5.7 the same query printed179.990000. HackerRank’s current environment page lists MySQL 8.0.33. UseCAST(... AS DECIMAL(12, 6)).FORMAT(SUM(...), 6). It gives 6 decimals but adds thousands separators (2,000.000000) and returns text. If you then sort by it, the sort is alphabetical: I tested99.5,120.01and1500withORDER BY ... DESCand got 99.500000 first.CAST(... AS DECIMAL(9, 6)). Several repos use this.DECIMAL(9, 6)has only 3 digits before the point, so anything from 1000 up becomes999.999999with an “out of range” warning. Give it more room.- Checking each invoice instead of each customer.
WHERE i.total_price <= ...without aGROUP BYlists small invoices, not small customers. In my sample data Bharat Foods then shows up twice (129.99 and 50.00) instead of once with 179.99. - Using the wrong average. The cut-off is 25% of the average invoice, not 25% of the average customer total. With the sample data the average invoice is 800.00, so the cut-off is 200.00.
Try it on sample data
Sample rows I used. The edge case: Om Electricals spent exactly 200.00, which is exactly the cut-off. Nila Stores and Zen Retail have no invoices.
customer
| id | customer_name |
|---|---|
| 1 | Asha Traders |
| 2 | Bharat Foods |
| 3 | Kraus GmbH |
| 4 | Rio Mart |
| 5 | Nila Stores |
| 6 | Delta Supplies |
| 7 | Zen Retail |
| 8 | Lumiere SARL |
| 9 | Om Electricals |
invoice
| id | customer_id | total_price |
|---|---|---|
| 1 | 1 | 1200.00 |
| 2 | 1 | 800.00 |
| 3 | 2 | 50.00 |
| 4 | 3 | 1700.00 |
| 5 | 3 | 1300.00 |
| 6 | 4 | 800.00 |
| 7 | 6 | 120.01 |
| 8 | 2 | 129.99 |
| 9 | 8 | 1100.00 |
| 10 | 9 | 200.00 |
| 11 | 8 | 1400.00 |
The 11 invoices total 8800.00, so the average invoice is 800.00 and the cut-off is 200.00. Customer totals: Asha 2000.00, Bharat 179.99, Kraus 3000.00, Rio 800.00, Delta 120.01, Lumiere 2500.00, Om 200.00.
Expected output
| customer_name | amount_spent |
|---|---|
| Om Electricals | 200.000000 |
| Bharat Foods | 179.990000 |
| Delta Supplies | 120.010000 |
MySQL 8.0 and 5.7 both gave this output. With < instead of <=, Om Electricals drops out and only the last two rows remain. To run it in SQLite, which has no fixed-point decimals, replace the CAST with PRINTF('%.6f', SUM(i.total_price)) and sort by SUM(i.total_price) DESC. That gives the same three rows.