SQL for Revenue Cycle Analysts

SQL for Revenue Cycle Analysts: Denials, A/R, and Underpayment Queries

SQL for revenue cycle analysts answers one question over and over: where is the money stuck, and why? Charges, claims, remittances, and adjustments live in separate tables with separate owners. This article shows the query patterns that connect them: days in A/R, charge lag, denial analysis, clean claim rate, and underpayment detection.

I have worked with patient accounting and revenue integrity teams on hospital and physician billing projects. The call usually came when a KPI moved and nobody could say why. The schema below is generic, with Epic mapping hints where I am confident. Names vary by site and version, so confirm them in your data dictionary. Your finance leaders own the metric definitions.

What you will take away
Seven KPI definitions with their traps, a generic revenue cycle schema with Epic mapping hints, eleven SQL patterns, two scenarios with tickets and before/after queries, a decision tree for locating stuck cash, a KPI definition template for business analysts, and a downloadable PDF pack with a denial analysis worksheet.

Revenue Cycle KPIs and the Definitions That Cause Arguments

Every revenue cycle meeting argues about a number. The argument is almost always about the definition. Agree on these seven before you write any SQL.

KPI Common definition Trap
Days in A/R Net A/R balance divided by average daily net revenue Which revenue window: last 90 days, last 3 months, or fiscal year to date?
Clean claim rate Claims accepted by the payer with no edits, divided by claims submitted Counting at the clearinghouse versus counting at the payer changes the answer.
First-pass resolution rate Claims paid on first submission divided by claims submitted Rebills and corrected claims can hide first-pass failures.
Denial rate Denied claims divided by claims submitted By count or by dollars? Initial denials or final write-offs?
Net collection rate Payments divided by (charges minus contractual adjustments) Timing: payments lag charges by weeks.
Charge lag Post date minus service date Average hides the tail. Report median and 90th percentile.
DNFB days Discharged, not final billed balance divided by average daily revenue Excluding accounts on hold understates the problem.

The Healthcare Financial Management Association publishes MAP Keys that standardize many of these metrics. Whether you adopt them or your own, write the definition beside the number. Use the KPI definition sheet template later in this article to capture each decision.

Hospital and professional billing are different ledgers

Hospital billing (facility) and professional billing (physician) run on different account structures, even in one organization. A hospital account may bundle a whole stay into one claim. A professional claim covers a provider’s services on a date. Never sum the two without labeling them, because contractual rules and denial patterns differ.

Attribute Facility (hospital) billing Professional billing
Billing unit Hospital account (HAR) per stay or visit Charge lines per provider and date
Claim form UB-04 (837I) CMS-1500 (837P)
Typical key Hospital account ID Transaction or claim ID
Payment basis Often DRG or per diem for inpatient Fee schedule per procedure code
Common denial Authorization, medical necessity, level of care Coding, modifiers, missing documentation

Epic stores the two in separate transaction structures, so queries for one rarely work on the other. Check which side your question belongs to before you begin.

The Revenue Cycle Data Flow: Where to Measure

Revenue cycle work splits into three stages. Each has its own tables, owners, and KPIs. A problem found late is expensive, so measure early where you can.

Front end
Scheduling, registration,
eligibility, authorization
KPIs
Registration error rate
Authorization on file
Point-of-service cash

Mid cycle
Charge capture, coding,
documentation, claim build
KPIs
Charge lag, DNFB days
Coding accuracy
Clean claim rate

Back end
Submission, adjudication,
payment, denials, collections
KPIs
Denial rate, days in A/R
Net collection rate
Underpayment recovery

Tie-out: account balances, payments, and adjustments must reconcile to the general ledger
Front-end defects surface as back-end denials. Query the earliest stage that can show the cause.

Denials are a back-end symptom of front-end and mid-cycle causes. A missing authorization shows up as a denial three weeks later. Trace each denial category to its origin stage before you recommend a fix.

A Reference Schema for Revenue Cycle Queries

Revenue cycle databases reduce to six structures. The generic names below keep the examples portable. The Epic column gives my best mapping, and you should verify each against your Clarity Data Dictionary.

