Portfolio Project · SQL · Revenue Operations

Revenue Reconciliation
Query Set

Five production-ready SQL queries that surface revenue leakage, aging receivables, duplicate charges, short payments, and contract billing variance — the financial pain points every company above Series B is actively trying to solve.

SQL Revenue Operations Billing Ops CTEs · Window Functions · CASE Industry-Agnostic
5
Queries
5
Tables
4
Leakage Vectors
$100K+
Target Role Range
Simulated schema: All table structures, column names, and sample output values in this project are entirely fictitious and built for portfolio demonstration. The queries are written against a generic SaaS billing schema — no real company data, client records, or proprietary system designs have been used or referenced.

Data Model

Schema Overview

Five tables representing a standard SaaS billing environment. Every query in this project runs against this schema — no joins beyond what's necessary, no assumptions about tooling. Compatible with PostgreSQL, BigQuery, Snowflake, and Redshift with minor syntax adjustments.

accounts
account_id PK
account_name
tier
region
csm_owner
invoices
invoice_id PK
account_id FK
invoice_date
due_date
amount_due
status
billing_period_start
billing_period_end
product_line
payments
payment_id PK
invoice_id FK
account_id FK
payment_date
amount_paid
payment_method
contracts
contract_id PK
account_id FK
contracted_arr
billing_frequency
contract_start
contract_end
auto_renew
credit_memos
memo_id PK
account_id FK
invoice_id FK
memo_date
memo_amount
reason_code

The Queries

Five Revenue Reconciliation Queries

1
Aging Receivables Report
Business question: Which accounts owe us money, how long have they owed it, and how much?
Surfaces cash the company has earned but not collected — the most direct form of revenue leakage
SQL — PostgreSQL
SELECT
    a.account_name,
    a.tier,
    a.csm_owner,
    COUNT(i.invoice_id)                        AS open_invoice_count,
    SUM(i.amount_due)                           AS total_outstanding,
    -- Age buckets
    SUM(CASE WHEN CURRENT_DATE - i.due_date BETWEEN 1  AND 30  THEN i.amount_due ELSE 0 END) AS bucket_1_30,
    SUM(CASE WHEN CURRENT_DATE - i.due_date BETWEEN 31 AND 60  THEN i.amount_due ELSE 0 END) AS bucket_31_60,
    SUM(CASE WHEN CURRENT_DATE - i.due_date BETWEEN 61 AND 90  THEN i.amount_due ELSE 0 END) AS bucket_61_90,
    SUM(CASE WHEN CURRENT_DATE - i.due_date >> 90               THEN i.amount_due ELSE 0 END) AS bucket_90_plus,
    -- Escalation flag
    CASE
        WHEN SUM(CASE WHEN CURRENT_DATE - i.due_date >> 90 THEN i.amount_due ELSE 0 END) > 10000 THEN 'ESCALATE'
        WHEN SUM(CASE WHEN CURRENT_DATE - i.due_date >> 60  THEN i.amount_due ELSE 0 END) > 5000  THEN 'PRIORITY FOLLOW-UP'
        ELSE 'STANDARD'
    END                                         AS collection_priority
FROM invoices i
JOIN accounts a ON i.account_id = a.account_id
WHERE i.status NOT IN ('paid', 'void', 'written_off')
GROUP BY a.account_name, a.tier, a.csm_owner
HAVING SUM(i.amount_due) > 0
ORDER BY total_outstanding DESC;

Sample output

account_nametiertotal_outstandingbucket_90_pluscollection_priority
Meridian FinancialEnterprise$84,200$41,000ESCALATE
Atlas SaaS GroupMid-Market$22,500$0PRIORITY FOLLOW-UP
Cornerstone RetailSMB$4,800$0STANDARD
2
Payment Shortfall Detection
Business question: Where has an account paid less than invoiced — and by exactly how much?
Automates the reconciliation that otherwise only surfaces during a dispute or manual audit
SQL — CTEs
WITH payment_totals AS (
    SELECT
        invoice_id,
        SUM(amount_paid)  AS total_paid,
        MAX(payment_date) AS last_payment_date
    FROM payments
    GROUP BY invoice_id
),
credit_totals AS (
    SELECT
        invoice_id,
        SUM(memo_amount) AS total_credits
    FROM credit_memos
    GROUP BY invoice_id
)
SELECT
    a.account_name,
    i.invoice_id,
    i.amount_due,
    COALESCE(pt.total_paid, 0)                AS total_paid,
    COALESCE(ct.total_credits, 0)             AS credits_applied,
    i.amount_due
        - COALESCE(pt.total_paid, 0)
        - COALESCE(ct.total_credits, 0)       AS net_balance_due,
    ROUND(COALESCE(pt.total_paid,0) / NULLIF(i.amount_due,0) * 100, 2) AS pct_paid,
    CURRENT_DATE - i.due_date                 AS days_past_due
