Foundation
Semantic Web for the Working Ontologist
Allemang, Hendler & Gandon. The conceptual foundation for this whole curriculum. Unusually honest about where RDF hurts. Read Chapters 1–3 alongside this module.
The conceptual comparison between RDF and labeled property graphs, followed by a working query lab on Greek and Roman mythology. Load mythology.ttl into Fuseki first — the understanding comes from running queries, not reading about them.
Anyone arriving at RDF from Neo4j, or at Neo4j from RDF, asks the same question. Most public answers are not neutral. Vendor pieces make property graphs feel practical and RDF feel ceremonial. Semantic-web defenders make RDF feel principled and property graphs feel parochial. Both framings contain truth and both hide where real system design happens.
The honest answer is that RDF and labeled property graphs model many of the same facts but start from different primitives. Once you choose the primitives, consequences cascade: syntax, query shape, metadata, reasoning, reuse, integration, tooling, and team skill. This page tries to make that choice hard in the right way.
The Resume Graph Explorer runs on Neo4j. Its ESCO skill links are RDF. Understanding where those two stacks meet — and where they diverge — is the concrete version of Module 1's 30-second test.
RDF starts with the triple: subject, predicate, object. Subjects and predicates are IRIs. Objects can be IRIs, literals, or blank nodes. Classes, vocabularies, entailment, and ontology design sit on top of that single move — there is nothing else.
A labeled property graph starts with nodes and relationships. Nodes carry labels and properties; relationships carry types and properties. The model feels closer to how many developers already think: this person worked at this company, and the relationship itself has a title and start date.
Both models can say "Alice knows Bob." The divergence starts when you want to say something about the relationship itself — like "they have known each other since 2020."
@prefix foaf: <http://xmlns.com/foaf/0.1/> . :Alice a foaf:Person . :Bob a foaf:Person . :Alice foaf:knows :Bob . # "since 2020" needs: event node, # RDF-star, or classical reification.
CREATE
(alice:Person {name: 'Alice'}),
(bob:Person {name: 'Bob'}),
(alice)-[:KNOWS {since: 2020}]->(bob)
That "since 2020" is not a footnote — it is the first hint of the whole trade-off. LPG puts it on the edge and moves on. RDF asks whether this relationship is itself a thing worth naming. Annoying sometimes; clarifying also sometimes.
Quoted triples (RDF-star) reduce some of this friction. You will meet RDF-star in Module 3, alongside the other reification approaches. For now, the event-class pattern teaches the underlying modeling question better: when does a relationship deserve to become a first-class thing?
One person, two jobs, one degree, three skills linked to ESCO-style skill concepts. Small enough to fit on screen; structured enough to surface every modeling choice. The ESCO IRIs are illustrative placeholders.
CREATE
(p:Person {
name: "Alex Rivera",
email: "alex@example.com" }),
(a:Company {name: "Riverbend Analytics"}),
(b:Company {name: "Northwind Health"}),
(s:School {name: "Midstate University"}),
(p)-[:EMPLOYED_BY {
role: "Junior Data Analyst",
startDate: date("2020-01-15"),
endDate: date("2022-06-30")
}]->(a),
(p)-[:EMPLOYED_BY {
role: "Data Scientist",
startDate: date("2022-07-01")
}]->(b),
(p)-[:EDUCATED_AT {
degree: "BS Statistics",
endDate: date("2019-05-15")
}]->(s),
(py:Skill {name:"Python", escoId:"ccd0..."}),
(sq:Skill {name:"SQL", escoId:"29cd..."}),
(ml:Skill {name:"Machine learning",
escoId:"4d4d..."}),
(p)-[:HAS_SKILL {level:"advanced"}]->(py),
(p)-[:HAS_SKILL {level:"advanced"}]->(sq),
(p)-[:HAS_SKILL {level:"intermediate"}]->(ml)
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> . @prefix xsd: <http://www.w3.org/2001/XMLSchema#> . @prefix foaf: <http://xmlns.com/foaf/0.1/> . @prefix schema: <https://schema.org/> . @prefix skos: <http://www.w3.org/2004/02/skos/core#> . @prefix sensemaking: <https://sensemaking-ai.com/ns/> . :alex a foaf:Person ; foaf:name "Alex Rivera" ; sensemaking:hasEmployment :emp1, :emp2 ; sensemaking:hasEducation :edu1 ; sensemaking:hasSkill :s_py, :s_sql, :s_ml . :emp1 a sensemaking:Employment ; sensemaking:employer :riverbend ; sensemaking:role "Junior Data Analyst" ; sensemaking:startDate "2020-01-15"^^xsd:date ; sensemaking:endDate "2022-06-30"^^xsd:date . :s_py a skos:Concept ; skos:prefLabel "Python" ; skos:exactMatch <http://data.europa.eu/esco/skill/ccd0> . :s_sql a skos:Concept ; skos:prefLabel "SQL" ; skos:exactMatch <http://data.europa.eu/esco/skill/29cd> . :s_ml a skos:Concept ; skos:prefLabel "Machine learning" ; skos:exactMatch <http://data.europa.eu/esco/skill/4d4d> .
Three things to notice. Dates in Cypher go on the edge; Turtle turns employment into an event node. Skills in Cypher store ESCO IDs as strings; Turtle types them as skos:Concept with a live skos:exactMatch link. And Turtle is longer — the URI tax arriving on the page. The section after next shows where that tax starts buying something.
MATCH (p:Person {name: "Alex Rivera"}) -[r:EMPLOYED_BY]->(c:Company) RETURN c.name, r.role, r.startDate ORDER BY r.startDate
SELECT ?orgName ?role ?start WHERE { :alex sensemaking:hasEmployment ?emp . ?emp sensemaking:employer ?org ; sensemaking:role ?role ; sensemaking:startDate ?start . ?org schema:name ?orgName . } ORDER BY ?start
The skills query is where the stacks separate. In Cypher, the ESCO ID is a string — the database has no idea what it points to. Following the ESCO hierarchy requires importing ESCO as a separate node set. In SPARQL, ESCO is published as RDF. Each skill's skos:exactMatch link is a live IRI; skos:broader traversal is a normal triple-pattern, no import required.
With RDF, skos:exactMatch connects your skill to ESCO where ESCO lives. With LPG, ESCO has to be imported as a parallel node set or accessed via a string join outside the database.
The SPARQL query is not shorter. The unlock is that the broader-category data exists, is queryable, and did not have to be imported. SKOS is already RDF. ESCO is published as SKOS. Following the graph is just following the graph.
RDF is more verbose because every important term carries a globally identifiable IRI. For an isolated application — one team, one database, no integration partners — the URI tax is pure overhead. LPG is the humane choice, and the team ships faster.
For a graph that participates in a broader ecosystem — ESCO, FIBO, schema.org, PROV-O, Wikidata — the tax becomes infrastructure. The cost arrives early. The value compounds: every external vocabulary you can read is a query you do not have to engineer separately.
RDF does not win because it is cleaner to type. RDF wins when the future integration surface matters more than the immediate typing experience. The inverse is also true — which is why "always use RDF" is as wrong as "always use Neo4j."
The starter kit's data/mythology.ttl follows the same structural conventions as example.ttl — same prefix block, same class/property pattern — with the domain swapped to Greek and Roman mythology. Three things to notice before the queries begin.
# Class declaration — reuse before defining
sensemaking:Deity a owl:Class ;
rdfs:subClassOf schema:Person ;
rdfs:label "Deity" ;
rdfs:comment "A divine being affiliated
with a realm." .
sensemaking:Ninja rdfs:subClassOf schema:Person in the Naruto dataset. A reasoner can infer every Deity is also a Person without that triple being asserted. The pattern carries across domains — only the class name changes.
# Greek and Latin names — the LANG gotcha
sensemaking:Apollo rdfs:label
"Ἀπόλλων"@el ,
"Apollo"@la .
sensemaking:Ares rdfs:label
"Ἄρης"@el ,
"Mars"@la .
FILTER (LANG(?label) = "la") returns "Apollo." FILTER (LANG(?label) = "el") returns "Ἀπόλλων." The Roman name is not a translation — it is the same name transliterated. Ares → Mars shows the contrast clearly. The realms (schema:name) use @en and @la labels, which drives the LANG filter in q02.
# championed: directed sponsorship
sensemaking:Zeus
sensemaking:championed
sensemaking:Athena ,
sensemaking:Apollo ,
sensemaking:Ares .
sensemaking:Poseidon
sensemaking:championed
sensemaking:Triton .
# Ares: intentionally no hasPower
sensemaking:Ares a sensemaking:Deity ;
schema:name "Ares" ;
rdfs:label "Ἄρης"@el , "Mars"@la ;
sensemaking:memberOfRealm
sensemaking:Olympians .
hasPower triple does not mean Ares has no powers — it means this dataset does not assert any. Ares appears in q04's OPTIONAL block with an unbound ?powerLabel. The comprehension question is whether the open-world assumption is a modeling flaw or a design choice.
From the starter-kit root: fuseki-server --config=fuseki/config.ttl — then in the Fuseki UI, create a new dataset or update the config to point at data/mythology.ttl instead of data/example.ttl. The queries below use the sensemaking: prefix bound to https://sensemaking-ai.com/ns/mythology# — they will not return results against the Naruto dataset.
The same five SPARQL patterns as the Naruto workbook — SELECT, GROUP BY, ASK, FILTER + OPTIONAL, CONSTRUCT — applied to mythology data. Copy each query, paste it into the Fuseki SPARQL editor, and run it. Work through the "Think about this" prompts before moving on.
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> PREFIX schema: <https://schema.org/> PREFIX sensemaking: <https://sensemaking-ai.com/ns/mythology#> SELECT ?deity ?name WHERE { ?deity a sensemaking:Deity ; schema:name ?name . } ORDER BY ?name
PREFIX schema: <https://schema.org/> PREFIX sensemaking: <https://sensemaking-ai.com/ns/mythology#> SELECT ?realmName (COUNT(?deity) AS ?memberCount) WHERE { ?realm a sensemaking:Realm ; schema:name ?realmName . FILTER (LANG(?realmName) = "en") ?deity sensemaking:memberOfRealm ?realm . } GROUP BY ?realmName ORDER BY DESC(?memberCount) ?realmName
PREFIX sensemaking: <https://sensemaking-ai.com/ns/mythology#> ASK WHERE { ?deity sensemaking:hasPower sensemaking:Prophecy . }
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> PREFIX schema: <https://schema.org/> PREFIX skos: <http://www.w3.org/2004/02/skos/core#> PREFIX sensemaking: <https://sensemaking-ai.com/ns/mythology#> SELECT ?name ?powerLabel WHERE { ?deity a sensemaking:Deity ; schema:name ?name ; sensemaking:memberOfRealm ?realm . ?realm rdfs:label ?realmLabel . FILTER (CONTAINS(LCASE(?realmLabel), "olymp")) OPTIONAL { ?deity sensemaking:hasPower ?power . ?power skos:prefLabel ?powerLabel . FILTER (LANG(?powerLabel) = "en") } } ORDER BY ?name
PREFIX sensemaking: <https://sensemaking-ai.com/ns/mythology#> CONSTRUCT { ?a sensemaking:pantheonmateOf ?b . } WHERE { ?a sensemaking:memberOfRealm ?realm . ?b sensemaking:memberOfRealm ?realm . FILTER (STR(?a) < STR(?b)) }
Each challenge asks you to write a query not in the starter kit. Start from the closest existing query and modify from there. Write the query before opening the answer panel.
Start from q01. Add a pattern that excludes deities who have at least one sensemaking:hasPower triple. Use FILTER NOT EXISTS { ... }.
Expected: 2 rows — Ares and Triton. Both appear in the dataset without any hasPower assertion.
PREFIX schema: <https://schema.org/> PREFIX sensemaking: <https://sensemaking-ai.com/ns/mythology#> SELECT ?name WHERE { ?deity a sensemaking:Deity ; schema:name ?name . FILTER NOT EXISTS { ?deity sensemaking:hasPower ?power . } } ORDER BY ?name
Follow-up: Ares is the god of war; Triton is a sea deity. Neither has a listed power. Are these the same kind of absence? What would you add to the dataset to distinguish "truly powerless" from "powers not yet asserted"?
Write a GROUP BY query returning each deity's name and a count of their powers. Only include deities who have at least one power. Start from q02's aggregation pattern.
Expected: 6 rows — Athena (2), Apollo (2), then Zeus, Poseidon, Hades, Persephone (1 each). Ares and Triton have no powers and should not appear.
PREFIX schema: <https://schema.org/> PREFIX sensemaking: <https://sensemaking-ai.com/ns/mythology#> SELECT ?name (COUNT(?power) AS ?powerCount) WHERE { ?deity a sensemaking:Deity ; schema:name ?name ; sensemaking:hasPower ?power . } GROUP BY ?name ORDER BY DESC(?powerCount) ?name
Write a SELECT returning every champion → championed pair. Then extend it with HAVING to find only those who championed more than one deity in the dataset.
All pairs: Zeus → Athena, Zeus → Apollo, Zeus → Ares; Poseidon → Triton; Hades → Persephone.
Championed 2+ others: Zeus only (3). Poseidon and Hades each championed one.
# All championed pairs PREFIX schema: <https://schema.org/> PREFIX sensemaking: <https://sensemaking-ai.com/ns/mythology#> SELECT ?championName ?championedName WHERE { ?champion sensemaking:championed ?championed ; schema:name ?championName . ?championed schema:name ?championedName . } ORDER BY ?championName # Champions of 2+ others only SELECT ?championName (COUNT(?championed) AS ?n) WHERE { ?champion sensemaking:championed ?championed ; schema:name ?championName . } GROUP BY ?championName HAVING (COUNT(?championed) > 1)
Notice: Zeus championed Ares, yet Athena (also championed by Zeus) rivals Ares. A knowledge graph holds both facts without contradiction. What would a query look like that finds "deities championed by the same deity who are also rivals of each other"?
Write a SELECT returning all rivalry pairs using sensemaking:rivalOf. Check mythology.ttl — is each rivalry stated in both directions? rivalOf is declared owl:SymmetricProperty. Does a plain SPARQL query pick up inferred triples, or only an OWL reasoner?
Stated in the file: Athena rivalOf Ares, Zeus rivalOf Hades — each asserted once. The reverse triples (Ares rivalOf Athena, Hades rivalOf Zeus) are inferable from owl:SymmetricProperty but not explicitly stored.
Plain SPARQL returns 2 rows. An OWL-entailed dataset would return 4. Fuseki's default configuration does not apply OWL inference — try enabling the OWL Micro reasoner in the Fuseki dataset configuration if you want to see the difference.
PREFIX schema: <https://schema.org/> PREFIX sensemaking: <https://sensemaking-ai.com/ns/mythology#> SELECT ?aName ?bName WHERE { ?a sensemaking:rivalOf ?b ; schema:name ?aName . ?b schema:name ?bName . } ORDER BY ?aName
Cross-domain observation: Zeus championed both Athena and Ares, who are rivals. Hades (Zeus's rival) is in the Underworld; they are not pantheonmates. This is the kind of multi-hop reasoning that knowledge graphs are built for — Module 3 covers how to make the inference engine do it for you.
This section is the answer to Module 1's 30-second test: explain to a working engineer, without slogans, why someone would choose RDF over a labeled property graph or vice versa.
| Pressure | Usually favors | Why |
|---|---|---|
| Path traversal inside one product | LPG | Traversal ergonomics and edge properties earn their keep. |
| Vocabulary reuse across systems | RDF | Global IRIs and shared vocabularies are the structural point. |
| Formal reasoning | RDF | RDF/RDFS/OWL have formal semantics; LPG semantics are application-defined. |
| Provenance-heavy assertions | Depends | LPG edge properties are easy; RDF reification is more portable but more expensive. Module 3 covers four options. |
| Fast team ramp-up | The stack the team knows | A correct LPG model beats a confused RDF model. Team fit is technical fit. |
Before moving to Module 2: explain to a working engineer why someone would choose RDF over a labeled property graph — without the words "semantic," "ontology," or "standards." If the answer is concrete and uses specific examples, the foundations have landed. If it feels vague, redo Exercise 1.3 before moving on.
Not equal-purpose links — read them for different kinds of evidence.
Other materials for Submodule 1.1: synthesis document (static reading companion with annotated diagrams and side-by-side queries) · Naruto domain variant · Module 1 cheatsheet (coming soon)
Allemang, Hendler & Gandon. The conceptual foundation for this whole curriculum. Unusually honest about where RDF hurts. Read Chapters 1–3 alongside this module.
Hogan et al. Places RDF and property graphs in the same landscape without declaring a winner. The paper to cite when debates get too confident.
DuCharme. The clearest single-source reference for SPARQL syntax and patterns — the curriculum's canonical reference. Chapters 1–2 belong in this module.
The largest public SPARQL endpoint. Fastest way to feel SPARQL on real, large data — modify the example queries in the helper UI rather than starting from scratch.
Author of Learning SPARQL. Ongoing posts on SPARQL patterns and the LLM + knowledge graph intersection. Worth bookmarking for the rest of the curriculum.
The full arc for Weeks 1–3: exercises, primary project, pain points, deliverables, and the 30-second test. This workbook covers Submodule 1.1; the README covers the rest.