Drugs and formularies

NLM RxClass — drug → class membership

drug_silver.fact_rxclass_membership·live·silver·static·Drug Classification (RxClass)
Rows
N/A
Columns
22
History
static to static (single snapshot, vintage rxclass_2026-06-03)
Card
yesgrain, traps, filters
Typed tools
0

What it is

  1. 1A drug-classification crosswalk: it maps each clinical drug class (ATC anatomical/therapeutic groups, FDA Established Pharmacologic Class, and Mechanism of Action) to the RxNorm drug-ingredient concepts (rxcui) that belong to that class, sourced from the NIH/NLM RxClass API.
  2. 2In the cost-data substrate it is the dimension that lets you roll commercial/Medicare drug prices up to therapeutic class or mechanism (e.g. "all platinum chemo agents", "all ATC L01 antineoplastics") rather than analyzing one rxcui at a time.

Grain

KeyGrain
class_idrxcui
  1. 1One row per (class_id, rxcui) pair — i.e. one row for each membership of a single RxNorm drug ingredient in a single drug class.
  2. 235,389 rows = 35,389 distinct class_id×rxcui pairs (verified).
  3. 3A drug appears in as many rows as it has classes (up to 16+); a class appears in as many rows as it has member drugs.

How to read it

  1. 1To roll drug prices up to a therapeutic class or mechanism: join a price fact (e.g.
  2. 2ASP/NADAC by rxcui) to this table on rxcui, then GROUP BY class_id/class_type.
  3. 3Example — list all RxNorm ingredients in ATC subgroup L01 (antineoplastics): SELECT DISTINCT rxcui, rxnorm_name FROM fact_rxclass_membership WHERE class_type='ATC1-4' AND class_id='L01'; this returns the 277 drugs that define the 'chemo' cohort you would then price.
  4. 4Use rela='DIRECT' to restrict to primary assignments.
  5. 5Remember the rxcui join fans out across classes, so deduplicate/aggregate prices before summing.

Traps

  1. 1MINIMAL GRAIN is just (class_id, rxcui) — 35,389 distinct = full row count. The profiler's reported 6-column key (adding source_id, bronze_object_key, s3_version_id, drug_source_version_id) is a SUPERSET inflated by 4 constant/NULL provenance columns; they add zero discrimination. Do not key or join on them.
  2. 2drug_source_version_id is 100% NULL — never use it as a key or join column despite its name and its appearance in the auto-profiler key.
  3. 3This is a single static snapshot: every temporal/provenance column (observed_period, asserted_at, effective_period_start='2026-06-03', effective_period_end='2099-12-31', cms_vintage_sequence=1, vintage_tag) holds exactly one value, and is_current is true for 100% of rows (no is_current=false rows exist). There is NO time series and is_current-filtering is currently a no-op — but it is bitemporally structured so future reloads will add vintages.
  4. 4Many-to-many fan-out is intentional and large: a drug (rxcui) can be in 16+ classes and one class can have thousands of drugs. Joining this table to a price/fact table on rxcui MULTIPLIES rows by the number of classes — aggregate (e.g. price first, then roll up by class_id) or you will double-count spend.
  5. 5class_id is a MULTI-SCHEME code: ATC1-4 rows use short ATC alphanumeric codes (incl. pseudo-code '0' = the 'all drugs' root bucket holding all 3,112 ATC drugs) while EPC/MOA rows use NDF-RT identifiers like 'N0000175413'. Always interpret class_id together with class_type; the same string space is not shared across schemes. Note 'ATC1-4' means levels 1-4 only (no level-5 chemical-substance codes).
  6. 6rela_source is fully redundant with class_type (ATC1-4->ATC, EPC/MOA->DAILYMED) and class_type is fully determined by class_id — treat class_type/rela_source as derived labels, not independent filter dimensions. Use rela='DIRECT' to get only primary class assignments vs the larger INDIRECT (hierarchy-inferred) set.

Columns (22)

