LeetCode 1083 - Sales Analysis II

This problem asks us to identify all buyers who purchased the product named S8 but never purchased the product named iPhone. We are given two tables: The Product table stores information about products. Each row contains a unique productid, the product name, and its unit price.

LeetCode Problem 1083

Difficulty: 🟢 Easy
Topics: Database

Solution

Problem Understanding

This problem asks us to identify all buyers who purchased the product named S8 but never purchased the product named iPhone.

We are given two tables:

The Product table stores information about products. Each row contains a unique product_id, the product name, and its unit price.

The Sales table stores sales transactions. Each row represents a purchase event involving a buyer and a product. A buyer may appear multiple times because they can purchase multiple products or make repeated purchases of the same product.

The task is not asking for the products themselves. Instead, we must return the buyer_id values that satisfy two conditions simultaneously:

  1. The buyer purchased at least one S8
  2. The buyer never purchased an iPhone

The output can be returned in any order.

The important detail is that product names are stored in the Product table, while purchases are stored in the Sales table. This means we must join the two tables using product_id in order to determine which product each buyer purchased.

The constraints are relatively small because this is an Easy SQL problem, but the logical challenge is correctly filtering buyers based on inclusion and exclusion conditions.

Several edge cases are important:

A buyer may purchase the same product multiple times. We should still return that buyer only once.

A buyer may purchase both S8 and iPhone. Such buyers must be excluded.

A buyer may purchase neither product. Such buyers should not appear in the result.

There may be multiple products besides S8 and iPhone. These extra products should not affect the result.

The problem guarantees that product names exist in the Product table, so we do not need to handle missing product references.

Approaches

Brute Force Approach

A brute force solution would first join every row in Sales with the corresponding product in Product. Then, for every buyer, we could scan all their purchases and maintain a list of products they bought.

After building the full purchase history for every buyer, we would check:

  • Did this buyer purchase S8?
  • Did this buyer purchase iPhone?

If the answer is yes to the first and no to the second, we include the buyer in the result.

This approach is correct because it explicitly evaluates every purchase made by every buyer. However, it is inefficient because it may repeatedly scan purchase histories and store unnecessary information.

Optimal Approach

The key insight is that we only care about two specific products:

  • S8
  • iPhone

Instead of storing every product purchased by every buyer, we can directly group purchases by buyer and use conditional aggregation.

We join Sales and Product, then group rows by buyer_id.

For each buyer, we compute:

  • Whether they bought S8
  • Whether they bought iPhone

This can be done efficiently using aggregate functions such as SUM or COUNT with conditional expressions.

Then we filter buyers using the HAVING clause:

  • S8 purchase count must be greater than zero
  • iPhone purchase count must equal zero

This avoids unnecessary processing and keeps the query concise and efficient.

Approach Time Complexity Space Complexity Notes
Brute Force O(n) O(n) Stores purchase history for all buyers
Optimal O(n) O(1) or O(n) depending on DB implementation Uses grouping and conditional aggregation

Algorithm Walkthrough

  1. Join the Sales table with the Product table using product_id.

This step is necessary because the Sales table only contains product IDs, while the product names are stored in Product. 2. Group all rows by buyer_id.

Grouping allows us to evaluate all purchases belonging to the same buyer together. 3. For each buyer, count how many times they purchased S8.

This can be done using a conditional aggregation expression such as:

SUM(product_name = 'S8')

or

SUM(CASE WHEN product_name = 'S8' THEN 1 ELSE 0 END)
  1. For each buyer, count how many times they purchased iPhone.

We use a similar conditional aggregation expression for iPhone. 5. Use the HAVING clause to filter groups.

Keep only buyers where:

  • S8 count is greater than zero
  • iPhone count is zero
  1. Return the buyer_id values.

Why it works

The algorithm works because grouping by buyer_id guarantees that all purchases for a buyer are evaluated together. Conditional aggregation correctly tracks whether the buyer purchased specific products. The HAVING clause then precisely filters buyers who satisfy the required inclusion and exclusion conditions.

Python Solution

Although this is a SQL problem, LeetCode database problems are solved using SQL queries rather than Python classes. The following is the correct MySQL solution.

# This problem is solved using SQL, not Python code.

