Most of an ontology is sitting in your schema. You can extract it without a language model — and there are good reasons to.
Not what columns exist — the catalog answers that. They mean the other thing:
orders.customer_id points at customers.id, what is that relationship called?Every technique for making enterprise data useful to AI — knowledge graphs, retrieval over structured data, semantic layers, agents that query a warehouse — needs a model of meaning, not a model of storage.
An architect and a domain expert, table by table. It works. It takes months, costs a fortune, and goes stale the moment someone ships a migration.
Fluent, well-organised, and confidently includes relationships that do not exist.
The failure mode isn't that the model is wrong. It's that wrong and right look identical in the output — and telling them apart means doing the manual work you were trying to skip.
Made deliberately, by a human — then ignored by most tools that read it.
| Declaration | What it actually asserts |
|---|---|
| PRIMARY KEY | Identity — what makes a row that thing |
| FOREIGN KEY | A relationship, including its direction |
| UNIQUE on an FK | Cardinality — the difference between 1:1 and 1:N |
| CHECK (x IN …) | The permitted states of a concept |
| COMMENT | Documentation written for exactly this purpose |
| junction table | A many-to-many relationship — not an entity |
None of this requires inference. It requires reading. Commit to reading it properly and much of the conceptual model falls out deterministically: same input, same output, every time.
A faithful record of the source: tables, typed columns, keys, constraints, indexes, comments, provenance.
Classes, relationships with cardinality, properties, class hierarchies — what the data means.
The back-reference. Every class and property points at the exact table, column or constraint it came from.
Exports are W3C standards, not a proprietary format: OWL for the ontology, R2RML for the mapping.
An optional language-model pass sits beside step 03, refining names and flagging judgment calls. It is never required, and it never invents structure.
$ relational-schema-analyzer analyze --source csv --url ./library
ENTITIES: Authors · Books · Loans · Members
Books → Authors 1:N
Loans → Books 1:N
Loans → Members 1:N
That is the right answer, recovered from column names and type compatibility alone. But the answer is not the interesting part.
"confidence": 0.64,
"reviewRequired": true,
"detectedPatterns": ["inferred_foreign_keys"],
"assumptions": [
"No foreign keys were declared; 3 relationship(s) were
inferred from naming heuristics (review)."
]
Give the same schema to PostgreSQL with the foreign keys declared and the relationships come back unflagged at high confidence — because now the database is the witness.
A tool that produces an ontology should say which parts it read, which it guessed, and how sure it is about the guesses.
The source catalog asserts it. Highest trust, no inference.
A human wrote it down, in version control, with a reason.
Derived from naming, types, and optional value sampling. Scored and flagged.
The interesting engineering in a tool like this is rarely the inference. It is the bookkeeping about how much you know — and resisting the urge to flatten three epistemic states into one confident-looking answer.
Pointed at a large public news-event dataset on a cloud warehouse, the extraction produced completely disconnected classes. The dataset declares no primary keys and no foreign keys — public datasets rarely bother. Correct, and useless.
The fix was not to guess harder. It was to let a human supply what the catalog lacks:
{ "tables": {
"events": { "primaryKey": ["GLOBALEVENTID"] },
"eventmentions": { "foreignKeys": [{
"columns": ["GLOBALEVENTID"],
"references": { "table": "events", "columns": ["GLOBALEVENTID"] },
"comment": "codebook: mentions reference their event" }] } } }
This is not an argument that language models are useless here. They are genuinely
good at the semantic leftovers: naming, noticing that cust_ref and
customer_id are the same idea, spotting that a table is really an event log.
The argument is that they should work on the 10% that needs judgment, grounded by the 90% you derived from evidence — rather than hallucinating over the whole thing.
| Kind | Sources |
|---|---|
| databases | PostgreSQL · MySQL / MariaDB · SQL Server · DuckDB |
| warehouses | Snowflake · Databricks (BigQuery in progress) |
| catalogs | dbt manifests · Open Semantic Interchange models |
| files | CSV |
The analysis is identical regardless of dialect — only the introspection differs. Adding a source is a connector plus its tests, with no change to the core.
:Books_Authors a owl:ObjectProperty, owl:FunctionalProperty ;
rdfs:domain :Books ;
rdfs:range :Authors .
:Books_Authors phys:mappingStyle "FOREIGN_KEY" .
:Books_Authors phys:tableName "books" .
:Books_Authors phys:fromColumns "author_id" .
:Books_Authors phys:toColumns "id" .
Every concept carries annotations pointing back at its physical origin, so "where did this come from?" always has an answer.
The companion R2RML mapping feeds straight into an off-the-shelf engine such as Ontop or Morph-KGC, giving a queryable knowledge graph over the live database with no hand-written mapping — normally where weeks of this work go.
Every denormalisation a DBA made for good reasons is a distortion of the concepts:
customers table with company_name, company_address — a hidden Company, folded in to avoid a joinparty table holding people and organisations, told apart by party_type — two classes in one coattags column containing "12, 45, 89" — a many-to-many stuffed into a stringeffective_date / expiration_date — a thing mixed with its historytotal_lifetime_value — not a property at all, but a calculation over relationships, frozenSome of this is measurable. Some is a judgment call no algorithm can settle — and that residue is exactly where a language model earns its place.
Next: a BigQuery connector, denormalisation detection, and SHACL validation shapes.
Default ontology IRIs are configurable; the library targets no particular database. It is used alongside ArangoDB — a database that stores documents and graphs together — but emits standards any consumer can read.
Ontology extraction is mostly a reading problem. The parts that need judgment deserve to be marked as such — not quietly absorbed into an answer that looks as confident as the rest.
github.com/ArthurKeen/relational-schema-analyzer