Ontologies, Taxonomies and Schemas
9 min read · updated August 4, 2026
A controlled vocabulary fixes the words. A taxonomy arranges them into a hierarchy. An ontology says what may be true of them and lets a machine derive consequences. Each rung costs roughly an order of magnitude more to build and maintain than the one below, and most projects need the second.
The ladder, and its price per rung
| Level | Description |
|---|---|
| controlled vocabulary | A closed list of permitted terms with definitions. Buys: consistency, joinability, a working filter. Costs: somebody must own additions. |
| taxonomy | The same terms arranged as broader/narrower. Buys: roll-up queries, faceted navigation, inheritance of meaning. Costs: hierarchy disputes, and the fact that real domains are not trees. |
| ontology | Classes, properties with domains and ranges, and axioms an engine can reason over. Buys: derived facts, consistency checking, portability. Costs: modelling expertise, a reasoner in the pipeline, and debugging why something was inferred. |
The ladder is worth naming because the word “ontology” is routinely used for all three, usually by whoever is selling the expensive version. A team that says it needs an ontology very often needs a list of forty terms that everybody agrees on.
Rung one: a controlled vocabulary
A controlled vocabulary is a table. Term, identifier, definition, owner, status, and the strings that should map onto it:
id pref_label alt_labels status sev-critical "Critical" "P0", "Sev1", "Sev 1", "urgent" active sev-major "Major" "P1", "Sev2" active sev-minor "Minor" "P2", "P3", "low" active sev-blocker "Blocker" - deprecated
That is not a toy. It is exactly what makes the difference between a dashboard that can count incidents by severity and one that cannot, and it is the same artefact described in a glossary your company and your model both use. The deprecated row matters as much as the active ones: a vocabulary that only ever grows is a vocabulary in which nobody can tell which term is current.
Rung two: a taxonomy
Add one relation — broader — and the vocabulary becomes a taxonomy. In RDF the standard for this is SKOS, which exists precisely because most people asking for an ontology want a taxonomy:
@prefix skos: <http://www.w3.org/2004/02/skos/core#> . @prefix ex: <https://example.com/vocab/> . ex:harmonicDrive a skos:Concept ; skos:prefLabel "Harmonic drive"@en ; skos:altLabel "Strain wave gearing"@en ; skos:broader ex:gearbox ; skos:definition "A gear mechanism using a flexible spline."@en . ex:gearbox a skos:Concept ; skos:prefLabel "Gearbox"@en ; skos:broader ex:powerTransmission .
What that buys is roll-up. A query for parts in the powerTransmission category returns harmonic drives without anyone having tagged them twice, because skos:broader chains and a query can walk the chain. In SPARQL that is a property path:
SELECT ?part WHERE {
?part ex:category ?c .
?c skos:broader* ex:powerTransmission .
}The two failure modes of taxonomies are both about shape. The first is that real domains are not trees: a harmonic drive is a gearbox and also a precision component and also, for the export team, a dual-use item. SKOS permits multiple broader parents, which makes it a directed acyclic graph rather than a tree — and immediately breaks any UI that assumed one path to the root.
The second is cycles. Nothing stops an editor asserting A broader B and B broader A, and a skos:broader* query over a cycle is fine in SPARQL but a naive recursive CTE over the same data will not terminate. Run a cycle check in CI; it is one query and it will fire eventually.
Rung three: an ontology
An ontology adds statements about the predicates themselves. In RDFS and OWL, the useful ones in practice are a short list:
| Axiom | Description |
|---|---|
| rdfs:subClassOf | Every Company is an Organisation. Lets a query for organisations find companies. |
| rdfs:domain / rdfs:range | employs goes from Organisation to Person. Note this is not a constraint — see below. |
| owl:inverseOf | employs is the inverse of employedBy. Assert one direction, query either. |
| owl:TransitiveProperty | partOf is transitive, so a bolt in a gearbox in a robot is part of the robot without that triple existing. |
| owl:sameAs | Two identifiers denote one thing. Powerful, dangerous, and the subject of canonical ids. |
| owl:FunctionalProperty | A person has at most one date of birth. Two different values is a contradiction, not two facts. |
The trap sits in the second row, and it catches nearly everybody. rdfs:domain does not validate. If you declare that employs has domain Organisation and then assert karlsson employs someone, a reasoner does not raise an error. It infers that Karlsson is an Organisation. RDFS and OWL operate under the open-world assumption: unstated things are unknown, not false, and an axiom is a licence to conclude rather than a rule to enforce.
That single semantic difference is why so many teams describe their first ontology as having “silently made everything wrong”. The ontology worked exactly as specified. It was specified to do something other than what they wanted.
What inference actually gives you
Inference is worth it in three situations and rarely otherwise:
- Hierarchy roll-up you would otherwise write into every query. Materialising
subClassOfclosure means an application asking forOrganisationdoes not need to know the list of subclasses. - Inverses, so that one loader writing
employsdoes not have to also writeemployedByand keep the two in step forever. - Transitive containment — organisational structure, geography, bills of materials — where the derived edges genuinely are wanted and computing them per-query is expensive.
Against that, an inferred triple has no provenance unless you build it, it changes when the ontology changes, and a user who sees it in a result set cannot tell it from an asserted one. If you materialise inferences, keep them in a separate named graph so they can be recomputed and so a retraction of a source does the right thing.
Validation is usually what you wanted
When someone says they want the ontology to “stop bad data getting in”, they want a constraint language, not a reasoner. In the RDF world that is SHACL, which is closed-world and does report violations:
@prefix sh: <http://www.w3.org/ns/shacl#> .
ex:CompanyShape a sh:NodeShape ;
sh:targetClass ex:Company ;
sh:property [
sh:path ex:legalName ;
sh:datatype xsd:string ;
sh:minCount 1 ;
sh:maxCount 1 ;
] ;
sh:property [
sh:path ex:foundingYear ;
sh:datatype xsd:integer ;
sh:maxInclusive 2026 ;
sh:maxCount 1 ;
] .In a property graph the equivalent is a mixture of database constraints and a validation job — uniqueness and existence constraints in the database, and everything else as scheduled queries that return the offending nodes. Either way the deliverable is the same: a report of violations that a person can work through, which is what people actually asked for when they asked for an ontology.
Where to stop
A workable default for a first knowledge graph: a controlled vocabulary for every field with a closed set of values, a SKOS taxonomy for the one or two dimensions people navigate by, subclass and inverse declarations where they save work, validation shapes for the invariants you actually care about, and no reasoner in production. Add expressiveness when a specific query you cannot currently answer demands it, and record which query that was.