HackerRank SQL (Intermediate) certification
Products Without Sales
Products Without Sales is the shortest question in the HackerRank SQL (Intermediate) test. It checks one idea: how to find rows in one table that have no match in another.
What the question asks
An online store has a product list and a table of invoice lines. Find every product that has never been sold, meaning it does not appear on any invoice line. Show each product’s SKU and name, sorted by SKU from lowest to highest.
Tables
| Table | Columns used |
|---|---|
product |
id, sku (varchar(32)), product_name |
invoice_item |
invoice_id, product_id (points to product.id) |
The problem shows the table names in capitals (PRODUCT, INVOICE_ITEM). Solutions that people report passing with use both capital and lower-case names, so either works there. I use lower case.
Solution
SELECT
p.sku,
p.product_name
FROM product AS p
LEFT JOIN invoice_item AS ii ON ii.product_id = p.id
WHERE ii.product_id IS NULL
ORDER BY p.sku ASC;How it works
LEFT JOINkeeps every product. A product that was sold gets one row per invoice line. A product that was never sold still gets one row, with everyinvoice_itemcolumn set to NULL.WHERE ii.product_id IS NULLkeeps only the unmatched ones. Those are the products with no sales. Each of them appears exactly once, so you do not needDISTINCT.- Sort by SKU. The question asks for SKU order, not id order. They are not always the same.
This version works the same as NOT EXISTS:
SELECT p.sku, p.product_name
FROM product AS p
WHERE NOT EXISTS (
SELECT 1 FROM invoice_item AS ii WHERE ii.product_id = p.id
)
ORDER BY p.sku ASC;Mistakes that fail the hidden tests
NOT INwhen the subquery can contain NULL. Most public answers useWHERE id NOT IN (SELECT product_id FROM invoice_item). It passes as long asproduct_idis never NULL. I added one invoice line with a NULLproduct_idto my test data, and theNOT INquery returned zero rows while theLEFT JOINquery still returned both unsold products. In SQL,x NOT IN (1, 2, NULL)is never true.LEFT JOINandNOT EXISTSdo not have this problem.- Sorting by
id. In my sample, Copper Bottle has a higher id but a lower SKU than Brass Diya Set.ORDER BY p.idputs them the wrong way round. - Putting the NULL check in the
ONclause.LEFT JOIN ... ON ii.product_id = p.id AND ii.product_id IS NULLmatches nothing, so every product comes back. The check belongs inWHERE. - Selecting the id instead of the SKU. The output wants
skuandproduct_name, in that order.
Try it on sample data
Sample rows I used. The edge cases: product 6 has a lower SKU than product 5, and several products appear on more than one invoice line.
product
| id | sku | product_name |
|---|---|---|
| 1 | 330101 | Basmati Rice 5kg |
| 2 | 330102 | Masala Chai 250g |
| 3 | 330103 | Steel Tiffin Box |
| 4 | 330104 | Cotton Kurta |
| 5 | 330105 | Brass Diya Set |
| 6 | 330099 | Copper Bottle |
invoice_item
| invoice_id | product_id | quantity |
|---|---|---|
| 1 | 1 | 2 |
| 1 | 2 | 3 |
| 2 | 1 | 1 |
| 2 | 3 | 1 |
| 3 | 2 | 1 |
| 4 | 3 | 4 |
| 4 | 4 | 2 |
| 5 | 3 | 1 |
| 5 | 2 | 1 |
| 6 | 4 | 2 |
| 7 | 2 | 1 |
| 8 | 2 | 1 |
| 9 | 4 | 2 |
Expected output
| sku | product_name |
|---|---|
| 330099 | Copper Bottle |
| 330105 | Brass Diya Set |
Products 1 to 4 all appear in invoice_item. Products 5 and 6 never do. MySQL 8.0, MySQL 5.7 and SQLite all returned these two rows in this order.