Skip to content

Commit c56c524

Browse files
authored
Added task 3554
1 parent 662cdbb commit c56c524

File tree

3 files changed

+256
-0
lines changed
  • src
    • main/kotlin/g3501_3600/s3554_find_category_recommendation_pairs
    • test/kotlin/g3501_3600/s3554_find_category_recommendation_pairs

3 files changed

+256
-0
lines changed
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
3554\. Find Category Recommendation Pairs
2+
3+
Table: `ProductPurchases`
4+
5+
+-------------+------+
6+
| Column Name | Type |
7+
+-------------+------+
8+
| user_id | int |
9+
| product_id | int |
10+
| quantity | int |
11+
+-------------+------+
12+
(user_id, product_id) is the unique identifier for this table.
13+
Each row represents a purchase of a product by a user in a specific quantity.
14+
15+
Table: `ProductInfo`
16+
17+
+-------------+---------+
18+
| Column Name | Type |
19+
+-------------+---------+
20+
| product_id | int |
21+
| category | varchar |
22+
| price | decimal |
23+
+-------------+---------+
24+
product_id is the unique identifier for this table.
25+
Each row assigns a category and price to a product.
26+
27+
Amazon wants to understand shopping patterns across product categories. Write a solution to:
28+
29+
1. Find all **category pairs** (where `category1` < `category2`)
30+
2. For **each category pair**, determine the number of **unique** **customers** who purchased products from **both** categories
31+
32+
A category pair is considered **reportable** if at least `3` different customers have purchased products from both categories.
33+
34+
Return _the result table of reportable category pairs ordered by **customer\_count** in **descending** order, and in case of a tie, by **category1** in **ascending** order lexicographically, and then by **category2** in **ascending** order._
35+
36+
The result format is in the following example.
37+
38+
**Example:**
39+
40+
**Input:**
41+
42+
ProductPurchases table:
43+
44+
+---------+------------+----------+
45+
| user_id | product_id | quantity |
46+
+---------+------------+----------+
47+
| 1 | 101 | 2 |
48+
| 1 | 102 | 1 |
49+
| 1 | 201 | 3 |
50+
| 1 | 301 | 1 |
51+
| 2 | 101 | 1 |
52+
| 2 | 102 | 2 |
53+
| 2 | 103 | 1 |
54+
| 2 | 201 | 5 |
55+
| 3 | 101 | 2 |
56+
| 3 | 103 | 1 |
57+
| 3 | 301 | 4 |
58+
| 3 | 401 | 2 |
59+
| 4 | 101 | 1 |
60+
| 4 | 201 | 3 |
61+
| 4 | 301 | 1 |
62+
| 4 | 401 | 2 |
63+
| 5 | 102 | 2 |
64+
| 5 | 103 | 1 |
65+
| 5 | 201 | 2 |
66+
| 5 | 202 | 3 |
67+
+---------+------------+----------+
68+
69+
ProductInfo table:
70+
71+
+------------+-------------+-------+
72+
| product_id | category | price |
73+
+------------+-------------+-------+
74+
| 101 | Electronics | 100 |
75+
| 102 | Books | 20 |
76+
| 103 | Books | 35 |
77+
| 201 | Clothing | 45 |
78+
| 202 | Clothing | 60 |
79+
| 301 | Sports | 75 |
80+
| 401 | Kitchen | 50 |
81+
+------------+-------------+-------+
82+
83+
**Output:**
84+
85+
+-------------+-------------+----------------+
86+
| category1 | category2 | customer_count |
87+
+-------------+-------------+----------------+
88+
| Books | Clothing | 3 |
89+
| Books | Electronics | 3 |
90+
| Clothing | Electronics | 3 |
91+
| Electronics | Sports | 3 |
92+
+-------------+-------------+----------------+
93+
94+
**Explanation:**
95+
96+
* **Books-Clothing**:
97+
* User 1 purchased products from Books (102) and Clothing (201)
98+
* User 2 purchased products from Books (102, 103) and Clothing (201)
99+
* User 5 purchased products from Books (102, 103) and Clothing (201, 202)
100+
* Total: 3 customers purchased from both categories
101+
* **Books-Electronics**:
102+
* User 1 purchased products from Books (102) and Electronics (101)
103+
* User 2 purchased products from Books (102, 103) and Electronics (101)
104+
* User 3 purchased products from Books (103) and Electronics (101)
105+
* Total: 3 customers purchased from both categories
106+
* **Clothing-Electronics**:
107+
* User 1 purchased products from Clothing (201) and Electronics (101)
108+
* User 2 purchased products from Clothing (201) and Electronics (101)
109+
* User 4 purchased products from Clothing (201) and Electronics (101)
110+
* Total: 3 customers purchased from both categories
111+
* **Electronics-Sports**:
112+
* User 1 purchased products from Electronics (101) and Sports (301)
113+
* User 3 purchased products from Electronics (101) and Sports (301)
114+
* User 4 purchased products from Electronics (101) and Sports (301)
115+
* Total: 3 customers purchased from both categories
116+
* Other category pairs like Clothing-Sports (only 2 customers: Users 1 and 4) and Books-Kitchen (only 1 customer: User 3) have fewer than 3 shared customers and are not included in the result.
117+
118+
The result is ordered by customer\_count in descending order. Since all pairs have the same customer\_count of 3, they are ordered by category1 (then category2) in ascending order.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Write your MySQL query statement below
2+
# #Hard #Database #2025_05_22_Time_623_ms_(82.76%)_Space_0.0_MB_(100.00%)
3+
SELECT
4+
pi1.category AS category1,
5+
pi2.category AS category2,
6+
COUNT(DISTINCT pp1.user_id) AS customer_count
7+
FROM
8+
ProductPurchases pp1,
9+
ProductPurchases pp2,
10+
ProductInfo pi1,
11+
ProductInfo pi2
12+
WHERE
13+
pp1.user_id = pp2.user_id
14+
AND pi1.category < pi2.category
15+
AND pp1.product_id = pi1.product_id
16+
AND pp2.product_id = pi2.product_id
17+
GROUP BY
18+
pi1.category,
19+
pi2.category
20+
HAVING
21+
COUNT(DISTINCT pp1.user_id) >= 3
22+
ORDER BY
23+
customer_count DESC,
24+
category1 ASC,
25+
category2 ASC;
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package g3501_3600.s3554_find_category_recommendation_pairs
2+
3+
import org.hamcrest.CoreMatchers.equalTo
4+
import org.hamcrest.MatcherAssert.assertThat
5+
import org.junit.jupiter.api.Test
6+
import org.zapodot.junit.db.annotations.EmbeddedDatabase
7+
import org.zapodot.junit.db.annotations.EmbeddedDatabaseTest
8+
import org.zapodot.junit.db.common.CompatibilityMode
9+
import java.io.BufferedReader
10+
import java.io.FileNotFoundException
11+
import java.io.FileReader
12+
import java.sql.SQLException
13+
import java.util.stream.Collectors
14+
import javax.sql.DataSource
15+
16+
@EmbeddedDatabaseTest(
17+
compatibilityMode = CompatibilityMode.MySQL,
18+
initialSqls = [
19+
(
20+
"CREATE TABLE ProductPurchases(user_id INTEGER, product_id INTEGER" +
21+
", quantity INTEGER); " +
22+
"INSERT INTO ProductPurchases(user_id, product_id, quantity)" +
23+
" VALUES " +
24+
"(1, 101, 2), " +
25+
"(1, 102, 1), " +
26+
"(1, 201, 3), " +
27+
"(1, 301, 1), " +
28+
"(2, 101, 1), " +
29+
"(2, 102, 2), " +
30+
"(2, 103, 1), " +
31+
"(2, 201, 5), " +
32+
"(3, 101, 2), " +
33+
"(3, 103, 1), " +
34+
"(3, 301, 4), " +
35+
"(3, 401, 2), " +
36+
"(4, 101, 1), " +
37+
"(4, 201, 3), " +
38+
"(4, 301, 1), " +
39+
"(4, 401, 2), " +
40+
"(5, 102, 2), " +
41+
"(5, 103, 1), " +
42+
"(5, 201, 2), " +
43+
"(5, 202, 3);" +
44+
"CREATE TABLE ProductInfo(product_id INTEGER, category VARCHAR(255)" +
45+
", price INTEGER); " +
46+
"INSERT INTO ProductInfo(product_id, category, price) VALUES " +
47+
"(101, 'Electronics', 100), " +
48+
"(102, 'Books', 20), " +
49+
"(103, 'Books', 35), " +
50+
"(201, 'Clothing', 45), " +
51+
"(202, 'Clothing', 60), " +
52+
"(301, 'Sports', 75), " +
53+
"(401, 'Kitchen', 50);"
54+
),
55+
],
56+
)
57+
internal class MysqlTest {
58+
@Test
59+
@Throws(SQLException::class, FileNotFoundException::class)
60+
fun testScript(@EmbeddedDatabase dataSource: DataSource) {
61+
dataSource.connection.use { connection ->
62+
connection.createStatement().use { statement ->
63+
statement.executeQuery(
64+
BufferedReader(
65+
FileReader(
66+
(
67+
"src/main/kotlin/g3501_3600/" +
68+
"s3554_find_category_recommendation_pairs/" +
69+
"script.sql"
70+
),
71+
),
72+
)
73+
.lines()
74+
.collect(Collectors.joining("\n"))
75+
.replace("#.*?\\r?\\n".toRegex(), ""),
76+
).use { resultSet ->
77+
assertThat<Boolean>(resultSet.next(), equalTo<Boolean>(true))
78+
assertThat<String>(resultSet.getNString(1), equalTo<String>("Books"))
79+
assertThat<String>(
80+
resultSet.getNString(2),
81+
equalTo<String>("Clothing"),
82+
)
83+
assertThat<String>(resultSet.getNString(3), equalTo<String>("3"))
84+
assertThat<Boolean>(resultSet.next(), equalTo<Boolean>(true))
85+
assertThat<String>(resultSet.getNString(1), equalTo<String>("Books"))
86+
assertThat<String>(
87+
resultSet.getNString(2),
88+
equalTo<String>("Electronics"),
89+
)
90+
assertThat<String>(resultSet.getNString(3), equalTo<String>("3"))
91+
assertThat<Boolean>(resultSet.next(), equalTo<Boolean>(true))
92+
assertThat<String>(
93+
resultSet.getNString(1),
94+
equalTo<String>("Clothing"),
95+
)
96+
assertThat<String>(
97+
resultSet.getNString(2),
98+
equalTo<String>("Electronics"),
99+
)
100+
assertThat<String>(resultSet.getNString(3), equalTo<String>("3"))
101+
assertThat<Boolean>(resultSet.next(), equalTo<Boolean>(true))
102+
assertThat<String>(
103+
resultSet.getNString(1),
104+
equalTo<String>("Electronics"),
105+
)
106+
assertThat<String>(resultSet.getNString(2), equalTo<String>("Sports"))
107+
assertThat<String>(resultSet.getNString(3), equalTo<String>("3"))
108+
assertThat<Boolean>(resultSet.next(), equalTo<Boolean>(false))
109+
}
110+
}
111+
}
112+
}
113+
}

0 commit comments

Comments
 (0)