Purchase Order Analysis — Order Cycle and Supplier Spend
Purchase Order Analysis — Order Cycle and Supplier Spend
Purchase order analysis tracks procurement spending, order cycle times, cancellation rates, and identifies which supplier-SKU combinations drive the most cost.
1. PO Status Summary
SELECT
status,
COUNT(*) AS orders,
SUM(quantity * unit_cost) AS total_value_usd,
ROUND((100.0 * COUNT(*) / SUM(COUNT(*)) OVER ())::NUMERIC, 1) AS pct_of_orders
FROM lg_purchase_orders
GROUP BY status
ORDER BY total_value_usd DESC;
What This Returns
status | orders | total_value_usd | pct_of_orders
-----------+--------+-----------------+--------------
received | 100 | 658000 | 25.0
in_transit | 100 | 645000 | 25.0
pending | 100 | 632000 | 25.0
cancelled | 100 | 624000 | 25.0
2. Order Cycle Time (Order to Receipt)
SELECT
sup.name AS supplier,
COUNT(po.id) FILTER (WHERE po.received_at IS NOT NULL) AS fulfilled_orders,
ROUND((AVG(
DATE_PART('day', po.received_at - po.ordered_at)
))::NUMERIC, 1) AS avg_cycle_days,
ROUND(PERCENTILE_CONT(0.5) WITHIN GROUP (
ORDER BY DATE_PART('day', po.received_at - po.ordered_at)
)::NUMERIC, 1) AS median_cycle_days,
ROUND(PERCENTILE_CONT(0.9) WITHIN GROUP (
ORDER BY DATE_PART('day', po.received_at - po.ordered_at)
)::NUMERIC, 1) AS p90_cycle_days
FROM lg_purchase_orders po
JOIN lg_suppliers sup ON sup.id = po.supplier_id
WHERE po.received_at IS NOT NULL
GROUP BY sup.name
ORDER BY avg_cycle_days;
3. Top 10 SKUs by Total Purchase Spend
SELECT
s.sku,
s.name,
s.category,
COUNT(po.id) AS orders,
SUM(po.quantity) AS units_ordered,
SUM(po.quantity * po.unit_cost) AS total_spend_usd,
ROUND((AVG(po.unit_cost))::NUMERIC, 2) AS avg_unit_cost_usd
FROM lg_purchase_orders po
JOIN lg_skus s ON s.id = po.sku_id
GROUP BY s.sku, s.name, s.category
ORDER BY total_spend_usd DESC
LIMIT 10;
4. Monthly Purchasing Spend Trend
SELECT
DATE_TRUNC('month', ordered_at)::DATE AS month,
COUNT(*) AS orders,
SUM(quantity * unit_cost) AS total_spend_usd,
COUNT(*) FILTER (WHERE status = 'cancelled') AS cancellations
FROM lg_purchase_orders
GROUP BY 1
ORDER BY 1;
5. Supplier Spend Concentration (Pareto)
Identify whether 20% of suppliers account for 80% of spend:
WITH supplier_spend AS (
SELECT
sup.name,
SUM(po.quantity * po.unit_cost) AS spend
FROM lg_purchase_orders po
JOIN lg_suppliers sup ON sup.id = po.supplier_id
GROUP BY sup.name
),
ranked AS (
SELECT
name,
spend,
SUM(spend) OVER (ORDER BY spend DESC) AS cumulative_spend,
SUM(spend) OVER () AS total_spend,
ROW_NUMBER() OVER (ORDER BY spend DESC) AS rank
FROM supplier_spend
)
SELECT
name,
ROUND((spend)::NUMERIC, 2) AS spend_usd,
ROUND((100.0 * spend / total_spend)::NUMERIC, 1) AS pct_of_total,
ROUND((100.0 * cumulative_spend / total_spend)::NUMERIC, 1) AS cumulative_pct
FROM ranked
ORDER BY rank;
Key Takeaway
SUM(spend) OVER (ORDER BY spend DESC) is the standard running-total technique for Pareto analysis. No GROUP BY is needed — the window function computes the cumulative sum within the result set. When cumulative_pct first exceeds 80, you have found the top suppliers that account for most of your spend — the suppliers that warrant the most negotiation attention and relationship management.