Browse Templates/Lab Management System

Lab Management System

A comprehensive laboratory information management system (LIMS) database schema for clinical and diagnostic laboratories. Covers patient intake, test catalog with reference ranges, sample collection and tracking with barcodes, detailed test results with abnormal flagging, and lab technician assignment. Suitable for building hospital lab systems, independent diagnostic centers, or pathology lab management software.

PostgreSQL5 tablesHealthcare
healthcarelaboratorydiagnosticstest-resultslimssamples
Created with ERD Studio

About this schema

The sample is the physical object the whole system tracks, and the barcode is its identity. samples carries the barcode, the sample_type, who collected it and a status, because from the moment a tube is filled everything that happens is about that tube — not about the patient directly and not about the test yet.

One sample produces many results. test_results keys on both the sample and the test, so a single blood draw can be run for a dozen analytes without drawing blood a dozen times. Each result carries its own status, because one test on the sample can be complete while another is still running.

Technicians appear twice, in different roles. samples.collected_by records who took it, and test_results.technician_id records who ran the analysis — and they are usually different people. Keeping both is what makes a result traceable from the needle to the number.

Key relationships

patients → samples → test_results
The chain from person to number. A result reaches the patient through the sample, never directly, so no number exists without the specimen behind it.
tests → test_results
The catalogue supplies the name, code, unit and reference_range; the result supplies the measured value for one sample.
lab_technicians → samples (collected_by)
Who drew the specimen. Collection errors are a real cause of bad results, so the person is recorded at that step too.
lab_technicians → test_results (technician_id)
Who performed and signed off the analysis — a different accountability from collection, and usually a different person.
tests.turnaround_hours
The promised time per test, which is what makes an overdue result detectable before a clinician has to ask about it.

Design decisions

Results attach to the sample, not the patient

Keying results directly to the patient is shorter and loses the specimen. Two draws on the same day can give different results, a haemolysed sample invalidates everything run on it, and a mislabelled tube has to be traceable to every number it produced. The sample in the middle is what makes all three answerable, at the cost of one more join on every patient-facing query.

The reference range lives on the test catalogue

Unlike a clinical record that freezes the range onto each result, this is a laboratory's own system where the range is a property of the method it currently runs. That keeps one definition to maintain and means changing a method updates every pending result's interpretation. The trade is explicit: historical results reinterpret against the current range unless the laboratory versions its tests.

is_abnormal is stored alongside the value

Comparing result_value to a range at read time fails as soon as a value is non-numeric — positive, negative, trace, or a value below the detection limit. A stored flag records the judgement that was made, works for every result type, and lets a list of abnormal results be an indexed filter rather than a parse of every row.

Status exists on both the sample and the result

They answer different questions. The sample's status tracks the physical object — collected, received, rejected. The result's status tracks the analysis — pending, complete, verified. One column could not say that a sample has arrived while three of its five tests are still running, which is the normal state of any working laboratory.

Tables in this template

patientsPatients
8 cols
testsTests
8 cols
lab_techniciansLab Technicians
7 cols
samplesSamples
7 cols
test_resultsTest Results
9 cols

Frequently asked

Why does the sample have a barcode as well as an id?
Because the id is for the database and the barcode is for the tube. The label is what gets scanned at every bench, and it has to survive being printed, frozen and read by equipment that knows nothing about this schema.
How do I find overdue tests?
Compare the sample's collected_at plus the test's turnaround_hours against now, for results still in a pending status. That is the point of storing turnaround per test rather than treating all tests the same.
Can one sample be used for tests from different categories?
Yes — test_results is a join between sample and test, so a single specimen supports as many analyses as the material allows. The limit is physical, not schematic.
How is a rejected sample handled?
Through the sample's status rather than deletion. A haemolysed or insufficient specimen must stay on record so the recollection is explainable and so any results already produced from it can be invalidated.
Where are orders from a doctor recorded?
Not in this schema. There is no requisition table, so what was requested versus what was run is not represented. An orders table pointing at patient and test is the first addition for a laboratory that accepts external referrals.