What Moved — Monthly P&L Driver Analysis

What Moved — Monthly P&L Driver Analysis: a 4-step workflow for QuickBooks / spreadsheet data over columns like section, account, 2025-08, 2025-09. Every step is plain SQL you can read — open it, connect your data, and make it your own. No data is shared in this template, only the recipe.

4 steps · August 4, 2026
Make it your own →
No data is shared in this template. It contains only the recipe — column names and SQL logic. When you run it, your data is processed in your own browser and never leaves your machine.

What data it expects

QBO_ProfitAndLoss_Month.csv
section · VARCHARaccount · VARCHAR2025-08 · DOUBLE2025-09 · DOUBLE2025-10 · DOUBLE2025-11 · DOUBLE2025-12 · DOUBLE2026-01 · DOUBLE2026-02 · DOUBLE2026-03 · DOUBLE2026-04 · DOUBLE2026-05 · DOUBLE2026-06 · DOUBLE2026-07 · DOUBLETotal · DOUBLE

Connect QuickBooks Online or drop a CSV / Excel export with a similar layout — the AI adapts the workflow if your columns differ.

How it works — every step, readable

  1. 011) Load QuickBooks P&L Grid
    SELECT * FROM read_csv_auto('QBO_ProfitAndLoss_Month.csv', header=true)
  2. 022) Unpivot Months to Long Form
    SELECT
      section,
      account,
      period,
      ROUND(COALESCE(TRY_CAST(REPLACE(CAST(amount AS VARCHAR), ',', '') AS DOUBLE), 0), 2) AS amount
    FROM (
      UNPIVOT input_1
      ON COLUMNS(* EXCLUDE (section, account, Total))
      INTO NAME period VALUE amount
    )
    ORDER BY period, section, account
  3. 033) Monthly Summary by Section
    WITH section_months AS (
      SELECT
        CASE
          WHEN lower(section) IN ('income', 'revenue') THEN 'Income'
          WHEN lower(section) IN ('cost of goods sold', 'cogs') THEN 'Cost of Goods Sold'
          WHEN lower(section) IN ('expenses', 'expense', 'operating expenses') THEN 'Expenses'
          ELSE section
        END AS section,
        period,
        SUM(amount) AS amount
      FROM input_1
      WHERE account NOT ILIKE '%total%'
      GROUP BY 1, 2
    ), pnl AS (
      SELECT 10 AS sort_order, 'Income' AS section, period, SUM(CASE WHEN section = 'Income' THEN amount ELSE 0 END) AS amount
      FROM section_months
      GROUP BY period
      UNION ALL
      SELECT 20 AS sort_order, 'Cost of Goods Sold' AS section, period, SUM(CASE WHEN section = 'Cost of Goods Sold' THEN amount ELSE 0 END) AS amount
      FROM section_months
      GROUP BY period
      UNION ALL
      SELECT 30 AS sort_order, 'Expenses' AS section, period, SUM(CASE WHEN section = 'Expenses' THEN amount ELSE 0 END) AS amount
      FROM section_months
      GROUP BY period
      UNION ALL
      SELECT 40 AS sort_order, 'Net' AS section, period,
        SUM(CASE WHEN section = 'Income' THEN amount ELSE 0 END)
        - SUM(CASE WHEN section = 'Cost of Goods Sold' THEN amount ELSE 0 END)
        - SUM(CASE WHEN section = 'Expenses' THEN amount ELSE 0 END) AS amount
      FROM section_months
      GROUP BY period
    )
    SELECT
      section,
      ROUND(SUM(CASE WHEN period = '2025-08' THEN amount ELSE 0 END), 0) AS "2025-08",
      ROUND(SUM(CASE WHEN period = '2025-09' THEN amount ELSE 0 END), 0) AS "2025-09",
      ROUND(SUM(CASE WHEN period = '2025-10' THEN amount ELSE 0 END), 0) AS "2025-10",
      ROUND(SUM(CASE WHEN period = '2025-11' THEN amount ELSE 0 END), 0) AS "2025-11",
      ROUND(SUM(CASE WHEN period = '2025-12' THEN amount ELSE 0 END), 0) AS "2025-12",
      ROUND(SUM(CASE WHEN period = '2026-01' THEN amount ELSE 0 END), 0) AS "2026-01",
      ROUND(SUM(CASE WHEN period = '2026-02' THEN amount ELSE 0 END), 0) AS "2026-02",
      ROUND(SUM(CASE WHEN period = '2026-03' THEN amount ELSE 0 END), 0) AS "2026-03",
      ROUND(SUM(CASE WHEN period = '2026-04' THEN amount ELSE 0 END), 0) AS "2026-04",
      ROUND(SUM(CASE WHEN period = '2026-05' THEN amount ELSE 0 END), 0) AS "2026-05",
      ROUND(SUM(CASE WHEN period = '2026-06' THEN amount ELSE 0 END), 0) AS "2026-06",
      ROUND(SUM(CASE WHEN period = '2026-07' THEN amount ELSE 0 END), 0) AS "2026-07"
    FROM pnl
    GROUP BY sort_order, section
    ORDER BY sort_order
  4. 044) Driver Analysis — Latest Month vs Prior Month
    WITH periods AS (
      SELECT
        MAX(period) AS latest_period,
        (SELECT MAX(period) FROM input_1 WHERE period < (SELECT MAX(period) FROM input_1)) AS prior_period
      FROM input_1
    ), account_months AS (
      SELECT
        section,
        account,
        SUM(CASE WHEN period = (SELECT prior_period FROM periods) THEN amount ELSE 0 END) AS prior_amount,
        SUM(CASE WHEN period = (SELECT latest_period FROM periods) THEN amount ELSE 0 END) AS latest_amount
      FROM input_1
      WHERE account NOT ILIKE '%total%'
        AND account NOT ILIKE 'net%'
        AND account NOT ILIKE 'gross profit%'
      GROUP BY section, account
    ), ranked AS (
      SELECT
        section,
        account,
        ROUND(prior_amount, 0) AS "prior month",
        ROUND(latest_amount, 0) AS "latest month",
        ROUND(latest_amount - prior_amount, 0) AS delta,
        CASE
          WHEN prior_amount = 0 AND latest_amount <> 0 THEN NULL
          WHEN prior_amount = 0 THEN 0
          ELSE ROUND(((latest_amount - prior_amount) / ABS(prior_amount)) * 100, 1)
        END AS "% change",
        ROW_NUMBER() OVER (ORDER BY ABS(latest_amount - prior_amount) DESC) AS rank
      FROM account_months
    )
    SELECT
      section,
      account,
      "prior month",
      "latest month",
      delta,
      "% change"
    FROM ranked
    WHERE rank <= 15
    ORDER BY ABS(delta) DESC

Run this on your books

Free to try — no sign-up, no card. The workflow runs in your browser; your data never leaves your machine.

Make it your own →