Linkr
Home Resources Tools Documentation Blog Demo
FR
  • What is Linkr?
  • Deployment modes
  • Quick start
  • Local install
  • With Docker
  • Manual install
  • Client-only
  • Your first project
  • Linkr in a clinical data warehouse
  • Workspaces and projects
  • The data pipeline
  • Entities and sharing
  • Versioning and collaboration
  • Overview
  • Projects
  • Wiki
  • Plugins
  • Members and roles
  • Settings
  • Schemas
  • Getting and exploring
  • Mapping
  • Databases
  • Derived sub-databases
  • Data quality
  • Data catalog
  • Build and publish
  • Anonymize
  • SQL script collections
  • ETL pipelines
  • Building and running
  • Generating the scripts
  • Overview
  • Mapping projects
  • Global view
  • Target concepts
  • Mapping editor
  • Suggestions
  • AI agent
  • Evaluation
  • Export
  • Overview
  • Databases
  • Concepts
  • Cohorts
  • Building
  • Results, SQL and report
  • Patient data
  • Pipeline
  • Datasets
  • IDE
  • Web apps
  • Versioning
  • Overview
  • Tabs and widgets
  • Built-in widgets
  • Analysis widgets
  • Control charts (SPC)
  • Surveys and eCRF
  • R and Python code
  • Filters, settings and export
  • Overview
  • Presentation mode
  • Exporting a report
  • Agents
  • MCP server
  • Skills
  • Import and export
  • Git versioning
  • Community catalog
  • Publishing content
  • Production install
  • Configuration
  • Authentication and permissions
  • Files on the server
  • Backup and restore
  • Contributing code
  • Glossary
  • Keyboard shortcuts
  • Release notes
Documentation Data warehouse Mapping

Mapping a schema

Giving a database's tables a role: the patient, stay, note, concept, event and drug relations, filled in through a form or written in SQL, the contract each one honours, and the databases that copy this mapping and override it.

Summary

A schema’s mapping tells Linkr where a database keeps its patients, stays, notes, concepts, events and drugs. Each of them is a relation: a query that returns a fixed list of columns, its contract. A relation is filled in through a form when one table is enough, or written in SQL when it takes joins — a check then verifies that it honours its contract.

Client Available in client-only mode — runs entirely in the browser, no backend. Backend Available with the FastAPI backend.

Relations, not tables

Linkr’s pages never query person or patients directly. They query relations with fixed names — linkr_patient, linkr_visit, linkr_event_measurements… — that always return the same columns, whatever the database’s model: patient_id, birth_date, gender for patients; visit_id, start_datetime, end_datetime for hospitalizations. That list of columns, with their types, is the relation’s contract.

Your tables

person

death

The database’s model, whatever it is.

mapping →

linkr_patient

patient_id · birth_date · gender · death_datetime…

Always the same columns: the contract.

→

Linkr

Patient data, cohorts, statistics, catalog…

Reads the relations only.

The mapping is how each relation is produced from your tables. An OMOP database and a MIMIC database produce the same relations by different routes — and the same dashboard works on both.

The Mapping tab

On a schema’s page, the Mapping tab opens on the Source view, the one with the forms; the Diagram view places the relations on the table diagram. It is read-only until you click Edit, top right; Save — or Cmd/Ctrl + S — commits.

The sub-tabs sort the relations by clinical subject, each with its colour, the same as in the diagram. All shows them one after the other.

demo.linkr.interhop.org
DiagramSource
Patient
Patientslinkr_patient
Tableperson
LEFT JOIN death d ON d.person_id = p.person_id
Columns
patient_id*
person_id
birth_date
birth_datetime
birth_yearⓘ
year_of_birth
gender_source_value
gender_source_value
death_datetime
d.death_datetime
Gender values

Raw values of gender_source_value meaning male and female.

MaleM
FemaleF
Unknown—
Hospitalization
Hospitalizationslinkr_visit
Tablevisit_occurrence
Columns
visit_id*
visit_occurrence_id
patient_id*
person_id
start_datetime*
visit_start_datetime
end_datetime
visit_end_datetime
visit_type
visit_source_value
care_site_id
care_site_id

Unit stays

Not mapped.

Notes

Clinical notes

Not mapped.