Generic table One row represents Likely Epic source
rc_accounts A billing account HSP_ACCOUNT (hospital), professional billing accounts
rc_transactions A charge, payment, or adjustment HSP_TRANSACTIONS (hospital), ARPB_TRANSACTIONS (professional)
rc_claims A submitted claim Claim tables under the billing application
rc_remits A remittance line from the payer Remittance (835) tables, site dependent
rc_payers A payer or plan CLARITY_EPM (payer), CLARITY_EPP (benefit plan)
rc_procedures A billable procedure CLARITY_EAP

Remittance and denial detail is the most site-specific part. Many organizations load 835 files into a reporting table through their clearinghouse or billing tool. The Epic Clarity SQL guide covers core encounter tables. The PAT_ENC table guide shows how an encounter links to the hospital account that carries the charges.

Sign conventions need a decision at the start. In most billing systems, charges are positive, while payments and adjustments are negative against the account balance. Confirm the convention in your tables, because one flipped sign inverts every collection metric.

Eleven SQL Patterns for Revenue Cycle Analysts

All patterns use SQL Server syntax and the generic schema. Amounts follow the convention that charges are positive and payments and adjustments are negative against the balance.

Pattern 1: Days in A/R with an explicit revenue window

Days in A/R over a trailing 90-day window
DECLARE @as_of date = '2026-09-30';

WITH ar AS (
    SELECT SUM(t.tx_amount) AS net_ar
    FROM   rc_transactions t
    WHERE  t.post_date <= @as_of),
rev AS (
    SELECT SUM(t.tx_amount) / 90.0 AS avg_daily_net_revenue
    FROM   rc_transactions t
    WHERE  t.tx_type IN ('CHARGE', 'CONTRACTUAL_ADJ')       -- net revenue = charges less contractuals
      AND  t.post_date >  DATEADD(day, -90, @as_of)
      AND  t.post_date <= @as_of)
SELECT  ar.net_ar, rev.avg_daily_net_revenue,
        CAST(ar.net_ar / NULLIF(rev.avg_daily_net_revenue, 0) AS decimal(9,1)) AS days_in_ar
FROM    ar CROSS JOIN rev;

Name the window in the report title: “Days in A/R (trailing 90 days, net).” A 30-day window swings with seasonality. A fiscal year-to-date window lags a sudden change. Tie the A/R balance to the ledger control account, as shown in the financial reporting queries article.

Pattern 2: A/R aging by payer

Aging from first bill date
SELECT  p.payer_name,
        SUM(CASE WHEN DATEDIFF(day, a.first_bill_date, @as_of) <= 30  THEN a.balance ELSE 0 END) AS d0_30,
        SUM(CASE WHEN DATEDIFF(day, a.first_bill_date, @as_of) BETWEEN 31 AND 60  THEN a.balance ELSE 0 END) AS d31_60,
        SUM(CASE WHEN DATEDIFF(day, a.first_bill_date, @as_of) BETWEEN 61 AND 90  THEN a.balance ELSE 0 END) AS d61_90,
        SUM(CASE WHEN DATEDIFF(day, a.first_bill_date, @as_of) BETWEEN 91 AND 120 THEN a.balance ELSE 0 END) AS d91_120,
        SUM(CASE WHEN DATEDIFF(day, a.first_bill_date, @as_of) > 120 THEN a.balance ELSE 0 END) AS d120_plus,
        SUM(a.balance) AS total_ar
FROM    rc_accounts a
JOIN    rc_payers p ON p.payer_id = a.primary_payer_id
WHERE   a.balance <> 0
GROUP BY p.payer_name
ORDER BY total_ar DESC;

Age from the first bill date, not the service date, when your question is payer performance. Age from the service date when the question is timely filing. The two answer different questions, and mixing them misleads.

Pattern 3: Charge lag, with the tail

Median and 90th percentile charge lag
WITH l AS (
    SELECT department_id, DATEDIFF(day, service_date, post_date) AS lag_days
    FROM   rc_transactions
    WHERE  tx_type = 'CHARGE' AND post_date >= '2026-07-01' AND post_date < '2026-10-01')
SELECT DISTINCT department_id,
       COUNT(*)      OVER (PARTITION BY department_id)                                   AS charges,
       AVG(lag_days * 1.0) OVER (PARTITION BY department_id)                             AS avg_lag_days,
       PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY lag_days) OVER (PARTITION BY department_id) AS median_lag_days,
       PERCENTILE_CONT(0.9) WITHIN GROUP (ORDER BY lag_days) OVER (PARTITION BY department_id) AS p90_lag_days