FROM invoices i
JOIN accounts a     ON i.account_id = a.account_id
LEFT JOIN payment_totals pt ON i.invoice_id = pt.invoice_id
LEFT JOIN credit_totals  ct ON i.invoice_id = ct.invoice_id
WHERE
    (i.amount_due
        - COALESCE(pt.total_paid, 0)
        - COALESCE(ct.total_credits, 0)) > 0.01
ORDER BY net_balance_due DESC;

Sample output

account_nameinvoice_idamount_duetotal_paidnet_balance_duepct_paiddays_past_due
NovaTech CorpINV-8821$12,000$9,500$2,50079.17%47
Riverstone LtdINV-9104$5,400$5,000$40092.59%12
Apex HoldingsINV-7733$3,200$0$3,2000.00%91
3
Duplicate Charge Detection
Business question: Are any accounts being billed more than once for the same period and product?
Catches duplicate invoices before they reach customers — preventing disputes, churn, and compliance exposure
SQL — Window Functions
WITH invoice_fingerprint AS (
    SELECT
        invoice_id, account_id,
        product_line, billing_period_start,
        billing_period_end, amount_due,
        -- Count invoices per account + product + period
        COUNT(*) OVER (
            PARTITION BY
                account_id, product_line,
                billing_period_start, billing_period_end
        )                          AS period_invoice_count
    FROM invoices
    WHERE status NOT IN ('void', 'written_off')
)
SELECT
    a.account_name,
    f.product_line,
    f.billing_period_start,
    f.billing_period_end,
    f.period_invoice_count         AS duplicate_count,
    SUM(f.amount_due)              AS total_billed,
    MIN(f.amount_due)              AS expected_charge,
    SUM(f.amount_due) - MIN(f.amount_due) AS overcharge_amount,
    STRING_AGG(f.invoice_id, ', ') AS invoice_ids
FROM invoice_fingerprint f
JOIN accounts a ON f.account_id = a.account_id
WHERE f.period_invoice_count > 1
GROUP BY
    a.account_name, f.product_line,
    f.billing_period_start, f.billing_period_end,
    f.period_invoice_count
ORDER BY overcharge_amount DESC;

Sample output

account_nameproduct_lineperiodduplicate_countovercharge_amountinvoice_ids
Meridian FinancialPlatform Pro2026-Q12$8,400INV-7701, INV-7742
BlueWave AnalyticsData Add-on2026-022$1,200INV-8830, INV-8831
4
Contracted vs Billed Revenue Reconciliation
Business question: Is every account being billed at their contracted ARR — not under, not over?
Surfaces revenue leakage from under-billing and compliance risk from over-billing simultaneously
SQL — CTEs + DATE functions
WITH annualised_billing AS (
    SELECT
        account_id,
        SUM(amount_due) AS actual_arr_billed
    FROM invoices
    WHERE
        invoice_date >= DATE_TRUNC('year', CURRENT_DATE)
        AND status NOT IN ('void', 'written_off')
    GROUP BY account_id
)
SELECT
    a.account_name,
    a.tier,
    c.contracted_arr,
    ab.actual_arr_billed,
    ab.actual_arr_billed - c.contracted_arr    AS arr_variance,
    ROUND(
        (ab.actual_arr_billed - c.contracted_arr)
        / NULLIF(c.contracted_arr, 0) * 100, 2
    )                                          AS variance_pct,
    CASE
        WHEN ABS(ab.actual_arr_billed - c.contracted_arr)
             / NULLIF(c.contracted_arr,0) > 0.05 THEN 'REVIEW REQUIRED'
        WHEN ab.actual_arr_billed > c.contracted_arr THEN 'OVER-BILLED'
        WHEN ab.actual_arr_billed < c.contracted_arr THEN 'UNDER-BILLED'
        ELSE 'IN LINE'
    END                                        AS billing_status,
    -- Surface renewal risk at same time
    CASE
        WHEN c.contract_end <= CURRENT_DATE + INTERVAL '90 days'
             AND c.auto_renew = FALSE THEN 'RENEWAL RISK'
        ELSE NULL
    END                                        AS renewal_flag