Concepts
omop(default)linkr_concept_omop
Tableconcept
Columns
concept_id*
concept_id
concept_name*
concept_name
concept_code
concept_code
terminology_id
vocabulary_id
category
domain_id
subcategory
concept_class_id
Events
Measurementslinkr_event_measurements
Tablemeasurement
Columns
patient_id*
person_id
concept_id*
measurement_concept_id
start_datetime*
measurement_datetime
visit_id
visit_occurrence_id
visit_detail_id
visit_detail_id
source_concept_id
measurement_source_concept_id
value_number
value_as_number
unit
unit_source_value
Dictionaryomop
Diagnoseslinkr_event_diagnoses
Tablecondition_occurrence
Columns
patient_id*
person_id
concept_id*
condition_concept_id
start_datetime*
condition_start_datetime
visit_id
visit_occurrence_id
end_datetime
condition_end_datetime
Dictionaryomop
Drugs
Drugslinkr_drug_drugs
Tabledrug_exposure
Columns
patient_id*
person_id
concept_id*
drug_concept_id
start_datetime*
drug_exposure_start_datetime
visit_id
visit_occurrence_id
end_datetime
drug_exposure_end_datetime
quantity
quantity
route
route_source_value
dose_source_value
dose_unit_source_value
Dictionaryomop
Kindadministration
The Mapping tab of the OMOP CDM 5.4 schema. Each block is a relation: its table, then the contract columns it fills. Click a sub-tab, then Edit to see the blocks as forms and the buttons of an empty block.
Sub-tabIts relationsWhat depends on it
PatientPatients, and its gender valuesEverything. With no patient relation, the Patient data page refuses to render.
HospitalizationHospitalizations and Unit staysStay-based cohort criteria, lengths of stay, the admission timeline, the catalog’s services.
NotesClinical notesThe Notes widget and text-based cohort criteria.
ConceptsOne or more dictionaries; the first is the defaultShowing names instead of codes, everywhere.
EventsAs many relations as needed — measurements, diagnoses, procedures…The Concepts page, cohort criteria, the patient timeline.
DrugsAs many relations as needed, each with its KindThe same uses as events, plus doses, rates and durations.

Patients, Hospitalizations, Unit stays and Clinical notes are single. Dictionaries, events and drugs are added in edit mode — Add dictionary, Add event relation, Add drug relation — and named: their Label, or their Key for a dictionary, gives the relation its name (linkr_event_measurements). A block’s bin removes it, after a confirmation — except the Patients block, which cannot be removed.

Filling a relation through the form

The form fits whenever one table carries the relation on its own. An empty block offers Map from a table; a filled block has three parts.

  • Table — the source schema and table. Both fields suggest the tables the DDL knows, without ever forcing the value: a half-described model stays editable.
  • Filter — a SQL condition on that table’s columns, to keep only part of it: category = 'Vital signs'. This is what lets you draw several event relations from one table.
  • Columns — one row per contract column. Each is filled by a Column of the table, or by a Constant — a fixed value, like 'ICU' for a stay type. Required columns carry an asterisk; a column left empty comes back empty (NULL).

Outside edit mode, only the filled columns show. Some carry an ⓘ explaining what Linkr derives from them: birth_year is the fallback when birth_date is not mapped, and gender is computed on its own from gender_source_value.

Gender values

The Patients block also asks for the Gender values: the raw value of gender_source_value meaning male, the one meaning female, and optionally the one meaning unknown — M and F in OMOP, 1 and 2 elsewhere. Linkr fills gender with them, which the statistics and the sex criteria use.

An event relation’s dictionary

Every event or drug relation states which Dictionary turns its codes into names: Default — the first dictionary of the Concepts sub-tab —, a specific dictionary, or None (concepts named inline).

“None” means the concept column already holds the name

That is the case of a MIMIC drug field carrying “Vancomycin” rather than a numeric identifier: the column is then used as the name, and no join is made.

Getting this wrong produces no error: the join happens anyway, between a drug name and an identifier, and returns nothing. It is the first thing to check when a Concepts page stays empty.

A dictionary also accepts, beyond its contract, extra columns — Add extra column —, which take the extra_ prefix.

Drugs

Drugs have their own sub-tab because they have their own columns. Each relation has a Kind — Administrations, what was given, or Prescriptions, what was prescribed — and its contract adds the dose columns to the event ones: quantity, amount, rate, concentration and duration, each with its unit, and is_continuous for a continuous infusion.

The event columns — value, unit — are derived from the dose columns when you do not map them. A drug relation therefore works wherever an event relation does.

Writing a relation in SQL

The form reads one table only. Everything else is written in SQL: a date of death kept in another table, a code that is only found by joining terminology and code, or an entity-attribute-value warehouse — where a single measurement is spread over several rows that have to be put back together.

Each block’s SQL button — or Write SQL on an empty block — opens the relation’s window. It opens on the SQL generated from the form; as soon as you change it, it is marked Modified, and Reset goes back to the generated SQL. The query must be a single SELECT that names its columns exactly as the contract does (AS patient_id).

demo.linkr.interhop.org

SQL — linkr_event_measurements

Generated from the form, or written by hand (Cmd+S saves). The contract on the right lists the columns to return.

