Chuyển đến nội dung chính

Lesson 4: PERSON table — Patient identity management

PERSON table structure, required fields (person_id, gender_concept_id, year_of_birth), demographic data, link with LOCATION and PROVIDER, ETL conventions for Vietnamese data.

🏗️ Architecture — Lesson 4 PERSON table — Management Patient identity OMOP CDM 5.4 for Beginners — Understand A to Z Part 2: Person & Visit — Data platform xdev.asia

PERSON — the heart of OMOP CDM, connecting all clinical panels

Introduction

PERSON is the central table of the entire OMOP CDM — every clinical table references PERSON person_id. This is where the patient's demographic information is stored.

Each line in PERSON = a unique patient (unique person).


1. PERSON table structure

1.1. Full column list

ColumnTypeRequiredDescription
person_idINTEGER✅ PKUnique ID for each patient
gender_concept_idINTEGER✅Gender (Standard Concept)
year_of_birthINTEGER✅Year of birth
month_of_birthINTEGERMonth of birth
day_of_birthINTEGERDate of birth
birth_datetimeDATETIMEFull date and time of birth
race_concept_idINTEGER✅Race (Standard Concept)
ethnicity_concept_idINTEGER✅Ethnicity (Standard Concept)
location_idINTEGERFKAddress (reference LOCATION)
provider_idINTEGERFKPrimary Physician (reference PROVIDER)
care_site_idINTEGERFKMedical facility (reference CARE_SITE)
person_source_valueVARCHAR(50)Original patient code from HIS
gender_source_valueVARCHAR(50)Original gender (eg: "Nu", "F")
gender_source_concept_idINTEGEROriginal Gender ID Concept
race_source_valueVARCHAR(50)Origin Race
race_source_concept_idINTEGEROrigin Race ID Concept
ethnicity_source_valueVARCHAR(50)Ethnic origin
ethnicity_source_concept_idINTEGERConcept ethnic ID

1.2. Entity-Relationship

  ┌──────────────┐       ┌──────────────┐       ┌──────────────┐
  │   LOCATION   │←──────│    PERSON    │──────→│   PROVIDER   │
  │ location_id  │       │  person_id   │       │ provider_id  │
  │ address_1    │       │  gender_*    │       │ provider_name│
  │ city         │       │  birth_*     │       │ specialty_*  │
  │ state        │       │  race_*      │       └──────────────┘
  │ zip          │       │  ethnicity_* │              ↑
  │ country_*    │       │  location_id │              │
  └──────────────┘       │  provider_id │       ┌──────┴───────┐
                         │  care_site_id│──────→│  CARE_SITE   │
                         └──────┬───────┘       │ care_site_id │
                                │               │ care_site_name│
                    ┌───────────┼───────────┐   └──────────────┘
                    ↓           ↓           ↓
             VISIT_OCC.    CONDITION    DRUG_EXPOSURE
             OBSERVATION   MEASUREMENT  ... (tất cả clinical)

2. Detailed important fields

2.1. person_id

  • Type: INTEGER, Primary Key
  • Rule: Unique, no change, no clinical significance
  • Not the original patient code (original patient code stored in person_source_value)
-- ĐÚNG: person_id là số tự tăng hoặc hash
person_id = 100001
person_source_value = 'BN-2024-00123'  -- Mã gốc từ HIS

-- SAI: Không dùng mã gốc làm person_id
-- person_id = 'BN-2024-00123'  ← SAI (phải là INTEGER)

2.2. gender_concept_id

Concept IDConcept NameDescription
8507MaleMale
8532FemaleFemale
8551UNKNOWNUnknown
8521OTHEROther
-- Ví dụ ETL cho dữ liệu Việt Nam
CASE
    WHEN gioi_tinh IN ('Nam', 'M', '1') THEN 8507    -- Male
    WHEN gioi_tinh IN ('Nữ', 'Nu', 'F', '2') THEN 8532  -- Female
    ELSE 8551  -- UNKNOWN
END AS gender_concept_id,
gioi_tinh AS gender_source_value

2.3. year_of_birth, month_of_birth, day_of_birth

  • year_of_birth: Required — if not present, do not load patient
  • month_of_birth, day_of_birth: Optional — set NULL if not present
  • birth_datetime: Optional — useful for pediatrics (accurate age calculation)
-- Ví dụ: BN sinh ngày 15/03/1980
year_of_birth  = 1980
month_of_birth = 3
day_of_birth   = 15
birth_datetime = '1980-03-15 00:00:00'

2.4. race_concept_id and ethnicity_concept_id

These are two schools according to US Census standards. For Vietnam data:

SchoolRecommendations for Vietnam
race_concept_id8515 (Asian)
race_source_value"Kinh", "Tay", "Muong"...
ethnicity_concept_id0 (No matching concepts)
ethnicity_source_valueRecord ethnic origin if any

Note: race and ethnicity in OMOP according to American standards (OMB). When ETL VN data, we still have to set the value (use 0 if it cannot be mapped) but keep the original information in *_source_value.


3. Real-life example

3.1. Vietnamese patient

