Как посчитать Inventory Turnover в SQL

Закрепи формулу inventory turnover в Карьернике
Запомнить надолго — 5 коротких сессий с задачами на эту тему. Бесплатно
Тренировать inventory turnover в Telegram

Зачем Inventory Turnover

Ритейлер: 10 млн в запасах. Продаёт 12 млн в год. Turnover = 1.2 — медленно. Конкурент = 8 — оборот в 7 раз быстрее, меньше capital tied up. Inventory turnover — критичная финансовая метрика.

Формула

Inventory Turnover = COGS / Avg Inventory
Days Inventory Outstanding (DIO) = 365 / Inventory Turnover

Базовый расчёт

Данные: inventory_snapshots(snapshot_date, sku, quantity, cost_per_unit), orders для COGS.

WITH avg_inventory AS (
    SELECT AVG(quantity * cost_per_unit) AS avg_inventory_value
    FROM inventory_snapshots
    WHERE snapshot_date >= CURRENT_DATE - INTERVAL '12 months'
),
cogs_year AS (
    SELECT SUM(quantity * cogs_per_unit) AS total_cogs
    FROM orders
    WHERE status = 'paid'
      AND created_at >= CURRENT_DATE - INTERVAL '12 months'
)
SELECT
    a.avg_inventory_value,
    c.total_cogs,
    c.total_cogs::NUMERIC / NULLIF(a.avg_inventory_value, 0) AS inventory_turnover
FROM avg_inventory a
CROSS JOIN cogs_year c;

Days Inventory Outstanding

SELECT
    inventory_turnover,
    365.0 / NULLIF(inventory_turnover, 0) AS days_inventory_outstanding
FROM (
    SELECT 5.0 AS inventory_turnover  -- example
) x;

DIO 30 = 30 days of inventory on hand. Низкое = эффективно. Высокое = lots stuck.

Закрепи формулу inventory turnover в Карьернике
Запомнить надолго — 5 коротких сессий с задачами на эту тему. Бесплатно
Тренировать inventory turnover в Telegram

По категориям

WITH cat_avg_inv AS (
    SELECT
        p.category,
        AVG(s.quantity * s.cost_per_unit) AS avg_inv
    FROM inventory_snapshots s
    JOIN products p ON p.sku = s.sku
    WHERE s.snapshot_date >= CURRENT_DATE - INTERVAL '12 months'
    GROUP BY p.category
),
cat_cogs AS (
    SELECT
        p.category,
        SUM(o.quantity * o.cogs_per_unit) AS cogs
    FROM orders o
    JOIN products p ON p.sku = o.sku
    WHERE o.status = 'paid'
      AND o.created_at >= CURRENT_DATE - INTERVAL '12 months'
    GROUP BY p.category
)
SELECT
    c.category,
    c.cogs,
    a.avg_inv,
    c.cogs::NUMERIC / NULLIF(a.avg_inv, 0) AS turnover,
    365.0 * a.avg_inv / NULLIF(c.cogs, 0) AS dio
FROM cat_cogs c
JOIN cat_avg_inv a USING (category)
ORDER BY turnover DESC;

FMCG: turnover 10-20. Fashion: 3-6. Electronics: 5-10.

Частые ошибки

Ошибка 1. End-of-period vs average inventory. Average — more accurate (учитывает sezonalitet). EOP — snapshot.

Ошибка 2. COGS vs Revenue. Turnover формула на COGS, не revenue. Иначе включаете margin — не fair.

Ошибка 3. Slow-mover hidden. Top SKUs могут вращаться быстро, dead stock skewing average. Watch distribution.

Ошибка 4. Seasonal averaging. Average inventory за 12 месяцев vs quarter — разные числа.

Ошибка 5. Shrinkage / dead stock. Inventory которая всё equal-likely shrinkage / dead stock — может быть в snapshot но not в sellable.

Связанные темы

FAQ

Какой Inventory Turnover ok?

FMCG: 10+. Fashion: 3-6. Big-ticket retail: 5-8.

Высокий Inventory Turnover — хорошо?

Обычно да. Но: too high — stockouts, потери продаж. Sweet spot.

Days Inventory Outstanding — сколько ok?

30-60 days — норма для big-ticket. 7-15 days — FMCG.

Slow-movers — что делать?

  1. Markdown / promo. 2) Bundle. 3) Discontinue. 4) Liquidate.

Inventory Turnover vs Sell-Through?

Turnover — annualized. Sell-through — % продано за конкретный период (часто season).