Use this file to discover all available pages before exploring further.
Session 4 of Going Meta (broadcast May 3, 2022) tackles a fundamental challenge in knowledge graphs: meaning is often implicit. A database might record that someone DIRECTED a movie, but the fact that this person is therefore a Director lives only in the developer’s head. This session shows how to lift that implicit knowledge into the graph itself — first by hand, then by building a lightweight ontology, and finally by wiring it to an APOC trigger so that inference happens automatically whenever new data arrives.
The movie database shipped with Neo4j is a convenient starting point. A director is anyone who has a DIRECTED relationship to a Movie — but that definition lives only in the query:
MATCH (x)-[:DIRECTED]->(:Movie)RETURN DISTINCT x.name AS director
We can materialise this with a label-setting script:
MATCH (x)-[:DIRECTED]->(:Movie)SET x:Director
But this is brittle: every time the model changes, someone has to remember to update the script. The session asks: can we make the definition of “Director” part of the data itself?
This single query reads the ontology and surfaces inferred types for every node in the graph — regardless of how many relationship types are defined:
MATCH (from:_Category)<-[:_from]-(r:_Relationship)-[:_to]->(to:_Category)MATCH (x)-[rel]->(y)WHERE type(rel) = r.nameRETURN x, " is a " + from.name, y, " is a " + to.name
4
Materialise the inferred labels in bulk
MATCH (from:_Category)<-[:_from]-(r:_Relationship)-[:_to]->(to:_Category)MATCH (x)-[rel]->(y)WHERE type(rel) = r.nameCALL apoc.create.addLabels(x, [from.name]) YIELD node AS xsCALL apoc.create.addLabels(y, [to.name]) YIELD node AS ysRETURN count(xs) + count(ys) + " nodes updated"
Instead of running the enrichment script manually, wire it to an APOC trigger so that inference is applied immediately when new relationships are created:
CALL apoc.trigger.add('microinferencer','MATCH (from:_Category)<-[:_from]-(r:_Relationship)-[:_to]->(to:_Category)MATCH (x)-[rel]->(y)WHERE rel IN $createdRelationships AND type(rel) = r.nameCALL apoc.create.addLabels(x, [from.name]) YIELD node AS xsCALL apoc.create.addLabels(y, [to.name]) YIELD node AS ysRETURN count(xs) + count(ys) + " nodes updated"', { phase: 'before' })
Triggers require apoc.trigger.enabled=true in your neo4j.conf configuration file. The trigger runs inside the same transaction as the write, ensuring the inference is either fully applied or fully rolled back.
Test the trigger by creating new nodes and relationships — the inferred labels appear immediately:
Rather than materialising inferred labels, you can query them at runtime using n10s.inference.nodesLabelled. This is useful when the ontology changes frequently or when you want to avoid storing derived data.
// Give me all Artists (includes Actors and Directors via subClassOf)CALL n10s.inference.nodesLabelled("Artist")
// Narrow down: how many of the inferred Artists are explicitly labelled Actor?CALL n10s.inference.nodesLabelled("Artist") YIELD nodeWHERE node:ActorRETURN count(node)
n10s.inference.nodesLabelled traverses the rdfs:subClassOf hierarchy stored in the graph, so it always reflects the current state of your ontology without requiring any re-materialisation step.