FROM   l
ORDER BY p90_lag_days DESC;

SELECT DISTINCT collapses the window results to one row per department. Report the tail. A department with a two-day average and a 20-day 90th percentile has a problem the average hides.

Pattern 4: Discharged, not final billed (DNFB)

Accounts not yet billed
SELECT  a.account_id, a.discharge_date,
        DATEDIFF(day, a.discharge_date, @as_of) AS days_since_discharge,
        a.hold_reason, SUM(t.tx_amount) AS unbilled_charges
FROM    rc_accounts a
JOIN    rc_transactions t ON t.account_id = a.account_id AND t.tx_type = 'CHARGE'
WHERE   a.discharge_date IS NOT NULL
  AND   a.final_bill_date IS NULL
GROUP BY a.account_id, a.discharge_date, a.hold_reason
ORDER BY unbilled_charges DESC;

Group by hold reason. Coding backlog, missing documentation, and pending authorization need different owners. A single DNFB total tells the CFO there is a problem and tells nobody what to fix.

Pattern 5: Clean claim and first-pass rate

Original submissions only
SELECT  FORMAT(c.submitted_date, 'yyyy-MM') AS submit_month,
        COUNT(*) AS claims,
        SUM(CASE WHEN c.rejected_at_clearinghouse = 0 THEN 1 ELSE 0 END)                         AS accepted,
        SUM(CASE WHEN c.rejected_at_clearinghouse = 0 AND c.first_remit_paid = 1 THEN 1 ELSE 0 END) AS first_pass_paid,
        CAST(100.0 * SUM(CASE WHEN c.rejected_at_clearinghouse = 0 THEN 1 ELSE 0 END) / COUNT(*) AS decimal(5,1)) AS clean_claim_pct,
        CAST(100.0 * SUM(CASE WHEN c.rejected_at_clearinghouse = 0 AND c.first_remit_paid = 1 THEN 1 ELSE 0 END) / COUNT(*) AS decimal(5,1)) AS first_pass_pct
FROM    rc_claims c
WHERE   c.is_original_submission = 1
GROUP BY FORMAT(c.submitted_date, 'yyyy-MM')
ORDER BY submit_month;

Filter to original submissions. Including corrected and rebilled claims inflates the clean rate, because a claim fixed after rejection looks like a success the second time.

Pattern 6: Denial Pareto by reason code

Denial reason codes come from the remittance advice. The X12 Claim Adjustment Reason Codes list maintained by X12 defines the standard codes, such as 16 (claim lacks information) and 50 (not medically necessary). Group by code to find the few reasons that drive most of the lost dollars.

Cumulative share by reason code
WITH d AS (
    SELECT r.carc_code, COUNT(DISTINCT r.claim_id) AS denied_claims, SUM(r.adjustment_amount) AS denied_amount
    FROM   rc_remits r
    WHERE  r.remit_date >= '2026-07-01' AND r.remit_date < '2026-10-01'
      AND  r.adjustment_group = 'CO'                              -- contractual obligation
      AND  r.carc_code NOT IN ('45', '253')                       -- exclude routine contractual write-downs
    GROUP BY r.carc_code)
SELECT  carc_code, denied_claims, denied_amount,
        CAST(100.0 * SUM(denied_amount) OVER (ORDER BY denied_amount DESC
             ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) / SUM(denied_amount) OVER () AS decimal(5,1)) AS cumulative_pct
FROM    d
ORDER BY denied_amount DESC;

Exclude codes that describe expected contractual write-downs. Code 45, charges exceeding the fee schedule, is a normal adjustment and not a denial. Your revenue integrity team should confirm which codes count as denials at your organization.

Pattern 7: Net collection rate