FROM contracts c
JOIN accounts a           ON c.account_id = a.account_id
LEFT JOIN annualised_billing ab ON c.account_id = ab.account_id
WHERE c.contract_end >= CURRENT_DATE
ORDER BY ABS(arr_variance) DESC;

Sample output

account_namecontracted_arractual_arr_billedarr_variancebilling_statusrenewal_flag
Cornerstone Retail$120,000$132,000+$12,000REVIEW REQUIRED
Atlas SaaS Group$84,000$78,000-$6,000UNDER-BILLEDRENEWAL RISK
NovaTech Corp$60,000$60,200+$200IN LINE
5
Revenue Leakage Summary Dashboard
Business question: Give me one number — total revenue at risk across all leakage categories.
The output you present to a VP of Finance or CRO — all four leakage vectors in a single prioritised view
SQL — UNION ALL + Window Functions
WITH aging AS (
    SELECT 'Aging Receivables' AS leakage_type,
        COUNT(DISTINCT invoice_id) AS record_count,
        SUM(amount_due) AS amount_at_risk
    FROM invoices
    WHERE status NOT IN ('paid','void','written_off')
      AND CURRENT_DATE > due_date
),
short_pay AS (
    SELECT 'Short Payments' AS leakage_type,
        COUNT(DISTINCT i.invoice_id) AS record_count,
        SUM(i.amount_due - COALESCE(p.total_paid,0)) AS amount_at_risk
    FROM invoices i
    LEFT JOIN (SELECT invoice_id, SUM(amount_paid) AS total_paid
               FROM payments GROUP BY invoice_id) p
        ON i.invoice_id = p.invoice_id
    WHERE i.status NOT IN ('void','written_off')
      AND COALESCE(p.total_paid,0) BETWEEN 0.01 AND i.amount_due - 0.01
),
duplicates AS (
    SELECT 'Duplicate Charges' AS leakage_type,
        COUNT(*) AS record_count,
        SUM(overcharge_amount) AS amount_at_risk
    FROM (
        SELECT SUM(amount_due) - MIN(amount_due) AS overcharge_amount
        FROM invoices
        WHERE status NOT IN ('void','written_off')
        GROUP BY account_id, product_line,
                 billing_period_start, billing_period_end
        HAVING COUNT(*) > 1
    ) dup
)
SELECT
    leakage_type,
    record_count,
    ROUND(amount_at_risk, 2)             AS amount_at_risk,
    ROUND(
        amount_at_risk / SUM(amount_at_risk) OVER () * 100, 1
    )                                    AS pct_of_total_risk
FROM (
    SELECT * FROM aging
    UNION ALL
    SELECT * FROM short_pay
    UNION ALL
    SELECT * FROM duplicates
) combined
ORDER BY amount_at_risk DESC;

Sample output

leakage_typerecord_countamount_at_riskpct_of_total_risk
Aging Receivables38$214,80061.2%
Short Payments12$88,40025.2%
Duplicate Charges4$47,60013.6%

Technical Skills

SQL Concepts & Business Domains

SQL Concepts Used

Common Table Expressions (CTEs) Window Functions (COUNT OVER PARTITION) CASE / CASE WHEN LEFT JOIN COALESCE / NULLIF UNION ALL HAVING STRING_AGG DATE_TRUNC INTERVAL arithmetic Aggregate functions (SUM, COUNT, MIN, MAX, ROUND) Subqueries NOT IN / BETWEEN Multi-table joins

Business & Finance Domains

Revenue Leakage Detection Accounts Receivable Aging Short-Pay Reconciliation Duplicate Invoice Detection ARR vs Billed Revenue Variance Contract Compliance Renewal Risk Flagging Collections Prioritisation Executive-Ready Reporting Credit Memo Handling SaaS Billing Logic

View the full query file

All five queries in a single clean .sql file — commented, structured, and ready to run against any PostgreSQL-compatible database.