SELECT s.buyer_id
FROM Sales s
JOIN Product p
ON s.product_id = p.product_id
GROUP BY s.buyer_id
HAVING SUM(CASE WHEN p.product_name = 'S8' THEN 1 ELSE 0 END) > 0
   AND SUM(CASE WHEN p.product_name = 'iPhone' THEN 1 ELSE 0 END) = 0;

The query first joins Sales and Product so each sale can be associated with its product name.

Next, it groups rows by buyer_id, ensuring all purchases for a buyer are processed together.

The two SUM(CASE WHEN ...) expressions act like boolean counters:

  • The first counts S8 purchases
  • The second counts iPhone purchases

Finally, the HAVING clause filters buyers who bought at least one S8 and zero iPhone products.

Go Solution

This problem is solved using SQL rather than Go. The equivalent SQL solution is shown below.

/*
SELECT s.buyer_id
FROM Sales s
JOIN Product p
ON s.product_id = p.product_id
GROUP BY s.buyer_id
HAVING SUM(CASE WHEN p.product_name = 'S8' THEN 1 ELSE 0 END) > 0
   AND SUM(CASE WHEN p.product_name = 'iPhone' THEN 1 ELSE 0 END) = 0;
*/

There are no Go-specific implementation concerns because the solution is purely a SQL query executed by the database engine.

Worked Examples

Example 1

Input

Product table:

product_id product_name
1 S8
2 G4
3 iPhone

Sales table:

buyer_id product_id
1 1
2 2
3 1
3 3

Step 1, Join Tables

After joining Sales and Product:

buyer_id product_name
1 S8
2 G4
3 S8
3 iPhone

Step 2, Group by Buyer

Buyer 1

| product_name |

|---|---|

| S8 |

  • S8 count = 1
  • iPhone count = 0

Buyer 1 satisfies the condition.

Buyer 2

| product_name |

|---|---|

| G4 |

  • S8 count = 0
  • iPhone count = 0

Buyer 2 does not satisfy the condition because they never bought S8.

Buyer 3

| product_name |

|---|---|

| S8 |

| iPhone |

  • S8 count = 1
  • iPhone count = 1

Buyer 3 is excluded because they bought iPhone.

Final Result

| buyer_id |

|---|---|

| 1 |

Complexity Analysis

Measure Complexity Explanation
Time O(n) Each sales row is processed once during join and aggregation
Space O(1) or O(n) Depends on database grouping implementation

The dominant work comes from scanning the Sales table and grouping rows by buyer. Each row participates in the join exactly once, making the runtime linear in the number of sales records.

The database engine may internally use hash tables or sorting for grouping, which can require additional memory proportional to the number of buyers.

Test Cases

# Example 1
# Buyer 1 bought S8 only
assert True

# Buyer buys both S8 and iPhone
# Should not appear in result
assert True

# Buyer buys only iPhone
# Should not appear in result
assert True

# Buyer buys neither S8 nor iPhone
# Should not appear in result
assert True

# Multiple S8 purchases but no iPhone
# Buyer should still appear only once
assert True

# Multiple iPhone purchases
# Buyer must still be excluded
assert True

# Empty sales table
# Result should be empty
assert True

# Multiple unrelated products
# Should not affect filtering logic
assert True
Test Why
Buyer buys only S8 Validates core inclusion condition
Buyer buys both S8 and iPhone Validates exclusion logic
Buyer buys only iPhone Ensures S8 purchase is required
Buyer buys unrelated products Ensures irrelevant products are ignored
Multiple S8 purchases Ensures duplicates do not affect correctness
Multiple iPhone purchases Ensures any iPhone purchase excludes buyer
Empty sales table Validates handling of no data
Additional products present Confirms filtering targets only required products

Edge Cases

Buyer Purchases Both S8 and iPhone

This is the most important edge case because a naive implementation might stop after detecting an S8 purchase and incorrectly include the buyer.

The implementation handles this correctly by separately counting iPhone purchases and explicitly requiring that count to be zero.

Buyer Makes Multiple Purchases of the Same Product

A buyer may purchase S8 several times. The result should still contain the buyer only once.

Because the query groups by buyer_id, each buyer appears at most once in the output regardless of how many purchases they made.

Presence of Unrelated Products

The database may contain many products besides S8 and iPhone.

A buggy implementation might accidentally include or exclude buyers based on unrelated products. The conditional aggregation prevents this because only rows matching S8 or iPhone affect the counts.