Cohort by discharge month
SELECT  FORMAT(a.discharge_date, 'yyyy-MM') AS cohort_month,
        SUM(CASE WHEN t.tx_type = 'CHARGE'          THEN  t.tx_amount ELSE 0 END) AS gross_charges,
        SUM(CASE WHEN t.tx_type = 'CONTRACTUAL_ADJ' THEN -t.tx_amount ELSE 0 END) AS contractual_adj,
        SUM(CASE WHEN t.tx_type = 'PAYMENT'         THEN -t.tx_amount ELSE 0 END) AS payments,
        CAST(100.0 * SUM(CASE WHEN t.tx_type = 'PAYMENT' THEN -t.tx_amount ELSE 0 END)
             / NULLIF(SUM(CASE WHEN t.tx_type = 'CHARGE' THEN t.tx_amount ELSE 0 END)
                    - SUM(CASE WHEN t.tx_type = 'CONTRACTUAL_ADJ' THEN -t.tx_amount ELSE 0 END), 0) AS decimal(5,1)) AS net_collection_pct
FROM    rc_accounts a
JOIN    rc_transactions t ON t.account_id = a.account_id
WHERE   a.discharge_date >= '2026-01-01' AND a.discharge_date < '2026-07-01'
GROUP BY FORMAT(a.discharge_date, 'yyyy-MM');

Group by the discharge or service cohort, not by payment month. A payment-month view mixes old and new accounts, and a billing backlog makes collections look worse than they are. Cohorts older than 120 days give the cleanest read.

Pattern 8: Underpayment detection against contract terms

Expected vs received
SELECT  c.claim_id, p.payer_name, c.procedure_code,
        e.expected_amount,
        r.paid_amount + r.patient_resp_amount AS received,
        e.expected_amount - (r.paid_amount + r.patient_resp_amount) AS shortfall
FROM    rc_claims c
JOIN    (SELECT claim_id, SUM(paid_amount) paid_amount, SUM(patient_resp_amount) patient_resp_amount
         FROM rc_remits GROUP BY claim_id) r ON r.claim_id = c.claim_id
JOIN    contract_rates e ON e.payer_id = c.payer_id AND e.procedure_code = c.procedure_code
                        AND c.service_date BETWEEN e.effective_from AND e.effective_to
JOIN    rc_payers p ON p.payer_id = c.payer_id
WHERE   c.service_date >= '2026-07-01'
  AND   e.expected_amount - (r.paid_amount + r.patient_resp_amount) > 5.00      -- tolerance
ORDER BY shortfall DESC;

Match the contract rate effective on the service date, not today’s rate. Add a tolerance so rounding does not flood the work queue. The expected amount must include the patient’s share. Compare it to payer payment plus patient responsibility, or you flag every copay as an underpayment.

Pattern 9: Payer mix by financial class

Share of charges by financial class
SELECT  fc.financial_class_name,
        COUNT(DISTINCT a.account_id)  AS accounts,
        SUM(t.tx_amount)              AS gross_charges,
        CAST(100.0 * SUM(t.tx_amount) / SUM(SUM(t.tx_amount)) OVER () AS decimal(5,1)) AS pct_of_charges
FROM    rc_accounts a
JOIN    financial_classes fc ON fc.financial_class_id = a.financial_class_id
JOIN    rc_transactions t    ON t.account_id = a.account_id AND t.tx_type = 'CHARGE'
WHERE   a.discharge_date >= '2026-01-01' AND a.discharge_date < '2026-10-01'
GROUP BY fc.financial_class_name
ORDER BY gross_charges DESC;

Pattern 10: Timely filing risk list

Unbilled accounts near the filing deadline
SELECT  a.account_id, p.payer_name, a.service_date,
        p.timely_filing_days,
        DATEADD(day, p.timely_filing_days, a.service_date) AS filing_deadline,
        DATEDIFF(day, @as_of, DATEADD(day, p.timely_filing_days, a.service_date)) AS days_left
FROM    rc_accounts a
JOIN    rc_payers p ON p.payer_id = a.primary_payer_id
WHERE   a.final_bill_date IS NULL
  AND   DATEDIFF(day, @as_of, DATEADD(day, p.timely_filing_days, a.service_date)) BETWEEN 0 AND 30
ORDER BY days_left;

Timely filing limits come from payer contracts and vary widely. Store them in a table you maintain, with an effective date. This list earns its place on the revenue cycle director’s desk every Monday, because a missed deadline turns recoverable revenue into a write-off.

Pattern 11: Credit balances and refund exposure

Negative balances by payer
SELECT  p.payer_name, COUNT(*) AS credit_accounts, SUM(a.balance) AS total_credit
FROM    rc_accounts a
JOIN    rc_payers p ON p.payer_id = a.primary_payer_id
WHERE   a.balance < 0
GROUP BY p.payer_name
ORDER BY total_credit;

