Skip to content

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

LevelDescription
controlled vocabularyA closed list of permitted terms with definitions. Buys: consistency, joinability, a working filter. Costs: somebody must own additions.
taxonomyThe 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.
ontologyClasses, 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:

AxiomDescription
rdfs:subClassOfEvery Company is an Organisation. Lets a query for organisations find companies.
rdfs:domain / rdfs:rangeemploys goes from Organisation to Person. Note this is not a constraint — see below.
owl:inverseOfemploys is the inverse of employedBy. Assert one direction, query either.
owl:TransitivePropertypartOf is transitive, so a bolt in a gearbox in a robot is part of the robot without that triple existing.
owl:sameAsTwo identifiers denote one thing. Powerful, dangerous, and the subject of canonical ids.
owl:FunctionalPropertyA 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 subClassOf closure means an application asking for Organisation does not need to know the list of subclasses.
  • Inverses, so that one loader writing employs does not have to also write employedBy and 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.

The strongest argument for a heavyweight ontology is interchange — publishing data other organisations must consume without a phone call. If your graph is internal and has one consumer, that argument does not apply to you and you are paying for portability you will never use.