Skip to content

RDF, SPARQL and the Semantic Web, Twenty Years On

10 min read · updated August 4, 2026

RDF is a data model in which everything is a triple and every thing has a global identifier. SPARQL is its query language, and it is genuinely good — pattern matching, path expressions and federation in a W3C standard that has been stable since 2013. The grand vision around them mostly did not arrive. The parts that did are worth knowing.

The data model in five minutes

  • Everything is a triple: subject, predicate, object. There is no other structure. A “graph” is a set of triples.
  • Subjects and predicates are IRIs — globally unique identifiers that look like URLs. The point is that two organisations using http://schema.org/name mean the same thing without coordination.
  • Objects are IRIs or literals. Literals carry a datatype (xsd:integer, xsd:date) or a language tag ("Gearbox"@en).
  • Blank nodes are anonymous subjects, used for structures with no natural identity. They are also the single most reliable source of pain in RDF: they cannot be referred to from outside the file, they make diffing two datasets hard, and they break incremental updates. Mint an IRI instead, almost always.
  • Named graphs add a fourth element, turning triples into quads. This is how provenance, versioning and per-source isolation are done — see provenance in a knowledge base.

Turtle, JSON-LD and the rest

The model is one thing; the file formats are another, and they are all interchangeable. Turtle is the readable one:

@prefix ex:     <https://example.com/id/> .
@prefix schema: <https://schema.org/> .

ex:acme a schema:Organization ;
  schema:name        "Acme Robotics GmbH" ;
  schema:foundingDate "2011-03-14"^^xsd:date ;
  schema:location    ex:dresden ;
  ex:supplies        ex:vandenberg .

The semicolon repeats the subject, the comma repeats subject and predicate, and a is shorthand for rdf:type. N-Triples is the same data one full statement per line, which is what you want for streaming and for diffs. JSON-LD is the same data as JSON with a @context mapping short names onto IRIs, and it is by a wide margin the most deployed RDF syntax on the planet — because search engines read it out of web pages.

SPARQL, properly

A SPARQL query is a graph pattern with variables. The engine finds every set of bindings that makes the pattern true. There are four query forms and most guides only show one:

FormDescription
SELECTReturns a table of bindings. The familiar one.
ASKReturns true or false. Much cheaper than SELECT with LIMIT 1 when you only need existence.
CONSTRUCTReturns a new graph built from a template. This is how you do transformation, materialise inferences, or extract a subgraph to send somewhere.
DESCRIBEReturns an implementation-defined description of a resource. Convenient, non-portable, and best avoided in code.

A SELECT with grouping, filtering and ordering, all standard:

PREFIX ex:     <https://example.com/id/>
PREFIX schema: <https://schema.org/>

SELECT ?city ?cityName (COUNT(?company) AS ?n)
WHERE {
  ?company a schema:Organization ;
           schema:location ?city ;
           schema:foundingDate ?founded .
  ?city schema:name ?cityName .
  FILTER (?founded >= "2010-01-01"^^xsd:date)
}
GROUP BY ?city ?cityName
HAVING (COUNT(?company) > 5)
ORDER BY DESC(?n)
LIMIT 20

CONSTRUCT deserves more attention than it gets. It is how you turn one shape of data into another without leaving the query language, which means a transformation that is declarative, portable and reviewable in a pull request:

CONSTRUCT {
  ?person ex:worksForCompany ?company .
}
WHERE {
  ?person ex:holdsRole ?role .
  ?role   ex:roleAt    ?company .
  FILTER NOT EXISTS { ?role ex:endDate ?end }
}

Property paths are the reason to use it

SPARQL 1.1 added path expressions, and they are the feature that makes it a graph language rather than a triple-shaped SQL. The operators:

PathDescription
p/qFollow p then q. A join written as a path.
p|qEither p or q.
^pFollow p backwards. Removes the need for inverse properties in many cases.
p*Zero or more p. Includes the starting node itself.
p+One or more p. Excludes the starting node.
p?Zero or one p.
!pAny predicate other than p.

Which turns the supply-chain question from the introduction to knowledge graphs into a single line:

SELECT DISTINCT ?downstream WHERE {
  ex:acme ex:supplies+ ?downstream .
}

Two honest caveats. Paths with * or + over a large graph are the easiest way to write an accidentally enormous query, and most public endpoints will kill it. And path queries return the reachable set, not the paths themselves — if you need to know how A reaches B, SPARQL is the wrong tool and Cypher’s shortestPath is the right one.

OPTIONAL and the trap inside it

OPTIONAL is a left join, and it composes in a way that surprises people who think of it as “this bit might be missing”.

SELECT ?company ?name ?ceoName WHERE {
  ?company a schema:Organization ;
           schema:name ?name .
  OPTIONAL {
    ?company ex:hasCEO ?ceo .
    ?ceo     schema:name ?ceoName .
  }
}

Written that way, a company with a CEO who has no name still yields a row with ?ceoName unbound — the whole OPTIONAL block fails together, which is what you want. Split it into two consecutive OPTIONAL blocks and the semantics change: the second block can bind against anything, and on some data you get combinatorial extra rows. The rule is to keep everything that must succeed or fail together inside one OPTIONAL, and to put FILTERs inside the block whose variables they constrain.

The related trap is FILTER NOT EXISTS versus MINUS. They differ when the pattern shares no variables with the outer query: NOT EXISTS evaluates against the current bindings and MINUS removes nothing when there is no shared variable. Use FILTER NOT EXISTS unless you have a reason.

What was adopted and what was not

The 2001-era vision was a web of machine-readable data, where any identifier could be dereferenced to get more data, and agents would reason across sources. Judged against that, the honest assessment is mixed, and it splits cleanly.

Adopted, and load-bearing

  • JSON-LD and schema.org in web pages. Structured markup for products, events, recipes and organisations is read by search engines and is a routine part of technical SEO. This is RDF, deployed at internet scale, and most of the people writing it do not know that.
  • Life sciences and cultural heritage. Protein, chemical, gene and bibliographic data are published as RDF with SPARQL endpoints, because those communities genuinely do need to join data across institutions with no central authority. That is the exact problem RDF was designed for and it works.
  • Wikidata. A large, general, queryable, free knowledge base with a public SPARQL endpoint — see the Wikidata guide.
  • SPARQL and SHACL as standards. Both are implemented by multiple independent vendors, which is more than can be said for most query languages.

Not adopted

  • The open linked-data web. Dereferenceable IRIs returning live data across organisations never became normal. Enterprises publish APIs, not graphs.
  • OWL reasoning at scale. Description-logic reasoning is expensive and its open-world semantics do not match what most engineers want. SHACL exists largely because of this.
  • RDF as a default application database. Property graphs took the developer mindshare, for readability reasons more than technical ones.

The practical conclusion: use RDF when you must interoperate with somebody else’s vocabulary or publish for external consumption, and use a property graph when the graph is yours and the readers are your own engineers. Both hold the same information; the choice is about who else has to read it.