INSERT INTO person VALUES (
    100001,                    -- person_id
    8532,                      -- gender_concept_id (Female)
    1980,                      -- year_of_birth
    3,                         -- month_of_birth
    15,                        -- day_of_birth
    '1980-03-15 00:00:00',     -- birth_datetime
    8515,                      -- race_concept_id (Asian)
    0,                         -- ethnicity_concept_id (N/A)
    1001,                      -- location_id (→ LOCATION table)
    5001,                      -- provider_id (→ PROVIDER table)
    2001,                      -- care_site_id (→ CARE_SITE table)
    'BN-2024-00123',           -- person_source_value
    'Nữ',                      -- gender_source_value
    0,                         -- gender_source_concept_id
    'Kinh',                    -- race_source_value
    0,                         -- race_source_concept_id
    NULL,                      -- ethnicity_source_value
    0                          -- ethnicity_source_concept_id
);

3.2. Basic SQL queries

-- Đếm bệnh nhân theo giới tính
SELECT
    c.concept_name AS gender,
    COUNT(*) AS patient_count
FROM person p
JOIN concept c ON p.gender_concept_id = c.concept_id
GROUP BY c.concept_name;

-- Phân bố tuổi
SELECT
    EXTRACT(YEAR FROM CURRENT_DATE) - year_of_birth AS age,
    COUNT(*) AS count
FROM person
GROUP BY 1
ORDER BY 1;

-- Tìm bệnh nhân có dữ liệu gốc
SELECT
    person_id,
    person_source_value AS ma_bn_goc,
    gender_source_value AS gioi_tinh_goc,
    race_source_value AS dan_toc
FROM person
WHERE person_source_value IS NOT NULL
LIMIT 10;

4. ETL Conventions

4.1. Important rule

RulesDetails
1 person = 1 recordNo duplication, need deduplicate
year_of_birth requiredIgnore patient if there is no year of birth
gender_concept_id requiredPut 8551 (UNKNOWN) if unknown
person_id has no meaningDo not use original patient code or ID card/CCCD
Does not store PII directlyThere are no columns for name, ID card, or phone number in PERSON

4.2. De-identification

OMOP CDM does not have columns for name, ID/CCCD number, phone number. This is intentional design:

  HIS gốc (có PII):                    OMOP CDM (de-identified):
  ┌─────────────────────────┐          ┌──────────────────────────┐
  │ ma_bn: BN-2024-00123    │    →     │ person_id: 100001        │
  │ ho_ten: Nguyễn Thị Lan  │    →     │ (không có cột tên!)      │
  │ cmnd: 079123456789      │    →     │ (không có cột CMND!)     │
  │ sdt: 0901234567         │    →     │ (không có cột SĐT!)     │
  │ ngay_sinh: 15/03/1980   │    →     │ year_of_birth: 1980      │
  │ gioi_tinh: Nữ           │    →     │ gender_concept_id: 8532  │
  └─────────────────────────┘          └──────────────────────────┘

Note: person_source_value may contain the original patient code (for tracing). Depending on the organization, this value can be hashed or encrypted.

4.3. Handling duplicates

When a patient has ≥2 codes in multiple systems:

  HIS BV Chợ Rẫy: BN-CR-001   ┐
  HIS BV Bạch Mai: BN-BM-555   ├──→ person_id = 100001
  BHXH: DN-7900123456789        ┘    (1 person duy nhất)

ETL needs to perform Patient Matching (patient matching) before loading into PERSON.


5. Relationships with other tables

-- Tất cả dữ liệu lâm sàng của 1 bệnh nhân
SELECT 'Visits' AS data_type, COUNT(*) AS count
FROM visit_occurrence WHERE person_id = 100001
UNION ALL
SELECT 'Conditions', COUNT(*)
FROM condition_occurrence WHERE person_id = 100001
UNION ALL
SELECT 'Drugs', COUNT(*)
FROM drug_exposure WHERE person_id = 100001
UNION ALL
SELECT 'Measurements', COUNT(*)
FROM measurement WHERE person_id = 100001
UNION ALL
SELECT 'Observations', COUNT(*)
FROM observation WHERE person_id = 100001;

6. Common ETL errors

ErrorConsequencesHow to fix
Duplicate person_idOverwrite dataCheck for unique constraints
year_of_birth = NULLViolation of NOT NULLIgnore or impute
gender_concept_id is wrongMisgender analysisAccurate Mapping
Put PII in source_valueViolation of de-identificationHash or remove
Do not duplicate1 patient becomes many peoplePatient matching before ETL

Summary

  1. PERSON is the central table — all clinical tables referenced person_id
  2. Required fields: person_id, gender_concept_id, year_of_birth, race_concept_id, ethnicity_concept_id
  3. Does not contain PII (name, ID card, phone number) — de-identified design
  4. Link: LOCATION (address), CARE_SITE (facility), PROVIDER (main doctor)
  5. ETL VN: gender mapping, race = Asian (8515), ethnicity = 0

Next article: OBSERVATION_PERIOD — why it's important to know the "monitoring period" and how it affects every analysis.


References