A credit balance means the organization holds more than it billed. Net A/R understates gross exposure when credits offset debits. Report credits separately, since refund obligations and state unclaimed property rules apply to them.

Scenario: Denial Rate Climbs from 6.1% to 11.4% After a Build Change

A health system moved outpatient imaging scheduling to a new workflow in August. By mid-September, the revenue cycle director saw the denial rate for outpatient claims rise from 6.1 to 11.4 percent. The IT team asked for evidence before touching the build.

I started with Pattern 6, then split the result by department and by the month the claim was created. The reason code with the sharpest rise was 16, claim lacks information needed for adjudication. Nearly every code 16 denial came from imaging.

RC-2051  |  Outpatient imaging denials with CARC 16 up from 0.8% to 6.9% of claimsHigh
EnvironmentProduction billing, claims created Aug 15 to Sept 30
Reported byDirector of Revenue Cycle
ExpectedImaging claims carry the referring provider NPI and are accepted.
ActualCARC 16 on 6.9% of imaging claims, up from 0.8%. 1,862 claims, $2.31M billed.
Pattern97% of affected claims have a blank referring provider NPI on the claim.
EvidenceDenial Pareto, department split, and blank-NPI count attached. No patient identifiers.
Suspected causeNew scheduling workflow does not require the referring provider for self-scheduled imaging.
Splitting CARC 16 by department and blank NPI
SELECT  d.department_name,
        COUNT(DISTINCT c.claim_id) AS claims,
        SUM(CASE WHEN r.carc_code = '16' THEN 1 ELSE 0 END) AS carc16_claims,
        SUM(CASE WHEN r.carc_code = '16' AND c.referring_npi IS NULL THEN 1 ELSE 0 END) AS carc16_blank_npi
FROM    rc_claims c
JOIN    departments d ON d.department_id = c.department_id
LEFT JOIN rc_remits r ON r.claim_id = c.claim_id
WHERE   c.created_date >= '2026-08-15' AND c.created_date < '2026-10-01'
GROUP BY d.department_name
HAVING  SUM(CASE WHEN r.carc_code = '16' THEN 1 ELSE 0 END) > 0
ORDER BY carc16_claims DESC;
Department Claims CARC 16 claims Blank referring NPI
Outpatient imaging 27,014 1,862 1,806
Outpatient lab 41,330 212 9
Cardiology clinic 9,848 88 4

The pattern was clear: 1,806 of 1,862 imaging denials had no referring NPI. The build team made the field required for self-scheduled imaging and added a pre-bill edit. Denials for that code returned to baseline within five weeks. Reworking the 1,862 claims took revenue integrity staff most of a month, which is the real cost of the defect.

Illustrative figures
The health system, ticket number, and counts are a composite from denial investigations. The method holds: Pareto by reason, split by department, then test one suspected field.

A related lesson: tie every denial initiative to the earliest stage that can prevent it. A rework team can recover claims. Only a front-end edit can stop the next 1,800.

Scenario: Underpayments Hidden Inside Routine Contractual Adjustments

A hospital’s patient accounting system auto-adjusted any payment shortfall under $50 as a contractual write-down. The team assumed the payer’s fee schedule drove every shortfall. A contract analyst suspected that one Medicare Advantage plan was paying below its contracted rate.

I ran Pattern 8 on six months of claims for the plan, joined to the contract rate table with effective dates. The shortfall was small per claim and consistent. Individually, none of them crossed the work-queue threshold.

Metric Value
Claims reviewed (Apr to Sept) 14,206
Claims with shortfall over $5 3,127 (22.0%)
Average shortfall per affected claim $23.80
Total shortfall $74,420
Claims over the $50 auto-adjust limit 41

Total shortfall reached $74,420, and most of it sat below the write-down limit. The pattern pointed to a rate table loaded with last year’s conversion factor. The contracting team confirmed the error, the payer reprocessed the claims, and revenue cycle added a monthly underpayment report to the close checklist.

Two cautions apply. Contract terms include carve-outs, stop-loss rules, and multiple procedure reductions that a simple rate lookup cannot model. Treat Pattern 8 output as leads for a contract analyst, not as proof. And confirm the expected amount against the payer’s own explanation of payment before you dispute anything.

