Update schemas and reset scripts

This commit is contained in:
2025-02-12 16:14:25 -05:00
parent 169407a729
commit fb9f959fe5
9 changed files with 1262 additions and 618 deletions

View File

@@ -1,8 +1,8 @@
-- Disable foreign key checks
SET FOREIGN_KEY_CHECKS = 0;
SET session_replication_role = 'replica';
-- Temporary tables for batch metrics processing
CREATE TABLE IF NOT EXISTS temp_sales_metrics (
CREATE TABLE temp_sales_metrics (
pid BIGINT NOT NULL,
daily_sales_avg DECIMAL(10,3),
weekly_sales_avg DECIMAL(10,3),
@@ -14,9 +14,9 @@ CREATE TABLE IF NOT EXISTS temp_sales_metrics (
PRIMARY KEY (pid)
);
CREATE TABLE IF NOT EXISTS temp_purchase_metrics (
CREATE TABLE temp_purchase_metrics (
pid BIGINT NOT NULL,
avg_lead_time_days INT,
avg_lead_time_days INTEGER,
last_purchase_date DATE,
first_received_date DATE,
last_received_date DATE,
@@ -24,7 +24,7 @@ CREATE TABLE IF NOT EXISTS temp_purchase_metrics (
);
-- New table for product metrics
CREATE TABLE IF NOT EXISTS product_metrics (
CREATE TABLE product_metrics (
pid BIGINT NOT NULL,
last_calculated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- Sales velocity metrics
@@ -32,16 +32,16 @@ CREATE TABLE IF NOT EXISTS product_metrics (
weekly_sales_avg DECIMAL(10,3),
monthly_sales_avg DECIMAL(10,3),
avg_quantity_per_order DECIMAL(10,3),
number_of_orders INT,
number_of_orders INTEGER,
first_sale_date DATE,
last_sale_date DATE,
-- Stock metrics
days_of_inventory INT,
weeks_of_inventory INT,
reorder_point INT,
safety_stock INT,
reorder_qty INT DEFAULT 0,
overstocked_amt INT DEFAULT 0,
days_of_inventory INTEGER,
weeks_of_inventory INTEGER,
reorder_point INTEGER,
safety_stock INTEGER,
reorder_qty INTEGER DEFAULT 0,
overstocked_amt INTEGER DEFAULT 0,
-- Financial metrics
avg_margin_percent DECIMAL(10,3),
total_revenue DECIMAL(10,3),
@@ -50,7 +50,7 @@ CREATE TABLE IF NOT EXISTS product_metrics (
gross_profit DECIMAL(10,3),
gmroi DECIMAL(10,3),
-- Purchase metrics
avg_lead_time_days INT,
avg_lead_time_days INTEGER,
last_purchase_date DATE,
first_received_date DATE,
last_received_date DATE,
@@ -60,48 +60,50 @@ CREATE TABLE IF NOT EXISTS product_metrics (
-- Turnover metrics
turnover_rate DECIMAL(12,3),
-- Lead time metrics
current_lead_time INT,
target_lead_time INT,
current_lead_time INTEGER,
target_lead_time INTEGER,
lead_time_status VARCHAR(20),
-- Forecast metrics
forecast_accuracy DECIMAL(5,2) DEFAULT NULL,
forecast_bias DECIMAL(5,2) DEFAULT NULL,
last_forecast_date DATE DEFAULT NULL,
PRIMARY KEY (pid),
FOREIGN KEY (pid) REFERENCES products(pid) ON DELETE CASCADE,
INDEX idx_metrics_revenue (total_revenue),
INDEX idx_metrics_stock_status (stock_status),
INDEX idx_metrics_lead_time (lead_time_status),
INDEX idx_metrics_turnover (turnover_rate),
INDEX idx_metrics_last_calculated (last_calculated_at),
INDEX idx_metrics_abc (abc_class),
INDEX idx_metrics_sales (daily_sales_avg, weekly_sales_avg, monthly_sales_avg),
INDEX idx_metrics_forecast (forecast_accuracy, forecast_bias)
FOREIGN KEY (pid) REFERENCES products(pid) ON DELETE CASCADE
);
CREATE INDEX idx_metrics_revenue ON product_metrics(total_revenue);
CREATE INDEX idx_metrics_stock_status ON product_metrics(stock_status);
CREATE INDEX idx_metrics_lead_time ON product_metrics(lead_time_status);
CREATE INDEX idx_metrics_turnover ON product_metrics(turnover_rate);
CREATE INDEX idx_metrics_last_calculated ON product_metrics(last_calculated_at);
CREATE INDEX idx_metrics_abc ON product_metrics(abc_class);
CREATE INDEX idx_metrics_sales ON product_metrics(daily_sales_avg, weekly_sales_avg, monthly_sales_avg);
CREATE INDEX idx_metrics_forecast ON product_metrics(forecast_accuracy, forecast_bias);
-- New table for time-based aggregates
CREATE TABLE IF NOT EXISTS product_time_aggregates (
CREATE TABLE product_time_aggregates (
pid BIGINT NOT NULL,
year INT NOT NULL,
month INT NOT NULL,
year INTEGER NOT NULL,
month INTEGER NOT NULL,
-- Sales metrics
total_quantity_sold INT DEFAULT 0,
total_quantity_sold INTEGER DEFAULT 0,
total_revenue DECIMAL(10,3) DEFAULT 0,
total_cost DECIMAL(10,3) DEFAULT 0,
order_count INT DEFAULT 0,
order_count INTEGER DEFAULT 0,
-- Stock changes
stock_received INT DEFAULT 0,
stock_ordered INT DEFAULT 0,
stock_received INTEGER DEFAULT 0,
stock_ordered INTEGER DEFAULT 0,
-- Calculated fields
avg_price DECIMAL(10,3),
profit_margin DECIMAL(10,3),
inventory_value DECIMAL(10,3),
gmroi DECIMAL(10,3),
PRIMARY KEY (pid, year, month),
FOREIGN KEY (pid) REFERENCES products(pid) ON DELETE CASCADE,
INDEX idx_date (year, month)
FOREIGN KEY (pid) REFERENCES products(pid) ON DELETE CASCADE
);
CREATE INDEX idx_date ON product_time_aggregates(year, month);
-- Create vendor_details table
CREATE TABLE vendor_details (
vendor VARCHAR(100) PRIMARY KEY,
@@ -110,45 +112,47 @@ CREATE TABLE vendor_details (
phone VARCHAR(50),
status VARCHAR(20) DEFAULT 'active',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_status (status)
) ENGINE=InnoDB;
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_vendor_details_status ON vendor_details(status);
-- New table for vendor metrics
CREATE TABLE IF NOT EXISTS vendor_metrics (
CREATE TABLE vendor_metrics (
vendor VARCHAR(100) NOT NULL,
last_calculated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- Performance metrics
avg_lead_time_days DECIMAL(10,3),
on_time_delivery_rate DECIMAL(5,2),
order_fill_rate DECIMAL(5,2),
total_orders INT DEFAULT 0,
total_late_orders INT DEFAULT 0,
total_orders INTEGER DEFAULT 0,
total_late_orders INTEGER DEFAULT 0,
total_purchase_value DECIMAL(10,3) DEFAULT 0,
avg_order_value DECIMAL(10,3),
-- Product metrics
active_products INT DEFAULT 0,
total_products INT DEFAULT 0,
active_products INTEGER DEFAULT 0,
total_products INTEGER DEFAULT 0,
-- Financial metrics
total_revenue DECIMAL(10,3) DEFAULT 0,
avg_margin_percent DECIMAL(5,2),
-- Status
status VARCHAR(20) DEFAULT 'active',
PRIMARY KEY (vendor),
FOREIGN KEY (vendor) REFERENCES vendor_details(vendor) ON DELETE CASCADE,
INDEX idx_vendor_performance (on_time_delivery_rate),
INDEX idx_vendor_status (status),
INDEX idx_metrics_last_calculated (last_calculated_at),
INDEX idx_vendor_metrics_orders (total_orders, total_late_orders)
FOREIGN KEY (vendor) REFERENCES vendor_details(vendor) ON DELETE CASCADE
);
CREATE INDEX idx_vendor_performance ON vendor_metrics(on_time_delivery_rate);
CREATE INDEX idx_vendor_status ON vendor_metrics(status);
CREATE INDEX idx_vendor_metrics_last_calculated ON vendor_metrics(last_calculated_at);
CREATE INDEX idx_vendor_metrics_orders ON vendor_metrics(total_orders, total_late_orders);
-- New table for category metrics
CREATE TABLE IF NOT EXISTS category_metrics (
CREATE TABLE category_metrics (
category_id BIGINT NOT NULL,
last_calculated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- Product metrics
product_count INT DEFAULT 0,
active_products INT DEFAULT 0,
product_count INTEGER DEFAULT 0,
active_products INTEGER DEFAULT 0,
-- Financial metrics
total_value DECIMAL(15,3) DEFAULT 0,
avg_margin DECIMAL(5,2),
@@ -157,255 +161,215 @@ CREATE TABLE IF NOT EXISTS category_metrics (
-- Status
status VARCHAR(20) DEFAULT 'active',
PRIMARY KEY (category_id),
FOREIGN KEY (category_id) REFERENCES categories(cat_id) ON DELETE CASCADE,
INDEX idx_category_status (status),
INDEX idx_category_growth (growth_rate),
INDEX idx_metrics_last_calculated (last_calculated_at),
INDEX idx_category_metrics_products (product_count, active_products)
FOREIGN KEY (category_id) REFERENCES categories(cat_id) ON DELETE CASCADE
);
CREATE INDEX idx_category_status ON category_metrics(status);
CREATE INDEX idx_category_growth ON category_metrics(growth_rate);
CREATE INDEX idx_metrics_last_calculated_cat ON category_metrics(last_calculated_at);
CREATE INDEX idx_category_metrics_products ON category_metrics(product_count, active_products);
-- New table for vendor time-based metrics
CREATE TABLE IF NOT EXISTS vendor_time_metrics (
CREATE TABLE vendor_time_metrics (
vendor VARCHAR(100) NOT NULL,
year INT NOT NULL,
month INT NOT NULL,
year INTEGER NOT NULL,
month INTEGER NOT NULL,
-- Order metrics
total_orders INT DEFAULT 0,
late_orders INT DEFAULT 0,
total_orders INTEGER DEFAULT 0,
late_orders INTEGER DEFAULT 0,
avg_lead_time_days DECIMAL(10,3),
-- Financial metrics
total_purchase_value DECIMAL(10,3) DEFAULT 0,
total_revenue DECIMAL(10,3) DEFAULT 0,
avg_margin_percent DECIMAL(5,2),
PRIMARY KEY (vendor, year, month),
FOREIGN KEY (vendor) REFERENCES vendor_details(vendor) ON DELETE CASCADE,
INDEX idx_vendor_date (year, month)
FOREIGN KEY (vendor) REFERENCES vendor_details(vendor) ON DELETE CASCADE
);
CREATE INDEX idx_vendor_date ON vendor_time_metrics(year, month);
-- New table for category time-based metrics
CREATE TABLE IF NOT EXISTS category_time_metrics (
CREATE TABLE category_time_metrics (
category_id BIGINT NOT NULL,
year INT NOT NULL,
month INT NOT NULL,
year INTEGER NOT NULL,
month INTEGER NOT NULL,
-- Product metrics
product_count INT DEFAULT 0,
active_products INT DEFAULT 0,
product_count INTEGER DEFAULT 0,
active_products INTEGER DEFAULT 0,
-- Financial metrics
total_value DECIMAL(15,3) DEFAULT 0,
total_revenue DECIMAL(15,3) DEFAULT 0,
avg_margin DECIMAL(5,2),
turnover_rate DECIMAL(12,3),
PRIMARY KEY (category_id, year, month),
FOREIGN KEY (category_id) REFERENCES categories(cat_id) ON DELETE CASCADE,
INDEX idx_category_date (year, month)
FOREIGN KEY (category_id) REFERENCES categories(cat_id) ON DELETE CASCADE
);
CREATE INDEX idx_category_date ON category_time_metrics(year, month);
-- New table for category-based sales metrics
CREATE TABLE IF NOT EXISTS category_sales_metrics (
CREATE TABLE category_sales_metrics (
category_id BIGINT NOT NULL,
brand VARCHAR(100) NOT NULL,
period_start DATE NOT NULL,
period_end DATE NOT NULL,
avg_daily_sales DECIMAL(10,3) DEFAULT 0,
total_sold INT DEFAULT 0,
num_products INT DEFAULT 0,
total_sold INTEGER DEFAULT 0,
num_products INTEGER DEFAULT 0,
avg_price DECIMAL(10,3) DEFAULT 0,
last_calculated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (category_id, brand, period_start, period_end),
FOREIGN KEY (category_id) REFERENCES categories(cat_id) ON DELETE CASCADE,
INDEX idx_category_brand (category_id, brand),
INDEX idx_period (period_start, period_end)
FOREIGN KEY (category_id) REFERENCES categories(cat_id) ON DELETE CASCADE
);
CREATE INDEX idx_category_brand ON category_sales_metrics(category_id, brand);
CREATE INDEX idx_period ON category_sales_metrics(period_start, period_end);
-- New table for brand metrics
CREATE TABLE IF NOT EXISTS brand_metrics (
CREATE TABLE brand_metrics (
brand VARCHAR(100) NOT NULL,
last_calculated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- Product metrics
product_count INT DEFAULT 0,
active_products INT DEFAULT 0,
product_count INTEGER DEFAULT 0,
active_products INTEGER DEFAULT 0,
-- Stock metrics
total_stock_units INT DEFAULT 0,
total_stock_units INTEGER DEFAULT 0,
total_stock_cost DECIMAL(15,2) DEFAULT 0,
total_stock_retail DECIMAL(15,2) DEFAULT 0,
-- Sales metrics
total_revenue DECIMAL(15,2) DEFAULT 0,
avg_margin DECIMAL(5,2) DEFAULT 0,
growth_rate DECIMAL(5,2) DEFAULT 0,
PRIMARY KEY (brand),
INDEX idx_brand_metrics_last_calculated (last_calculated_at),
INDEX idx_brand_metrics_revenue (total_revenue),
INDEX idx_brand_metrics_growth (growth_rate)
PRIMARY KEY (brand)
);
CREATE INDEX idx_brand_metrics_last_calculated ON brand_metrics(last_calculated_at);
CREATE INDEX idx_brand_metrics_revenue ON brand_metrics(total_revenue);
CREATE INDEX idx_brand_metrics_growth ON brand_metrics(growth_rate);
-- New table for brand time-based metrics
CREATE TABLE IF NOT EXISTS brand_time_metrics (
CREATE TABLE brand_time_metrics (
brand VARCHAR(100) NOT NULL,
year INT NOT NULL,
month INT NOT NULL,
year INTEGER NOT NULL,
month INTEGER NOT NULL,
-- Product metrics
product_count INT DEFAULT 0,
active_products INT DEFAULT 0,
product_count INTEGER DEFAULT 0,
active_products INTEGER DEFAULT 0,
-- Stock metrics
total_stock_units INT DEFAULT 0,
total_stock_units INTEGER DEFAULT 0,
total_stock_cost DECIMAL(15,2) DEFAULT 0,
total_stock_retail DECIMAL(15,2) DEFAULT 0,
-- Sales metrics
total_revenue DECIMAL(15,2) DEFAULT 0,
avg_margin DECIMAL(5,2) DEFAULT 0,
PRIMARY KEY (brand, year, month),
INDEX idx_brand_date (year, month)
growth_rate DECIMAL(5,2) DEFAULT 0,
PRIMARY KEY (brand, year, month)
);
CREATE INDEX idx_brand_time_date ON brand_time_metrics(year, month);
-- New table for sales forecasts
CREATE TABLE IF NOT EXISTS sales_forecasts (
CREATE TABLE sales_forecasts (
pid BIGINT NOT NULL,
forecast_date DATE NOT NULL,
forecast_units DECIMAL(10,2) DEFAULT 0,
forecast_revenue DECIMAL(10,2) DEFAULT 0,
confidence_level DECIMAL(5,2) DEFAULT 0,
last_calculated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
forecast_quantity INTEGER,
confidence_level DECIMAL(5,2),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (pid, forecast_date),
FOREIGN KEY (pid) REFERENCES products(pid) ON DELETE CASCADE,
INDEX idx_forecast_date (forecast_date),
INDEX idx_forecast_last_calculated (last_calculated_at)
FOREIGN KEY (pid) REFERENCES products(pid) ON DELETE CASCADE
);
CREATE INDEX idx_forecast_date ON sales_forecasts(forecast_date);
-- New table for category forecasts
CREATE TABLE IF NOT EXISTS category_forecasts (
CREATE TABLE category_forecasts (
category_id BIGINT NOT NULL,
forecast_date DATE NOT NULL,
forecast_units DECIMAL(10,2) DEFAULT 0,
forecast_revenue DECIMAL(10,2) DEFAULT 0,
confidence_level DECIMAL(5,2) DEFAULT 0,
last_calculated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
forecast_revenue DECIMAL(15,2),
forecast_units INTEGER,
confidence_level DECIMAL(5,2),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (category_id, forecast_date),
FOREIGN KEY (category_id) REFERENCES categories(cat_id) ON DELETE CASCADE,
INDEX idx_category_forecast_date (forecast_date),
INDEX idx_category_forecast_last_calculated (last_calculated_at)
FOREIGN KEY (category_id) REFERENCES categories(cat_id) ON DELETE CASCADE
);
-- Create view for inventory health
CREATE INDEX idx_cat_forecast_date ON category_forecasts(forecast_date);
-- Create views for common calculations
CREATE OR REPLACE VIEW inventory_health AS
WITH product_thresholds AS (
WITH stock_levels AS (
SELECT
p.pid,
COALESCE(
-- Try category+vendor specific
(SELECT critical_days FROM stock_thresholds st
JOIN product_categories pc ON st.category_id = pc.cat_id
WHERE pc.pid = p.pid
AND st.vendor = p.vendor LIMIT 1),
-- Try category specific
(SELECT critical_days FROM stock_thresholds st
JOIN product_categories pc ON st.category_id = pc.cat_id
WHERE pc.pid = p.pid
AND st.vendor IS NULL LIMIT 1),
-- Try vendor specific
(SELECT critical_days FROM stock_thresholds st
WHERE st.category_id IS NULL
AND st.vendor = p.vendor LIMIT 1),
-- Fall back to default
(SELECT critical_days FROM stock_thresholds st
WHERE st.category_id IS NULL
AND st.vendor IS NULL LIMIT 1),
7
) as critical_days,
COALESCE(
-- Try category+vendor specific
(SELECT reorder_days FROM stock_thresholds st
JOIN product_categories pc ON st.category_id = pc.cat_id
WHERE pc.pid = p.pid
AND st.vendor = p.vendor LIMIT 1),
-- Try category specific
(SELECT reorder_days FROM stock_thresholds st
JOIN product_categories pc ON st.category_id = pc.cat_id
WHERE pc.pid = p.pid
AND st.vendor IS NULL LIMIT 1),
-- Try vendor specific
(SELECT reorder_days FROM stock_thresholds st
WHERE st.category_id IS NULL
AND st.vendor = p.vendor LIMIT 1),
-- Fall back to default
(SELECT reorder_days FROM stock_thresholds st
WHERE st.category_id IS NULL
AND st.vendor IS NULL LIMIT 1),
14
) as reorder_days,
COALESCE(
-- Try category+vendor specific
(SELECT overstock_days FROM stock_thresholds st
JOIN product_categories pc ON st.category_id = pc.cat_id
WHERE pc.pid = p.pid
AND st.vendor = p.vendor LIMIT 1),
-- Try category specific
(SELECT overstock_days FROM stock_thresholds st
JOIN product_categories pc ON st.category_id = pc.cat_id
WHERE pc.pid = p.pid
AND st.vendor IS NULL LIMIT 1),
-- Try vendor specific
(SELECT overstock_days FROM stock_thresholds st
WHERE st.category_id IS NULL
AND st.vendor = p.vendor LIMIT 1),
-- Fall back to default
(SELECT overstock_days FROM stock_thresholds st
WHERE st.category_id IS NULL
AND st.vendor IS NULL LIMIT 1),
90
) as overstock_days
p.title,
p.SKU,
p.stock_quantity,
p.preorder_count,
pm.daily_sales_avg,
pm.weekly_sales_avg,
pm.monthly_sales_avg,
pm.reorder_point,
pm.safety_stock,
pm.days_of_inventory,
pm.weeks_of_inventory,
pm.stock_status,
pm.abc_class,
pm.turnover_rate,
pm.avg_lead_time_days,
pm.current_lead_time,
pm.target_lead_time,
pm.lead_time_status,
p.cost_price,
p.price,
pm.inventory_value,
pm.gmroi
FROM products p
LEFT JOIN product_metrics pm ON p.pid = pm.pid
WHERE p.managing_stock = true AND p.visible = true
)
SELECT
p.pid,
p.SKU,
p.title,
p.stock_quantity,
COALESCE(pm.daily_sales_avg, 0) as daily_sales_avg,
COALESCE(pm.days_of_inventory, 0) as days_of_inventory,
COALESCE(pm.reorder_point, 0) as reorder_point,
COALESCE(pm.safety_stock, 0) as safety_stock,
*,
CASE
WHEN pm.daily_sales_avg = 0 THEN 'New'
WHEN p.stock_quantity <= CEIL(pm.daily_sales_avg * pt.critical_days) THEN 'Critical'
WHEN p.stock_quantity <= CEIL(pm.daily_sales_avg * pt.reorder_days) THEN 'Reorder'
WHEN p.stock_quantity > (pm.daily_sales_avg * pt.overstock_days) THEN 'Overstocked'
WHEN stock_quantity <= safety_stock THEN 'Critical'
WHEN stock_quantity <= reorder_point THEN 'Low'
WHEN stock_quantity > (reorder_point * 3) THEN 'Excess'
ELSE 'Healthy'
END as stock_status
FROM
products p
LEFT JOIN
product_metrics pm ON p.pid = pm.pid
LEFT JOIN
product_thresholds pt ON p.pid = pt.pid
WHERE
p.managing_stock = true;
END as inventory_status,
CASE
WHEN lead_time_status = 'delayed' AND stock_status = 'low' THEN 'High'
WHEN lead_time_status = 'delayed' OR stock_status = 'low' THEN 'Medium'
ELSE 'Low'
END as risk_level
FROM stock_levels;
-- Create view for category performance trends
CREATE OR REPLACE VIEW category_performance_trends AS
WITH monthly_trends AS (
SELECT
c.cat_id,
c.name as category_name,
ctm.year,
ctm.month,
ctm.product_count,
ctm.active_products,
ctm.total_value,
ctm.total_revenue,
ctm.avg_margin,
ctm.turnover_rate,
LAG(ctm.total_revenue) OVER (PARTITION BY c.cat_id ORDER BY ctm.year, ctm.month) as prev_month_revenue,
LAG(ctm.turnover_rate) OVER (PARTITION BY c.cat_id ORDER BY ctm.year, ctm.month) as prev_month_turnover
FROM categories c
JOIN category_time_metrics ctm ON c.cat_id = ctm.category_id
)
SELECT
c.cat_id as category_id,
c.name,
c.description,
p.name as parent_name,
c.status,
cm.product_count,
cm.active_products,
cm.total_value,
cm.avg_margin,
cm.turnover_rate,
cm.growth_rate,
*,
CASE
WHEN cm.growth_rate >= 20 THEN 'High Growth'
WHEN cm.growth_rate >= 5 THEN 'Growing'
WHEN cm.growth_rate >= -5 THEN 'Stable'
ELSE 'Declining'
END as performance_rating
FROM
categories c
LEFT JOIN
categories p ON c.parent_id = p.cat_id
LEFT JOIN
category_metrics cm ON c.cat_id = cm.category_id;
WHEN prev_month_revenue IS NULL THEN 0
ELSE ((total_revenue - prev_month_revenue) / prev_month_revenue) * 100
END as revenue_growth_percent,
CASE
WHEN prev_month_turnover IS NULL THEN 0
ELSE ((turnover_rate - prev_month_turnover) / prev_month_turnover) * 100
END as turnover_growth_percent
FROM monthly_trends;
-- Re-enable foreign key checks
SET FOREIGN_KEY_CHECKS = 1;
SET session_replication_role = 'origin';