Calculate Adjusted EBITDA - Latest LTM and latest 2 full years

This template calculates adjusted EBITDA for latest LTM and last two full years

9 steps · August 13, 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 · VARCHAR2024-01 · DOUBLE2024-02 · DOUBLE2024-03 · DOUBLE2024-04 · DOUBLE2024-05 · DOUBLE2024-06 · DOUBLE2024-07 · DOUBLE2024-08 · DOUBLE2024-09 · DOUBLE2024-10 · DOUBLE2024-11 · DOUBLE2024-12 · DOUBLE2025-01 · DOUBLE2025-02 · DOUBLE2025-03 · DOUBLE2025-04 · DOUBLE2025-05 · DOUBLE2025-06 · DOUBLE2025-07 · DOUBLE2025-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
QBO_GeneralLedger.csv
account · VARCHARdate · VARCHARname · VARCHARmemo_description · VARCHARamount · 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. 01Node 1 — Load QBO Monthly P&L
    SELECT * FROM read_csv_auto('QBO_ProfitAndLoss_Month.csv', header=true)
  2. 02Load QBO General Ledger Detail
    SELECT * FROM read_csv_auto('QBO_GeneralLedger.csv', header=true)
  3. 03Normalize Monthly P&L to Date-Based Lines
    WITH unpvt AS (
      SELECT section, account, period, raw_amount
      FROM input_1
      UNPIVOT (raw_amount FOR period IN (
        "2024-01", "2024-02", "2024-03", "2024-04", "2024-05", "2024-06", "2024-07", "2024-08", "2024-09", "2024-10", "2024-11", "2024-12",
        "2025-01", "2025-02", "2025-03", "2025-04", "2025-05", "2025-06", "2025-07", "2025-08", "2025-09", "2025-10", "2025-11", "2025-12",
        "2026-01", "2026-02", "2026-03", "2026-04", "2026-05", "2026-06", "2026-07"
      ))
    )
    SELECT
      CAST(period || '-01' AS DATE) AS month_date,
      period AS "Month",
      TRIM(CAST(section AS VARCHAR)) AS section,
      TRIM(CAST(account AS VARCHAR)) AS account,
      ROUND(COALESCE(TRY_CAST(REPLACE(CAST(raw_amount AS VARCHAR), ',', '') AS DOUBLE), 0), 2) AS amount
    FROM unpvt
    WHERE account IS NOT NULL
      AND TRIM(CAST(account AS VARCHAR)) <> ''
      AND account NOT ILIKE '%total%'
      AND period <> 'Total'
    ORDER BY month_date, section, account
  4. 04Node 2 — Monthly Summary Financials, Months as Columns
    WITH monthly AS (
      SELECT
        month_date,
        "Month",
        SUM(CASE WHEN section ILIKE '%Income%' AND section NOT ILIKE '%Other%' THEN amount ELSE 0 END) AS sales,
        SUM(CASE WHEN section ILIKE '%Cost of Goods Sold%' THEN amount ELSE 0 END) AS cogs,
        SUM(CASE WHEN section ILIKE '%Expenses%' AND section NOT ILIKE '%Other%' THEN amount ELSE 0 END) AS opex,
        SUM(CASE WHEN section ILIKE '%Other Income%' THEN amount ELSE 0 END) AS other_income,
        SUM(CASE WHEN section ILIKE '%Other Expense%' THEN amount ELSE 0 END) AS other_expense,
        SUM(CASE WHEN account = 'Depreciation expenses' THEN amount ELSE 0 END) AS depreciation_amortization,
        SUM(CASE WHEN account = 'Interest expense' THEN amount ELSE 0 END) AS interest_expense,
        SUM(CASE WHEN LOWER(account) IN ('income tax expense', 'tax expense', 'provision for income taxes') OR LOWER(account) LIKE '%income tax%' THEN amount ELSE 0 END) AS income_tax_expense
      FROM input_1
      GROUP BY month_date, "Month"
    ), lines AS (
      SELECT month_date, "Month", 10 AS sort_order, 'Sales' AS "Line Item", sales AS amount FROM monthly
      UNION ALL SELECT month_date, "Month", 20, 'Cost of Goods Sold', cogs FROM monthly
      UNION ALL SELECT month_date, "Month", 30, 'Gross Profit', sales - cogs FROM monthly
      UNION ALL SELECT month_date, "Month", 40, 'Operating Expenses', opex FROM monthly
      UNION ALL SELECT month_date, "Month", 50, 'Operating Income', sales - cogs - opex FROM monthly
      UNION ALL SELECT month_date, "Month", 60, 'Other Income', other_income FROM monthly
      UNION ALL SELECT month_date, "Month", 70, 'Other Expense', other_expense FROM monthly
      UNION ALL SELECT month_date, "Month", 80, 'Net Income', sales - cogs - opex + other_income - other_expense FROM monthly
      UNION ALL SELECT month_date, "Month", 90, 'D&A Add-Back', depreciation_amortization FROM monthly
      UNION ALL SELECT month_date, "Month", 100, 'Interest Add-Back', interest_expense FROM monthly
      UNION ALL SELECT month_date, "Month", 110, 'Income Tax Add-Back', income_tax_expense FROM monthly
      UNION ALL SELECT month_date, "Month", 120, 'EBITDA', sales - cogs - opex + other_income - other_expense + depreciation_amortization + interest_expense + income_tax_expense FROM monthly
    )
    SELECT
      "Line Item",
      ROUND(SUM(CASE WHEN "Month" = '2024-01' THEN amount ELSE 0 END), 2) AS "Jan 2024",
      ROUND(SUM(CASE WHEN "Month" = '2024-02' THEN amount ELSE 0 END), 2) AS "Feb 2024",
      ROUND(SUM(CASE WHEN "Month" = '2024-03' THEN amount ELSE 0 END), 2) AS "Mar 2024",
      ROUND(SUM(CASE WHEN "Month" = '2024-04' THEN amount ELSE 0 END), 2) AS "Apr 2024",
      ROUND(SUM(CASE WHEN "Month" = '2024-05' THEN amount ELSE 0 END), 2) AS "May 2024",
      ROUND(SUM(CASE WHEN "Month" = '2024-06' THEN amount ELSE 0 END), 2) AS "Jun 2024",
      ROUND(SUM(CASE WHEN "Month" = '2024-07' THEN amount ELSE 0 END), 2) AS "Jul 2024",
      ROUND(SUM(CASE WHEN "Month" = '2024-08' THEN amount ELSE 0 END), 2) AS "Aug 2024",
      ROUND(SUM(CASE WHEN "Month" = '2024-09' THEN amount ELSE 0 END), 2) AS "Sep 2024",
      ROUND(SUM(CASE WHEN "Month" = '2024-10' THEN amount ELSE 0 END), 2) AS "Oct 2024",
      ROUND(SUM(CASE WHEN "Month" = '2024-11' THEN amount ELSE 0 END), 2) AS "Nov 2024",
      ROUND(SUM(CASE WHEN "Month" = '2024-12' THEN amount ELSE 0 END), 2) AS "Dec 2024",
      ROUND(SUM(CASE WHEN "Month" = '2025-01' THEN amount ELSE 0 END), 2) AS "Jan 2025",
      ROUND(SUM(CASE WHEN "Month" = '2025-02' THEN amount ELSE 0 END), 2) AS "Feb 2025",
      ROUND(SUM(CASE WHEN "Month" = '2025-03' THEN amount ELSE 0 END), 2) AS "Mar 2025",
      ROUND(SUM(CASE WHEN "Month" = '2025-04' THEN amount ELSE 0 END), 2) AS "Apr 2025",
      ROUND(SUM(CASE WHEN "Month" = '2025-05' THEN amount ELSE 0 END), 2) AS "May 2025",
      ROUND(SUM(CASE WHEN "Month" = '2025-06' THEN amount ELSE 0 END), 2) AS "Jun 2025",
      ROUND(SUM(CASE WHEN "Month" = '2025-07' THEN amount ELSE 0 END), 2) AS "Jul 2025",
      ROUND(SUM(CASE WHEN "Month" = '2025-08' THEN amount ELSE 0 END), 2) AS "Aug 2025",
      ROUND(SUM(CASE WHEN "Month" = '2025-09' THEN amount ELSE 0 END), 2) AS "Sep 2025",
      ROUND(SUM(CASE WHEN "Month" = '2025-10' THEN amount ELSE 0 END), 2) AS "Oct 2025",
      ROUND(SUM(CASE WHEN "Month" = '2025-11' THEN amount ELSE 0 END), 2) AS "Nov 2025",
      ROUND(SUM(CASE WHEN "Month" = '2025-12' THEN amount ELSE 0 END), 2) AS "Dec 2025",
      ROUND(SUM(CASE WHEN "Month" = '2026-01' THEN amount ELSE 0 END), 2) AS "Jan 2026",
      ROUND(SUM(CASE WHEN "Month" = '2026-02' THEN amount ELSE 0 END), 2) AS "Feb 2026",
      ROUND(SUM(CASE WHEN "Month" = '2026-03' THEN amount ELSE 0 END), 2) AS "Mar 2026",
      ROUND(SUM(CASE WHEN "Month" = '2026-04' THEN amount ELSE 0 END), 2) AS "Apr 2026",
      ROUND(SUM(CASE WHEN "Month" = '2026-05' THEN amount ELSE 0 END), 2) AS "May 2026",
      ROUND(SUM(CASE WHEN "Month" = '2026-06' THEN amount ELSE 0 END), 2) AS "Jun 2026",
      ROUND(SUM(CASE WHEN "Month" = '2026-07' THEN amount ELSE 0 END), 2) AS "Jul 2026",
      ROUND(SUM(amount), 2) AS "Total"
    FROM lines
    GROUP BY sort_order, "Line Item"
    ORDER BY sort_order
  5. 05Node 3 — Add FY 2024, FY 2025, YTD 2026, and Latest LTM Columns
    WITH monthly AS (
      SELECT
        month_date,
        "Month",
        SUM(CASE WHEN section ILIKE '%Income%' AND section NOT ILIKE '%Other%' THEN amount ELSE 0 END) AS sales,
        SUM(CASE WHEN section ILIKE '%Cost of Goods Sold%' THEN amount ELSE 0 END) AS cogs,
        SUM(CASE WHEN section ILIKE '%Expenses%' AND section NOT ILIKE '%Other%' THEN amount ELSE 0 END) AS opex,
        SUM(CASE WHEN section ILIKE '%Other Income%' THEN amount ELSE 0 END) AS other_income,
        SUM(CASE WHEN section ILIKE '%Other Expense%' THEN amount ELSE 0 END) AS other_expense,
        SUM(CASE WHEN account = 'Depreciation expenses' THEN amount ELSE 0 END) AS depreciation_amortization,
        SUM(CASE WHEN account = 'Interest expense' THEN amount ELSE 0 END) AS interest_expense,
        SUM(CASE WHEN LOWER(account) IN ('income tax expense', 'tax expense', 'provision for income taxes') OR LOWER(account) LIKE '%income tax%' THEN amount ELSE 0 END) AS income_tax_expense
      FROM input_1
      GROUP BY month_date, "Month"
    ), latest AS (
      SELECT MAX(month_date) AS latest_month FROM monthly
    ), lines AS (
      SELECT month_date, 10 AS sort_order, 'Sales' AS "Line Item", sales AS amount FROM monthly
      UNION ALL SELECT month_date, 20, 'Cost of Goods Sold', cogs FROM monthly
      UNION ALL SELECT month_date, 30, 'Gross Profit', sales - cogs FROM monthly
      UNION ALL SELECT month_date, 40, 'Operating Expenses', opex FROM monthly
      UNION ALL SELECT month_date, 50, 'Operating Income', sales - cogs - opex FROM monthly
      UNION ALL SELECT month_date, 60, 'Other Income', other_income FROM monthly
      UNION ALL SELECT month_date, 70, 'Other Expense', other_expense FROM monthly
      UNION ALL SELECT month_date, 80, 'Net Income', sales - cogs - opex + other_income - other_expense FROM monthly
      UNION ALL SELECT month_date, 90, 'D&A Add-Back', depreciation_amortization FROM monthly
      UNION ALL SELECT month_date, 100, 'Interest Add-Back', interest_expense FROM monthly
      UNION ALL SELECT month_date, 110, 'Income Tax Add-Back', income_tax_expense FROM monthly
      UNION ALL SELECT month_date, 120, 'EBITDA', sales - cogs - opex + other_income - other_expense + depreciation_amortization + interest_expense + income_tax_expense FROM monthly
    )
    SELECT
      "Line Item",
      ROUND(SUM(CASE WHEN month_date BETWEEN DATE '2024-01-01' AND DATE '2024-12-01' THEN amount ELSE 0 END), 2) AS "FY 2024",
      ROUND(SUM(CASE WHEN month_date BETWEEN DATE '2025-01-01' AND DATE '2025-12-01' THEN amount ELSE 0 END), 2) AS "FY 2025",
      ROUND(SUM(CASE WHEN month_date BETWEEN DATE '2026-01-01' AND (SELECT latest_month FROM latest) THEN amount ELSE 0 END), 2) AS "YTD 2026",
      ROUND(SUM(CASE WHEN month_date BETWEEN ((SELECT latest_month FROM latest) - INTERVAL 11 MONTH) AND (SELECT latest_month FROM latest) THEN amount ELSE 0 END), 2) AS "Latest LTM"
    FROM lines
    GROUP BY sort_order, "Line Item"
    ORDER BY sort_order
  6. 06Node 4 — EBITDA Bridge from Net Income with Add-Backs
    SELECT
      CASE "Line Item"
        WHEN 'Sales' THEN 10
        WHEN 'Net Income' THEN 20
        WHEN 'D&A Add-Back' THEN 30
        WHEN 'Interest Add-Back' THEN 40
        WHEN 'Income Tax Add-Back' THEN 50
        WHEN 'EBITDA' THEN 60
        ELSE 99
      END AS sort_order,
      "Line Item",
      "FY 2024",
      "FY 2025",
      "YTD 2026",
      "Latest LTM"
    FROM input_1
    WHERE "Line Item" IN ('Sales', 'Net Income', 'D&A Add-Back', 'Interest Add-Back', 'Income Tax Add-Back', 'EBITDA')
    ORDER BY sort_order
  7. 07Node 5 — Annual QoE Base: Latest Two Full Years and LTM Only
    SELECT
      "Line Item",
      ROUND("FY 2024", 2) AS "2024",
      ROUND("FY 2025", 2) AS "2025",
      ROUND("Latest LTM", 2) AS "LTM"
    FROM input_1
    ORDER BY sort_order
  8. 08Node 6 — GL Detail One-Off Identification and Add-Back Candidates
    WITH pl_months AS (
      SELECT DISTINCT month_date
      FROM input_1
    ), gl_clean AS (
      SELECT
        TRY_CAST("date" AS DATE) AS txn_date,
        DATE_TRUNC('month', TRY_CAST("date" AS DATE))::DATE AS month_date,
        TRIM(CAST(account AS VARCHAR)) AS account,
        TRIM(COALESCE(CAST(name AS VARCHAR), '')) AS name,
        TRIM(COALESCE(CAST(memo_description AS VARCHAR), '')) AS memo_description,
        COALESCE(TRY_CAST(REPLACE(CAST(amount AS VARCHAR), ',', '') AS DOUBLE), 0) AS amount,
        LOWER(TRIM(COALESCE(CAST(account AS VARCHAR), '') || ' ' || COALESCE(CAST(name AS VARCHAR), '') || ' ' || COALESCE(CAST(memo_description AS VARCHAR), ''))) AS search_text
      FROM input_2
      WHERE TRY_CAST("date" AS DATE) IS NOT NULL
    ), joined_to_pl_periods AS (
      SELECT g.*
      FROM gl_clean g
      INNER JOIN pl_months p
        ON g.month_date = p.month_date
    ), flagged AS (
      SELECT
        txn_date,
        month_date,
        account,
        name,
        memo_description,
        amount,
        ABS(amount) AS addback_amount,
        CASE
          WHEN regexp_matches(search_text, 'settlement|lawsuit|litigation|legal claim|judgment') THEN 'Legal / settlement / litigation'
          WHEN regexp_matches(search_text, 'acquisition|transaction|due diligence|qofe|quality of earnings|deal cost') THEN 'Transaction / acquisition-related cost'
          WHEN regexp_matches(search_text, 'restructuring|severance|layoff|termination') THEN 'Restructuring / severance'
          WHEN regexp_matches(search_text, 'relocation|moving|move |office move|facility move') THEN 'Relocation / facility move'
          WHEN regexp_matches(search_text, 'write.?off|impairment|obsolete|disposal') THEN 'Write-off / impairment / disposal'
          WHEN regexp_matches(search_text, 'penalty|fine|late fee') THEN 'Penalty / fine / late fee'
          WHEN regexp_matches(search_text, 'one.?time|non.?recurring|nonrecurring|extraordinary|unusual') THEN 'Explicit one-time / non-recurring'
          WHEN account IN ('Legal & professional services', 'Repairs & maintenance', 'Other operating expenses') AND ABS(amount) >= 5000 THEN 'Large discretionary or unusual expense for review'
          ELSE NULL
        END AS "Add-Back Category"
      FROM joined_to_pl_periods
      WHERE account NOT IN ('Depreciation expenses', 'Interest expense')
    ), reviewed AS (
      SELECT *
      FROM flagged
      WHERE "Add-Back Category" IS NOT NULL
        AND addback_amount > 0
    )
    SELECT
      "Add-Back Category",
      COUNT(*) AS "Transaction Count",
      ROUND(MAX(addback_amount), 2) AS "Largest Transaction",
      STRING_AGG(DISTINCT account, ' | ') AS "Accounts Flagged",
      STRING_AGG(DISTINCT LEFT(NULLIF(name || ' - ' || memo_description, ' - '), 120), ' | ') AS "Example Vendors / Memos",
      ROUND(SUM(CASE WHEN month_date BETWEEN DATE '2024-01-01' AND DATE '2024-12-01' THEN addback_amount ELSE 0 END), 2) AS "2024",
      ROUND(SUM(CASE WHEN month_date BETWEEN DATE '2025-01-01' AND DATE '2025-12-01' THEN addback_amount ELSE 0 END), 2) AS "2025",
      ROUND(SUM(CASE WHEN month_date BETWEEN DATE '2026-01-01' AND (SELECT MAX(month_date) FROM pl_months) THEN addback_amount ELSE 0 END), 2) AS "YTD 2026",
      ROUND(SUM(CASE WHEN month_date BETWEEN ((SELECT MAX(month_date) FROM pl_months) - INTERVAL 11 MONTH) AND (SELECT MAX(month_date) FROM pl_months) THEN addback_amount ELSE 0 END), 2) AS "LTM"
    FROM reviewed
    GROUP BY "Add-Back Category"
    ORDER BY "LTM" DESC, "2025" DESC, "2024" DESC
  9. 09Node 7 — Final QoE Table: Sales, EBITDA, Add-Backs, Adjusted EBITDA
    WITH base AS (
      SELECT
        MAX(CASE WHEN "Line Item" = 'Sales' THEN TRY_CAST("2024" AS DOUBLE) END) AS sales_2024,
        MAX(CASE WHEN "Line Item" = 'Sales' THEN TRY_CAST("2025" AS DOUBLE) END) AS sales_2025,
        MAX(CASE WHEN "Line Item" = 'Sales' THEN TRY_CAST("LTM" AS DOUBLE) END) AS sales_ltm,
        MAX(CASE WHEN "Line Item" = 'EBITDA' THEN TRY_CAST("2024" AS DOUBLE) END) AS ebitda_2024,
        MAX(CASE WHEN "Line Item" = 'EBITDA' THEN TRY_CAST("2025" AS DOUBLE) END) AS ebitda_2025,
        MAX(CASE WHEN "Line Item" = 'EBITDA' THEN TRY_CAST("LTM" AS DOUBLE) END) AS ebitda_ltm
      FROM input_1
    ),
    
    input2_json AS (
      SELECT to_json(i2) AS j
      FROM input_2 i2
    ),
    
    input2_normalized AS (
      SELECT
        json_extract_string(j, '$."Add-Back Category"') AS addback_category,
    
        TRY_CAST(json_extract_string(j, '$."Transaction Count"') AS BIGINT) AS transaction_count_summary,
        TRY_CAST(json_extract_string(j, '$."Largest Transaction"') AS DOUBLE) AS largest_transaction_summary,
        json_extract_string(j, '$."Accounts Flagged"') AS accounts_flagged_summary,
        json_extract_string(j, '$."Example Vendors / Memos"') AS examples_summary,
    
        TRY_CAST(json_extract_string(j, '$."2024"') AS DOUBLE) AS summary_2024,
        TRY_CAST(json_extract_string(j, '$."2025"') AS DOUBLE) AS summary_2025,
        TRY_CAST(json_extract_string(j, '$."LTM"') AS DOUBLE) AS summary_ltm,
    
        TRY_CAST(json_extract_string(j, '$."Transaction Date"') AS DATE) AS transaction_date,
        TRY_CAST(json_extract_string(j, '$."Add-Back Amount"') AS DOUBLE) AS addback_amount,
    
        COALESCE(
          json_extract_string(j, '$."account"'),
          json_extract_string(j, '$."Account"')
        ) AS account_text,
    
        NULLIF(
          CONCAT_WS(
            ' / ',
            COALESCE(
              json_extract_string(j, '$."name"'),
              json_extract_string(j, '$."Name"')
            ),
            COALESCE(
              json_extract_string(j, '$."memo_description"'),
              json_extract_string(j, '$."Memo Description"'),
              json_extract_string(j, '$."Memo"')
            )
          ),
          ''
        ) AS example_text
      FROM input2_json
    ),
    
    input2_with_max_date AS (
      SELECT
        *,
        MAX(transaction_date) OVER () AS max_transaction_date
      FROM input2_normalized
      WHERE addback_category IS NOT NULL
    ),
    
    addback_detail AS (
      SELECT
        addback_category AS "Add-Back Category",
    
        COALESCE(
          MAX(transaction_count_summary),
          COUNT(*) FILTER (WHERE addback_amount IS NOT NULL),
          0
        ) AS "Transaction Count",
    
        COALESCE(
          MAX(largest_transaction_summary),
          MAX(ABS(addback_amount)),
          0
        ) AS "Largest Transaction",
    
        COALESCE(
          MAX(accounts_flagged_summary),
          STRING_AGG(DISTINCT account_text, ', ' ORDER BY account_text)
            FILTER (WHERE account_text IS NOT NULL AND account_text <> '')
        ) AS "Accounts Flagged",
    
        COALESCE(
          MAX(examples_summary),
          STRING_AGG(DISTINCT example_text, ', ' ORDER BY example_text)
            FILTER (WHERE example_text IS NOT NULL AND example_text <> '')
        ) AS "Example Vendors / Memos",
    
        COALESCE(
          SUM(summary_2024),
          SUM(CASE WHEN EXTRACT(YEAR FROM transaction_date) = 2024 THEN addback_amount ELSE 0 END),
          0
        ) AS addback_2024,
    
        COALESCE(
          SUM(summary_2025),
          SUM(CASE WHEN EXTRACT(YEAR FROM transaction_date) = 2025 THEN addback_amount ELSE 0 END),
          0
        ) AS addback_2025,
    
        COALESCE(
          SUM(summary_ltm),
          SUM(
            CASE
              WHEN transaction_date > max_transaction_date - INTERVAL 12 MONTH
               AND transaction_date <= max_transaction_date
              THEN addback_amount
              ELSE 0
            END
          ),
          0
        ) AS addback_ltm
      FROM input2_with_max_date
      GROUP BY addback_category
    ),
    
    addback_totals AS (
      SELECT
        COALESCE(SUM(addback_2024), 0) AS addbacks_2024,
        COALESCE(SUM(addback_2025), 0) AS addbacks_2025,
        COALESCE(SUM(addback_ltm), 0) AS addbacks_ltm
      FROM addback_detail
    ),
    
    report_rows AS (
      SELECT
        10 AS sort_order,
        'Sales' AS "Line Item",
        sales_2024 AS "2024",
        sales_2025 AS "2025",
        sales_ltm AS "LTM",
        NULL::BIGINT AS "Transaction Count",
        NULL::DOUBLE AS "Largest Transaction",
        NULL::VARCHAR AS "Accounts Flagged",
        NULL::VARCHAR AS "Example Vendors / Memos"
      FROM base
    
      UNION ALL
    
      SELECT
        20,
        'EBITDA',
        ebitda_2024,
        ebitda_2025,
        ebitda_ltm,
        NULL::BIGINT,
        NULL::DOUBLE,
        NULL::VARCHAR,
        NULL::VARCHAR
      FROM base
    
      UNION ALL
    
      SELECT
        30 + ROW_NUMBER() OVER (
          ORDER BY addback_ltm DESC, addback_2025 DESC, addback_2024 DESC
        ) AS sort_order,
        'Add-Back: ' || "Add-Back Category" AS "Line Item",
        addback_2024 AS "2024",
        addback_2025 AS "2025",
        addback_ltm AS "LTM",
        "Transaction Count",
        "Largest Transaction",
        "Accounts Flagged",
        "Example Vendors / Memos"
      FROM addback_detail
    
      UNION ALL
    
      SELECT
        80,
        'Total Add-Backs',
        addbacks_2024,
        addbacks_2025,
        addbacks_ltm,
        NULL::BIGINT,
        NULL::DOUBLE,
        NULL::VARCHAR,
        NULL::VARCHAR
      FROM addback_totals
    
      UNION ALL
    
      SELECT
        90,
        'Adjusted EBITDA',
        ebitda_2024 + addbacks_2024,
        ebitda_2025 + addbacks_2025,
        ebitda_ltm + addbacks_ltm,
        NULL::BIGINT,
        NULL::DOUBLE,
        NULL::VARCHAR,
        NULL::VARCHAR
      FROM base CROSS JOIN addback_totals
    
      UNION ALL
    
      SELECT
        100,
        'Adj. EBITDA % of Sales',
        CASE
          WHEN sales_2024 <> 0
          THEN (ebitda_2024 + addbacks_2024) / sales_2024 * 100
          ELSE NULL
        END,
        CASE
          WHEN sales_2025 <> 0
          THEN (ebitda_2025 + addbacks_2025) / sales_2025 * 100
          ELSE NULL
        END,
        CASE
          WHEN sales_ltm <> 0
          THEN (ebitda_ltm + addbacks_ltm) / sales_ltm * 100
          ELSE NULL
        END,
        NULL::BIGINT,
        NULL::DOUBLE,
        NULL::VARCHAR,
        NULL::VARCHAR
      FROM base CROSS JOIN addback_totals
    )
    
    SELECT
      "Line Item",
      ROUND("2024", 2) AS "2024",
      ROUND("2025", 2) AS "2025",
      ROUND("LTM", 2) AS "LTM",
      "Transaction Count",
      ROUND("Largest Transaction", 2) AS "Largest Transaction",
      "Accounts Flagged",
      "Example Vendors / Memos"
    FROM report_rows
    ORDER BY sort_order

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 →