A MySQL-based inventory management system simulating stock tracking, order processing, and reporting for a small retail business. Built as a hands-on project to practice core to advanced SQL concepts, from schema design through stored procedures, triggers, transactions, and window functions.
The system tracks products, suppliers, stock levels across warehouses, and customer orders. It includes business logic to prevent overselling, an automatic audit trail of every stock change, and reporting views/queries for analysis.
suppliers (supplier_id, supplier_name, contact_email)
|
| 1-to-many
v
products (product_id, product_name, category, price, supplier_id)
|
| 1-to-many
v
inventory (inventory_id, product_id, stock, warehouse_location)
products (product_id) --< orders (order_id, customer_name, product_id, qty, order_date)
products (product_id) --< inventory_log (log_id, product_id, change_type, qty_changed, old_stock, new_stock, changed_at)
- suppliers → products: one supplier can supply many products
- products → inventory: one product has one stock record (per warehouse, in this version)
- products → orders: one product can appear in many orders
- products → inventory_log: every stock change for a product is logged
| Area | Details |
|---|---|
| Schema design | Multi-table schema with primary/foreign keys |
| Joins & aggregation | JOIN, GROUP BY, HAVING |
| Views | Filtered vs. unfiltered reporting views |
| Stored procedures | Parameters, conditional logic (IF / ELSEIF), input validation |
| Triggers | AFTER UPDATE (auto-logging), BEFORE UPDATE (data validation / rejecting bad writes) |
| Transactions | START TRANSACTION, COMMIT, ROLLBACK, SAVEPOINT / ROLLBACK TO |
| Subqueries & CTEs | Correlated subqueries, WITH ... AS |
| Window functions | RANK(), ROW_NUMBER(), AVG() OVER (PARTITION BY ...) |
PlaceOrderprocedure — validates a purchase end-to-end: rejects orders for products that don't exist ("Item not in list"), rejects orders that exceed available stock ("Insufficient stock"), and otherwise reduces stock, records the order, and logs the change.RestockProductprocedure — increases stock and logs the before/after values toinventory_log.- Audit trail —
inventory_logrecords every stock change, either via the procedures above or automatically via theafter_inventory_updatetrigger (which also catches directUPDATEstatements outside the procedures). - Data integrity trigger —
before_inventory_updaterejects any update that would push stock negative, regardless of how the update was issued.
Known trade-off:
RestockProductandPlaceOrderinsert their own log rows and theAFTER UPDATEtrigger also logs the same change — meaning a single call currently produces two log rows for one event (oneRESTOCK/SALErow, oneAUTO_ADJUSTrow). This was kept intentionally during development to compare both logging approaches side by side; a production version would use one approach only (most likely trigger-only, since it also catches changes made outside the procedures).
Run the files in order against a MySQL instance:
01_schema.sql— creates the database and tables02_sample_data.sql— inserts sample suppliers, products, inventory, orders03_views.sql— creates the reporting views04_procedures.sql— creates the stored procedures05_triggers.sql— creates the triggers06_transactions_demo.sql— optional; demonstratesCOMMIT/ROLLBACK/SAVEPOINT07_advanced_queries.sql— subqueries, CTEs, window function examples08_sample_queries.sql— basic analysis queries
mysql -u root -p < 01_schema.sql
mysql -u root -p < 02_sample_data.sql
mysql -u root -p < 03_views.sql
mysql -u root -p < 04_procedures.sql
mysql -u root -p < 05_triggers.sql-- Find low-stock products
CALL GetLowStockProducts(20);
-- Restock a product
CALL RestockProduct(9, 50);
-- Place an order (with validation)
CALL PlaceOrder('Test Customer', 9, 5);
-- View category-level reporting
SELECT * FROM category_stock_summary_all;
-- Rank products by price within category
SELECT product_name, category, price,
RANK() OVER (PARTITION BY category ORDER BY price DESC) AS price_rank
FROM products;MySQL 8.0+ (uses window functions and CTEs, which require MySQL 8.0 or later)