Modified
1-- Values and units: two rows per measurement, matched by document
2SELECT
3 o.pat_id AS patient_id,
4 o.code_id AS concept_id,
5 o.ts AS start_datetime,
6 o.sejour_id AS visit_id,
7 o.val AS value_number,
8 u.val AS unit,
9 o.doc_id
10FROM obs o
11LEFT JOIN obs u
12 ON u.doc_id = o.doc_id AND u.attr = 'UNITE'
13WHERE o.attr = 'VALEUR'
Contract

The columns to return, named exactly (AS …). Bold = required.

  • patient_id*id
  • concept_id*id
  • start_datetime*timestamp
  • visit_idid
  • visit_detail_idid
  • concept_terminologytext
  • concept_codetext
  • source_concept_idid
  • concept_nametext
  • end_datetimetimestamp
  • value_numberVARCHAR ≠ number
  • value_stringtext
  • unittext
  • unit_concept_idid
  • routetext
  • route_concept_idid
The SQL window of a hand-written event relation: two rows of the same table matched by document. After a check, the contract on the right shows the filled columns, the one with the wrong type (value_number returns text), and those left empty. Click the Contract check and Preview tabs.

Three tabs:

  • SQL — the editor, and the Contract on the right: the columns to return, the required ones in bold, each with its expected type. Cmd/Ctrl + S saves, and runs the check again.
  • Contract check — on the chosen database, Check describes what the query returns, without reading the data. Every contract column is filled, required, missing, of the wrong type, or not returned (NULL); columns outside the contract are ignored. A column of the wrong type is fixed with a CAST in the query.
  • Preview — Run shows the first 100 rows the relation returns.

A schema has no data: the check runs on a database

The check and the preview run on a database that uses this schema, picked in the window. If there is none yet, the window says so: install one from this schema — or create it with New from schema, see Databases.

Once the SQL is saved, the block reads Defined in SQL, followed by the columns the last check found filled. Those columns are recorded with the query: that is how the rest of the app knows, for instance, whether an age criterion or a value in the timeline is available. Until a check has run, the block says so.

The SQL window saves straight away, without going through Edit, as long as you may edit the schema.

The form and the SQL do not add up

A relation defined in SQL ignores its form. Changing the form of such a relation regenerates the SQL and discards your edit: Linkr asks first, with Discard the custom SQL?

One last warning from the check is worth knowing: a window function over the whole relation (OVER without PARTITION BY) stops Linkr from filtering by patient upstream, and every patient page then scans the whole table. Use a source key as the id instead, or partition the window by patient.

One schema, several databases

A database copies the schema at the moment it is attached — the DDL as well as the mapping — and records which schema, and which version, its copy comes from. It does not point at it.

The schema

OMOP CDM 5.4 — version 1.3.0

Installed once in the workspace.

copy

”ICU” database

Copy of version 1.2.0

OverriddenMeasurements: redefined in SQL for this database

”MIMIC-IV demo” database

Copy of version 1.3.0

That is what makes a database robust: it keeps working if the schema is edited, deleted, or was never installed on the instance it lands on.

And it is what lets it have its own quirks. A database can override a relation of its copy: redefine it for itself only — for instance because its measurements are stored differently —, while the schema and the other databases stay untouched. In the database’s Mapping tab, such a relation carries the Overridden badge, and the SQL window checks and previews on the database directly. See A database’s mapping.

Editing a schema does not update existing databases

The correction does not propagate on its own. To apply it to a database, open that database’s Mapping tab: when the schema has changed since the copy, an Update from preset button replaces the copy with the current version, keeping the database’s overrides. Choosing a different schema in Edit, on the other hand, starts over: the copy is made again and the overrides are dropped.

Symmetrically, deleting a schema breaks no database — each one keeps its copy. Its card simply shows “Schema (not installed)” and stops being a link.

A few limits

  • A column that does not exist raises no error. A typo in a column name does not break the relation: that column simply comes back empty. The contract check or the preview reveals it.
  • A preset’s joins stay read-only in the form. A relation installed with a join — OMOP’s date of death, read from the death table — shows its join greyed out: the table, the filter and the plain columns stay editable, the join itself is changed in SQL.
  • The check needs a database. With no database installed from the schema, a SQL relation saves but cannot be verified.

Going further

  • Schemas — what a schema is, and its two halves.
  • Getting and exploring a schema — the catalog, importing, the DDL and the diagram.
  • A database’s mapping — overriding a relation, and following the schema’s updates.
  • Concepts — what the dictionaries make possible.
  • Concept mapping — aligning your local codes to a standard terminology.
PreviousGetting and exploringNextDatabases

Product

  • Home
  • Demo

Resources

  • Documentation
  • Resources
  • Tools
  • Blog

Community

  • Framagit source code
  • Github source code

About

  • InterHop.org
  • Contact

2021–2026 InterHop — CC BY-NC-SA 4.0 (site) · GPLv3 (software)