mdatool
Healthcare Data Dictionary for the Modern Data Stack
LibraryBlogPricing
mdatool
mdatool

The healthcare data dictionary for dbt, Snowflake, Databricks, and BigQuery. 100,000+ ISO-11179 standard terms, free SQL tools, and AI data modeling.

HIPAA-AlignedEnterprise Ready

Tools

  • SQL Linter
  • DDL Converter
  • Bulk Sanitizer
  • Naming Auditor
  • Name Generator
  • AI Data Modeling
  • HCC Calculator
  • Data Model Canvas

Library

  • Glossary
  • Guides
  • Blog

Company

  • About
  • Contact
  • Pricing

Account

  • Sign Up Free
  • Sign In
  • Upgrade to Pro
  • Dashboard

Legal

  • Privacy Policy
  • Terms of Service

© 2026 mdatool. All rights reserved.

Built for healthcare data teams.

HomeBlogPharmacy DataPharmacy Data Model: Prescription & Dispensing Schema Guide
Pharmacy Data

Pharmacy Data Model: Prescription & Dispensing Schema Guide

The complete data model for pharmacy claims, prescription management, and PBM data — including NDC normalization, DEA number validation, and dispensing fact table design for Snowflake and Databricks.

mdatool Team·May 29, 2026·11 min read
pharmacy data modelNDCPBMprescriptiondispensingDEA numberSnowflakeDatabricks

Why Pharmacy Data Is Uniquely Complex

Pharmacy data sits at the intersection of clinical care, regulatory compliance, and financial reconciliation. A data engineer building a pharmacy data warehouse must simultaneously handle NDC code normalization (the same drug arrives in three different formats from different sources), PBM adjudication (real-time at the point of dispensing through a chain involving pharmacy, PBM, and payer), DEA controlled substance compliance, and days supply validation (PBM feeds frequently contain clinically impossible values that corrupt adherence metrics).

This guide covers the core tables, reference data, and data quality logic for a production-grade pharmacy data warehouse on Snowflake or Databricks.


NDC Code: The Drug Identifier

The NDC code (National Drug Code) is the FDA's universal drug product identifier. It is an 11-digit number with three segments: labeler (manufacturer), product, and package. The format problem is that NDC codes appear in multiple forms across different systems:

FormatExampleDescription
5-4-2 segmented12345-6789-01Standard labeled format with dashes
11-digit flat12345678901No dashes, left-padded to 11 digits
10-digit legacy1234567890Missing leading zero in one segment
11-digit with leading zeros00093-0058-01Manufacturer-padded with dashes

Always normalize to 11-digit no-dash format at ingestion. This is the canonical form used in CMS reference files and drug crosswalks.

-- NDC normalization — BigQuery implementation
CREATE OR REPLACE FUNCTION util.normalize_ndc(raw_ndc STRING)
RETURNS STRING AS (
  CASE
    WHEN REGEXP_CONTAINS(REPLACE(raw_ndc, '-', ''), r'^\d{10,11}$')
      THEN LPAD(REPLACE(raw_ndc, '-', ''), 11, '0')
    ELSE NULL  -- return null for truly invalid NDCs
  END
);

-- Apply at ingestion — flag unnormalizable NDCs for review
SELECT
    raw_ndc,
    util.normalize_ndc(raw_ndc) AS ndc_11,
    COUNT(*) AS occurrences
FROM staging.pbm_claims_raw
GROUP BY raw_ndc, ndc_11
HAVING ndc_11 IS NULL
ORDER BY occurrences DESC;

Core Pharmacy Data Model

Drug Reference Table

The drug reference table is your foundational lookup. Load it from the FDA NDC directory (published weekly) and supplement with therapeutic class data from your formulary vendor.

CREATE TABLE pharmacy.drug_ref (
  ndc_11              VARCHAR(11)    NOT NULL,
  proprietary_nm      VARCHAR(255)   NOT NULL,
  nonproprietary_nm   VARCHAR(255),
  dosage_form         VARCHAR(50),
  route               VARCHAR(50),
  strength            VARCHAR(100),
  labeler_nm          VARCHAR(255),
  dea_schedule_cd     VARCHAR(5),                -- CII, CIII, CIV, CV, or null
  gpi_cd              VARCHAR(14),               -- Generic Product Identifier
  therapeutic_class   VARCHAR(100),
  is_generic_ind      BOOLEAN        DEFAULT FALSE,
  fda_approval_dt     DATE,
  market_dt           DATE,
  discontinued_dt     DATE,
  ndc_refresh_dt      DATE           NOT NULL,
  PRIMARY KEY (ndc_11)
);

While ERwin requires a complex setup for schema generation, you can generate clean DDL in seconds using our free converter.

Convert your first 5 DDLs — No Credit Card Required

Pharmacy Claims (Dispensing) Fact Table