Decision Tree: Where Is the Cash Stuck?

When A/R rises or collections fall, walk the stages in order. Stop at the first stage that explains the movement.

A/R up or collections down
Locate the stage before proposing a fix

1. Are accounts unbilled? (DNFB, charge lag)
Patterns 3 and 4. Owner: coding, charge capture

2. Are claims rejected before the payer?
Pattern 5. Owner: billing edits, clearinghouse

3. Are adjudicated claims denied?
Pattern 6. Owner: front end and denial management

4. Are paid claims underpaid?
Pattern 8. Owner: contracting, payer relations

5. Patient balances, credits, timely filing (Patterns 10, 11)

Most of the time I find the cause by stage two or three. Underpayment and credit balances take longer to surface, because no one looks for them until a contract review or audit.

Building the Denial Reference Table

Every denial query depends on a mapping from reason codes to categories. Hard-coded lists inside queries drift, and each analyst keeps a slightly different copy. Build the mapping once, as a governed table.

Governed mapping of reason codes to categories and owners
CREATE TABLE ref.denial_code_map (
    carc_code        VARCHAR(10)   NOT NULL,
    adjustment_group VARCHAR(2)    NOT NULL,      -- CO, PR, OA, PI
    is_denial        BIT           NOT NULL,      -- 0 for routine contractual write-downs
    category         VARCHAR(40)   NOT NULL,      -- Registration, Authorization, Coding, Clinical, Payer error
    owner_team       VARCHAR(40)   NOT NULL,
    effective_from   DATE          NOT NULL,
    effective_to     DATE          NULL,
    CONSTRAINT pk_denial_code_map PRIMARY KEY (carc_code, adjustment_group, effective_from)
);

INSERT INTO ref.denial_code_map VALUES
 ('16',  'CO', 1, 'Registration', 'Patient Access',  '2020-01-01', NULL),
 ('50',  'CO', 1, 'Clinical',     'Utilization Review', '2020-01-01', NULL),
 ('197', 'CO', 1, 'Authorization','Patient Access',  '2020-01-01', NULL),
 ('45',  'CO', 0, 'Contractual',  'Contracting',     '2020-01-01', NULL);

The category and owner columns turn a Pareto chart into an action list. The date columns handle changes, such as a payer that begins using a different code for the same problem. Review the table each quarter with revenue integrity, and log each change.

Seed values here are examples. Confirm the category for each code with your denial management team. Some organizations route code 16 to coding, others to registration, depending on which field is missing.

Tracing One Denied Claim from Charge to Remittance

Aggregates tell you where to look. A single traced claim tells you what happened. Pick a denied claim from your Pareto top code and follow it through each stage.

Step Question Where to look What a defect looks like
1. Registration Was coverage active and the plan correct on the date of service? Account and coverage tables Wrong payer, inactive plan, missing subscriber ID
2. Authorization Was an authorization required and on file? Referral and authorization records No authorization number on the account
3. Charge capture Did every service produce a charge on the right date? Charge transactions Missing charge, wrong procedure code, late posting
4. Coding Do diagnosis and procedure codes support each other? Coding abstract or claim lines Missing modifier, diagnosis not linked
5. Claim build Did the claim carry required fields such as referring NPI? Claim header and line tables Blank required field
6. Submission Did the clearinghouse accept the file? Clearinghouse acknowledgments Rejected for format or eligibility
7. Remittance What did the payer say? Remittance lines with reason codes Denial code and remark code

Use a test account or an approved sample of three to five claims. For privacy, work inside the secured reporting environment and record only claim IDs in your notes. The scenario above used this walk on five imaging claims, and all five stopped at step five.

Validating KPI Queries Against the Billing System’s Own Reports

Your SQL becomes trusted when it matches the numbers the billing system or finance already reports. Run the comparison before you publish, and keep the evidence. A table like the one below goes into the test record.

KPI Your SQL result System or finance report Difference Cause
Gross charges, Sept $41,206,310 $41,206,310 $0 Exact match
Payments posted, Sept $18,442,905 $18,447,105 -$4,200 One batch posted Oct 1 with a Sept deposit date
Net A/R, Sept 30 $62,880,115 $62,880,115 $0 Exact match
Days in A/R 47.2 46.9 0.3 Revenue window: SQL uses 90 days, report uses 3 calendar months
Denials, Q3 (claims) 3,418 3,502 -84 Report counts CARC 45 on 84 claims; SQL excludes them

