The Semantic Layer, From ScratchChapter 2
Chapter 2

The triple: the atom of knowledge

Everything Stardog knows is a three-part sentence. By the end of this chapter you'll read, write and query them.

A riddle to start. How many columns does a table need to hold every fact your company knows: every customer's email, every order's price, which address belongs to whom, which product is in which category? Your Aurora and Redshift schemas use about fifty columns across eight tables; a real company has thousands. The answer is three: subject, predicate, object. That's the entire data model of this book's world, and once you read those three-part sentences fluently, the rest is mostly vocabulary. We'll start with Carolyn Inworth of Milwaukee, customer 225.

2.1One shape for everything

Stardog stores data using RDF, the Resource Description Framework, a W3C standard. RDF is to Stardog what the relational model is to Postgres. Its central idea fits on one line: every fact is a triple.

subject  →  predicate  →  object

Read one aloud and it's a plain English sentence. Customer 225 has first name "Carolyn". Order 0e7e… was purchased by customer 225. Here are the terms, each with what it is, why it exists, and a tiny example:

TermWhat it isWhy it existsTiny example
RDFA standard data model in which all information is triples.One shape for all data, so data from anywhere combines without reconciling schemas.The six facts you'll write in Section 2.6.
TripleOne fact: subject, predicate, object.The smallest unit you can add, delete, query or secure.customer:225 email "cinworth68@g.co"
SubjectThe thing the fact is about.Every fact needs an anchor.customer:225
PredicateThe property or relationship being stated.Names what kind of fact this is, like a column name, but global.firstName, purchasedBy
ObjectThe value, or the other thing at the end of the relationship.The fact's content."Carolyn", or customer:225
GraphA set of triples.It's what a database holds; drawn out, it's literally a network (Section 2.4).Everything in kit-c360.

No tables, no columns, no rows, no NULLs. Just a set of sentences.

Every fact is one sentence: subject → predicate → object.

Example: customer:225 :firstName "Carolyn"

2.2A SQL row becomes triples

Here's a real row from your Aurora database. Find it in DBeaver with SELECT * FROM c360.customer WHERE cid = 225, and its address with SELECT * FROM c360.address WHERE id = 'b80ea2ce-2361-408a-b443-721594cbbb35'. Press Play to watch them turn into triples.

c360.customer cidfirst_namelast_nameemaillocation 225CarolynInworthcinworth68@g.cob80ea2ce-2361-… c360.address idcitystate b80ea2ce-2361-…MilwaukeeWisconsin firstName "Carolyn" lastName "Inworth" email "cinworth68@g.co" address location:b80e… another thing city "Milwaukee" state "Wisconsin" customer:225 a thing

Done: two rows, six triples. One thing per primary key, one arrow per column.

Figure 2.1: Two SQL rows become six triples. Rounded nodes are things; square boxes are plain values. Each arrow is one triple.

Three rules did all the work, the same rules the mappings in Chapter 8 follow:

  1. The primary key becomes the subject's name. Every triple from the row is about customer 225. The key isn't a triple of its own; it's baked into the name, customer:225 (Chapter 3 gives the full-length names).
  2. Each ordinary column becomes one triple. first_name = 'Carolyn' becomes customer:225 firstName "Carolyn".
  3. A foreign key becomes an arrow to another thing. location holds a reference, not a value you'd show anyone. So the object is the address itself, location:b80e…, which then has its own triples.

