ebuzima is a ClickHouse mirror of a Frappe/ERPNext health information system — 1,422 tables, 604 of them clinical. Before you go looking for data, it helps to know that ClickHouse enforces none of the relationships between them. This deck covers the three mechanisms that hold the schema together instead, each verified against real rows.
Every table name starting with tab corresponds to one DocType — Frappe's word for an entity or form: tabPatient, tabPatient Encounter, tabDrug Prescription. That's not a coincidence you need to reverse-engineer — the app's own metadata tables (tabDocType, tabDocField) tell you exactly what fields each one has, and which of those fields point elsewhere.
That last number is the whole reason this deck exists.
In a normal relational database, a foreign key guarantees a row on one side exists on the other. ClickHouse has no such constraint — every relationship you'll find is a convention the Frappe application maintains in code, not something the database checks. That means three different things can look identical (a column holding a string) but behave very differently:
The rest of this deck is one slide per type — what it is, how to query it, and how much to trust it.
When a form has a repeatable section — e.g. a list of prescribed drugs — Frappe stores those rows in their own table. Each child row carries three columns that tie it back: parent (the parent row's key), parenttype (which doctype that parent is), and parentfield (which section it belongs to). This is the closest thing here to a real foreign key — the data physically can't exist without it.
A Link field just stores a string that's meant to match the name column of another table — no different, physically, from any other text column. Whether it actually matches is entirely up to the application that wrote it.
I checked this specific one against live data rather than assuming:
Rule of thumb: Link fields are usually trustworthy, but "usually" is doing real work in that sentence — spot-check before you rely on a join being complete.
Here two columns work as a pair: one names which table to look in, the other holds the value to look up there — and the target table can be different on every row. Metadata alone can't tell you the target; you have to look at the data.
Resolved by querying the real column — this field points to one of five doctypes depending on the row:
One more thing worth knowing before you go searching: some tables that current forms still reference — and that still hold live rows — have no current entry in tabDocType. Their metadata was removed at some point (likely a rename or deprecation that didn't fully clean up), but the physical table and its data were left behind.
If you're hunting for a table and it's not in tabDocType or the app's UI, check system.tables directly before assuming it doesn't exist.
tabDocType, or search the schema map (next slide).tabDocField to see its Link and Table fields — that's the real edge list, not guesswork from column names.parenttype — the same table can hold rows from a dozen unrelated forms.GROUP BY reference_doctype) before you try to join anything.Everything in this deck comes from that map — a searchable, click-through view of all 604 clinical tables where every relationship is drawn from live metadata and the trickier ones are verified against real rows.
Click any table there to see exactly what it's a child of, what children it has, and what it links to or from — the same three colors as this deck.