In the Resource Description Framework (RDF) world, there are no relations. The data model is quite different. It is built on top of triples: subject – predicate – object. Despite there are no tables (compared to relational databases), RDF is not a schema-less clutter – actually RDF has a schema (ontology, vocabulary), just differently shaped. Subjects and predicates are identified by IRIs (or formerly URIs) that are globally unique (compared to primary keys in relational databases that are almost never globally unique). Objects are also identified by IRIs (and yes, one can be both subject and object) or they can be a primitive values like a text string or a number.
This triple is also called a statement. In the following statement:
Relational pipes tools are released under the GNU GPL license.
we recognize:
This data model is seemingly simple: just a graph, two kinds of nodes and edges connecting them together. Or a flat list of statements (triples). But it can be also very complicated, depending on how we use it and how rich ontologies we design. RDF can be studied for years and is a great topic for diploma thesis and dissertations, but in this example, we will keep it as simple as possible.
Collections of statements are stored in special databases called triplestores. The data inside can be queried using the SPARQL language through the endpoint provided by the triplestore. Popular implementations are Jena, Virtuoso and RDF4J (all free software).
Relational model can be easily mapped to RDF. We can just simply add a prefix to the primary keys to make them globally unique IRIs. The attributes will become predicates (also prefixed). And the values will become objects (either primitive values or IRIs in case of foreign keys). Of course, more complex transformation can be done – this is the most straightforward way.
Mapping RDF data to relational model is bit more difficult. Sometimes easy, sometimes very cumbersome. We can always design some kind of EAV (entity – attribute – value) model in the relational database or we can create a relation for each predicate… If we do some universal automatic mapping and retain the flexibility of RDF and richness of the original ontology, we usually lose the performance and simplicity of our relational queries. Good mapping that will feel natural and idiomatic in the relational world and will perform well usually poses some hard work.
But mapping mere results of a SPARQL query obtained from an RDF endpoint is a different story. These results can be seen as records and processed using our relational tools, stored, transformed or converted to other formats, displayed in GUI windows or safely passed to shell scripts. This example shows how we can bridge the RDF and relational worlds.
Currently there is no official relpipe-in-rdf
or relpipe-in-sparql
tool.
It will be probably part of some future release of Relational pipes.
But until then, despite this lack, we still have several options how to join the RDF world
and let the data from an RDF triplestore flow through our relational pipelines:
relpipe-in-xml
relpipe-in-sql
relpipe-in-sql
relpipe-in-csv
or relpipe-in-xml
In this example, we will look at the first and the last option.
Apache Jena is not only a triplestore, it is a framework consisting of several parts and provides also a special JDBC driver that is ready to use (despite this small bug). Thanks to this driver, we can use existing Java tools and run SPARQL queries instead of SQL ones.
Such a tool that uses this standard API (JDBC)
is SQL-DK.
This tool integrates well with Relational pipes because it can output results in the XML format (or alternatively the Recfile format)
that can be directly consumed by relpipe-in-xml
(or alternatively relpipe-in-recfile
).
First we download Jena source codes:
mkdir -p ~/src; cd ~/src
git clone https://gitbox.apache.org/repos/asf/jena.git
and apply the patch for abovementioned bug (if not already merged in the upstream).
n.b. As always when doing such experiments, we would probably run this under a separate user account or in a virtual machine.
Then we will compile the JDBC driver:
cd ~/src/jena/jena-jdbc/
mvn clean install
Now we will install SQL-DK (either from sources or from .deb
or .rpm
package)
and run it for the first time (which creates the configuration directory and files):
sql-dk --list-databases
Then we will register the previously compiled Jena JDBC driver in the ~/.sql-dk/environment.sh
CUSTOM_JDBC=(
~/src/jena/jena-jdbc/jena-jdbc-driver-bundle/target/jena-jdbc-driver-bundle-*.jar
);
And we should see it among other drivers:
$ sql-dk --list-jdbc-drivers ╭──────────────────────────────────────────────────┬───────────────────┬─────────────────┬─────────────────┬──────────────────────────╮ │ class (VARCHAR) │ version (VARCHAR) │ major (INTEGER) │ minor (INTEGER) │ jdbc_compliant (BOOLEAN) │ ├──────────────────────────────────────────────────┼───────────────────┼─────────────────┼─────────────────┼──────────────────────────┤ │ org.postgresql.Driver │ 9.4 │ 9 │ 4 │ false │ │ com.mysql.jdbc.Driver │ 5.1 │ 5 │ 1 │ false │ │ org.sqlite.JDBC │ 3.25 │ 3 │ 25 │ false │ │ org.apache.jena.jdbc.mem.MemDriver │ 1.0 │ 1 │ 0 │ false │ │ org.apache.jena.jdbc.remote.RemoteEndpointDriver │ 1.0 │ 1 │ 0 │ false │ │ org.apache.jena.jdbc.tdb.TDBDriver │ 1.0 │ 1 │ 0 │ false │ ╰──────────────────────────────────────────────────┴───────────────────┴─────────────────┴─────────────────┴──────────────────────────╯ Record count: 6
The driver seems present so we can configure the connection in the ~/.sql-dk/config.xml
file:
<database>
<name>rdf-dbpedia</name>
<url>jdbc:jena:remote:query=http://dbpedia.org/sparql</url>
<userName></userName>
<password></password>
</database>
This will connect us to the DBpedia endpoint (more datasources are mentioned in the chapter below). We can test the connection:
$ sql-dk --test-connection rdf-dbpedia ╭─────────────────────────┬──────────────────────┬─────────────────────┬────────────────────────┬───────────────────────────╮ │ database_name (VARCHAR) │ configured (BOOLEAN) │ connected (BOOLEAN) │ product_name (VARCHAR) │ product_version (VARCHAR) │ ├─────────────────────────┼──────────────────────┼─────────────────────┼────────────────────────┼───────────────────────────┤ │ rdf-dbpedia │ true │ true │ │ │ ╰─────────────────────────┴──────────────────────┴─────────────────────┴────────────────────────┴───────────────────────────╯ Record count: 1
and run our first SPARQL query:
$ sql-dk --db rdf-dbpedia --formatter tabular-prefetching --sql "SELECT * WHERE { ?subject ?predicate ?object . } LIMIT 8" ╭──────────────────────────────────────────────────────────────────────────────┬─────────────────────────────────────────────────┬─────────────────────────────────────────────────────────╮ │ subject (org.apache.jena.graph.Node) │ predicate (org.apache.jena.graph.Node) │ object (org.apache.jena.graph.Node) │ ├──────────────────────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────┼─────────────────────────────────────────────────────────┤ │ http://www.openlinksw.com/virtrdf-data-formats#default-iid │ http://www.w3.org/1999/02/22-rdf-syntax-ns#type │ http://www.openlinksw.com/schemas/virtrdf#QuadMapFormat │ │ http://www.openlinksw.com/virtrdf-data-formats#default-iid-nullable │ http://www.w3.org/1999/02/22-rdf-syntax-ns#type │ http://www.openlinksw.com/schemas/virtrdf#QuadMapFormat │ │ http://www.openlinksw.com/virtrdf-data-formats#default-iid-nonblank │ http://www.w3.org/1999/02/22-rdf-syntax-ns#type │ http://www.openlinksw.com/schemas/virtrdf#QuadMapFormat │ │ http://www.openlinksw.com/virtrdf-data-formats#default-iid-nonblank-nullable │ http://www.w3.org/1999/02/22-rdf-syntax-ns#type │ http://www.openlinksw.com/schemas/virtrdf#QuadMapFormat │ │ http://www.openlinksw.com/virtrdf-data-formats#default │ http://www.w3.org/1999/02/22-rdf-syntax-ns#type │ http://www.openlinksw.com/schemas/virtrdf#QuadMapFormat │ │ http://www.openlinksw.com/virtrdf-data-formats#default-nullable │ http://www.w3.org/1999/02/22-rdf-syntax-ns#type │ http://www.openlinksw.com/schemas/virtrdf#QuadMapFormat │ │ http://www.openlinksw.com/virtrdf-data-formats#sql-varchar │ http://www.w3.org/1999/02/22-rdf-syntax-ns#type │ http://www.openlinksw.com/schemas/virtrdf#QuadMapFormat │ │ http://www.openlinksw.com/virtrdf-data-formats#sql-varchar-nullable │ http://www.w3.org/1999/02/22-rdf-syntax-ns#type │ http://www.openlinksw.com/schemas/virtrdf#QuadMapFormat │ ╰──────────────────────────────────────────────────────────────────────────────┴─────────────────────────────────────────────────┴─────────────────────────────────────────────────────────╯ Record count: 8
Not a big fun yet, but it proves that the connection is working and we are getting some results from the endpoint. We will run some more interesting queries later.
When we switch to the --formatter xml
we can pipe the stream from SQL-DK
to relpipe-in-xml
and then process it using relational tools.
We can also use the --sql-in
option of SQL-DK which reads the query from STDIN (instead of from command line argument)
and then wrap it as a reusable script that reads SPARQL and outputs relational data:
sql-dk --db "rdf-dbpedia" --formatter "xml" --sql-in | relpipe-in-xml
For accessing remote SPARQL endpoint this is a bit overkill with lot of dependencies (so we will use different approach in the next chapter). But Jena JDBC driver is not only for accessing remote endpoints – we can use it as an embedded database, either an in-memory one or regular DB backed by persistent files.
The in-memory database loads some initial data and then operates on them. So we configure such connection:
<database>
<name>rdf-in-memory</name>
<url>jdbc:jena:mem:dataset=/tmp/rdf-initial-data.ttl</url>
<userName></userName>
<password></password>
</database>
It runs fine, but turtles are not at home:
$ echo > /tmp/rdf-initial-data.ttl $ echo "SELECT * WHERE { ?subject ?predicate ?object . }" | sql-dk --db rdf-in-memory --formatter tabular-prefetching --sql-in ╭──────────────────────────────────────┬────────────────────────────────────────┬─────────────────────────────────────╮ │ subject (org.apache.jena.graph.Node) │ predicate (org.apache.jena.graph.Node) │ object (org.apache.jena.graph.Node) │ ├──────────────────────────────────────┼────────────────────────────────────────┼─────────────────────────────────────┤ ╰──────────────────────────────────────┴────────────────────────────────────────┴─────────────────────────────────────╯ Record count: 0
If we are in a desperate need of turtles and have installed any LV2 plugins, we can find some and put them in our initial data file or reconfigure the database connection:
$ find /usr/lib -name '*.ttl' | head /usr/lib/lv2/fil4.lv2/manifest.ttl /usr/lib/lv2/fil4.lv2/fil4.ttl /usr/lib/ardour5/LV2/a-fluidsynth.lv2/manifest.ttl /usr/lib/ardour5/LV2/a-fluidsynth.lv2/a-fluidsynth.ttl /usr/lib/ardour5/LV2/reasonablesynth.lv2/manifest.ttl /usr/lib/ardour5/LV2/reasonablesynth.lv2/reasonablesynth.ttl /usr/lib/ardour5/LV2/a-delay.lv2/manifest.ttl /usr/lib/ardour5/LV2/a-delay.lv2/presets.ttl /usr/lib/ardour5/LV2/a-delay.lv2/a-delay.ttl /usr/lib/ardour5/LV2/a-eq.lv2/manifest.ttl $ cat /usr/lib/lv2/fil4.lv2/manifest.ttl > /tmp/rdf-initial-data.ttl $ sed s@/tmp/rdf-initial-data.ttl@/usr/lib/lv2/fil4.lv2/manifest.ttl@g -i ~/.sql-dk/config.xml
and look through Jena/RDF/SPARQL what is inside:
$ echo "SELECT * WHERE { ?subject ?predicate ?object . }" | sql-dk --db rdf-in-memory --formatter xml --sql-in | relpipe-in-xml | relpipe-out-tabular r1: ╭───────────────────────────────────────┬─────────────────────────────────────────────────┬───────────────────────────────────────────╮ │ subject (string) │ predicate (string) │ object (string) │ ├───────────────────────────────────────┼─────────────────────────────────────────────────┼───────────────────────────────────────────┤ │ http://gareus.org/oss/lv2/fil4#ui_gl │ http://www.w3.org/2000/01/rdf-schema#seeAlso │ file:///usr/lib/lv2/fil4.lv2/fil4.ttl │ │ http://gareus.org/oss/lv2/fil4#ui_gl │ http://lv2plug.in/ns/extensions/ui#binary │ file:///usr/lib/lv2/fil4.lv2/fil4UI_gl.so │ │ http://gareus.org/oss/lv2/fil4#ui_gl │ http://www.w3.org/1999/02/22-rdf-syntax-ns#type │ http://lv2plug.in/ns/extensions/ui#X11UI │ │ http://gareus.org/oss/lv2/fil4#mono │ http://www.w3.org/2000/01/rdf-schema#seeAlso │ file:///usr/lib/lv2/fil4.lv2/fil4.ttl │ │ http://gareus.org/oss/lv2/fil4#mono │ http://lv2plug.in/ns/lv2core#binary │ file:///usr/lib/lv2/fil4.lv2/fil4.so │ │ http://gareus.org/oss/lv2/fil4#mono │ http://www.w3.org/1999/02/22-rdf-syntax-ns#type │ http://lv2plug.in/ns/lv2core#Plugin │ │ http://gareus.org/oss/lv2/fil4#stereo │ http://www.w3.org/2000/01/rdf-schema#seeAlso │ file:///usr/lib/lv2/fil4.lv2/fil4.ttl │ │ http://gareus.org/oss/lv2/fil4#stereo │ http://lv2plug.in/ns/lv2core#binary │ file:///usr/lib/lv2/fil4.lv2/fil4.so │ │ http://gareus.org/oss/lv2/fil4#stereo │ http://www.w3.org/1999/02/22-rdf-syntax-ns#type │ http://lv2plug.in/ns/lv2core#Plugin │ ╰───────────────────────────────────────┴─────────────────────────────────────────────────┴───────────────────────────────────────────╯ Record count: 9
Now we can be sure that LV2 uses the Turtle format for plugin configurations, which is quite ingenious and inspirational – such configuration is well structured and its options (predicates in general) have globally unique identifiers (IRIs). Also plugins are identified by IRIs which is great, because it avoids name collisions.
Let us make some own turtles. Reconfigure the database connection back:
sed s@/usr/lib/lv2/fil4.lv2/manifest.ttl@/tmp/rdf-initial-data.ttl@g -i ~/.sql-dk/config.xml
and fill the /tmp/rdf-initial-data.ttl
with some new data:
<http://example.org/person/you>
<http://example.org/predicate/have>
<http://example.org/thing/nice-day> .
Turtle is a simple format that contains statements. Subjects, predicates and objects are separated by spaces (tabs and line-ends are here just to make it more readable for us). And statements end with full stop like ordinary sentences.
To avoid repeating common parts of IRIs we can declare namespace prefixes:
@prefix person: <http://example.org/person/> .
@prefix predicate: <http://example.org/predicate/> .
@prefix thing: <http://example.org/thing/> .
person:you
predicate:have
thing:nice-day .
This format is very concise. If we describe the same subject, we use semicolon to avoid repeating it. And if even the predicate is the same (multiple values), we use comma:
@prefix person: <http://example.org/person/> .
@prefix predicate: <http://example.org/predicate/> .
@prefix thing: <http://example.org/thing/> .
person:you
predicate:have
thing:nice-day, thing:much-fun;
predicate:read-about
thing:relational-pipes .
Jena will parse our file and respond to our basic query with these data:
$ echo "SELECT * WHERE { ?subject ?predicate ?object . }" | sql-dk --db rdf-in-memory --formatter xml --sql-in --relation rdf_results | relpipe-in-xml | relpipe-out-tabular rdf_results: ╭───────────────────────────────┬─────────────────────────────────────────┬───────────────────────────────────────────╮ │ subject (string) │ predicate (string) │ object (string) │ ├───────────────────────────────┼─────────────────────────────────────────┼───────────────────────────────────────────┤ │ http://example.org/person/you │ http://example.org/predicate/read-about │ http://example.org/thing/relational-pipes │ │ http://example.org/person/you │ http://example.org/predicate/have │ http://example.org/thing/much-fun │ │ http://example.org/person/you │ http://example.org/predicate/have │ http://example.org/thing/nice-day │ ╰───────────────────────────────┴─────────────────────────────────────────┴───────────────────────────────────────────╯ Record count: 3
Or if we prefer more vertical formats like Recfile:
$ echo "SELECT * WHERE { ?subject ?predicate ?object . }" | sql-dk --db rdf-in-memory --formatter xml --sql-in --relation rdf_results | relpipe-in-xml | relpipe-out-recfile %rec: rdf_results subject: http://example.org/person/you predicate: http://example.org/predicate/read-about object: http://example.org/thing/relational-pipes subject: http://example.org/person/you predicate: http://example.org/predicate/have object: http://example.org/thing/much-fun subject: http://example.org/person/you predicate: http://example.org/predicate/have object: http://example.org/thing/nice-day
Let us create some more data:
@prefix person: <tag:heathers.globalcode.info,2020:person:> .
@prefix predicate: <tag:heathers.globalcode.info,2020:predicate:> .
@prefix thing: <tag:heathers.globalcode.info,2020:thing:> .
person:veronica-sawyer
predicate:has-firstname "Veronica";
predicate:has-surname "Sawyer";
predicate:says
"How very!",
"I use my grand IQ to decide what colour gloss to wear",
"Are we going to prom or to hell?";
predicate:dates person:jason-dean;
predicate:is-member-of
thing:sharp-shooters,
thing:croquet-team,
thing:future-leaders;
predicate:is-played-by person:winona-ryder .
person:jason-dean
predicate:has-firstname "Jason";
predicate:has-surname "Dean";
predicate:says
"Greetings and salutations!",
"The extreme always seems to make an impression.",
"Did you say a cherry or Coke slushie?";
predicate:dates person:veronica-sawyer;
predicate:is-member-of
thing:sharp-shooters,
thing:future-leaders;
predicate:is-played-by person:christian-slater .
person:heather-duke
predicate:has-firstname "Heather";
predicate:has-surname "Duke";
predicate:says
"Color me stoked!",
"Jealous much?",
"Veronica, you look like hell.";
predicate:is-member-of
thing:croquet-team,
thing:future-leaders;
predicate:is-played-by person:shannen-doherty .
person:heather-mcnamara
predicate:has-firstname "Heather";
predicate:has-surname "McNamara";
predicate:says
"Drool much?",
"God, aren't they fed, yet?",
"Suicide is a private thing.";
predicate:is-member-of
thing:croquet-team,
thing:future-leaders;
predicate:is-played-by person:lisanne-falk .
person:heather-chandler
predicate:has-firstname "Heather";
predicate:has-surname "Chandler";
predicate:says
"What's your damage?",
"You're such a pillowcase!",
"Grow up Heather, bulimia is so '87.";
predicate:is-member-of
thing:croquet-team,
thing:future-leaders;
predicate:is-played-by person:kim-walker .
person:betty-finn
predicate:has-firstname "Betty";
predicate:has-surname "Finn";
predicate:is-true-friend-of person:veronica-sawyer;
predicate:is-played-by person:renee-estevez .
person:martha-dunnstock
predicate:has-firstname "Martha";
predicate:has-surname "Dunnstock";
predicate:has-nickname "Martha Dumptruck";
predicate:listens-to thing:big-fun;
predicate:is-played-by person:carrie-lynn .
thing:sharp-shooters
predicate:has-title "Sharp Shooters" .
thing:croquet-team
predicate:has-title "Croquet Team" .
thing:future-leaders
predicate:has-title "Future Leaders of America" .
thing:big-fun
predicate:has-title "Big Fun" .
person:winona-ryder
predicate:has-firstname "Winona";
predicate:has-surname "Ryder" .
person:christian-slater
predicate:has-firstname "Christian";
predicate:has-surname "Slater" .
person:shannen-doherty
predicate:has-firstname "Shannen";
predicate:has-surname "Doherty" .
person:lisanne-falk
predicate:has-firstname "Lisanne";
predicate:has-surname "Falk" .
person:kim-walker
predicate:has-firstname "Kim";
predicate:has-surname "Walker" .
person:renee-estevez
predicate:has-firstname "Renée";
predicate:has-surname "Estevez" .
person:carrie-lynn
predicate:has-firstname "Carrie";
predicate:has-surname "Lynn" .
list them as statements:
rdf_results:
╭───────────────────────────────────────────────────────────┬───────────────────────────────────────────────────────────────┬───────────────────────────────────────────────────────────╮
│ subject (string) │ predicate (string) │ object (string) │
├───────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────┤
│ tag:heathers.globalcode.info,2020:person:martha-dunnstock │ tag:heathers.globalcode.info,2020:predicate:is-played-by │ tag:heathers.globalcode.info,2020:person:carrie-lynn │
│ tag:heathers.globalcode.info,2020:person:martha-dunnstock │ tag:heathers.globalcode.info,2020:predicate:listens-to │ tag:heathers.globalcode.info,2020:thing:big-fun │
│ tag:heathers.globalcode.info,2020:person:martha-dunnstock │ tag:heathers.globalcode.info,2020:predicate:has-nickname │ Martha Dumptruck │
│ tag:heathers.globalcode.info,2020:person:martha-dunnstock │ tag:heathers.globalcode.info,2020:predicate:has-surname │ Dunnstock │
│ tag:heathers.globalcode.info,2020:person:martha-dunnstock │ tag:heathers.globalcode.info,2020:predicate:has-firstname │ Martha │
│ tag:heathers.globalcode.info,2020:person:lisanne-falk │ tag:heathers.globalcode.info,2020:predicate:has-surname │ Falk │
│ tag:heathers.globalcode.info,2020:person:lisanne-falk │ tag:heathers.globalcode.info,2020:predicate:has-firstname │ Lisanne │
│ tag:heathers.globalcode.info,2020:thing:sharp-shooters │ tag:heathers.globalcode.info,2020:predicate:has-title │ Sharp Shooters │
│ tag:heathers.globalcode.info,2020:person:heather-duke │ tag:heathers.globalcode.info,2020:predicate:is-played-by │ tag:heathers.globalcode.info,2020:person:shannen-doherty │
│ tag:heathers.globalcode.info,2020:person:heather-duke │ tag:heathers.globalcode.info,2020:predicate:is-member-of │ tag:heathers.globalcode.info,2020:thing:future-leaders │
│ tag:heathers.globalcode.info,2020:person:heather-duke │ tag:heathers.globalcode.info,2020:predicate:is-member-of │ tag:heathers.globalcode.info,2020:thing:croquet-team │
│ tag:heathers.globalcode.info,2020:person:heather-duke │ tag:heathers.globalcode.info,2020:predicate:says │ Veronica, you look like hell. │
│ tag:heathers.globalcode.info,2020:person:heather-duke │ tag:heathers.globalcode.info,2020:predicate:says │ Jealous much? │
│ tag:heathers.globalcode.info,2020:person:heather-duke │ tag:heathers.globalcode.info,2020:predicate:says │ Color me stoked! │
│ tag:heathers.globalcode.info,2020:person:heather-duke │ tag:heathers.globalcode.info,2020:predicate:has-surname │ Duke │
│ tag:heathers.globalcode.info,2020:person:heather-duke │ tag:heathers.globalcode.info,2020:predicate:has-firstname │ Heather │
│ tag:heathers.globalcode.info,2020:thing:big-fun │ tag:heathers.globalcode.info,2020:predicate:has-title │ Big Fun │
│ tag:heathers.globalcode.info,2020:person:heather-mcnamara │ tag:heathers.globalcode.info,2020:predicate:is-played-by │ tag:heathers.globalcode.info,2020:person:lisanne-falk │
│ tag:heathers.globalcode.info,2020:person:heather-mcnamara │ tag:heathers.globalcode.info,2020:predicate:is-member-of │ tag:heathers.globalcode.info,2020:thing:future-leaders │
│ tag:heathers.globalcode.info,2020:person:heather-mcnamara │ tag:heathers.globalcode.info,2020:predicate:is-member-of │ tag:heathers.globalcode.info,2020:thing:croquet-team │
│ tag:heathers.globalcode.info,2020:person:heather-mcnamara │ tag:heathers.globalcode.info,2020:predicate:says │ Suicide is a private thing. │
│ tag:heathers.globalcode.info,2020:person:heather-mcnamara │ tag:heathers.globalcode.info,2020:predicate:says │ God, aren't they fed, yet? │
│ tag:heathers.globalcode.info,2020:person:heather-mcnamara │ tag:heathers.globalcode.info,2020:predicate:says │ Drool much? │
│ tag:heathers.globalcode.info,2020:person:heather-mcnamara │ tag:heathers.globalcode.info,2020:predicate:has-surname │ McNamara │
│ tag:heathers.globalcode.info,2020:person:heather-mcnamara │ tag:heathers.globalcode.info,2020:predicate:has-firstname │ Heather │
│ tag:heathers.globalcode.info,2020:person:jason-dean │ tag:heathers.globalcode.info,2020:predicate:is-played-by │ tag:heathers.globalcode.info,2020:person:christian-slater │
│ tag:heathers.globalcode.info,2020:person:jason-dean │ tag:heathers.globalcode.info,2020:predicate:is-member-of │ tag:heathers.globalcode.info,2020:thing:future-leaders │
│ tag:heathers.globalcode.info,2020:person:jason-dean │ tag:heathers.globalcode.info,2020:predicate:is-member-of │ tag:heathers.globalcode.info,2020:thing:sharp-shooters │
│ tag:heathers.globalcode.info,2020:person:jason-dean │ tag:heathers.globalcode.info,2020:predicate:dates │ tag:heathers.globalcode.info,2020:person:veronica-sawyer │
│ tag:heathers.globalcode.info,2020:person:jason-dean │ tag:heathers.globalcode.info,2020:predicate:says │ Did you say a cherry or Coke slushie? │
│ tag:heathers.globalcode.info,2020:person:jason-dean │ tag:heathers.globalcode.info,2020:predicate:says │ The extreme always seems to make an impression. │
│ tag:heathers.globalcode.info,2020:person:jason-dean │ tag:heathers.globalcode.info,2020:predicate:says │ Greetings and salutations! │
│ tag:heathers.globalcode.info,2020:person:jason-dean │ tag:heathers.globalcode.info,2020:predicate:has-surname │ Dean │
│ tag:heathers.globalcode.info,2020:person:jason-dean │ tag:heathers.globalcode.info,2020:predicate:has-firstname │ Jason │
│ tag:heathers.globalcode.info,2020:person:shannen-doherty │ tag:heathers.globalcode.info,2020:predicate:has-surname │ Doherty │
│ tag:heathers.globalcode.info,2020:person:shannen-doherty │ tag:heathers.globalcode.info,2020:predicate:has-firstname │ Shannen │
│ tag:heathers.globalcode.info,2020:person:heather-chandler │ tag:heathers.globalcode.info,2020:predicate:is-played-by │ tag:heathers.globalcode.info,2020:person:kim-walker │
│ tag:heathers.globalcode.info,2020:person:heather-chandler │ tag:heathers.globalcode.info,2020:predicate:is-member-of │ tag:heathers.globalcode.info,2020:thing:future-leaders │
│ tag:heathers.globalcode.info,2020:person:heather-chandler │ tag:heathers.globalcode.info,2020:predicate:is-member-of │ tag:heathers.globalcode.info,2020:thing:croquet-team │
│ tag:heathers.globalcode.info,2020:person:heather-chandler │ tag:heathers.globalcode.info,2020:predicate:says │ Grow up Heather, bulimia is so '87. │
│ tag:heathers.globalcode.info,2020:person:heather-chandler │ tag:heathers.globalcode.info,2020:predicate:says │ You're such a pillowcase! │
│ tag:heathers.globalcode.info,2020:person:heather-chandler │ tag:heathers.globalcode.info,2020:predicate:says │ What's your damage? │
│ tag:heathers.globalcode.info,2020:person:heather-chandler │ tag:heathers.globalcode.info,2020:predicate:has-surname │ Chandler │
│ tag:heathers.globalcode.info,2020:person:heather-chandler │ tag:heathers.globalcode.info,2020:predicate:has-firstname │ Heather │
│ tag:heathers.globalcode.info,2020:person:veronica-sawyer │ tag:heathers.globalcode.info,2020:predicate:is-member-of │ tag:heathers.globalcode.info,2020:thing:future-leaders │
│ tag:heathers.globalcode.info,2020:person:veronica-sawyer │ tag:heathers.globalcode.info,2020:predicate:dates │ tag:heathers.globalcode.info,2020:person:jason-dean │
│ tag:heathers.globalcode.info,2020:person:veronica-sawyer │ tag:heathers.globalcode.info,2020:predicate:says │ I use my grand IQ to decide what colour gloss to wear │
│ tag:heathers.globalcode.info,2020:person:veronica-sawyer │ tag:heathers.globalcode.info,2020:predicate:is-played-by │ tag:heathers.globalcode.info,2020:person:winona-ryder │
│ tag:heathers.globalcode.info,2020:person:veronica-sawyer │ tag:heathers.globalcode.info,2020:predicate:says │ How very! │
│ tag:heathers.globalcode.info,2020:person:veronica-sawyer │ tag:heathers.globalcode.info,2020:predicate:is-member-of │ tag:heathers.globalcode.info,2020:thing:sharp-shooters │
│ tag:heathers.globalcode.info,2020:person:veronica-sawyer │ tag:heathers.globalcode.info,2020:predicate:is-member-of │ tag:heathers.globalcode.info,2020:thing:croquet-team │
│ tag:heathers.globalcode.info,2020:person:veronica-sawyer │ tag:heathers.globalcode.info,2020:predicate:has-surname │ Sawyer │
│ tag:heathers.globalcode.info,2020:person:veronica-sawyer │ tag:heathers.globalcode.info,2020:predicate:has-firstname │ Veronica │
│ tag:heathers.globalcode.info,2020:person:veronica-sawyer │ tag:heathers.globalcode.info,2020:predicate:says │ Are we going to prom or to hell? │
│ tag:heathers.globalcode.info,2020:thing:croquet-team │ tag:heathers.globalcode.info,2020:predicate:has-title │ Croquet Team │
│ tag:heathers.globalcode.info,2020:person:renee-estevez │ tag:heathers.globalcode.info,2020:predicate:has-surname │ Estevez │
│ tag:heathers.globalcode.info,2020:person:renee-estevez │ tag:heathers.globalcode.info,2020:predicate:has-firstname │ Renée │
│ tag:heathers.globalcode.info,2020:person:kim-walker │ tag:heathers.globalcode.info,2020:predicate:has-surname │ Walker │
│ tag:heathers.globalcode.info,2020:person:kim-walker │ tag:heathers.globalcode.info,2020:predicate:has-firstname │ Kim │
│ tag:heathers.globalcode.info,2020:thing:future-leaders │ tag:heathers.globalcode.info,2020:predicate:has-title │ Future Leaders of America │
│ tag:heathers.globalcode.info,2020:person:winona-ryder │ tag:heathers.globalcode.info,2020:predicate:has-surname │ Ryder │
│ tag:heathers.globalcode.info,2020:person:winona-ryder │ tag:heathers.globalcode.info,2020:predicate:has-firstname │ Winona │
│ tag:heathers.globalcode.info,2020:person:betty-finn │ tag:heathers.globalcode.info,2020:predicate:is-played-by │ tag:heathers.globalcode.info,2020:person:renee-estevez │
│ tag:heathers.globalcode.info,2020:person:betty-finn │ tag:heathers.globalcode.info,2020:predicate:is-true-friend-of │ tag:heathers.globalcode.info,2020:person:veronica-sawyer │
│ tag:heathers.globalcode.info,2020:person:betty-finn │ tag:heathers.globalcode.info,2020:predicate:has-surname │ Finn │
│ tag:heathers.globalcode.info,2020:person:betty-finn │ tag:heathers.globalcode.info,2020:predicate:has-firstname │ Betty │
│ tag:heathers.globalcode.info,2020:person:carrie-lynn │ tag:heathers.globalcode.info,2020:predicate:has-surname │ Lynn │
│ tag:heathers.globalcode.info,2020:person:carrie-lynn │ tag:heathers.globalcode.info,2020:predicate:has-firstname │ Carrie │
│ tag:heathers.globalcode.info,2020:person:christian-slater │ tag:heathers.globalcode.info,2020:predicate:has-surname │ Slater │
│ tag:heathers.globalcode.info,2020:person:christian-slater │ tag:heathers.globalcode.info,2020:predicate:has-firstname │ Christian │
╰───────────────────────────────────────────────────────────┴───────────────────────────────────────────────────────────────┴───────────────────────────────────────────────────────────╯
Record count: 70
and run some more SPARQL queries…
Note: we use The tag: URI scheme for our IRIs. It makes URIs (IRIs) globally unique not only in space but also in time (domain owners change during time). Which is great. In the semantic web and linked data world, it is not common and locators (URLs) are used rather than pure identifiers (URIs, IRIs). But here we want to emphasise that we work strictly with our local data and make it clear that we do not depend on any on-line resources and nothing will be downloaded from remote servers. And in a real project, we should use existing ontologies / vocabularies as much as possible instead of inventing new ones. But we keep this example rather isolated from the complexity of the outer world and bit synthetic.
Find all quotes and names of their authors:
PREFIX person: <tag:heathers.globalcode.info,2020:person:>
PREFIX predicate: <tag:heathers.globalcode.info,2020:predicate:>
PREFIX thing: <tag:heathers.globalcode.info,2020:thing:>
SELECT
?firstname
?surname
?quote
WHERE {
?person predicate:says ?quote .
?person predicate:has-firstname ?firstname .
?person predicate:has-surname ?surname .
}
ORDER BY ?firstname ?surname ?quote
Download: examples/rdf-heathers-quotes.sparql
Result:
rdf_results:
╭────────────────────┬──────────────────┬───────────────────────────────────────────────────────╮
│ firstname (string) │ surname (string) │ quote (string) │
├────────────────────┼──────────────────┼───────────────────────────────────────────────────────┤
│ Heather │ Chandler │ Grow up Heather, bulimia is so '87. │
│ Heather │ Chandler │ What's your damage? │
│ Heather │ Chandler │ You're such a pillowcase! │
│ Heather │ Duke │ Color me stoked! │
│ Heather │ Duke │ Jealous much? │
│ Heather │ Duke │ Veronica, you look like hell. │
│ Heather │ McNamara │ Drool much? │
│ Heather │ McNamara │ God, aren't they fed, yet? │
│ Heather │ McNamara │ Suicide is a private thing. │
│ Jason │ Dean │ Did you say a cherry or Coke slushie? │
│ Jason │ Dean │ Greetings and salutations! │
│ Jason │ Dean │ The extreme always seems to make an impression. │
│ Veronica │ Sawyer │ Are we going to prom or to hell? │
│ Veronica │ Sawyer │ How very! │
│ Veronica │ Sawyer │ I use my grand IQ to decide what colour gloss to wear │
╰────────────────────┴──────────────────┴───────────────────────────────────────────────────────╯
Record count: 15
List groups and counts of their members:
PREFIX person: <tag:heathers.globalcode.info,2020:person:>
PREFIX predicate: <tag:heathers.globalcode.info,2020:predicate:>
PREFIX thing: <tag:heathers.globalcode.info,2020:thing:>
SELECT
?group_name
(count(*) AS ?member_count)
WHERE {
?person predicate:is-member-of ?group .
?group predicate:has-title ?group_name .
}
GROUP BY ?group_name
ORDER BY DESC(?member_count)
Download: examples/rdf-heathers-members.sparql
Result:
rdf_results:
╭───────────────────────────┬───────────────────────╮
│ group_name (string) │ member_count (string) │
├───────────────────────────┼───────────────────────┤
│ Future Leaders of America │ 5 │
│ Croquet Team │ 4 │
│ Sharp Shooters │ 2 │
╰───────────────────────────┴───────────────────────╯
Record count: 3
Filter by a regular expression and list actor names rather than characters:
PREFIX person: <tag:heathers.globalcode.info,2020:person:>
PREFIX predicate: <tag:heathers.globalcode.info,2020:predicate:>
PREFIX thing: <tag:heathers.globalcode.info,2020:thing:>
SELECT
?firstname
?surname
?quote
WHERE {
?character predicate:says ?quote .
?character predicate:is-played-by ?actor .
?actor predicate:has-firstname ?firstname .
?actor predicate:has-surname ?surname .
FILTER (regex(?quote, "much", "i" )) . # i = case-insensitive
}
ORDER BY ?firstname ?surname ?quote
Download: examples/rdf-heathers-much.sparql
Result:
rdf_results:
╭────────────────────┬──────────────────┬────────────────╮
│ firstname (string) │ surname (string) │ quote (string) │
├────────────────────┼──────────────────┼────────────────┤
│ Lisanne │ Falk │ Drool much? │
│ Shannen │ Doherty │ Jealous much? │
╰────────────────────┴──────────────────┴────────────────╯
Record count: 2
Now imagine semantic model of Twin Peaks… How very!
Starting the JVM and creating always a new database from scratch on each query is quite… heavy.
We can keep Jena running in the background and connect to its SPARQL endpoint – or connect to any other endpoint on the internet.
So we will hack together a light script and name it relpipe-in-sparql
(in some future release there will be such official tool).
Because SPARQL endpoints accept plain HTTP requests, support besides XML also CSV and we already have relpipe-in-csv
the script can be very simple:
curl \
--header "Accept: text/csv" \
--data-urlencode query="SELECT * WHERE { ?subject ?predicate ?object . } LIMIT 3" \
https://dbpedia.org/sparql | relpipe-in-csv | relpipe-out-tabular
It becomes bit longer if we add some documentation, argument parsing and configuration:
#!/bin/bash
# Relational pipes
# Copyright © 2020 František Kučera (Frantovo.cz, GlobalCode.info)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, version 3 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
relpipe_in_sparql_help() {
cat <<-EOF
SPARQL query is expected on the STDIN. Exapmple:
echo 'SELECT * WHERE { ?subject ?predicate ?object . } LIMIT 3' \
| relpipe-in-sparql | relpipe-out-tabular
Namespace prefixes are part of the query.
But because they are usually constant, they can be set as the
RELPIPE_IN_SPARQL_PREFIXES environmental variable.
Then they are prepended to the query.
SPARQL endpoint can be set ad-hoc by the --endpoint option.
To configure default SPARQL endpoint, add something like this to your environment:
export RELPIPE_IN_SPARQL_ENDPOINT="https://query.wikidata.org/sparql"
export RELPIPE_IN_SPARQL_ENDPOINT="https://dbpedia.org/sparql"
export RELPIPE_IN_SPARQL_ENDPOINT="https://data.gov.cz/sparql"
export RELPIPE_IN_SPARQL_ENDPOINT="https://data.cssz.cz/sparql"
The relation name defaults to "rdf". Custom name can be set using the --relation option.
EOF
}
relation="rdf";
endpoint="${RELPIPE_IN_SPARQL_ENDPOINT:-https://dbpedia.org/sparql}";
while [[ $# -gt 0 ]]; do
argument="$1";
case "$argument" in
"--relation") relation="$2"; shift; shift; ;;
"--endpoint") endpoint="$2"; shift; shift; ;;
"--help") relpipe_in_sparql_help; exit; ;;
esac
done
[[ -n "$RELPIPE_IN_SPARQL_PREFIXES" ]] && query="$RELPIPE_IN_SPARQL_PREFIXES"; query+=$'\n\n';
query+="$(</dev/stdin)";
# Simple implementation that utilizes the CSV output of the SPARQL endpoint.
relpipe_in_sparql_implementation_csv() {
curl \
--header "Accept: text/csv" \
--data-urlencode query="$query" \
--fail \
--silent \
--show-error \
"$endpoint" \
| relpipe-in-csv --relation "$relation"
}
# More powerful implementation based on XML.
# Can be customized through XSLT.
# But: has more dependencies and avoids streaming.
relpipe_in_sparql_implementation_xml() {
DIR="$(dirname $(realpath "$0"))";
curl \
--header "Accept: application/sparql-results+xml" \
--data-urlencode query="$query" \
--fail \
--silent \
--show-error \
"$endpoint" \
| xsltproc --stringparam "relation" "$relation" "$DIR/relpipe-in-sparql.xsl" - \
| relpipe-in-xml
}
relpipe_in_sparql_implementation_${RELPIPE_IN_SPARQL_IMPLEMENTATION:-csv}
Download: examples/relpipe-in-sparql.sh
Here we have even two implementations that could be switched using the RELPIPE_IN_SPARQL_IMPLEMENTATION
environmental variable.
The XML one is more powerful and can be customized (e.g. to specifically handle localized strings or add some new attributes to the relational output).
On the other hand, the CSV one has fewer dependencies and support streaming of long result sets (XSLT needs to load whole document first).
Both implementation should work:
export RELPIPE_IN_SPARQL_IMPLEMENTATION=xml
export RELPIPE_IN_SPARQL_IMPLEMENTATION=csv
echo 'SELECT * WHERE { ?subject ?predicate "Laura Dern"@en . } LIMIT 3' \
| relpipe-in-sparql \
--relation "jurassic" \
--endpoint "https://dbpedia.org/sparql" \
| relpipe-out-tabular
and produce the same output:
jurassic: ╭────────────────────────────────────────┬────────────────────────────────────────────╮ │ subject (string) │ predicate (string) │ ├────────────────────────────────────────┼────────────────────────────────────────────┤ │ http://dbpedia.org/resource/Laura_Dern │ http://www.w3.org/2000/01/rdf-schema#label │ │ http://www.wikidata.org/entity/Q220901 │ http://www.w3.org/2000/01/rdf-schema#label │ │ http://dbpedia.org/resource/Laura_Dern │ http://xmlns.com/foaf/0.1/name │ ╰────────────────────────────────────────┴────────────────────────────────────────────╯ Record count: 3
And maybe somewhere nearby in the graph we will find:
It's a Unix System… I know this!
The bad news are that we are not querying the real world. We are querying an imperfect, incomplete and outdated snapshot of the reality stored in someone's database. The good news are that we can improve the content of certain databases like we improve articles in Wikipedia.
Some addresses have already leaked in the relpipe-in-sparql --help
above.
Here is brief description of some publicly available sources of RDF data
that we can play with.
A free and open knowledge base, a sister project of Wikipedia. Anyone can use and even edit its content.
export RELPIPE_IN_SPARQL_ENDPOINT="https://query.wikidata.org/sparql"
# or relpipe-in-sparql --endpoint "https://query.wikidata.org/sparql"
Website: Wikidata
They extract structured content from the information created in various Wikimedia projects. And publish this knowledge graph for everyone.
export RELPIPE_IN_SPARQL_ENDPOINT="https://dbpedia.org/sparql"
# or relpipe-in-sparql --endpoint "https://dbpedia.org/sparql"
Website: DBpedia
Ministries and other institutions publish some data as open data and part of them as linked open data (LOD).
export RELPIPE_IN_SPARQL_ENDPOINT="https://data.gov.cz/sparql"
# or relpipe-in-sparql --endpoint "https://data.gov.cz/sparql"
Website: Open data portal of the Czech Republic
export RELPIPE_IN_SPARQL_ENDPOINT="https://data.cssz.cz/sparql"
# or relpipe-in-sparql --endpoint "https://data.cssz.cz/sparql"
Website: Open data portal of the Czech Social Security Administration
export RELPIPE_IN_SPARQL_ENDPOINT="https://cedropendata.mfcr.cz/c3lod/cedr/sparql"
# or relpipe-in-sparql --endpoint "https://cedropendata.mfcr.cz/c3lod/cedr/sparql"
Website: Open Data CEDR III
Besides piping SPARQL queries through relpipe-in-sparql
like this:
cat query.sparql | relpipe-in-sparql | relpipe-out-tabular
we can make them executable and run like a (Bash, Perl, PHP etc.) script:
chmod +x query.sparql
./query.sparql | relpipe-out-csv # output in the CSV format
./query.sparql | relpipe-out-recfile # output in the Recfile format
./query.sparql # automatically appends relpipe-out-tabular to the pipeline
(see the Implementation page for complete list of available transformations and output filters)
We need to add the first line comment that points to the interpreter.
The endpoint
and relation
parameters
are optional – we can say, where this query will be executed and how the output relation will be named:
#!/usr/bin/env rdf-sparql-interpreter.sh
# endpoint: https://query.wikidata.org/sparql
# relation: few_statements
SELECT * WHERE { ?subject ?predicate ?object } LIMIT 3
Download: examples/rdf-sample-triples.sparql
Environmental variables RELPIPE_IN_SPARQL_ENDPOINT
and RELPIPE_IN_SPARQL_RELATION
can be set to override the parameters from the file.
All the magic is done by this (bit hackish) helper script:
#!/bin/bash
# Relational pipes
# Copyright © 2020 František Kučera (Frantovo.cz, GlobalCode.info)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, version 3 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# This is a helper script that could be used as a shebang interpreter for SPARQL files.
# Just set the SPARQL file executable and add the first line comment.
# The endpoint and relation parameters are optional.
# Parameters must be at the beginning of the file before any empty lines.
# Only the first occurrence of each parameter is relevant.
# This interpreter produces a human-readable table if STDOUT is a terminal
# and machine-readable relational data if STDOUT is not a terminal.
# So the stream can be piped to any relational transformation (e.g. relpipe-tr-sql)
# or output filter (e.g. relpipe-out-csv).
#
# Example:
#
# #!/usr/bin/env rdf-sparql-interpreter.sh
# # endpoint: https://query.wikidata.org/sparql
# # relation: few_statements
#
# SELECT * WHERE { ?subject ?predicate ?object } LIMIT 3
# Execute the SPARQL query and send the results to the STDOUT
# either raw or formatted as a table depending on whether STDOUT is a terminal or not.
interpret_sparql() {
if [ -t 1 ] ; then relpipe-in-sparql "$@" | relpipe-out-tabular;
else relpipe-in-sparql "$@"; fi
}
# Fetches the parameter $2 from file $1.
# Parameters must be formatted as comments: „# name: value“.
# Only the first occurrence is relevant. And empty line terminates processing.
find_parameter() {
perl -e 'while(<>) { if (/^\s*$/) { last; } elsif (/^# ($ARGV[0]): (.*)$/) { print "$2"; last; } }' "$1" "$2"
}
# Appends the "${options[@]}" array with option $2 taken from the $1 file
# or from environmental variable RELPIPE_IN_SPARQL_${2^^} (the variable has precedence).
prepare_option() {
variableName="RELPIPE_IN_SPARQL_${2^^}"
parameterName="$2"
[[ -n "${!variableName}" ]] && value="${!variableName}" || value="$(find_parameter "$1" "$parameterName")"
[[ -n "$value" ]] && options+=("--$parameterName" "$value")
}
options=()
prepare_option "$1" endpoint
prepare_option "$1" relation
cat "$1" | interpret_sparql "${options[@]}"
Download: examples/rdf-sparql-interpreter.sh
This script requires the relpipe-in-sparql
we put together earlier.
Both scripts are just examples (not part of any release yet).
Hey kid, rock and roll, let us list the films where both Coreys starred:
#!/usr/bin/env rdf-sparql-interpreter.sh
# endpoint: https://dbpedia.org/sparql
# relation: coreys
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX dbo: <http://dbpedia.org/ontology/>
SELECT
?film_name
(?film AS ?film_uri)
WHERE {
?actor1 foaf:name "Corey Haim"@en .
?actor2 foaf:name "Corey Feldman"@en .
?film dbo:starring ?actor1 .
?film dbo:starring ?actor2 .
?film foaf:name ?film_name .
}
ORDER BY ?film_name
Download: examples/rdf-coreys.sparql
Result:
coreys:
╭────────────────────────────────┬────────────────────────────────────────────────────────────╮
│ film_name (string) │ film_uri (string) │
├────────────────────────────────┼────────────────────────────────────────────────────────────┤
│ Blown Away │ http://dbpedia.org/resource/Blown_Away_(1992_film) │
│ Busted │ http://dbpedia.org/resource/Busted_(film) │
│ Dream a Little Dream │ http://dbpedia.org/resource/Dream_a_Little_Dream_(film) │
│ Dream a Little Dream 2 │ http://dbpedia.org/resource/Dream_a_Little_Dream_2 │
│ License to Drive │ http://dbpedia.org/resource/License_to_Drive │
│ National Lampoon's Last Resort │ http://dbpedia.org/resource/National_Lampoon's_Last_Resort │
│ The Two Coreys │ http://dbpedia.org/resource/The_Two_Coreys_(TV_series) │
╰────────────────────────────────┴────────────────────────────────────────────────────────────╯
Record count: 7
So Mercedes has scratched our Cadillac, but it was still a great night.
Now it is time to visit our friends from the club:
#!/usr/bin/env rdf-sparql-interpreter.sh
# endpoint: https://dbpedia.org/sparql
# relation: breakfast_club
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX dbo: <http://dbpedia.org/ontology/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT
?firstname
?surname
WHERE {
?film a dbo:Film .
?film rdfs:label "The Breakfast Club"@en .
?film dbo:starring ?actor .
?actor foaf:givenName ?firstname .
?actor foaf:surname ?surname .
FILTER (lang(?firstname) = "en" && lang(?surname) = "en")
?actor dbo:birthYear ?born .
FILTER(?born > "1950"^^xsd:gYear)
}
ORDER BY ?firstname ?surname
Download: examples/rdf-breakfast-club.sparql
Result:
breakfast_club:
╭────────────────────┬──────────────────╮
│ firstname (string) │ surname (string) │
├────────────────────┼──────────────────┤
│ Ally │ Sheedy │
│ Anthony │ Hall │
│ Emilio │ Estevez │
│ Judd │ Nelson │
│ Molly │ Ringwald │
╰────────────────────┴──────────────────╯
Record count: 5
Not only pretty in pink, this is true wisdom and we could have much fun traversing this part of the graph. But let us turn the globe around… there is also a lot to see in the Eastern Bloc.
#!/usr/bin/env rdf-sparql-interpreter.sh
# endpoint: https://dbpedia.org/sparql
# relation: blonde_and_brunette
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX dbo: <http://dbpedia.org/ontology/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX schema: <http://schema.org/>
SELECT DISTINCT
?star_name
WHERE {
{
?film rdfs:label "Ирония судьбы, или С лёгким паром!"@ru .
?film dbo:starring ?blonde .
?blonde dbo:birthYear "1941"^^xsd:gYear .
?blonde rdfs:label ?star_name .
FILTER (lang(?star_name) = "pl") .
}
UNION
{
?brunette ?born "1956-04-27"^^xsd:date .
?brunette schema:description "czech actress, presenter and singer"@en .
?brunette rdfs:label ?star_name .
FILTER (lang(?star_name) = "cs") .
}
}
ORDER BY ?star_name
Download: examples/rdf-blonde-and-brunette.sparql
Result:
blonde_and_brunette:
╭────────────────────╮
│ star_name (string) │
├────────────────────┤
│ Barbara Brylska │
│ Dagmar Patrasová │
╰────────────────────╯
Record count: 2
Dad, what is this place?
Where are we?
Is there anyone here?
No. Just us.
#!/usr/bin/env rdf-sparql-interpreter.sh
# endpoint: https://dbpedia.org/sparql
# relation: return
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX dbo: <http://dbpedia.org/ontology/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX schema: <http://schema.org/>
SELECT DISTINCT
?actor_name
?film_name
?lang
WHERE {
?actor foaf:name "Konstantin Lavronenko"@en .
?film dbo:starring ?actor .
?film dbo:abstract ?film_abstract .
FILTER (regex(?film_abstract, "mythic"))
?actor rdfs:label ?actor_name .
?film rdfs:label ?film_name .
FILTER (lang(?actor_name) = "en" && lang(?film_name) IN ("ru", "en", "nl"))
BIND (lang(?film_name) AS ?lang)
}
Download: examples/rdf-return.sparql
Result:
return:
╭───────────────────────┬───────────────────────────────────┬───────────────╮
│ actor_name (string) │ film_name (string) │ lang (string) │
├───────────────────────┼───────────────────────────────────┼───────────────┤
│ Konstantin Lavronenko │ Возвращение (фильм, 2003, Россия) │ ru │
│ Konstantin Lavronenko │ The Return (2003 film) │ en │
│ Konstantin Lavronenko │ Vozvrasjtsjenie │ nl │
╰───────────────────────┴───────────────────────────────────┴───────────────╯
Record count: 3
If you got an impression that RDF is just a poor relational database with a single table consisting of mere three columns and with freaky SQL dialect, please be assured that this example shows just a small fraction of the wonderful RDF world.
Relational pipes, open standard and free software © 2018-2022 GlobalCode