Two more things startle SQL people. NULL disappears. Carolyn's picture column is empty, so there's simply no picture triple: the fact is absent. (That's the seed of the open-world assumption, Chapter 5.) And multiple values are free. Two emails are two email triples: no email2 column, no child table, no schema change.

2.3Things and literals

Figure 2.1 draws its objects in two shapes on purpose, and the distinction matters early:

TermWhat it isWhy it existsTiny example
IRIA globally unique name, written like a URL or URN, in angle brackets.So a thing or property means the same everywhere, even when data from different systems is mixed (Chapter 3).<urn:stardog:demos:c360:customer:225>
Resource (a "thing")Anything named by an IRI: a customer, an address, a property.Things can have their own facts: arrows can start from them.The address node, which has a city and a state.
LiteralA plain value: text, number, date. Always an object, never a subject.Facts eventually bottom out in values you display, filter and add up."Milwaukee", 22, 439.78

Why is the address a thing but the city a literal? Because the address has facts of its own and, in our model, the city doesn't. You decide it the way you decide whether something deserves its own table: if it has attributes, or other things point to it, make it a thing. To record Milwaukee's population, you'd promote the city to a thing (city:milwaukee-wi) and hang facts off it.

Things have IRIs and can have facts of their own; literals are plain values and are always dead ends.

Example: customer:225 :address location:b80e… points to a thing; "Milwaukee" is a literal.

2.4The graph as a picture, and why merging is free

Figure 2.1's bottom half isn't an illustration of the data. It is the data. Subjects and objects are nodes; each triple is an edge labeled with its predicate. That's why the whole field is called knowledge graphs.

This matters enormously in Chapter 9. Because every triple has the same shape, two sets of triples combine by simply pouring them together, with no schemas to reconcile. Where both sets use the same name for the same thing, the pictures join up at that node by themselves.

Here's a real order from Redshift (SELECT * FROM c360.purchase WHERE id = '0e7edc35-b578-44a0-a58c-e6388bc83337'): on 2011-01-21, customer 225 bought 22 of product 258 ("Flip Flops" by Havaianas, in c360.product) for $439.78. Pour its triples next to Carolyn's:

FROM AURORA FROM REDSHIFT firstNameaddressstate purchasedByquantitypurchasePriceitemPurchasedname customer:225 same name, both sides location:b80e… order:0e7e… 2011-01-21 product:258 "Carolyn" "Wisconsin" 22 439.78 "Flip Flops"
Figure 2.2: Triples from two databases, poured together. Both sides use the same name for customer 225, so the order connects to Carolyn with no join logic at all. This is federation in miniature; Chapter 9 does it for real.

Nobody wrote a JOIN. The order's purchasedBy arrow points at customer:225, and the Aurora triples are about a node with exactly the same name. That's the whole secret of the federated join, and why Chapter 3 spends so long on how names are built.

2.5Follow the arrow instead of writing a JOIN

"Which city does customer 225 live in?" You could write the SQL in your sleep:

SELECT a.city
FROM c360.customer c
JOIN c360.address  a ON a.id = c.location
WHERE c.cid = 225;

In a graph you describe the path through the picture, with blanks where you want answers. The language is SPARQL (Chapter 4); for now just read the shape (the runnable version is in the Try it box):

SELECT ?city
WHERE {
  customer:225  address  ?home .
  ?home         city     ?city .
}
TermWhat it isWhy it existsTiny example
SPARQLThe W3C query language for RDF (say "sparkle").SQL assumes tables; triples need a language built around patterns and paths.The query above.
Triple patternA triple where some positions are variables (placeholders) instead of fixed values.You describe the shape of the facts you want; Stardog finds every triple that fits.?home :city ?city . matches location:b80ea2ce… :city "Milwaukee", giving ?home = location:b80ea2ce…, ?city = "Milwaukee"
VariableA placeholder name, starting with ?, for a value you don't know yet. Stardog fills it in from the matching triples, and each variable becomes a column in your results.Marks what you want back, and links lines together: the same name on two lines must be the same value (like a JOIN condition).customer:225 :address ?home .
?home :city ?city .
→ ?home = location:b80ea2ce…, ?city = "Milwaukee"
SQL: MATCH KEYS GRAPH: FOLLOW ARROWS customer cid = 225first_nameemaillocation address idstreet_namecitystate ON a.id = c.location you must know both tables and the key columns customer:225 address ?home city ?city "Milwaukee" = ?home in both patterns = the join key you only need the names of the arrows

A shared variable does the job of the join key, and you only needed the names of two arrows.

Figure 2.3: The same question two ways. SQL matches key columns between tables; the graph query walks the arrows, and a shared variable does the job of the join key.

To be honest about what's underneath: Stardog is still joining, on ?home, much like a self-join on the imaginary three-column table. What changes is what you need to know to write the query: not table names or foreign keys, just the names of the relationships, address then city. The "how do these connect" knowledge has moved out of every query and into the data.

Two more gifts. Arrows can be followed backwards, as the Java box shows: just move the variable. And paths across sources look like paths within one: in Figure 2.2, going from an order to its customer's state is purchasedBy, address, state, even though the first arrow comes from Redshift and the rest from Aurora. The query doesn't care where triples live.

2.6Hands-on: six triples about Carolyn

Your turn. You'll put the six triples from Figure 2.1 into a scratch folder, query them three ways, and delete the folder. Three new terms first:

TermWhat it isWhy it existsTiny example
Named graphA set of triples with its own IRI as a label; a folder inside the database (Chapter 3).To keep groups of triples separate: to query, secure or delete them as a unit.<urn:book:ch02>
INSERT DATAA SPARQL command that adds exactly the triples you list.The simplest way to write facts by hand.Step 1 below.
DROP GRAPHA SPARQL command that deletes a named graph and every triple in it.One-line cleanup, like DROP TABLE.DROP GRAPH <urn:book:ch02>

The names are long: <urn:stardog:demos:c360:customer:225> for Carolyn, <tag:stardog:api:ecomm:firstName> for "first name". Chapter 3 explains how to build and shorten them. For today, read <…customer:225> as "customer 225" and <…ecomm:firstName> as "first name".

Check yourself

A SQL row has six non-null columns, including the primary key. About how many triples does it become?

About five, one per non-key column. The primary key becomes part of the subject's name (customer:225) rather than a triple. NULL columns produce nothing.

Is "Milwaukee" a thing or a literal in our model? Why?

A literal: a plain text value in quotes, with no arrows leaving it. To state facts about the city itself, you'd make it a thing with its own IRI.

Why is the address a thing and not a literal?

It has facts of its own (city, state, zip), and only things can be subjects. It also started as a foreign-key target, which is the usual sign.

You run the same INSERT DATA twice. How many triples are there?

The same as after the first run. A graph is a set; restating a triple changes nothing.

In the "which city" query, what plays the role of the JOIN condition?

The shared variable ?home: it's the object of the first pattern and the subject of the second, so both must match the same node. You express the join by naming relationships, not matching key columns.

Why do the Aurora and Redshift triples in Figure 2.2 connect without any join logic?

Both use exactly the same name for customer 225, and triples naming the same thing are about the same node. Building those names consistently from cid is the IRI trick of Chapters 3 and 9.

Chapter recap