Model electronic medical records with temporal versioning. Store patient histories, lab results, and medication logs with time-ordered clinical data.

Note
This tutorial assumes you have a running 3-node Ferrosa cluster. If you have not set one up yet, follow the cluster-setup guide first — it only takes about 5 minutes.

Create the Keyspace

Every Ferrosa project starts with a keyspace. Think of it as a namespace that groups your tables together and tells the cluster how many copies of your data to keep. For healthcare data, we definitely want redundancy.

Connect to your cluster and run:

CREATE KEYSPACE IF NOT EXISTS healthcare
  WITH replication = {
    'class': 'SimpleStrategy',
    'replication_factor': 3
  };

With a replication factor of 3, every piece of patient data is stored on all three nodes. If one node goes down, your records are still fully available. That is exactly the kind of reliability you want for medical data.

Design the Schema

We will create four tables that work together to model a patient’s medical record. Each table is designed around the queries you will run most often.

Patients

The patients table stores demographic information. Each patient gets a unique UUID, and we use a set for allergies since a patient can have zero or many.

CREATE TABLE IF NOT EXISTS patients (
    patient_id      uuid PRIMARY KEY,
    name            text,
    dob             date,
    blood_type      text,
    allergies       set<text>,
    emergency_contact text
);

Clinical Events

Clinical events form the patient’s timeline: admissions, discharges, procedures, consultations. We cluster by event_time in descending order so the most recent events come back first — exactly what a nurse or doctor needs.

CREATE TABLE IF NOT EXISTS clinical_events (
    patient_id  uuid,
    event_time  timestamp,
    event_id    uuid,
    event_type  text,
    department  text,
    provider    text,
    notes       text,
    PRIMARY KEY (patient_id, event_time, event_id)
) WITH CLUSTERING ORDER BY (event_time DESC, event_id ASC);

Lab Results

Lab results are keyed by patient and collection time. The abnormal flag makes it easy to filter for results that need attention.

    patient_id      uuid,
    collected_at    timestamp,
    test_name       text,
    result_value    text,
    unit            text,
    reference_range text,
    abnormal        boolean,
    PRIMARY KEY (patient_id, collected_at)
) WITH CLUSTERING ORDER BY (collected_at DESC);

Medications

The medications table tracks current and past prescriptions. We partition by patient and cluster by medication name so you can look up any specific drug quickly.

    patient_id    uuid,
    prescribed_at timestamp,
    medication    text,
    dosage        text,
    frequency     text,
    prescriber    text,
    active        boolean,
    PRIMARY KEY (patient_id, medication)
);

That is four tables, each optimized for a different access pattern.

Register Patients

Let’s add three patients. Notice how we use uuid() to generate unique identifiers and curly braces for the set of allergies.

-- ── Patients ──

INSERT INTO patients (patient_id, name, dob, blood_type, allergies, emergency_contact)
VALUES (11111111-1111-1111-1111-111111111111,
        'Maria Chen', '1985-03-14', 'A+',
        {'penicillin', 'sulfa'}, 'James Chen (spouse) 555-0142');

INSERT INTO patients (patient_id, name, dob, blood_type, allergies, emergency_contact)
VALUES (22222222-2222-2222-2222-222222222222,
        'Robert Okafor', '1972-11-28', 'O-',
        {'latex'}, 'Amara Okafor (daughter) 555-0198');