CREATE TABLE pharmacy.rx_claim (
  rx_clm_id           VARCHAR(50)    NOT NULL,
  mbr_id              VARCHAR(50)    NOT NULL,
  pharmacy_npi        VARCHAR(10)    NOT NULL,
  prescriber_npi      VARCHAR(10)    NOT NULL,
  prescriber_dea_nbr  VARCHAR(9),               -- required for controlled substances
  ndc_11              VARCHAR(11)    NOT NULL,
  fill_dt             DATE           NOT NULL,
  written_dt          DATE,
  days_supply         SMALLINT       NOT NULL,
  qty_dispensed       NUMBER(10,3)   NOT NULL,
  refill_nbr          SMALLINT       DEFAULT 0,
  daw_cd              SMALLINT,                  -- Dispense As Written code (0-9)
  billed_amt          NUMBER(12,2)   NOT NULL,
  ingredient_cost     NUMBER(12,2),
  dispensing_fee      NUMBER(6,2),
  copay_amt           NUMBER(8,2),
  plan_paid_amt       NUMBER(12,2),
  clm_sts_cd          VARCHAR(20)    NOT NULL,   -- PAID, REVERSED, REJECTED
  reject_cd           VARCHAR(5),
  rx_nbr              VARCHAR(20),
  plan_id             VARCHAR(20)    NOT NULL,
  formulary_tier      SMALLINT,
  pbm_id              VARCHAR(20),
  PRIMARY KEY (rx_clm_id)
);

Prescription (Written Rx) Table

This is distinct from the dispensing fact. A prescription may be written but never filled, or filled multiple times as refills.

CREATE TABLE pharmacy.rx_prescription (
  rx_nbr              VARCHAR(20)    NOT NULL,
  mbr_id              VARCHAR(50)    NOT NULL,
  prescriber_npi      VARCHAR(10)    NOT NULL,
  prescriber_dea_nbr  VARCHAR(9),
  ndc_11              VARCHAR(11)    NOT NULL,
  written_dt          DATE           NOT NULL,
  qty_prescribed      NUMBER(10,3)   NOT NULL,
  days_supply_ord     SMALLINT       NOT NULL,
  refills_authorized  SMALLINT       DEFAULT 0,
  sig_text            VARCHAR(500),
  PRIMARY KEY (rx_nbr)
);

DEA Number Validation

The DEA number identifies prescribers authorized to prescribe controlled substances. Format: 2 letters + 7 digits. The 7th digit is a check digit: sum digits 1, 3, 5 of the numeric suffix; sum digits 2, 4, 6 and double; add both results; the last digit of the total must equal digit 7.

-- DEA check-digit validation (Snowflake)
CREATE OR REPLACE FUNCTION util.validate_dea(dea VARCHAR)
RETURNS BOOLEAN
LANGUAGE JAVASCRIPT
AS
$$
  if (!/^[A-Za-z]{2}\d{7}$/.test(DEA)) return false;
  const n = DEA.slice(2);
  const sum = (parseInt(n[0]) + parseInt(n[2]) + parseInt(n[4]))
            + 2 * (parseInt(n[1]) + parseInt(n[3]) + parseInt(n[5]));
  return sum % 10 === parseInt(n[6]);
$$;

-- Flag invalid DEA numbers on controlled substance fills
SELECT
    r.rx_clm_id,
    r.prescriber_dea_nbr,
    r.ndc_11,
    d.dea_schedule_cd
FROM pharmacy.rx_claim r
JOIN pharmacy.drug_ref d USING (ndc_11)
WHERE d.dea_schedule_cd IS NOT NULL
  AND r.prescriber_dea_nbr IS NOT NULL
  AND NOT util.validate_dea(r.prescriber_dea_nbr);

Days Supply Validation and Medication Adherence

Days supply is critical for adherence analytics (PDC, MPR, HEDIS measures) — and one of the most commonly corrupted fields. PBM feeds contain values of 999, 0, and negative numbers. Build validation into ingestion.

-- Days supply outlier analysis by drug class
SELECT
    d.therapeutic_class,
    MIN(r.days_supply)     AS min_days,
    MAX(r.days_supply)     AS max_days,
    PERCENTILE_CONT(0.99) WITHIN GROUP (ORDER BY r.days_supply) AS p99_days,
    COUNT(*) FILTER (WHERE r.days_supply > 365) AS gt_365_count,
    COUNT(*) FILTER (WHERE r.days_supply <= 0)  AS invalid_count
FROM pharmacy.rx_claim r
JOIN pharmacy.drug_ref d USING (ndc_11)
WHERE r.clm_sts_cd = 'PAID'
GROUP BY d.therapeutic_class
ORDER BY gt_365_count DESC;

Standard caps by drug class: maintenance medications (antihypertensives, statins, diabetes agents) should be capped at 90 days supply per fill. Specialty drugs are typically 30 days. Any value above 365 should be treated as a data error and excluded from adherence calculations.