Every difference needs an owner and a reason. Differences with a known cause are not defects. Differences without one stop the release. The figures above are illustrative, and the pattern is real: most gaps trace to a window, a cutoff, or a code list.

Scheduling KPI Rollups Without Rebuilding History Each Night

KPI queries over several years of transactions get slow. Pre-aggregate to a daily rollup table and refresh only recent days. Closed history stays untouched, which also helps reproducibility.

Incremental rollup, 35-day refresh window
-- Refresh the last 35 days of the rollup, leave older days alone
DELETE FROM rpt.rc_daily_rollup WHERE post_date >= DATEADD(day, -35, CAST(GETDATE() AS date));

INSERT INTO rpt.rc_daily_rollup (post_date, payer_id, tx_type, tx_count, tx_amount)
SELECT  t.post_date, t.payer_id, t.tx_type, COUNT(*), SUM(t.tx_amount)
FROM    rc_transactions t
WHERE   t.post_date >= DATEADD(day, -35, CAST(GETDATE() AS date))
GROUP BY t.post_date, t.payer_id, t.tx_type;

The 35-day window catches late postings and corrections. Choose a window that covers your longest normal posting delay, and run a monthly full reconciliation of the rollup to the source to catch drift. Always record the refresh time in the rollup so reports can show data currency.

Template: KPI Definition Sheet for Business Analysts

Copy this sheet for each KPI before development begins. Two minutes spent on the “trap” rows save weeks of reconciliation later.

Field Question Example: Days in A/R
Name and owner Who is accountable for the number? Days in A/R, VP Revenue Cycle
Formula Numerator and denominator in words Net A/R divided by average daily net revenue
Scope Hospital, professional, or both? Which entities? Hospital billing, all facilities
Window Which period feeds the denominator? Trailing 90 days, ending the as-of date
Revenue basis Gross, net of contractuals, or cash? Net of contractual adjustments
Inclusions Which statuses and balances count? All open balances, credits shown separately
Exclusions What is removed, and who approved it? Bad debt placed with collections; approved by CFO
Grain One row per what? Account
Refresh and cutoff When does the number lock? Business day 5 after month end
Source and tie-out What must it reconcile to? GL control account for patient A/R
Benchmark What is the comparison and its source? Internal target; peer data from an approved vendor

Denial work queue specification

Element Specification
Trigger Remittance line with an adjustment group and reason code that map to “denial” in the reference table
Priority Dollar amount, then days to appeal deadline
Routing Reason code category to team: registration, coding, clinical, contracting
Required fields Claim ID, payer, reason code, denied amount, appeal deadline, last action date
Exit criteria Paid, appealed with outcome, or written off with approved reason
Metrics Overturn rate, recovery dollars, days to resolution

The reference table that maps reason codes to denial categories is a business asset. Version it, name an owner, and review it every quarter. The X12 Claim Adjustment Reason Codes list changes over time, and new codes will appear in your remittance files before anyone updates your table.

Role Checklists for Revenue Cycle Analytics

Business Analyst

  • Complete the KPI definition sheet with the finance owner before design starts.
  • Name the stage, owner, and escalation path for each denial category.
  • Capture sign conventions for charges, payments, and adjustments in the requirement.
  • Add the contract and timely filing tables to the data inventory.

QA Analyst

  • Reconcile account balances to the ledger control account for at least two months.
  • Test accounts with credit balances, rebills, and secondary payers.
  • Check that corrected claims do not count as first-pass successes.
  • Test the aging boundaries at 30, 60, 90, and 120 days.

Data Analyst

  • Report medians and percentiles for lag metrics, not averages alone.
  • Cohort collections by service or discharge month.
  • Keep payer rules and contract rates in dated reference tables.
  • Aggregate before export, and strip identifiers from shared files.

Common Revenue Cycle SQL Mistakes