INSERT INTO patients (patient_id, name, dob, blood_type, allergies, emergency_contact)
VALUES (33333333-3333-3333-3333-333333333333,
        'Priya Sharma', '1998-07-02', 'B+',

Priya has no known allergies, so we pass an empty set {}. Ferrosa stores that efficiently — no wasted space.

Record Clinical Events

Now let’s build out a realistic timeline. We will add events across multiple departments for each patient. Each event captures who, what, where, and when.

-- ── Clinical Events ──

-- Maria Chen: ER visit and follow-up
INSERT INTO clinical_events (patient_id, event_time, event_id, event_type, department, provider, notes)
VALUES (11111111-1111-1111-1111-111111111111, '2026-03-10 08:15:00', uuid(), 'admission', 'Emergency', 'Dr. Williams', 'Patient presents with acute abdominal pain, onset 6 hours ago');

INSERT INTO clinical_events (patient_id, event_time, event_id, event_type, department, provider, notes)
VALUES (11111111-1111-1111-1111-111111111111, '2026-03-10 09:30:00', uuid(), 'imaging', 'Radiology', 'Dr. Park', 'CT abdomen with contrast ordered, pending penicillin allergy review');

INSERT INTO clinical_events (patient_id, event_time, event_id, event_type, department, provider, notes)
VALUES (11111111-1111-1111-1111-111111111111, '2026-03-10 11:00:00', uuid(), 'procedure', 'Surgery', 'Dr. Reyes', 'Laparoscopic appendectomy performed without complications');

INSERT INTO clinical_events (patient_id, event_time, event_id, event_type, department, provider, notes)
VALUES (11111111-1111-1111-1111-111111111111, '2026-03-11 07:00:00', uuid(), 'discharge', 'Surgery', 'Dr. Reyes', 'Discharged in stable condition, follow-up in 2 weeks');

INSERT INTO clinical_events (patient_id, event_time, event_id, event_type, department, provider, notes)
VALUES (11111111-1111-1111-1111-111111111111, '2026-03-15 14:00:00', uuid(), 'consultation', 'Primary Care', 'Dr. Nguyen', 'Post-op follow-up, incision healing well, no signs of infection');

-- Robert Okafor: cardiology workup
INSERT INTO clinical_events (patient_id, event_time, event_id, event_type, department, provider, notes)
VALUES (22222222-2222-2222-2222-222222222222, '2026-03-08 10:00:00', uuid(), 'consultation', 'Cardiology', 'Dr. Adams', 'Annual cardiac evaluation, patient reports occasional palpitations');

INSERT INTO clinical_events (patient_id, event_time, event_id, event_type, department, provider, notes)
VALUES (22222222-2222-2222-2222-222222222222, '2026-03-08 11:30:00', uuid(), 'diagnostic', 'Cardiology', 'Tech. Rivera', '12-lead ECG performed, sinus rhythm, no acute changes');

INSERT INTO clinical_events (patient_id, event_time, event_id, event_type, department, provider, notes)
VALUES (22222222-2222-2222-2222-222222222222, '2026-03-09 08:00:00', uuid(), 'diagnostic', 'Cardiology', 'Tech. Rivera', '24-hour Holter monitor placed');

INSERT INTO clinical_events (patient_id, event_time, event_id, event_type, department, provider, notes)
VALUES (22222222-2222-2222-2222-222222222222, '2026-03-12 15:00:00', uuid(), 'consultation', 'Cardiology', 'Dr. Adams', 'Holter results normal, palpitations likely benign, follow-up in 6 months');

-- Priya Sharma: sports medicine
INSERT INTO clinical_events (patient_id, event_time, event_id, event_type, department, provider, notes)
VALUES (33333333-3333-3333-3333-333333333333, '2026-03-05 16:45:00', uuid(), 'admission', 'Emergency', 'Dr. Kim', 'Right ankle injury during soccer match, significant swelling');

INSERT INTO clinical_events (patient_id, event_time, event_id, event_type, department, provider, notes)
VALUES (33333333-3333-3333-3333-333333333333, '2026-03-05 17:30:00', uuid(), 'imaging', 'Radiology', 'Dr. Park', 'X-ray right ankle: no fracture, grade 2 lateral sprain');

INSERT INTO clinical_events (patient_id, event_time, event_id, event_type, department, provider, notes)
VALUES (33333333-3333-3333-3333-333333333333, '2026-03-05 18:15:00', uuid(), 'discharge', 'Emergency', 'Dr. Kim', 'RICE protocol, air cast fitted, follow-up with sports medicine');

INSERT INTO clinical_events (patient_id, event_time, event_id, event_type, department, provider, notes)
VALUES (33333333-3333-3333-3333-333333333333, '2026-03-12 09:00:00', uuid(), 'consultation', 'Sports Medicine', 'Dr. Torres', 'Swelling reduced, starting physical therapy, 4-6 week recovery');

That is 15 clinical events across three patients. Notice how each patient’s events tell a coherent medical story — this is exactly how real EMR data looks.

Store Lab Results

Lab results are the backbone of clinical decision-making. Let’s add a realistic panel for each patient.

INSERT INTO clinical_events (patient_id, event_time, event_id, event_type, department, provider, notes)
VALUES (33333333-3333-3333-3333-333333333333, '2026-03-17 09:00:00', uuid(), 'therapy', 'Physical Therapy', 'PT. Marshall', 'First PT session, range of motion exercises, patient tolerating well');

-- ── Lab Results ──

-- Maria Chen: pre-surgical and post-op labs
INSERT INTO lab_results (patient_id, collected_at, test_name, result_value, unit, reference_range, abnormal)
VALUES (11111111-1111-1111-1111-111111111111, '2026-03-10 08:30:00', 'WBC', '14.2', 'x10^9/L', '4.5-11.0', true);

INSERT INTO lab_results (patient_id, collected_at, test_name, result_value, unit, reference_range, abnormal)
VALUES (11111111-1111-1111-1111-111111111111, '2026-03-10 08:30:01', 'Hemoglobin', '13.8', 'g/dL', '12.0-16.0', false);

INSERT INTO lab_results (patient_id, collected_at, test_name, result_value, unit, reference_range, abnormal)
VALUES (11111111-1111-1111-1111-111111111111, '2026-03-10 08:30:02', 'CRP', '48.5', 'mg/L', '0-10', true);

INSERT INTO lab_results (patient_id, collected_at, test_name, result_value, unit, reference_range, abnormal)
VALUES (11111111-1111-1111-1111-111111111111, '2026-03-11 06:00:00', 'WBC', '9.1', 'x10^9/L', '4.5-11.0', false);

-- Robert Okafor: cardiac panel
INSERT INTO lab_results (patient_id, collected_at, test_name, result_value, unit, reference_range, abnormal)
VALUES (22222222-2222-2222-2222-222222222222, '2026-03-08 10:15:00', 'Total Cholesterol', '242', 'mg/dL', '<200', true);

INSERT INTO lab_results (patient_id, collected_at, test_name, result_value, unit, reference_range, abnormal)
VALUES (22222222-2222-2222-2222-222222222222, '2026-03-08 10:15:01', 'LDL', '158', 'mg/dL', '<100', true);

INSERT INTO lab_results (patient_id, collected_at, test_name, result_value, unit, reference_range, abnormal)
VALUES (22222222-2222-2222-2222-222222222222, '2026-03-08 10:15:02', 'Troponin I', '0.02', 'ng/mL', '<0.04', false);

-- Priya Sharma: routine bloods
INSERT INTO lab_results (patient_id, collected_at, test_name, result_value, unit, reference_range, abnormal)

Notice Maria’s elevated WBC and CRP on admission — classic markers for appendicitis. Her post-op WBC came back to normal. That is the kind of trend these time-ordered tables make easy to spot.

Track Medications

Let’s record current and past prescriptions. The active column lets you quickly filter for what a patient is taking right now.

INSERT INTO lab_results (patient_id, collected_at, test_name, result_value, unit, reference_range, abnormal)
VALUES (33333333-3333-3333-3333-333333333333, '2026-03-05 17:00:01', 'Platelets', '285', 'x10^9/L', '150-400', false);

INSERT INTO lab_results (patient_id, collected_at, test_name, result_value, unit, reference_range, abnormal)
VALUES (33333333-3333-3333-3333-333333333333, '2026-03-05 17:00:02', 'D-Dimer', '0.3', 'mg/L', '<0.5', false);

-- ── Medications ──

-- Maria Chen: post-surgical meds
INSERT INTO medications (patient_id, prescribed_at, medication, dosage, frequency, prescriber, active)
VALUES (11111111-1111-1111-1111-111111111111, '2026-03-11 07:00:00', 'Acetaminophen', '500mg', 'every 6 hours', 'Dr. Reyes', true);

INSERT INTO medications (patient_id, prescribed_at, medication, dosage, frequency, prescriber, active)
VALUES (11111111-1111-1111-1111-111111111111, '2026-03-11 07:00:00', 'Cephalexin', '500mg', 'twice daily', 'Dr. Reyes', true);

INSERT INTO medications (patient_id, prescribed_at, medication, dosage, frequency, prescriber, active)
VALUES (11111111-1111-1111-1111-111111111111, '2026-03-10 09:00:00', 'Morphine IV', '4mg', 'as needed', 'Dr. Williams', false);

-- Robert Okafor: cardiac meds
INSERT INTO medications (patient_id, prescribed_at, medication, dosage, frequency, prescriber, active)
VALUES (22222222-2222-2222-2222-222222222222, '2026-03-08 12:00:00', 'Atorvastatin', '20mg', 'once daily', 'Dr. Adams', true);

INSERT INTO medications (patient_id, prescribed_at, medication, dosage, frequency, prescriber, active)

Notice that Maria is on Cephalexin (a cephalosporin) instead of penicillin-based antibiotics — her allergy record in the patients table flagged that. In a real system, you would enforce this with application-level checks.

Query Clinical Data

Now for the rewarding part. Let’s run the kinds of queries that doctors, nurses, and analysts use every day.

Patient Timeline

Pull up everything that happened to a patient, most recent first. Because we used CLUSTERING ORDER BY (event_time DESC), this is extremely fast.

-- Full clinical timeline for Maria Chen
SELECT event_time, event_type, department, provider, notes
FROM clinical_events
WHERE patient_id = 11111111-1111-1111-1111-111111111111;

Recent Lab Results

Get the most recent labs for a patient. The LIMIT clause keeps it manageable.

-- Last 5 lab results for Robert Okafor
SELECT collected_at, test_name, result_value, unit, reference_range, abnormal
FROM lab_results
WHERE patient_id = 22222222-2222-2222-2222-222222222222
LIMIT 5;

Active Medications

What is a patient currently taking? Filter by active = true to get only current prescriptions.

-- Current medications for Maria Chen
SELECT medication, dosage, frequency, prescriber
FROM medications
WHERE patient_id = 11111111-1111-1111-1111-111111111111
  AND active = true
Important
About ALLOW FILTERING: We need this because active is not part of the primary key. In production, you might create a separate active_medications table to avoid filtering. For now, with small datasets, it works just fine.

Abnormal Lab Results

Quickly find results that are outside the normal range — the ones that need a doctor’s attention.

-- All abnormal results for Maria Chen
SELECT collected_at, test_name, result_value, unit, reference_range
FROM lab_results
WHERE patient_id = 11111111-1111-1111-1111-111111111111
  AND abnormal = true

Clinical Events by Type

Filter the timeline to just procedures, just imaging, or any other event type you need.

-- All imaging events for Priya Sharma
SELECT event_time, provider, notes
FROM clinical_events
WHERE patient_id = 33333333-3333-3333-3333-333333333333

Patient Lookup

Sometimes the simplest query is the most important one. When a patient arrives, the first thing staff need is their demographic information and allergy list.

ALLOW FILTERING;

-- Look up Maria Chen's full profile
SELECT name, dob, blood_type, allergies, emergency_contact

This returns instantly because patient_id is the partition key. No matter how many millions of patients your system holds, this lookup hits exactly one partition on one node.

Pull events from a specific date range. This is useful for generating discharge summaries or insurance claim documentation.

WHERE patient_id = 11111111-1111-1111-1111-111111111111;

-- Maria's clinical events from March 10-11 (hospital stay)
SELECT event_time, event_type, department, provider, notes
FROM clinical_events
WHERE patient_id = 11111111-1111-1111-1111-111111111111

Because event_time is a clustering column, Ferrosa can efficiently seek to the start of the range and scan forward. No full-partition scan needed.

All Medications for a Patient

For a complete medication reconciliation — both active and discontinued — simply omit the filter on active.

  AND event_time <= '2026-03-11 23:59:59';

-- Full medication history for Priya Sharma
SELECT medication, dosage, frequency, prescriber, active, prescribed_at
Tip
Schema design insight: Notice how each table serves a different audience. The patients table is for registration desks. The clinical_events table is for doctors reviewing a timeline. The lab_results table is for lab technicians and nurses. The medications table is for pharmacists. Designing tables around who reads the data — not just who writes it — is a core Ferrosa pattern.

What You Learned

You just built the foundation of an electronic medical records system. Here is what you accomplished:

  • Created a replicated keyspace with RF=3 for data durability critical to healthcare

  • Designed a temporal schema with descending clustering order for instant access to the most recent events

  • Used CQL collection types (sets) to model multi-valued fields like allergies

  • Built a patient timeline that returns events in reverse chronological order without sorting

  • Wrote analytical queries to filter abnormal lab results and active medications

The data model you built here scales to millions of patients. Each patient’s data lives on the same partition, so lookups are always fast regardless of how large the cluster grows. That is the power of partition-based modeling.