Medication Adherence (PDC) Calculation

Proportion of Days Covered (PDC) is the standard HEDIS adherence metric. It requires a timeline approach, not a simple average of days supply, because fills can overlap.

-- Build coverage timeline, then calculate PDC
WITH coverage AS (
    SELECT
        mbr_id,
        ndc_11,
        fill_dt                                    AS start_dt,
        DATEADD('day', days_supply - 1, fill_dt)   AS end_dt
    FROM pharmacy.rx_claim
    WHERE clm_sts_cd = 'PAID'
      AND fill_dt BETWEEN '2025-01-01' AND '2025-12-31'
),
covered_days AS (
    SELECT
        mbr_id,
        ndc_11,
        DATEDIFF('day',
            GREATEST(start_dt, '2025-01-01'),
            LEAST(end_dt,      '2025-12-31')
        ) + 1 AS days_covered
    FROM coverage
    WHERE end_dt >= '2025-01-01'
      AND start_dt <= '2025-12-31'
)
SELECT
    mbr_id,
    ndc_11,
    SUM(days_covered)                                  AS total_covered_days,
    365                                                AS measurement_period,
    ROUND(LEAST(SUM(days_covered), 365) / 365.0, 3)   AS pdc
FROM covered_days
GROUP BY mbr_id, ndc_11
ORDER BY pdc;

Frequently Asked Questions

What is the correct format for NDC codes in a data warehouse?

Always store NDC codes as 11-digit no-dash strings, left-padded with zeros. This is the canonical format used by the FDA NDC directory, CMS drug reference files, and NCPDP standards. Normalizing at ingestion prevents duplicate drug records caused by the same NDC appearing as 10-digit, 11-digit, and dash-formatted variants from different source systems.

What does the DAW code mean on a pharmacy claim?

DAW (Dispense As Written) is a single-digit code indicating whether the prescriber authorized generic substitution. DAW 0 means no instruction — the pharmacist may dispense a generic. DAW 1 means the prescriber specifically requested the brand product. DAW 2 means the patient requested brand. DAW codes 3–9 cover regulatory and pharmacy-specific reasons. This field matters for formulary analytics, generic substitution rates, and PBM contract performance.

How should I handle pharmacy claim reversals?

Store reversals as separate records with clm_sts_cd = 'REVERSED' linked to the original paid claim via rx_clm_id. For adherence calculations, exclude reversed fills from the coverage timeline. For financial reporting, net reversals against paid amounts rather than deleting original records. Pharmacy reversals are far more frequent than medical claim reversals because real-time adjudication errors are caught and corrected within days.

What is the difference between a prescription and a pharmacy claim?

A prescription (rx_prescription) is the written order from a prescriber authorizing a dispensing. A pharmacy claim (rx_claim) is the adjudicated record of a specific fill at a specific pharmacy on a specific date. One prescription generates multiple claims: the original fill plus each authorized refill. A prescription may also be written but never filled — these exist only in the prescription table, which makes linking them via rx_nbr essential for medication non-adherence analysis.

M

mdatool Team

The mdatool team builds free engineering tools for healthcare data architects, analysts, and engineers working across payer, provider, and life sciences data.

Related Guides

Pharmacy Benefits Management

PBM integration, formulary management, and pharmacy claims processing.

Read Guide

NDC Codes: National Drug Codes

Drug identification with NDC codes, labeler segments, and pharmacy data.

Read Guide

Key Terms in This Article

prescription birth dateprescription scheduled datendc descriptionndc surgery dateprescription onset datendc plan

Free Tools

Free SQL Linter

Catch SQL bugs, performance issues, and naming violations before production.

Try it free

Ready to improve your data architecture?

Free tools for DDL conversion, SQL analysis, naming standards, and more.

Get Started Free

Get weekly healthcare data engineering tips

Practical guides on data modeling, SQL standards, and healthcare domain conventions — straight to your inbox.

No spam. Unsubscribe any time.

On this page

  • Why Pharmacy Data Is Uniquely Complex
  • NDC Code: The Drug Identifier
  • Core Pharmacy Data Model
  • Drug Reference Table
  • Pharmacy Claims (Dispensing) Fact Table
  • Prescription (Written Rx) Table
  • DEA Number Validation
  • Days Supply Validation and Medication Adherence
  • Medication Adherence (PDC) Calculation
  • Frequently Asked Questions
  • What is the correct format for NDC codes in a data warehouse?
  • What does the DAW code mean on a pharmacy claim?
  • How should I handle pharmacy claim reversals?
  • What is the difference between a prescription and a pharmacy claim?

Share

Share on XShare on LinkedIn

Engineering Tools

Convert DDL, lint SQL, and audit naming conventions — free.

Explore Tools