Sensemaking AI · Sensemaking Semantic Web
Submodule 1.1 · Foundations

RDF, Turtle, and SPARQL

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.

Module 1 · Weeks 1–3 Requires Fuseki + mythology.ttl ~90 min hands-on RDF · LPG · Turtle · SPARQL
Dataset Naruto Greek mythology
Checking for Fuseki at localhost:3030…

Which one should I be using?

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.

Same fact, different primitives.

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.

Turtle
@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.
Cypher
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.

RDF 1.2 note

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?

A small career graph, twice.

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.

Cypher create statements
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)
Turtle equivalent
@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.

Three queries, three answers

Cypher — all jobs, chronological
MATCH (p:Person {name: "Alex Rivera"})
      -[r:EMPLOYED_BY]->(c:Company)
RETURN c.name, r.role, r.startDate
ORDER BY r.startDate
Ergonomic edge: LPG
SPARQL — all jobs, chronological
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 ESCO interop unlock

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.

The unlock

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.

The verbosity is real.

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.

Mental shift

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."

Eight deities, three realms, five patterns.

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." .
Reuse before defining. Exactly the same pattern as 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 .
Apollo's name is identical in Greek and Latin. 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 .
Championed is more than parenthood. Zeus championed Athena into wisdom and strategy, Apollo into the arts and prophecy, Ares into war — yet Athena and Ares are rivals. A knowledge graph holds both facts without resolving the tension. Challenge C3 asks which deity championed the most others; Challenge C4 asks what it means for a champion to also champion their champion's rivals.
# Ares: intentionally no hasPower
sensemaking:Ares a sensemaking:Deity ;
    schema:name "Ares" ;
    rdfs:label "Ἄρης"@el , "Mars"@la ;
    sensemaking:memberOfRealm
        sensemaking:Olympians .
The god of war has no listed powers. This is intentional. Under the open-world assumption, the absence of a 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.

Load mythology.ttl before running queries

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.

Five queries, five patterns.

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.

q01
Who are all the deities in the dataset?
Pattern: SELECT · two triple patterns · ORDER BY
Open Fuseki ↗
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
q02
How many deities are in each realm?
Pattern: GROUP BY · COUNT aggregation · bilingual literal trap
Open Fuseki ↗
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
q03
Does any deity in the dataset have the power of prophecy?
Pattern: ASK · existence check · open-world assumption
Open Fuseki ↗
PREFIX sensemaking: <https://sensemaking-ai.com/ns/mythology#>

ASK
WHERE {
  ?deity sensemaking:hasPower sensemaking:Prophecy .
}
q04
Which Olympian deities have powers listed, and which don't?
Pattern: FILTER on bound variable · OPTIONAL left-join · row multiplication
Open Fuseki ↗
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
q05
If two deities share a realm, emit a new "pantheonmate" triple for each pair.
Pattern: CONSTRUCT · inference materialization · combinatorial growth
Open Fuseki ↗
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))
}

Four things to build.

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.

C1 Find all deities with no powers listed

Start from q01. Add a pattern that excludes deities who have at least one sensemaking:hasPower triple. Use FILTER NOT EXISTS { ... }.

Expected result and one approach

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"?

C2 Count powers per deity, most to fewest

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 result and one approach

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
C3 List all championed relationships; then find the deity who championed the most

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.

Expected result and one approach

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"?

C4 Find rivals — then reason about what symmetry means in plain SPARQL

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?

Expected result and the reasoning note

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.

Heuristics, not slogans.

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.

Choose LPG when

  • The system is path-heavy and contained in one product.
  • Relationship metadata is central and local — dates on edges, weights on edges.
  • Your team already ships comfortably in Neo4j or similar.
  • No external vocabulary needs to be queried as first-class data.

Choose RDF when

  • The data is vocabulary-driven or integration-heavy.
  • External graphs — ESCO, FIBO, Wikidata, schema.org — already speak your terms.
  • Reasoning, equivalence, or subsumption matter to the application.
  • Long-term portability matters more than short-term terseness.
PressureUsually favorsWhy
Path traversal inside one productLPGTraversal ergonomics and edge properties earn their keep.
Vocabulary reuse across systemsRDFGlobal IRIs and shared vocabularies are the structural point.
Formal reasoningRDFRDF/RDFS/OWL have formal semantics; LPG semantics are application-defined.
Provenance-heavy assertionsDependsLPG edge properties are easy; RDF reification is more portable but more expensive. Module 3 covers four options.
Fast team ramp-upThe stack the team knowsA correct LPG model beats a confused RDF model. Team fit is technical fit.

The 30-second test

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.

Six good next reads.

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)

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.

Balanced frame

Knowledge Graphs, section 3

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.

Query reference

Learning SPARQL

DuCharme. The clearest single-source reference for SPARQL syntax and patterns — the curriculum's canonical reference. Chapters 1–2 belong in this module.

Hands-on endpoint

Wikidata Query Service

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.

Practitioner blog

Bob DuCharme's blog

Author of Learning SPARQL. Ongoing posts on SPARQL patterns and the LLM + knowledge graph intersection. Worth bookmarking for the rest of the curriculum.

Curriculum context

Module 1 README

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.