What OMOP actually is¶
OMOP is usually taught as a schema. That framing produces learners who can name the tables and still cannot make a good decision, because the schema is the visible part and the vocabulary is the load-bearing part.
So this chapter teaches OMOP as a set of commitments about what health data is for.
The one-sentence version¶
The OMOP Common Data Model is a relational structure in which every clinical event is recorded as a row carrying a person, a date, and a standardized concept identifier, so that the same analysis can run unchanged against data from different institutions.
The current reference version is OMOP CDM v5.4, stable since 2021 and supported across the OHDSI tool suite. Version 6.0 exists but is not fully supported by the tools and the community guidance is to stay on 5.4. An incremental update to the model has been in development; check the OHDSI CommonDataModel repository for its current status before starting new work.
The commitments¶
Before the tables, the ideas. OMOP makes four commitments, and every awkward part of the model follows from one of them.
One. Analysis should be portable. A study designed at one institution should run unmodified at another. This is why the model standardizes vocabulary rather than just structure. Structural standardization alone would let two sites store the same clinical idea under different codes, and the query would silently return different populations.
Two. The person and the date are the spine. Every clinical row has a person_id and a date. That is what makes it possible to assemble a longitudinal picture from data that arrived as disconnected fragments.
Three. The original data should remain visible. For each standardized field there is a corresponding source field. The model does not ask you to throw away what you started with, because the translation needs to be auditable.
Four. Absence of evidence must be distinguishable from evidence of absence. This is why OBSERVATION_PERIOD exists, and it is the commitment that FHIR has no equivalent for.
The six categories of tables¶
OMOP CDM v5.4 has roughly forty tables. They fall into six groups, and knowing the groups is more useful than memorizing the list.
Clinical data¶
Where the events live. These are the tables your transformation actually fills.
| Table | Holds |
|---|---|
PERSON |
One row per person. Demographics only, deliberately minimal |
OBSERVATION_PERIOD |
The spans during which a person was under observation |
VISIT_OCCURRENCE |
Care episodes: an inpatient stay, an office visit, an emergency visit |
VISIT_DETAIL |
Finer granularity within a visit: a ward transfer, an ICU stay within an admission |
CONDITION_OCCURRENCE |
Diagnoses and problems |
DRUG_EXPOSURE |
Anything a person was exposed to as a drug, from any source |
PROCEDURE_OCCURRENCE |
Procedures performed |
DEVICE_EXPOSURE |
Devices used or implanted |
MEASUREMENT |
Structured values with a result: laboratory tests, vital signs |
OBSERVATION |
Clinical facts that are not the above: social history, family history, allergies, many survey answers |
DEATH |
Date and cause of death |
NOTE, NOTE_NLP |
Clinical text and its extracted structure |
SPECIMEN |
Samples collected |
FACT_RELATIONSHIP |
Explicit links between rows in other tables |
Health system data¶
Context about where and by whom.
LOCATION, CARE_SITE, PROVIDER.
Health economics¶
PAYER_PLAN_PERIOD, COST. Frequently empty in EHR-derived instances, and that is acceptable.
Standardized derived elements¶
Computed rather than loaded. DRUG_ERA, DOSE_ERA, CONDITION_ERA, EPISODE, EPISODE_EVENT, COHORT.
An era collapses many overlapping exposures into a continuous period. If a person filled the same prescription eleven times, DRUG_EXPOSURE has eleven rows and DRUG_ERA may have one. You do not populate these from FHIR; you generate them after loading.
Vocabulary¶
The tables that make the whole thing work. CONCEPT, VOCABULARY, DOMAIN, CONCEPT_CLASS, CONCEPT_RELATIONSHIP, RELATIONSHIP, CONCEPT_SYNONYM, CONCEPT_ANCESTOR, SOURCE_TO_CONCEPT_MAP, DRUG_STRENGTH.
You do not build these. You download them from Athena. They are the same for everyone, which is precisely the point.
Metadata¶
CDM_SOURCE, METADATA. Small, easy to skip, and the first thing a careful collaborator looks at. Fill them in.
The column pattern that repeats everywhere¶
Once you see this pattern, most of the model becomes predictable. Take CONDITION_OCCURRENCE:
| Column | Purpose |
|---|---|
condition_occurrence_id |
Surrogate key |
person_id |
Who |
condition_concept_id |
The standard concept. This is what analyses query |
condition_start_date, condition_start_datetime |
When it began |
condition_end_date, condition_end_datetime |
When it resolved, often null |
condition_type_concept_id |
Provenance. Where this record came from: EHR problem list, claim, registry |
condition_status_concept_id |
Clinical status, where the source supports it |
stop_reason |
Free text |
provider_id, visit_occurrence_id, visit_detail_id |
Context |
condition_source_value |
The original code as text. Exactly as it appeared |
condition_source_concept_id |
The original code as a concept. The non-standard concept representing the source code |
condition_status_source_value |
The original status text |
Four of those columns deserve emphasis because they are where the meaning lives.
condition_concept_id is what you analyze. It is always a standard concept, or zero if no mapping was found.
condition_source_concept_id is what you started with. It is usually a non-standard concept, and preserving it is what makes your translation auditable. A reviewer can ask "what did ICD-10-CM E11.9 become in your instance" and get an answer.
condition_source_value is the raw string, kept because concepts occasionally fail to capture local coding quirks.
condition_type_concept_id is provenance, and it is the column most often filled in carelessly. It answers "how do I know this". A condition from a billing claim and a condition from an actively maintained problem list have different reliability, and researchers need to be able to tell them apart. This column is where the four FHIR medication resources become distinguishable after they all land in DRUG_EXPOSURE.
Every clinical table follows this same pattern with its domain's noun substituted. Learn it once.
Concept ID zero¶
When a source code cannot be mapped to a standard concept, the _concept_id column gets 0, not null.
Zero is a real value meaning "we looked and found nothing". It is queryable, countable, and reportable. A well-run instance tracks its rate of zeros as a quality metric, because a rising zero rate means the vocabulary has drifted from the source data.
Zero is also a trap. Rows with concept_id = 0 are invisible to any cohort definition, because cohort definitions are built from concept sets and no concept set contains zero. Data can be present, loaded, counted in your row totals, and completely absent from every study. This is the failure mode most likely to make a rare disease disappear from an otherwise healthy instance.
Where this quietly breaks¶
PERSON is deliberately thin and people try to widen it. There is no column for marital status, language, or education. Those belong in OBSERVATION as dated facts, because they change over time. Adding columns to PERSON breaks tool compatibility and defeats the portability commitment.
Type concepts get filled in mechanically. A pipeline that assigns the same _type_concept_id to every row has thrown away provenance. Reconstructing it later means going back to source.
Dates and datetimes coexist and disagree. v5.4 has both _date and _datetime columns. Populating them inconsistently produces analyses that give different answers depending on which column the analyst reached for.
Era tables get loaded instead of generated. They are derived. Loading them directly from source produces something that looks like an era table and does not behave like one.
Empty tables are fine, missing tables are not. The convention is that all tables should exist in an OMOP CDM instance even if unpopulated. Tools expect them.
Next¶
- Two shapes, one reality, the two models side by side
- Concepts and domains, the highest-leverage chapter on this site