ColumnTypeMeaning
class_idstringThe drug-class identifier (the class side of the membership). Code SYSTEM depends on class_type: for ATC1-4 it is an ATC code — 1 char = anatomical main group (e.g. 'A','N','L','J','C'), 3 chars = therapeutic subgroup (e.g. 'L01'), 4-5 chars = deeper ATC level (no level-5 substance codes present), plus a pseudo-code '0' = the ATC root/'all drugs' bucket (3,112 members, the single largest class); for EPC and MOA it is an NDF-RT/NUI identifier of the form 'N0000175413'. 2,295 distinct classes. NOT unique on its own.
class_typestringWhich classification system the class belongs to. Exactly 3 values: 'ATC1-4' (WHO Anatomical Therapeutic Chemical, levels 1-4; 17,783 rows / 1,039 classes), 'EPC' (FDA Established Pharmacologic Class; 10,584 rows / 677 classes), 'MOA' (Mechanism of Action; 7,022 rows / 579 classes). Strictly functionally determined by class_id (one class_id maps to exactly one class_type) — it is a derived attribute, not part of the key.
rxcuistringRxNorm Concept Unique Identifier of the drug (the drug side of the membership), stored as a numeric string e.g. '2555'=cisplatin. 4,159 distinct drugs. These are ingredient-level concepts (see rxnorm_tty: IN/PIN/MIN), the join key to other drug_silver price/identity tables. NOT unique on its own.
rxnorm_namestringHuman-readable RxNorm name of the rxcui, e.g. 'cisplatin', 'doxorubicin', 'dutasteride / tamsulosin'. Functionally determined by rxcui (1:1). Convenience label, not a key.
rxnorm_ttystringRxNorm term type of the rxcui — the granularity of the drug concept. 3 values: 'IN' = ingredient (32,404 rows, the bulk), 'PIN' = precise ingredient (2,149), 'MIN' = multiple ingredient / combination drug (836). Determined by rxcui. Tells you the membership is at ingredient level, not packaged-product (NDC) level.
relastringRelationship directness of the drug-to-class link as reported by RxClass. 2 values: 'INDIRECT' (27,568) = the drug is assigned to the class via the RxNorm hierarchy/transitive relationship; 'DIRECT' (7,821) = the class is asserted directly on the drug. Functionally determined by (class_id, rxcui). Filter to rela='DIRECT' if you want only primary/explicit class assignments.
rela_sourcestringSource pipeline RxClass used to derive the membership. 2 values, strictly determined by class_type: 'ATC' (17,783) for all ATC1-4 rows; 'DAILYMED' (17,606) for all EPC and MOA rows (FDA structured-product-label / DailyMed source). Redundant with class_type — not an independent dimension.
observed_perioddateDate the data was observed/snapshotted. Constant '2026-06-03' for every row (single snapshot). Provenance/bitemporal field, not analytical.
asserted_atdateDate this assertion was recorded into the silver table. Constant '2026-06-03'. Provenance.
effective_period_startdateStart of the bitemporal validity window. Constant '2026-06-03'. Provenance; despite being the profiler's 'time column', it carries no real history (1 distinct value).
effective_period_enddateEnd of the validity window. Constant '2099-12-31' = open/current sentinel (this snapshot is the live one). Provenance.
cms_vintage_sequenceintVintage ordinal for this source. Constant 1 (only one vintage loaded). Provenance; would increment on a re-load.
parse_revisionintParser revision number. Constant 1. Provenance.
parser_artifact_hashstringSHA-256 of the parser artifact that produced these rows. Single constant value. Provenance/lineage.
source_urlstringOrigin endpoint. Constant 'https://rxnav.nlm.nih.gov/REST/rxclass' (NIH/NLM RxNav RxClass REST API). Confirms the authoritative source.
sha256_content_hashstringSHA-256 of the raw bronze source content. Single constant value (one source file). Provenance/dedup key.
source_idstringLogical source identifier. Constant 'nih_rxclass'. Provenance; the profiler listed it in the 'unique key' only because it is constant (1 distinct), not because it discriminates rows.
is_currentbooleanSnapshot-currency flag. Constant true for ALL 35,389 rows; zero rows have is_current=false. The table holds only the current vintage — superseded snapshots are not retained here — so filtering on is_current is a no-op today (but keep it for forward compatibility).
bronze_object_keystringS3 key of the bronze source object, e.g. 'drug/bronze/nih_rxclass/2026-06-03/...json'. Single constant value. Provenance; the profiler put it in the 'unique key' only because it is constant.
s3_version_idstringS3 object version id of the bronze file. Single constant value. Provenance.
vintage_tagstringHuman vintage label, constant 'rxclass_2026-06-03'. Provenance; the readable handle for this snapshot.
drug_source_version_idstring100% NULL across all 35,389 rows (0 distinct). An unpopulated lineage passthrough. TRAP: the profiler included it in its 'verified unique key', but a column that is entirely NULL cannot be part of a real grain — ignore it.

Query it

describe_data_source(source_fqn="drug_silver.fact_rxclass_membership")     # the card, live
query_substrate(sql="SELECT * FROM drug_silver.fact_rxclass_membership WHERE class_id = '<value>' AND rxcui = '<value>' LIMIT 100")
ItemValue
Typed tools that read itnone; raw SQL
Identity keyrxcui
Joinhow_to_join(source_fqn="drug_silver.fact_rxclass_membership")
Raw doorAthena (read-only via query_substrate): drug_silver.fact_rxclass_membership

This page as markdown: /docs-md/data/drug_silver_fact_rxclass_membership.md · every page in one file: /llms-full.txt