The triple store model is mostly equivalent to the property graph model, using different words to describe the same ideas. It is nevertheless worth discussing, because various tools and languages for triple stores can be valuable additions to your toolbox for building applications.
In a triple store, all information is stored in the form of very simple three-part statements: (subject, predicate, object). For example, in the triple (Jim, likes, bananas), Jim is the subject, likes is the predicate (verb), and bananas is the object.
Note
To be precise, databases that offer a triple-like data model often need to store additional metadata on each tuple. For example, AWS Neptune uses quads (4-tuples) by adding a graph ID to each triple; Datomic uses 5-tuples, extending each triple with a transaction ID and a Boolean to indicate deletion. Since these databases retain the basic subject-predicate-object structure explained here, these notes nevertheless calls them triple stores.
The subject of a triple is equivalent to a vertex in a graph. The object is one of two things:
-
A value of a primitive datatype, such as a string or a number. In that case, the predicate and object of the triple are equivalent to the key and value of a property on the subject vertex. Using the example from Figure 3-6, (lucy, birthYear, 1989) is like a vertex lucy with properties {"birthYear": 1989}.
-
Another vertex in the graph. In that case, the predicate is an edge in the graph, the subject is the tail vertex, and the object is the head vertex. For example, in (lucy, marriedTo, alain), the subject and object lucy and alain are both vertices, and the predicate marriedTo is the label of the edge that connects them.
Example 3-7 shows the same data as in Example 3-4 written as triples in a format called Turtle, a subset of Notation3 (N3).
Example 3-7. A subset of the data in Figure 3-6, represented as Turtle triples
@prefix : <urn:example:>.
_:lucy a :Person.
_:lucy :name "Lucy".
_:lucy :bornIn _:idaho.
_:idaho a :Location.
_:idaho :name "Idaho".
_:idaho :type "state".
_:idaho :within _:usa.
_:usa a :Location.
_:usa :name "United States".
_:usa :type "country".
_:usa :within _:namerica.
_:namerica a :Location.
_:namerica :name "North America".
_:namerica :type "continent".
In this example, vertices of the graph are written as :someName. The name doesn’t mean anything outside of this file; it exists only because we otherwise wouldn’t know which triples refer to the same vertex. When the predicate represents an edge, the object is a vertex, as in :idaho:within :usa. When the predicate is a property, the object is a string literal, as in :usa:name "United States".
For a more compact representation, you can use semicolons to say multiple things about the same subject, as shown in Example 3-8. This makes the Turtle format quite readable.
Example 3-8. A more concise way of writing the data in Example 3-7
@prefix : <urn:example:>.
_:lucy a :Person; :name "Lucy"; :bornIn _:idaho.
_:idaho a :Location; :name "Idaho"; :type "state"; :within _:usa.
_:usa a :Location; :name "United States"; :type "country"; :within _:namerica.
_:namerica a :Location; :name "North America"; :type "continent".
The Semantic Web
Some of the research and development effort on triple stores was motivated by the Semantic Web, an early 2000s effort to facilitate internet-wide data exchange by publishing data not only as human-readable web pages but also in a standardized, machine-readable format. Although the Semantic Web as originally envisioned did not succeed, the project’s legacy lives on in, for example, linked data standards such as JSON-LD, the ontologies used in biomedical science, Facebook’s Open Graph protocol (which is used for link unfurling), knowledge graphs such as Wikidata, and the standardized vocabularies for structured data maintained by Schema.org.
Triple stores are another Semantic Web technology that has found use outside its original use case; even if you have no interest in the Semantic Web, triples can be a good internal data model for applications.
The RDF data model
The Turtle language we used in Example 3-8 is actually a way of encoding data in the Resource Description Framework (RDF), a data model that was designed for the Semantic Web. RDF data can also be encoded in other ways, including (more verbosely) XML, as shown in Example 3-9. Tools like Apache Jena can automatically convert between different RDF encodings.
Example 3-9. The data from Example 3-8 expressed using RDF/XML syntax
<rdf:RDF xmlns="urn:example:"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<Location rdf:nodeID="idaho">
<name>Idaho</name>
<type>state</type>
<within>
<Location rdf:nodeID="usa">
<name>United States</name>
<type>country</type>
<within>
<Location rdf:nodeID="namerica">
<name>North America</name>
<type>continent</type>
</Location>
</within>
</Location>
</within>
</Location>
<Person rdf:nodeID="lucy">
<name>Lucy</name>
<bornIn rdf:nodeID="idaho"/>
</Person>
</rdf:RDF>
RDF has a few quirks because it is designed for internet-wide data exchange. The subject, predicate, and object of a triple are often URIs. For example, a predicate might be a URI such as http://my-company.com/namespace#within or http://my-company.com/namespace#lives_in, rather than just WITHIN or LIVES_IN. The reasoning behind this design is that you should be able to combine your data with someone else’s data, and if they attach a different meaning to the word within or lives_in, you won’t get a conflict because their predicates are actually http://other.org/foo#within and http://other.org/foo#lives_in.
The URL http://my-company.com/namespace doesn’t necessarily need to resolve to anything—from RDF’s point of view, it is simply a namespace. To avoid potential confusion with http:// URLs, the examples in this section use nonresolvable URIs such as urn:example:within. Fortunately, you can specify this prefix just once at the top of the file and then forget about it.
The SPARQL query language
SPARQL is a query language for triple stores using the RDF data model. (The name is a recursive acronym for SPARQL Protocol and RDF Query Language, pronounced “sparkle.”) It predates Cypher, and since Cypher’s pattern matching is borrowed from SPARQL, they look quite similar.
The same query as before—finding people who have moved from the US to Europe—is similarly concise in SPARQL as it is in Cypher (see Example 3-10).
Example 3-10. The same query as Example 3-5, expressed in SPARQL
PREFIX : <urn:example:>
SELECT ?personName WHERE {
?person :name ?personName.
?person :bornIn / :within* / :name "United States".
?person :livesIn / :within* / :name "Europe".
}
The structure is very similar. The following two expressions are equivalent (variables start with a question mark in SPARQL):
(person) -[:BORN_IN]-> () -[:WITHIN*0..]-> (location) # Cypher
?person :bornIn / :within* ?location. # SPARQL
Because RDF doesn’t distinguish between properties and edges but just uses predicates for both, you can use the same syntax for matching properties. In the following expression, the variable usa is bound to any vertex that has a name property whose value is the string United States:
(usa {name:'United States'}) # Cypher
?usa :name "United States". # SPARQL
SPARQL is supported by Amazon Neptune, AllegroGraph, Blazegraph, OpenLink Virtuoso, Apache Jena, and various other triple stores.