Mistake Effect Fix
Flipped sign on payments and adjustments Collection rates invert or exceed 100% Confirm the convention with a known account
Mixing hospital and professional billing Duplicate or missing revenue Label and report them separately
Counting rebills in first-pass rate Clean claim rate looks better than it is Filter to original submissions
Treating CARC 45 as a denial Denial rate inflated by routine write-downs Maintain a reference table of denial codes
Aging from the wrong date Wrong team gets the work First bill date for payer performance; service date for filing
Comparing payment to expected without patient share Every copay flagged as underpaid Add patient responsibility to the received amount
Using today’s contract rate for old claims Wrong shortfall Join on the effective date range
Reporting totals without cohorts Backlog distorts collections Cohort by service or discharge month
The mistake I made once
I reported a denial rate of 14 percent for a clinic and the manager was alarmed. I had counted routine contractual adjustments with reason code 45 as denials. The real rate was 5 percent. Always confirm which reason codes your organization treats as denials before you publish a rate.

When I Would Use SQL and When I Would Use Other Tools

Need Best tool Why
Root-cause analysis on a KPI move SQL Split by any dimension, trace to the source
Daily work queues for billers The billing application’s work queues Embedded in the workflow and security model
Executive KPI dashboard BI tool on a governed model Consistent definitions and access control
Quick self-service volume questions Epic SlicerDicer or similar Fast for clinicians and managers, limited for finance logic
Clearinghouse rejection trends Clearinghouse analytics Closest to the submission data
Contract modeling for payment accuracy Contract management system Handles carve-outs and stop-loss rules SQL cannot

I use SQL when I need to prove a cause or reconcile to the ledger. I avoid it for work queues, where the billing system already enforces routing and audit trails. Practice patterns like these on safe data, and see the SQL for IT Analysts hub for related guides.

Edge Cases That Break Clean Revenue Cycle Logic

Edge case Why it breaks a clean query How to handle it
Secondary and tertiary payers One account has several expected payments Model payer sequence; report by payer level
Rebills and corrected claims Several claims for one service Flag original versus replacement; link by account
Late charges and retroactive rate changes Posted amounts change after billing Use effective dates and keep an as-of view
Split billing One stay produces several claims Aggregate to the account before computing rates
Capitated or bundled payments No per-claim expected amount Exclude from underpayment logic; track separately
Patient responsibility timing Deductible resets and balance billing lag Separate payer A/R from patient A/R
Write-offs reversed later Net effect hides the original adjustment Report gross adjustments and reversals separately
Charity care and financial assistance Not a denial, not a bad debt Use a distinct transaction type

PHI and Revenue Cycle Data

Account numbers, service dates, and payer IDs identify patients when combined. Use account-level data inside the secured environment, and aggregate before you share. The HIPAA-safe Clarity SQL article covers minimum necessary access, small-cell suppression, and masked views. Standard electronic transactions, which CMS oversees under CMS HIPAA administrative simplification, carry their own security requirements.

Questions Analysts Ask About Revenue Cycle SQL

What is a good days in A/R number?

It depends on payer mix, service lines, and definition. Compare against your own trend and peer data that uses the same formula. A number without its definition has no meaning.

Where do denial reason codes come from?

From the electronic remittance advice, which carries standard claim adjustment reason codes and remark codes. Your billing system or clearinghouse loads them into reporting tables.

Should I use charges or net revenue for KPIs?

Use net revenue for A/R metrics and gross charges for volume and charge lag. State the basis next to the number.

How often should I rebuild denial reports?

Weekly for work queues and monthly for trend analysis. Remittances arrive continuously, so a weekly view catches build defects while the volume is still small.

Run This Before Your Next Revenue Cycle Review

Pick the KPI your leaders discuss most. Write its definition in one sentence on the report itself, then run the Pareto in Pattern 6 for the last quarter. If one reason code drives more than a fifth of the lost dollars, trace it to its origin stage before the meeting. One well-traced code is worth more than a dashboard of averages.

Download the Revenue Cycle KPI SQL Pack and Denial Analysis Worksheet (PDF)
Seven KPI definitions, eleven query patterns, stuck-cash decision tree, and a denial analysis worksheet on two printable pages.

Download the PDF

Sources and Further Reading

Table names are generic, with Epic mapping hints marked as likely. Confirm each in your own data dictionary. Contract and coding rules vary by payer, so involve revenue integrity and contracting before acting on any query result.

Scroll to Top