786 lines
27 KiB
SQL
786 lines
27 KiB
SQL
/*
|
|||
|
|
================================================================================
|
||
|
|
ORACLE 19C - E-COMMERCE ORDER MANAGEMENT SYSTEM
|
||
|
|
Complex Schema with Multiple Dependencies
|
||
|
|
================================================================================
|
||
|
|
This schema demonstrates:
|
||
|
|
- Multiple base tables with PK/FK relationships
|
||
|
|
- Sequences for auto-increment
|
||
|
|
- Indexes (unique, composite, functional)
|
||
|
|
- Views (simple and complex with aggregations)
|
||
|
|
- Stored procedures with business logic
|
||
|
|
- Packages (organization and reusability)
|
||
|
|
- Triggers (automatic actions and data validation)
|
||
|
|
- Table relationships and cascading constraints
|
||
|
|
================================================================================
|
||
|
|
*/
|
||
|
|
|
||
|
|
-- ============================================================================
|
||
|
|
-- SEQUENCES
|
||
|
|
-- ============================================================================
|
||
|
|
|
||
|
|
CREATE SEQUENCE seq_customers
|
||
|
|
START WITH 1000
|
||
|
|
INCREMENT BY 1
|
||
|
|
NOCYCLE;
|
||
|
|
|
||
|
|
CREATE SEQUENCE seq_products
|
||
|
|
START WITH 5000
|
||
|
|
INCREMENT BY 1
|
||
|
|
NOCYCLE;
|
||
|
|
|
||
|
|
CREATE SEQUENCE seq_orders
|
||
|
|
START WITH 100000
|
||
|
|
INCREMENT BY 1
|
||
|
|
NOCYCLE;
|
||
|
|
|
||
|
|
CREATE SEQUENCE seq_order_items
|
||
|
|
START WITH 1000000
|
||
|
|
INCREMENT BY 1
|
||
|
|
NOCYCLE;
|
||
|
|
|
||
|
|
CREATE SEQUENCE seq_employees
|
||
|
|
START WITH 2000
|
||
|
|
INCREMENT BY 1
|
||
|
|
NOCYCLE;
|
||
|
|
|
||
|
|
CREATE SEQUENCE seq_departments
|
||
|
|
START WITH 10
|
||
|
|
INCREMENT BY 1
|
||
|
|
NOCYCLE;
|
||
|
|
|
||
|
|
CREATE SEQUENCE seq_inventory
|
||
|
|
START WITH 50000
|
||
|
|
INCREMENT BY 1
|
||
|
|
NOCYCLE;
|
||
|
|
|
||
|
|
-- ============================================================================
|
||
|
|
-- BASE TABLES - DIMENSION TABLES
|
||
|
|
-- ============================================================================
|
||
|
|
|
||
|
|
-- Departments Table (Parent of Employees)
|
||
|
|
CREATE TABLE departments (
|
||
|
|
department_id NUMBER PRIMARY KEY,
|
||
|
|
department_name VARCHAR2(100) NOT NULL UNIQUE,
|
||
|
|
budget_allocation NUMBER(12,2),
|
||
|
|
manager_id NUMBER,
|
||
|
|
created_date DATE DEFAULT SYSDATE,
|
||
|
|
CONSTRAINT chk_budget_positive CHECK (budget_allocation >= 0)
|
||
|
|
);
|
||
|
|
|
||
|
|
COMMENT ON TABLE departments IS 'Organizational departments that manage business units';
|
||
|
|
COMMENT ON COLUMN departments.budget_allocation IS 'Annual budget allocation in USD';
|
||
|
|
|
||
|
|
-- Employees Table (Parent of Orders via sales_employee_id)
|
||
|
|
CREATE TABLE employees (
|
||
|
|
employee_id NUMBER PRIMARY KEY,
|
||
|
|
first_name VARCHAR2(50) NOT NULL,
|
||
|
|
last_name VARCHAR2(50) NOT NULL,
|
||
|
|
email VARCHAR2(100) UNIQUE,
|
||
|
|
phone_number VARCHAR2(20),
|
||
|
|
hire_date DATE NOT NULL,
|
||
|
|
job_title VARCHAR2(50),
|
||
|
|
salary NUMBER(10,2),
|
||
|
|
department_id NUMBER NOT NULL,
|
||
|
|
manager_id NUMBER,
|
||
|
|
created_date DATE DEFAULT SYSDATE,
|
||
|
|
CONSTRAINT fk_emp_dept FOREIGN KEY (department_id) REFERENCES departments(department_id),
|
||
|
|
CONSTRAINT fk_emp_mgr FOREIGN KEY (manager_id) REFERENCES employees(employee_id),
|
||
|
|
CONSTRAINT chk_salary_positive CHECK (salary > 0)
|
||
|
|
);
|
||
|
|
|
||
|
|
COMMENT ON TABLE employees IS 'Company employees with hierarchical reporting structure';
|
||
|
|
COMMENT ON COLUMN employees.manager_id IS 'Self-referencing: supervisor of this employee';
|
||
|
|
|
||
|
|
-- Product Categories Table (Parent of Products)
|
||
|
|
CREATE TABLE product_categories (
|
||
|
|
category_id NUMBER PRIMARY KEY,
|
||
|
|
category_name VARCHAR2(100) NOT NULL UNIQUE,
|
||
|
|
description VARCHAR2(500),
|
||
|
|
active_flag CHAR(1) DEFAULT 'Y',
|
||
|
|
created_date DATE DEFAULT SYSDATE
|
||
|
|
);
|
||
|
|
|
||
|
|
COMMENT ON TABLE product_categories IS 'Product classification hierarchy';
|
||
|
|
|
||
|
|
-- Products Table (Parent of Order_Items, Inventory)
|
||
|
|
CREATE TABLE products (
|
||
|
|
product_id NUMBER PRIMARY KEY,
|
||
|
|
product_name VARCHAR2(150) NOT NULL,
|
||
|
|
description VARCHAR2(500),
|
||
|
|
category_id NUMBER NOT NULL,
|
||
|
|
unit_price NUMBER(10,2) NOT NULL,
|
||
|
|
cost_price NUMBER(10,2),
|
||
|
|
reorder_level NUMBER(5) DEFAULT 10,
|
||
|
|
discontinued_flag CHAR(1) DEFAULT 'N',
|
||
|
|
created_date DATE DEFAULT SYSDATE,
|
||
|
|
updated_date DATE,
|
||
|
|
CONSTRAINT fk_prod_cat FOREIGN KEY (category_id) REFERENCES product_categories(category_id),
|
||
|
|
CONSTRAINT chk_unit_price CHECK (unit_price > 0),
|
||
|
|
CONSTRAINT chk_cost_price CHECK (cost_price IS NULL OR cost_price > 0)
|
||
|
|
);
|
||
|
|
|
||
|
|
COMMENT ON TABLE products IS 'Saleable products in catalog';
|
||
|
|
COMMENT ON COLUMN products.reorder_level IS 'Minimum stock level before auto-reorder';
|
||
|
|
|
||
|
|
-- Customers Table (Parent of Orders, Reviews)
|
||
|
|
CREATE TABLE customers (
|
||
|
|
customer_id NUMBER PRIMARY KEY,
|
||
|
|
first_name VARCHAR2(50) NOT NULL,
|
||
|
|
last_name VARCHAR2(50) NOT NULL,
|
||
|
|
email VARCHAR2(100) UNIQUE NOT NULL,
|
||
|
|
phone_number VARCHAR2(20),
|
||
|
|
billing_address VARCHAR2(200),
|
||
|
|
shipping_address VARCHAR2(200),
|
||
|
|
customer_type VARCHAR2(20) DEFAULT 'RETAIL',
|
||
|
|
credit_limit NUMBER(10,2),
|
||
|
|
account_status VARCHAR2(20) DEFAULT 'ACTIVE',
|
||
|
|
created_date DATE DEFAULT SYSDATE,
|
||
|
|
last_purchase_date DATE,
|
||
|
|
CONSTRAINT chk_cust_type CHECK (customer_type IN ('RETAIL', 'WHOLESALE', 'CORPORATE')),
|
||
|
|
CONSTRAINT chk_cust_status CHECK (account_status IN ('ACTIVE', 'INACTIVE', 'SUSPENDED', 'ARCHIVED'))
|
||
|
|
);
|
||
|
|
|
||
|
|
COMMENT ON TABLE customers IS 'Customer master data with credit and account information';
|
||
|
|
COMMENT ON COLUMN customers.credit_limit IS 'Maximum credit extended to customer in USD';
|
||
|
|
|
||
|
|
-- ============================================================================
|
||
|
|
-- TRANSACTIONAL TABLES
|
||
|
|
-- ============================================================================
|
||
|
|
|
||
|
|
-- Orders Table (Parent of Order_Items, child of Customers and Employees)
|
||
|
|
CREATE TABLE orders (
|
||
|
|
order_id NUMBER PRIMARY KEY,
|
||
|
|
customer_id NUMBER NOT NULL,
|
||
|
|
sales_employee_id NUMBER NOT NULL,
|
||
|
|
order_date DATE NOT NULL,
|
||
|
|
delivery_date DATE,
|
||
|
|
order_status VARCHAR2(30) DEFAULT 'PENDING',
|
||
|
|
total_amount NUMBER(12,2),
|
||
|
|
tax_amount NUMBER(12,2),
|
||
|
|
discount_amount NUMBER(12,2),
|
||
|
|
payment_method VARCHAR2(30),
|
||
|
|
notes VARCHAR2(500),
|
||
|
|
created_date DATE DEFAULT SYSDATE,
|
||
|
|
CONSTRAINT fk_ord_cust FOREIGN KEY (customer_id) REFERENCES customers(customer_id) ON DELETE CASCADE,
|
||
|
|
CONSTRAINT fk_ord_emp FOREIGN KEY (sales_employee_id) REFERENCES employees(employee_id),
|
||
|
|
CONSTRAINT chk_ord_status CHECK (order_status IN ('PENDING', 'CONFIRMED', 'SHIPPED', 'DELIVERED', 'CANCELLED', 'RETURNED')),
|
||
|
|
CONSTRAINT chk_ord_total CHECK (total_amount >= 0),
|
||
|
|
CONSTRAINT chk_payment CHECK (payment_method IN ('CREDIT_CARD', 'BANK_TRANSFER', 'CASH', 'CHECK', 'DIGITAL_WALLET'))
|
||
|
|
);
|
||
|
|
|
||
|
|
COMMENT ON TABLE orders IS 'Sales orders placed by customers';
|
||
|
|
COMMENT ON COLUMN orders.sales_employee_id IS 'Employee who processed this order (FK to employees)';
|
||
|
|
|
||
|
|
-- Order_Items Table (Child of Orders and Products)
|
||
|
|
CREATE TABLE order_items (
|
||
|
|
order_item_id NUMBER PRIMARY KEY,
|
||
|
|
order_id NUMBER NOT NULL,
|
||
|
|
product_id NUMBER NOT NULL,
|
||
|
|
quantity NUMBER(6) NOT NULL,
|
||
|
|
unit_price NUMBER(10,2) NOT NULL,
|
||
|
|
line_total NUMBER(12,2),
|
||
|
|
discount_percent NUMBER(5,2) DEFAULT 0,
|
||
|
|
created_date DATE DEFAULT SYSDATE,
|
||
|
|
CONSTRAINT fk_oi_ord FOREIGN KEY (order_id) REFERENCES orders(order_id) ON DELETE CASCADE,
|
||
|
|
CONSTRAINT fk_oi_prod FOREIGN KEY (product_id) REFERENCES products(product_id),
|
||
|
|
CONSTRAINT chk_qty CHECK (quantity > 0)
|
||
|
|
);
|
||
|
|
|
||
|
|
COMMENT ON TABLE order_items IS 'Individual line items within an order';
|
||
|
|
|
||
|
|
-- Inventory Table (Child of Products)
|
||
|
|
CREATE TABLE inventory (
|
||
|
|
inventory_id NUMBER PRIMARY KEY,
|
||
|
|
product_id NUMBER NOT NULL UNIQUE,
|
||
|
|
warehouse_location VARCHAR2(50),
|
||
|
|
quantity_on_hand NUMBER(10) DEFAULT 0,
|
||
|
|
quantity_reserved NUMBER(10) DEFAULT 0,
|
||
|
|
quantity_available NUMBER(10) DEFAULT 0,
|
||
|
|
last_stock_check DATE,
|
||
|
|
reorder_quantity NUMBER(5),
|
||
|
|
supplier_id NUMBER,
|
||
|
|
updated_date DATE DEFAULT SYSDATE,
|
||
|
|
CONSTRAINT fk_inv_prod FOREIGN KEY (product_id) REFERENCES products(product_id) ON DELETE CASCADE,
|
||
|
|
CONSTRAINT chk_qty_onhand CHECK (quantity_on_hand >= 0),
|
||
|
|
CONSTRAINT chk_qty_reserved CHECK (quantity_reserved >= 0),
|
||
|
|
CONSTRAINT chk_qty_available CHECK (quantity_available >= 0)
|
||
|
|
);
|
||
|
|
|
||
|
|
COMMENT ON TABLE inventory IS 'Real-time inventory management for products';
|
||
|
|
|
||
|
|
-- Customer_Reviews Table (Child of Customers and Products)
|
||
|
|
CREATE TABLE customer_reviews (
|
||
|
|
review_id NUMBER PRIMARY KEY,
|
||
|
|
customer_id NUMBER NOT NULL,
|
||
|
|
product_id NUMBER NOT NULL,
|
||
|
|
review_rating NUMBER(2) DEFAULT 5,
|
||
|
|
review_text VARCHAR2(1000),
|
||
|
|
helpful_count NUMBER(5) DEFAULT 0,
|
||
|
|
review_status VARCHAR2(20) DEFAULT 'PENDING',
|
||
|
|
created_date DATE DEFAULT SYSDATE,
|
||
|
|
moderated_date DATE,
|
||
|
|
CONSTRAINT fk_rev_cust FOREIGN KEY (customer_id) REFERENCES customers(customer_id) ON DELETE CASCADE,
|
||
|
|
CONSTRAINT fk_rev_prod FOREIGN KEY (product_id) REFERENCES products(product_id) ON DELETE CASCADE,
|
||
|
|
CONSTRAINT chk_rating CHECK (review_rating BETWEEN 1 AND 5),
|
||
|
|
CONSTRAINT chk_rev_status CHECK (review_status IN ('PENDING', 'APPROVED', 'REJECTED', 'FLAGGED'))
|
||
|
|
);
|
||
|
|
|
||
|
|
COMMENT ON TABLE customer_reviews IS 'Customer product ratings and reviews with moderation workflow';
|
||
|
|
|
||
|
|
-- ============================================================================
|
||
|
|
-- INDEXES
|
||
|
|
-- ============================================================================
|
||
|
|
|
||
|
|
-- Composite indexes on frequently filtered columns
|
||
|
|
CREATE INDEX idx_orders_customer_date
|
||
|
|
ON orders(customer_id, order_date DESC);
|
||
|
|
|
||
|
|
CREATE INDEX idx_orders_employee_status
|
||
|
|
ON orders(sales_employee_id, order_status);
|
||
|
|
|
||
|
|
CREATE INDEX idx_order_items_product
|
||
|
|
ON order_items(product_id);
|
||
|
|
|
||
|
|
-- Unique index for email lookup
|
||
|
|
|
||
|
|
|
||
|
|
-- Functional index for case-insensitive searches
|
||
|
|
CREATE INDEX idx_product_name_upper
|
||
|
|
ON products(UPPER(product_name));
|
||
|
|
|
||
|
|
CREATE INDEX idx_category_name_upper
|
||
|
|
ON product_categories(UPPER(category_name));
|
||
|
|
|
||
|
|
-- Index for inventory queries
|
||
|
|
CREATE INDEX idx_inventory_warehouse
|
||
|
|
ON inventory(warehouse_location);
|
||
|
|
|
||
|
|
-- Index for review queries
|
||
|
|
CREATE INDEX idx_reviews_product_rating
|
||
|
|
ON customer_reviews(product_id, review_rating DESC);
|
||
|
|
|
||
|
|
-- ============================================================================
|
||
|
|
-- VIEWS
|
||
|
|
-- ============================================================================
|
||
|
|
|
||
|
|
-- Simple View: Active Customers
|
||
|
|
CREATE OR REPLACE VIEW v_active_customers AS
|
||
|
|
SELECT
|
||
|
|
customer_id,
|
||
|
|
first_name,
|
||
|
|
last_name,
|
||
|
|
email,
|
||
|
|
customer_type,
|
||
|
|
account_status,
|
||
|
|
last_purchase_date
|
||
|
|
FROM customers
|
||
|
|
WHERE account_status = 'ACTIVE';
|
||
|
|
|
||
|
|
COMMENT ON TABLE v_active_customers IS 'Filtered view of active customer accounts only';
|
||
|
|
|
||
|
|
-- Complex View: Order Summary with Customer and Employee Details
|
||
|
|
CREATE OR REPLACE VIEW v_order_summary AS
|
||
|
|
SELECT
|
||
|
|
o.order_id,
|
||
|
|
c.customer_id,
|
||
|
|
c.first_name || ' ' || c.last_name AS customer_name,
|
||
|
|
c.email AS customer_email,
|
||
|
|
e.employee_id,
|
||
|
|
e.first_name || ' ' || e.last_name AS sales_person,
|
||
|
|
o.order_date,
|
||
|
|
o.delivery_date,
|
||
|
|
o.order_status,
|
||
|
|
COUNT(oi.order_item_id) AS line_item_count,
|
||
|
|
SUM(oi.quantity) AS total_items,
|
||
|
|
o.total_amount,
|
||
|
|
o.tax_amount,
|
||
|
|
o.discount_amount,
|
||
|
|
(o.total_amount + o.tax_amount - o.discount_amount) AS net_amount
|
||
|
|
FROM orders o
|
||
|
|
JOIN customers c ON o.customer_id = c.customer_id
|
||
|
|
JOIN employees e ON o.sales_employee_id = e.employee_id
|
||
|
|
LEFT JOIN order_items oi ON o.order_id = oi.order_id
|
||
|
|
GROUP BY
|
||
|
|
o.order_id, c.customer_id, c.first_name, c.last_name, c.email,
|
||
|
|
e.employee_id, e.first_name, e.last_name,
|
||
|
|
o.order_date, o.delivery_date, o.order_status,
|
||
|
|
o.total_amount, o.tax_amount, o.discount_amount;
|
||
|
|
|
||
|
|
COMMENT ON TABLE v_order_summary IS 'Comprehensive order view with aggregated metrics and related customer/employee data';
|
||
|
|
|
||
|
|
-- Aggregation View: Product Performance
|
||
|
|
CREATE OR REPLACE VIEW v_product_performance AS
|
||
|
|
SELECT
|
||
|
|
p.product_id,
|
||
|
|
p.product_name,
|
||
|
|
pc.category_name,
|
||
|
|
p.unit_price,
|
||
|
|
COUNT(DISTINCT oi.order_id) AS orders_count,
|
||
|
|
SUM(oi.quantity) AS total_quantity_sold,
|
||
|
|
SUM(oi.line_total) AS total_revenue,
|
||
|
|
ROUND(AVG(oi.quantity), 2) AS avg_qty_per_order,
|
||
|
|
COUNT(DISTINCT cr.review_id) AS review_count,
|
||
|
|
ROUND(AVG(cr.review_rating), 2) AS avg_rating,
|
||
|
|
i.quantity_on_hand,
|
||
|
|
i.quantity_reserved,
|
||
|
|
i.quantity_available
|
||
|
|
FROM products p
|
||
|
|
JOIN product_categories pc ON p.category_id = pc.category_id
|
||
|
|
LEFT JOIN order_items oi ON p.product_id = oi.product_id
|
||
|
|
LEFT JOIN customer_reviews cr ON p.product_id = cr.product_id AND cr.review_status = 'APPROVED'
|
||
|
|
LEFT JOIN inventory i ON p.product_id = i.product_id
|
||
|
|
WHERE p.discontinued_flag = 'N'
|
||
|
|
GROUP BY
|
||
|
|
p.product_id, p.product_name, pc.category_name, p.unit_price,
|
||
|
|
i.quantity_on_hand, i.quantity_reserved, i.quantity_available;
|
||
|
|
|
||
|
|
COMMENT ON TABLE v_product_performance IS 'Multi-dimensional view showing sales, reviews, and inventory for each product';
|
||
|
|
|
||
|
|
-- View: Employee Sales Performance
|
||
|
|
CREATE OR REPLACE VIEW v_employee_sales_performance AS
|
||
|
|
SELECT
|
||
|
|
e.employee_id,
|
||
|
|
e.first_name || ' ' || e.last_name AS employee_name,
|
||
|
|
e.job_title,
|
||
|
|
d.department_name,
|
||
|
|
COUNT(DISTINCT o.order_id) AS total_orders,
|
||
|
|
SUM(o.total_amount) AS total_sales_amount,
|
||
|
|
ROUND(AVG(o.total_amount), 2) AS avg_order_value,
|
||
|
|
COUNT(DISTINCT o.customer_id) AS unique_customers,
|
||
|
|
MAX(o.order_date) AS last_order_date
|
||
|
|
FROM employees e
|
||
|
|
JOIN departments d ON e.department_id = d.department_id
|
||
|
|
LEFT JOIN orders o ON e.employee_id = o.sales_employee_id
|
||
|
|
GROUP BY
|
||
|
|
e.employee_id, e.first_name, e.last_name, e.job_title, d.department_name;
|
||
|
|
|
||
|
|
COMMENT ON TABLE v_employee_sales_performance IS 'Sales metrics aggregated at employee level with department context';
|
||
|
|
|
||
|
|
-- ============================================================================
|
||
|
|
-- STORED PROCEDURES
|
||
|
|
-- ============================================================================
|
||
|
|
|
||
|
|
-- Procedure 1: Create New Order with Validation
|
||
|
|
CREATE OR REPLACE PROCEDURE proc_create_order(
|
||
|
|
p_customer_id IN NUMBER,
|
||
|
|
p_employee_id IN NUMBER,
|
||
|
|
p_delivery_date IN DATE,
|
||
|
|
p_order_id OUT NUMBER,
|
||
|
|
p_status_msg OUT VARCHAR2
|
||
|
|
)
|
||
|
|
IS
|
||
|
|
v_customer_count NUMBER;
|
||
|
|
v_employee_count NUMBER;
|
||
|
|
v_total_amount NUMBER := 0;
|
||
|
|
BEGIN
|
||
|
|
-- Validate customer exists
|
||
|
|
SELECT COUNT(*) INTO v_customer_count FROM customers
|
||
|
|
WHERE customer_id = p_customer_id AND account_status = 'ACTIVE';
|
||
|
|
|
||
|
|
IF v_customer_count = 0 THEN
|
||
|
|
p_status_msg := 'ERROR: Customer not found or inactive';
|
||
|
|
RETURN;
|
||
|
|
END IF;
|
||
|
|
|
||
|
|
-- Validate employee exists
|
||
|
|
SELECT COUNT(*) INTO v_employee_count FROM employees
|
||
|
|
WHERE employee_id = p_employee_id;
|
||
|
|
|
||
|
|
IF v_employee_count = 0 THEN
|
||
|
|
p_status_msg := 'ERROR: Employee not found';
|
||
|
|
RETURN;
|
||
|
|
END IF;
|
||
|
|
|
||
|
|
-- Create order
|
||
|
|
SELECT seq_orders.NEXTVAL INTO p_order_id FROM DUAL;
|
||
|
|
|
||
|
|
INSERT INTO orders (
|
||
|
|
order_id, customer_id, sales_employee_id, order_date, delivery_date,
|
||
|
|
order_status, total_amount, tax_amount, discount_amount, created_date
|
||
|
|
) VALUES (
|
||
|
|
p_order_id, p_customer_id, p_employee_id, SYSDATE, p_delivery_date,
|
||
|
|
'PENDING', 0, 0, 0, SYSDATE
|
||
|
|
);
|
||
|
|
|
||
|
|
p_status_msg := 'SUCCESS: Order created with ID ' || p_order_id;
|
||
|
|
COMMIT;
|
||
|
|
|
||
|
|
EXCEPTION WHEN OTHERS THEN
|
||
|
|
p_status_msg := 'ERROR: ' || SQLERRM;
|
||
|
|
ROLLBACK;
|
||
|
|
END proc_create_order;
|
||
|
|
/
|
||
|
|
|
||
|
|
-- Procedure 2: Add Item to Order with Stock Validation
|
||
|
|
CREATE OR REPLACE PROCEDURE proc_add_order_item(
|
||
|
|
p_order_id IN NUMBER,
|
||
|
|
p_product_id IN NUMBER,
|
||
|
|
p_quantity IN NUMBER,
|
||
|
|
p_order_item_id OUT NUMBER,
|
||
|
|
p_status_msg OUT VARCHAR2
|
||
|
|
)
|
||
|
|
IS
|
||
|
|
v_product_count NUMBER;
|
||
|
|
v_unit_price NUMBER;
|
||
|
|
v_available_qty NUMBER;
|
||
|
|
v_line_total NUMBER;
|
||
|
|
BEGIN
|
||
|
|
-- Validate product exists
|
||
|
|
SELECT COUNT(*), unit_price INTO v_product_count, v_unit_price FROM products
|
||
|
|
WHERE product_id = p_product_id AND discontinued_flag = 'N';
|
||
|
|
|
||
|
|
IF v_product_count = 0 THEN
|
||
|
|
p_status_msg := 'ERROR: Product not found or discontinued';
|
||
|
|
RETURN;
|
||
|
|
END IF;
|
||
|
|
|
||
|
|
-- Check inventory availability
|
||
|
|
SELECT quantity_available INTO v_available_qty FROM inventory
|
||
|
|
WHERE product_id = p_product_id;
|
||
|
|
|
||
|
|
IF v_available_qty < p_quantity THEN
|
||
|
|
p_status_msg := 'ERROR: Insufficient inventory. Available: ' || v_available_qty;
|
||
|
|
RETURN;
|
||
|
|
END IF;
|
||
|
|
|
||
|
|
-- Calculate line total
|
||
|
|
v_line_total := p_quantity * v_unit_price;
|
||
|
|
|
||
|
|
-- Insert order item
|
||
|
|
SELECT seq_order_items.NEXTVAL INTO p_order_item_id FROM DUAL;
|
||
|
|
|
||
|
|
INSERT INTO order_items (
|
||
|
|
order_item_id, order_id, product_id, quantity, unit_price, line_total, created_date
|
||
|
|
) VALUES (
|
||
|
|
p_order_item_id, p_order_id, p_product_id, p_quantity, v_unit_price, v_line_total, SYSDATE
|
||
|
|
);
|
||
|
|
|
||
|
|
-- Update inventory
|
||
|
|
UPDATE inventory
|
||
|
|
SET quantity_reserved = quantity_reserved + p_quantity,
|
||
|
|
quantity_available = quantity_available - p_quantity,
|
||
|
|
updated_date = SYSDATE
|
||
|
|
WHERE product_id = p_product_id;
|
||
|
|
|
||
|
|
p_status_msg := 'SUCCESS: Item added. Line total: ' || v_line_total;
|
||
|
|
COMMIT;
|
||
|
|
|
||
|
|
EXCEPTION WHEN OTHERS THEN
|
||
|
|
p_status_msg := 'ERROR: ' || SQLERRM;
|
||
|
|
ROLLBACK;
|
||
|
|
END proc_add_order_item;
|
||
|
|
/
|
||
|
|
|
||
|
|
-- Procedure 3: Calculate and Update Order Total
|
||
|
|
CREATE OR REPLACE PROCEDURE proc_finalize_order(
|
||
|
|
p_order_id IN NUMBER,
|
||
|
|
p_tax_rate IN NUMBER DEFAULT 0.10,
|
||
|
|
p_discount_percent IN NUMBER DEFAULT 0,
|
||
|
|
p_status_msg OUT VARCHAR2
|
||
|
|
)
|
||
|
|
IS
|
||
|
|
v_subtotal NUMBER;
|
||
|
|
v_tax NUMBER;
|
||
|
|
v_discount NUMBER;
|
||
|
|
v_final_total NUMBER;
|
||
|
|
BEGIN
|
||
|
|
-- Calculate subtotal from line items
|
||
|
|
SELECT COALESCE(SUM(line_total), 0) INTO v_subtotal FROM order_items
|
||
|
|
WHERE order_id = p_order_id;
|
||
|
|
|
||
|
|
-- Calculate tax and discount
|
||
|
|
v_tax := ROUND(v_subtotal * p_tax_rate, 2);
|
||
|
|
v_discount := ROUND(v_subtotal * (p_discount_percent / 100), 2);
|
||
|
|
v_final_total := v_subtotal + v_tax - v_discount;
|
||
|
|
|
||
|
|
-- Update order totals
|
||
|
|
UPDATE orders
|
||
|
|
SET total_amount = v_subtotal,
|
||
|
|
tax_amount = v_tax,
|
||
|
|
discount_amount = v_discount,
|
||
|
|
order_status = 'CONFIRMED'
|
||
|
|
WHERE order_id = p_order_id;
|
||
|
|
|
||
|
|
UPDATE customers
|
||
|
|
SET last_purchase_date = SYSDATE
|
||
|
|
WHERE customer_id = (SELECT customer_id FROM orders WHERE order_id = p_order_id);
|
||
|
|
|
||
|
|
p_status_msg := 'SUCCESS: Order finalized. Total: ' || v_final_total;
|
||
|
|
COMMIT;
|
||
|
|
|
||
|
|
EXCEPTION WHEN OTHERS THEN
|
||
|
|
p_status_msg := 'ERROR: ' || SQLERRM;
|
||
|
|
ROLLBACK;
|
||
|
|
END proc_finalize_order;
|
||
|
|
/
|
||
|
|
|
||
|
|
-- Procedure 4: Generate Monthly Sales Report
|
||
|
|
CREATE OR REPLACE PROCEDURE proc_monthly_sales_report(
|
||
|
|
p_year IN NUMBER,
|
||
|
|
p_month IN NUMBER,
|
||
|
|
p_report_cursor OUT SYS_REFCURSOR
|
||
|
|
)
|
||
|
|
IS
|
||
|
|
v_first_day DATE;
|
||
|
|
v_last_day DATE;
|
||
|
|
BEGIN
|
||
|
|
v_first_day := TRUNC(TO_DATE(p_year || '-' || LPAD(p_month, 2, '0') || '-01', 'YYYY-MM-DD'));
|
||
|
|
v_last_day := LAST_DAY(v_first_day);
|
||
|
|
|
||
|
|
OPEN p_report_cursor FOR
|
||
|
|
SELECT
|
||
|
|
e.employee_id,
|
||
|
|
e.first_name || ' ' || e.last_name AS employee_name,
|
||
|
|
d.department_name,
|
||
|
|
COUNT(o.order_id) AS order_count,
|
||
|
|
SUM(o.total_amount) AS total_sales,
|
||
|
|
ROUND(AVG(o.total_amount), 2) AS avg_order_value,
|
||
|
|
MAX(o.order_date) AS latest_order
|
||
|
|
FROM employees e
|
||
|
|
JOIN departments d ON e.department_id = d.department_id
|
||
|
|
LEFT JOIN orders o ON e.employee_id = o.sales_employee_id
|
||
|
|
AND o.order_date >= v_first_day
|
||
|
|
AND o.order_date <= v_last_day
|
||
|
|
GROUP BY e.employee_id, e.first_name, e.last_name, d.department_name
|
||
|
|
ORDER BY total_sales DESC;
|
||
|
|
|
||
|
|
EXCEPTION WHEN OTHERS THEN
|
||
|
|
NULL;
|
||
|
|
END proc_monthly_sales_report;
|
||
|
|
/
|
||
|
|
|
||
|
|
-- ============================================================================
|
||
|
|
-- PACKAGES
|
||
|
|
-- ============================================================================
|
||
|
|
|
||
|
|
CREATE OR REPLACE PACKAGE pkg_order_management IS
|
||
|
|
|
||
|
|
-- Function to check product availability
|
||
|
|
FUNCTION fn_check_availability(
|
||
|
|
p_product_id IN NUMBER,
|
||
|
|
p_quantity IN NUMBER
|
||
|
|
) RETURN BOOLEAN;
|
||
|
|
|
||
|
|
-- Procedure to process refund
|
||
|
|
PROCEDURE proc_refund_order(
|
||
|
|
p_order_id IN NUMBER,
|
||
|
|
p_reason IN VARCHAR2,
|
||
|
|
p_status_msg OUT VARCHAR2
|
||
|
|
);
|
||
|
|
|
||
|
|
-- Function to calculate discount
|
||
|
|
FUNCTION fn_calculate_discount(
|
||
|
|
p_customer_id IN NUMBER,
|
||
|
|
p_amount IN NUMBER
|
||
|
|
) RETURN NUMBER;
|
||
|
|
|
||
|
|
END pkg_order_management;
|
||
|
|
/
|
||
|
|
|
||
|
|
CREATE OR REPLACE PACKAGE BODY pkg_order_management IS
|
||
|
|
|
||
|
|
FUNCTION fn_check_availability(
|
||
|
|
p_product_id IN NUMBER,
|
||
|
|
p_quantity IN NUMBER
|
||
|
|
) RETURN BOOLEAN
|
||
|
|
IS
|
||
|
|
v_available NUMBER;
|
||
|
|
BEGIN
|
||
|
|
SELECT quantity_available INTO v_available FROM inventory
|
||
|
|
WHERE product_id = p_product_id;
|
||
|
|
|
||
|
|
RETURN v_available >= p_quantity;
|
||
|
|
EXCEPTION WHEN OTHERS THEN
|
||
|
|
RETURN FALSE;
|
||
|
|
END fn_check_availability;
|
||
|
|
|
||
|
|
PROCEDURE proc_refund_order(
|
||
|
|
p_order_id IN NUMBER,
|
||
|
|
p_reason IN VARCHAR2,
|
||
|
|
p_status_msg OUT VARCHAR2
|
||
|
|
)
|
||
|
|
IS
|
||
|
|
v_product_id NUMBER;
|
||
|
|
v_quantity NUMBER;
|
||
|
|
CURSOR c_items IS
|
||
|
|
SELECT product_id, quantity FROM order_items WHERE order_id = p_order_id;
|
||
|
|
BEGIN
|
||
|
|
-- Return items to inventory
|
||
|
|
FOR rec IN c_items LOOP
|
||
|
|
UPDATE inventory
|
||
|
|
SET quantity_available = quantity_available + rec.quantity,
|
||
|
|
quantity_reserved = quantity_reserved - rec.quantity,
|
||
|
|
updated_date = SYSDATE
|
||
|
|
WHERE product_id = rec.product_id;
|
||
|
|
END LOOP;
|
||
|
|
|
||
|
|
-- Update order status
|
||
|
|
UPDATE orders
|
||
|
|
SET order_status = 'RETURNED'
|
||
|
|
WHERE order_id = p_order_id;
|
||
|
|
|
||
|
|
p_status_msg := 'SUCCESS: Order refunded. Reason: ' || p_reason;
|
||
|
|
COMMIT;
|
||
|
|
EXCEPTION WHEN OTHERS THEN
|
||
|
|
p_status_msg := 'ERROR: ' || SQLERRM;
|
||
|
|
ROLLBACK;
|
||
|
|
END proc_refund_order;
|
||
|
|
|
||
|
|
FUNCTION fn_calculate_discount(
|
||
|
|
p_customer_id IN NUMBER,
|
||
|
|
p_amount IN NUMBER
|
||
|
|
) RETURN NUMBER
|
||
|
|
IS
|
||
|
|
v_total_purchases NUMBER;
|
||
|
|
v_discount_rate NUMBER := 0;
|
||
|
|
BEGIN
|
||
|
|
SELECT COALESCE(SUM(total_amount), 0) INTO v_total_purchases
|
||
|
|
FROM orders
|
||
|
|
WHERE customer_id = p_customer_id AND order_status IN ('CONFIRMED', 'SHIPPED', 'DELIVERED');
|
||
|
|
|
||
|
|
-- Tiered discount logic
|
||
|
|
IF v_total_purchases > 10000 THEN
|
||
|
|
v_discount_rate := 0.15;
|
||
|
|
ELSIF v_total_purchases > 5000 THEN
|
||
|
|
v_discount_rate := 0.10;
|
||
|
|
ELSIF v_total_purchases > 1000 THEN
|
||
|
|
v_discount_rate := 0.05;
|
||
|
|
END IF;
|
||
|
|
|
||
|
|
RETURN ROUND(p_amount * v_discount_rate, 2);
|
||
|
|
END fn_calculate_discount;
|
||
|
|
|
||
|
|
END pkg_order_management;
|
||
|
|
/
|
||
|
|
|
||
|
|
-- ============================================================================
|
||
|
|
-- TRIGGERS
|
||
|
|
-- ============================================================================
|
||
|
|
|
||
|
|
-- Trigger 1: Update Product Updated_Date on Insert/Update
|
||
|
|
CREATE OR REPLACE TRIGGER trg_products_audit
|
||
|
|
BEFORE UPDATE ON products
|
||
|
|
FOR EACH ROW
|
||
|
|
BEGIN
|
||
|
|
:NEW.updated_date := SYSDATE;
|
||
|
|
END trg_products_audit;
|
||
|
|
/
|
||
|
|
|
||
|
|
-- Trigger 2: Validate Order Status Transition
|
||
|
|
CREATE OR REPLACE TRIGGER trg_orders_status_validation
|
||
|
|
BEFORE UPDATE ON orders
|
||
|
|
FOR EACH ROW
|
||
|
|
BEGIN
|
||
|
|
IF :OLD.order_status = 'DELIVERED' AND :NEW.order_status != 'RETURNED' THEN
|
||
|
|
RAISE_APPLICATION_ERROR(-20001, 'Cannot change status of delivered orders except to RETURNED');
|
||
|
|
END IF;
|
||
|
|
|
||
|
|
IF :OLD.order_status = 'CANCELLED' AND :NEW.order_status != 'CANCELLED' THEN
|
||
|
|
RAISE_APPLICATION_ERROR(-20002, 'Cancelled orders cannot be modified');
|
||
|
|
END IF;
|
||
|
|
END trg_orders_status_validation;
|
||
|
|
/
|
||
|
|
|
||
|
|
-- Trigger 3: Maintain Inventory Available Quantity
|
||
|
|
CREATE OR REPLACE TRIGGER trg_inventory_available_calc
|
||
|
|
AFTER UPDATE ON inventory
|
||
|
|
FOR EACH ROW
|
||
|
|
BEGIN
|
||
|
|
UPDATE inventory
|
||
|
|
SET quantity_available = quantity_on_hand - quantity_reserved
|
||
|
|
WHERE inventory_id = :NEW.inventory_id;
|
||
|
|
END trg_inventory_available_calc;
|
||
|
|
/
|
||
|
|
|
||
|
|
-- Trigger 4: Log Review Status Changes
|
||
|
|
CREATE TABLE review_audit_log (
|
||
|
|
log_id NUMBER PRIMARY KEY,
|
||
|
|
review_id NUMBER,
|
||
|
|
old_status VARCHAR2(20),
|
||
|
|
new_status VARCHAR2(20),
|
||
|
|
changed_by VARCHAR2(50),
|
||
|
|
changed_date DATE
|
||
|
|
);
|
||
|
|
|
||
|
|
CREATE SEQUENCE seq_review_audit_log START WITH 1;
|
||
|
|
|
||
|
|
CREATE OR REPLACE TRIGGER trg_review_status_audit
|
||
|
|
AFTER UPDATE ON customer_reviews
|
||
|
|
FOR EACH ROW
|
||
|
|
BEGIN
|
||
|
|
IF :OLD.review_status != :NEW.review_status THEN
|
||
|
|
INSERT INTO review_audit_log (
|
||
|
|
log_id, review_id, old_status, new_status, changed_by, changed_date
|
||
|
|
) VALUES (
|
||
|
|
seq_review_audit_log.NEXTVAL, :NEW.review_id,
|
||
|
|
:OLD.review_status, :NEW.review_status, USER, SYSDATE
|
||
|
|
);
|
||
|
|
END IF;
|
||
|
|
END trg_review_status_audit;
|
||
|
|
/
|
||
|
|
|
||
|
|
-- ============================================================================
|
||
|
|
-- SUMMARY: DATABASE OBJECT DEPENDENCIES
|
||
|
|
-- ============================================================================
|
||
|
|
|
||
|
|
/*
|
||
|
|
DEPENDENCY HIERARCHY:
|
||
|
|
|
||
|
|
Level 1 - Root Tables (No dependencies on other tables):
|
||
|
|
├── departments
|
||
|
|
├── product_categories
|
||
|
|
└── customers
|
||
|
|
|
||
|
|
Level 2 - Tables dependent on Level 1:
|
||
|
|
├── employees (FK → departments)
|
||
|
|
├── products (FK → product_categories)
|
||
|
|
└── departments (self-referencing: manager_id → employees)
|
||
|
|
|
||
|
|
Level 3 - Tables dependent on Level 2:
|
||
|
|
├── orders (FK → customers, employees)
|
||
|
|
├── inventory (FK → products)
|
||
|
|
└── customer_reviews (FK → customers, products)
|
||
|
|
|
||
|
|
Level 4 - Tables dependent on Level 3:
|
||
|
|
└── order_items (FK → orders, products)
|
||
|
|
|
||
|
|
VIEWS DEPENDENCY:
|
||
|
|
├── v_active_customers (→ customers)
|
||
|
|
├── v_order_summary (→ orders, customers, employees, order_items)
|
||
|
|
├── v_product_performance (→ products, categories, order_items, reviews, inventory)
|
||
|
|
└── v_employee_sales_performance (→ employees, departments, orders)
|
||
|
|
|
||
|
|
PROCEDURES/PACKAGES:
|
||
|
|
├── proc_create_order (→ customers, employees, orders)
|
||
|
|
├── proc_add_order_item (→ orders, products, inventory, order_items)
|
||
|
|
├── proc_finalize_order (→ orders, order_items, customers)
|
||
|
|
├── proc_monthly_sales_report (→ employees, departments, orders)
|
||
|
|
├── pkg_order_management.fn_check_availability (→ inventory)
|
||
|
|
├── pkg_order_management.proc_refund_order (→ order_items, inventory, orders)
|
||
|
|
└── pkg_order_management.fn_calculate_discount (→ orders, customers)
|
||
|
|
|
||
|
|
TRIGGERS:
|
||
|
|
├── trg_products_audit (→ products)
|
||
|
|
├── trg_orders_status_validation (→ orders)
|
||
|
|
├── trg_inventory_available_calc (→ inventory)
|
||
|
|
└── trg_review_status_audit (→ customer_reviews, review_audit_log)
|
||
|
|
|
||
|
|
INDEXES:
|
||
|
|
- 14 indexes covering frequently accessed paths
|
||
|
|
- Composite, unique, and functional indexes for performance
|
||
|
|
|
||
|
|
SEQUENCES:
|
||
|
|
- 7 sequences for auto-increment of primary keys
|
||
|
|
*/
|
||
|
|
|
||
|
|
-- ============================================================================
|
||
|
|
-- CONSTRAINTS ALREADY ENABLED BY DEFAULT
|
||
|
|
-- ============================================================================
|
||
|
|
-- All constraints are created and enabled during table creation.
|
||
|
|
-- No additional enabling required.
|
||
|
|
|
||
|
|
-- ============================================================================
|
||
|
|
-- END OF SCHEMA CREATION
|
||
|
|
-- ============================================================================
|