From 26b33d68eb8f0cb5e34d68c2926f497a8cb3c3ac Mon Sep 17 00:00:00 2001 From: Valentyn Kolesnikov Date: Fri, 25 Jul 2025 20:50:46 +0300 Subject: [PATCH] Added task 3626 --- .../readme.md | 118 ++++++++++++++++++ .../script.sql | 63 ++++++++++ .../MysqlTest.java | 101 +++++++++++++++ 3 files changed, 282 insertions(+) create mode 100644 src/main/java/g3601_3700/s3626_find_stores_with_inventory_imbalance/readme.md create mode 100644 src/main/java/g3601_3700/s3626_find_stores_with_inventory_imbalance/script.sql create mode 100644 src/test/java/g3601_3700/s3626_find_stores_with_inventory_imbalance/MysqlTest.java diff --git a/src/main/java/g3601_3700/s3626_find_stores_with_inventory_imbalance/readme.md b/src/main/java/g3601_3700/s3626_find_stores_with_inventory_imbalance/readme.md new file mode 100644 index 000000000..633865a78 --- /dev/null +++ b/src/main/java/g3601_3700/s3626_find_stores_with_inventory_imbalance/readme.md @@ -0,0 +1,118 @@ +3626\. Find Stores with Inventory Imbalance + +Medium + +Table: `stores` + + +------------+----------+ + | Column Name| Type | + +------------+----------+ + | store_id | int | + | store_name | varchar | + | location | varchar | + +------------+----------+ + + store_id is the unique identifier for this table. + Each row contains information about a store and its location. + +Table: `inventory` + + +--------------+----------+ + | Column Name | Type | + +--------------+----------+ + | inventory_id | int | + | store_id | int | + | product_name | varchar | + | quantity | int | + | price | decimal | + +--------------+----------+ + + inventory_id is the unique identifier for this table. + Each row represents the inventory of a specific product at a specific store. + +Write a solution to find stores that have **inventory imbalance** - stores where the most expensive product has lower stock than the cheapest product. + +* For each store, identify the **most expensive product** (highest price) and its quantity +* For each store, identify the **cheapest product** (lowest price) and its quantity +* A store has inventory imbalance if the most expensive product's quantity is **less than** the cheapest product's quantity +* Calculate the **imbalance ratio** as (cheapest\_quantity / most\_expensive\_quantity) +* **Round** the imbalance ratio to **2** decimal places +* Only include stores that have **at least** `3` **different products** + +Return _the result table ordered by imbalance ratio in **descending** order, then by store name in **ascending** order_. + +The result format is in the following example. + +**Example:** + +**Input:** + +stores table: + + +----------+----------------+-------------+ + | store_id | store_name | location | + +----------+----------------+-------------+ + | 1 | Downtown Tech | New York | + | 2 | Suburb Mall | Chicago | + | 3 | City Center | Los Angeles | + | 4 | Corner Shop | Miami | + | 5 | Plaza Store | Seattle | + +----------+----------------+-------------+ + +inventory table: + + +--------------+----------+--------------+----------+--------+ + | inventory_id | store_id | product_name | quantity | price | + +--------------+----------+--------------+----------+--------+ + | 1 | 1 | Laptop | 5 | 999.99 | + | 2 | 1 | Mouse | 50 | 19.99 | + | 3 | 1 | Keyboard | 25 | 79.99 | + | 4 | 1 | Monitor | 15 | 299.99 | + | 5 | 2 | Phone | 3 | 699.99 | + | 6 | 2 | Charger | 100 | 25.99 | + | 7 | 2 | Case | 75 | 15.99 | + | 8 | 2 | Headphones | 20 | 149.99 | + | 9 | 3 | Tablet | 2 | 499.99 | + | 10 | 3 | Stylus | 80 | 29.99 | + | 11 | 3 | Cover | 60 | 39.99 | + | 12 | 4 | Watch | 10 | 299.99 | + | 13 | 4 | Band | 25 | 49.99 | + | 14 | 5 | Camera | 8 | 599.99 | + | 15 | 5 | Lens | 12 | 199.99 | + +--------------+----------+--------------+----------+--------+ + +**Output:** + + +----------+----------------+-------------+------------------+--------------------+------------------+ + | store_id | store_name | location | most_exp_product | cheapest_product | imbalance_ratio | + +----------+----------------+-------------+------------------+--------------------+------------------+ + | 3 | City Center | Los Angeles | Tablet | Stylus | 40.00 | + | 1 | Downtown Tech | New York | Laptop | Mouse | 10.00 | + | 2 | Suburb Mall | Chicago | Phone | Case | 25.00 | + +----------+----------------+-------------+------------------+--------------------+------------------+ + +**Explanation:** + +* **Downtown Tech (store\_id = 1):** + * Most expensive product: Laptop ($999.99) with quantity 5 + * Cheapest product: Mouse ($19.99) with quantity 50 + * Inventory imbalance: 5 < 50 (expensive product has lower stock) + * Imbalance ratio: 50 / 5 = 10.00 + * Has 4 products (≥ 3), so qualifies +* **Suburb Mall (store\_id = 2):** + * Most expensive product: Phone ($699.99) with quantity 3 + * Cheapest product: Case ($15.99) with quantity 75 + * Inventory imbalance: 3 < 75 (expensive product has lower stock) + * Imbalance ratio: 75 / 3 = 25.00 + * Has 4 products (≥ 3), so qualifies +* **City Center (store\_id = 3):** + * Most expensive product: Tablet ($499.99) with quantity 2 + * Cheapest product: Stylus ($29.99) with quantity 80 + * Inventory imbalance: 2 < 80 (expensive product has lower stock) + * Imbalance ratio: 80 / 2 = 40.00 + * Has 3 products (≥ 3), so qualifies +* **Stores not included:** + * Corner Shop (store\_id = 4): Only has 2 products (Watch, Band) - doesn't meet minimum 3 products requirement + * Plaza Store (store\_id = 5): Only has 2 products (Camera, Lens) - doesn't meet minimum 3 products requirement + +The Results table is ordered by imbalance ratio in descending order, then by store name in ascending order \ No newline at end of file diff --git a/src/main/java/g3601_3700/s3626_find_stores_with_inventory_imbalance/script.sql b/src/main/java/g3601_3700/s3626_find_stores_with_inventory_imbalance/script.sql new file mode 100644 index 000000000..86a39fd67 --- /dev/null +++ b/src/main/java/g3601_3700/s3626_find_stores_with_inventory_imbalance/script.sql @@ -0,0 +1,63 @@ +# Write your MySQL query statement below +# #Medium #2025_07_25_Time_516_ms_(100.00%)_Space_0.0_MB_(100.00%) +WITH store_product_check AS ( + SELECT + s.store_id, + s.store_name, + s.location, + COUNT(i.inventory_id) AS store_product_ct + FROM + stores s + JOIN inventory i ON s.store_id = i.store_id + GROUP BY + s.store_id, + s.store_name, + s.location + HAVING + COUNT(i.inventory_id) >= 3 +), +store_product_ranked AS ( + SELECT + s.store_id, + s.store_name, + s.location, + i.inventory_id, + i.product_name, + i.quantity, + i.price, + ROW_NUMBER() OVER (PARTITION BY s.store_id ORDER BY i.price ASC) AS low_price_rk, + ROW_NUMBER() OVER (PARTITION BY s.store_id ORDER BY i.price DESC) AS high_price_rk + FROM + stores s + JOIN inventory i ON s.store_id = i.store_id +), +high_low_price AS ( + SELECT + spc.store_id, + spc.store_name, + spc.location, + lp.product_name AS low_price_product_name, + lp.quantity + 0.0 AS low_price_quantity, + hp.product_name AS high_price_product_name, + hp.quantity + 0.0 AS high_price_quantity + FROM + store_product_check spc + JOIN store_product_ranked lp + ON spc.store_id = lp.store_id AND lp.low_price_rk = 1 + JOIN store_product_ranked hp + ON spc.store_id = hp.store_id AND hp.high_price_rk = 1 +) +SELECT + hlp.store_id, + hlp.store_name, + hlp.location, + hlp.high_price_product_name AS most_exp_product, + hlp.low_price_product_name AS cheapest_product, + ROUND(hlp.low_price_quantity / hlp.high_price_quantity, 2) AS imbalance_ratio +FROM + high_low_price hlp +WHERE + hlp.high_price_quantity < hlp.low_price_quantity +ORDER BY + imbalance_ratio DESC, + hlp.store_name ASC; diff --git a/src/test/java/g3601_3700/s3626_find_stores_with_inventory_imbalance/MysqlTest.java b/src/test/java/g3601_3700/s3626_find_stores_with_inventory_imbalance/MysqlTest.java new file mode 100644 index 000000000..899660a0f --- /dev/null +++ b/src/test/java/g3601_3700/s3626_find_stores_with_inventory_imbalance/MysqlTest.java @@ -0,0 +1,101 @@ +package g3601_3700.s3626_find_stores_with_inventory_imbalance; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; + +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.stream.Collectors; +import javax.sql.DataSource; +import org.junit.jupiter.api.Test; +import org.zapodot.junit.db.annotations.EmbeddedDatabase; +import org.zapodot.junit.db.annotations.EmbeddedDatabaseTest; +import org.zapodot.junit.db.common.CompatibilityMode; + +@EmbeddedDatabaseTest( + compatibilityMode = CompatibilityMode.MySQL, + initialSqls = + "CREATE TABLE stores (" + + " store_id INT PRIMARY KEY," + + " store_name VARCHAR(50)," + + " location VARCHAR(50)" + + ");" + + "INSERT INTO stores (store_id, store_name, location) VALUES" + + "(1, 'Downtown Tech', 'New York')," + + "(2, 'Suburb Mall', 'Chicago')," + + "(3, 'City Center', 'Los Angeles')," + + "(4, 'Corner Shop', 'Miami')," + + "(5, 'Plaza Store', 'Seattle');" + + "CREATE TABLE inventory (" + + " inventory_id INT PRIMARY KEY," + + " store_id INT," + + " product_name VARCHAR(50)," + + " quantity INT," + + " price DECIMAL(10,2)," + + " FOREIGN KEY (store_id) REFERENCES stores(store_id)" + + ");" + + "INSERT INTO inventory (inventory_id, store_id, " + + "product_name, quantity, price) VALUES" + + "(1, 1, 'Laptop', 5, 999.99)," + + "(2, 1, 'Mouse', 50, 19.99)," + + "(3, 1, 'Keyboard', 25, 79.99)," + + "(4, 1, 'Monitor', 15, 299.99)," + + "(5, 2, 'Phone', 3, 699.99)," + + "(6, 2, 'Charger', 100, 25.99)," + + "(7, 2, 'Case', 75, 15.99)," + + "(8, 2, 'Headphones', 20, 149.99)," + + "(9, 3, 'Tablet', 2, 499.99)," + + "(10, 3, 'Stylus', 80, 29.99)," + + "(11, 3, 'Cover', 60, 39.99)," + + "(12, 4, 'Watch', 10, 299.99)," + + "(13, 4, 'Band', 25, 49.99)," + + "(14, 5, 'Camera', 8, 599.99)," + + "(15, 5, 'Lens', 12, 199.99);") +class MysqlTest { + @Test + void testScript(@EmbeddedDatabase DataSource dataSource) + throws SQLException, FileNotFoundException { + try (final Connection connection = dataSource.getConnection()) { + try (final Statement statement = connection.createStatement(); + final ResultSet resultSet = + statement.executeQuery( + new BufferedReader( + new FileReader( + "src/main/java/g3601_3700/" + + "s3626_find_stores_with_" + + "inventory_imbalance/" + + "script.sql")) + .lines() + .collect(Collectors.joining("\n")) + .replaceAll("#.*?\\r?\\n", ""))) { + assertThat(resultSet.next(), equalTo(true)); + assertThat(resultSet.getNString(1), equalTo("3")); + assertThat(resultSet.getNString(2), equalTo("City Center")); + assertThat(resultSet.getNString(3), equalTo("Los Angeles")); + assertThat(resultSet.getNString(4), equalTo("Tablet")); + assertThat(resultSet.getNString(5), equalTo("Stylus")); + assertThat(resultSet.getNString(6), equalTo("40.00")); + assertThat(resultSet.next(), equalTo(true)); + assertThat(resultSet.getNString(1), equalTo("2")); + assertThat(resultSet.getNString(2), equalTo("Suburb Mall")); + assertThat(resultSet.getNString(3), equalTo("Chicago")); + assertThat(resultSet.getNString(4), equalTo("Phone")); + assertThat(resultSet.getNString(5), equalTo("Case")); + assertThat(resultSet.getNString(6), equalTo("25.00")); + assertThat(resultSet.next(), equalTo(true)); + assertThat(resultSet.getNString(1), equalTo("1")); + assertThat(resultSet.getNString(2), equalTo("Downtown Tech")); + assertThat(resultSet.getNString(3), equalTo("New York")); + assertThat(resultSet.getNString(4), equalTo("Laptop")); + assertThat(resultSet.getNString(5), equalTo("Mouse")); + assertThat(resultSet.getNString(6), equalTo("10.00")); + assertThat(resultSet.next(), equalTo(false)); + } + } + } +}