This demonstration focuses on clarifying how SPARQL behaves in Stardog, GraphDB and Amazon Neptune when querying data across multiple graphs in a database.
Try it yourself: The demo queries shown in this article can be executed in the RDF Studio Query Editor. If you're not already connected to Northwind, connect via Connect tab first, then navigate to Query -> Editor.
The RDF data model represents information as graphs composed of triples: subject, predicate, and object. Many RDF stores support multiple named graphs, each encapsulating a distinct subset of data. These stores can also maintain metadata about each graph, enabling applications to perform queries that span across graphs — integrating and comparing information from different sources or contexts.
A SPARQL query is executed against an RDF dataset, which consists of a collection of graphs structured as follows:
Each graph in a dataset is still a set of triples which means there can be no duplication of triples within a graph. However, there is no similar requirement across multiple graphs, so the same triple may appear in multiple graphs and each occurrence is considered a distinct triple.
When querying a collection of graphs, the GRAPH keyword is used to match patterns against named graphs. GRAPH can provide an IRI to select one graph or use a variable which will range over the IRI of all the named graphs in the query's RDF dataset.
GRAPH
The use of GRAPH changes the active graph for matching graph patterns within that part of the query. Outside the use of GRAPH, matching is done using the default graph.
Another way to think of named graphs is as a set of quads where the fourth component added to the triple is the name of the graph, which is possibly empty for the default graph.
Named graphs are a powerful feature in RDF triplestores that allow subsets of triples to be grouped and identified with a unique URI. This structure is invaluable for dataset management, enabling the separation of different data sources, versions, or domains within the same triple store. Named graphs also support performance optimization, as queries can be scoped to relevant graphs, reducing search space and improving response times. They are particularly effective for refreshing external datasets, where a new version can be inserted into a dedicated graph and swapped in without impacting others. Beyond that, named graphs are essential for provenance tracking, allowing systems to trace the origin and trustworthiness of specific triples. They also facilitate access control, making it possible to restrict read or write operations at the graph level. In collaborative or multi-tenant environments, named graphs support modular knowledge representation, allowing different users or applications to maintain their own isolated knowledge domains while still coexisting in the same store.
You can execute all the SPARQL queries in this demonstration directly in the Query > Editor tab using the built-in SPARQL editor. Alternatively, if you prefer to run them on Stardog or GraphDB, set up an empty RDF database on your local machine by following the instructions in the Setting up the Northwind database on Stardog or GraphDB sections of the Northwind SQL vs SPARQL article.
Execute each of the following statements individually in an existing database of your choice. There will be examples to be executed in Stardog and GraphDB.
Populate graph1
PREFIX ns: <http://mysparql.ai/ns#> INSERT DATA { GRAPH ns:graph1 { ns:book1 ns:price 10 . } }
Populate graph2
PREFIX ns: <http://mysparql.ai/ns#> INSERT DATA { GRAPH ns:graph2 { ns:book1 ns:price 10 . ns:book2 ns:price 20 . } }
Populate graph3
PREFIX ns: <http://mysparql.ai/ns#> INSERT DATA { GRAPH ns:graph3 { ns:book1 ns:price 10 . ns:book2 ns:price 20 . ns:book3 ns:price 30 . } }
Note that some of the books are added to more than one graph for demonstration purposes.
Stardog is configured with query.all.graphs=false by default, which has been used for the initial queries below. See details on how to set up this database property further down in this article.
query.all.graphs=false
Each FROM clause contains an IRI that indicates a graph to be used to form the default graph. This does not put the graph in as a named graph.
FROM
PREFIX ns: <http://mysparql.ai/ns#> SELECT * FROM ns:graph1 FROM ns:graph2 FROM ns:graph3 WHERE {?s ?p ?o}
A query can supply IRIs for the named graphs in the RDF Dataset using the FROM NAMED clause. The GRAPH keyword is used to bind the ?g variable to each named graph in the RDF Dataset.
FROM NAMED
?g
PREFIX ns: <http://mysparql.ai/ns#> SELECT * FROM NAMED ns:graph1 FROM NAMED ns:graph2 FROM NAMED ns:graph3 WHERE { GRAPH ?g { ?s ?p ?o } } ORDER BY ?g ?o
The Basic Graph Patterns (BGPs) outside of GRAPH {} are evaluated against the default part of the RDF Dataset (i.e. defined using FROM) while BGPs within GRAPH {} are evaluated for each graph in the named part of the RDF Dataset.
GRAPH {}
The following query is evaluated against the default part of the RDF Dataset, which is made of graph1 and graph2, and completely ignores graph3.
PREFIX ns: <http://mysparql.ai/ns#> SELECT * FROM ns:graph1 FROM ns:graph2 FROM NAMED ns:graph3 WHERE { ?s ?p ?o }
The following query is evaluated for each graph in the named part of the RDF Dataset, which is graph3, and completely ignores graph1 and graph2.
PREFIX ns: <http://mysparql.ai/ns#> SELECT * FROM ns:graph1 FROM ns:graph2 FROM NAMED ns:graph3 WHERE { GRAPH ?g { ?s ?p ?o } }
The following is an example of a query with FROM (and no FROM NAMED) and GRAPH {} which cannot return results.
PREFIX ns: <http://mysparql.ai/ns#> SELECT * FROM ns:graph1 FROM ns:graph2 WHERE { GRAPH ?g { ?s ?p ?o } }
As seen in a previous example, you can restrict the named graphs in the RDF Dataset by using the FROM NAMED clause. Note that the result of the query below is a “union” of both named graphs.
PREFIX ns: <http://mysparql.ai/ns#> SELECT ?g ?s ?p ?o FROM NAMED ns:graph2 FROM NAMED ns:graph3 WHERE { GRAPH ?g { ?s ?p ?o } } ORDER BY ?g ?s
However, you can restrict the named graph by supplying their IRIs.
In the following example, the Basic Graph Patterns will be evaluated against graphs 2 and 3. Note that the result of the query below is a “join” of both named graphs.
PREFIX ns: <http://mysparql.ai/ns#> SELECT * WHERE { GRAPH ns:graph2 { ?s ?p ?o } GRAPH ns:graph3 { ?s ?p ?o } }
Only book1 and book2 can be found in both graphs.
In the following query, the Basic Graph Patterns (BGPs) will be evaluated against graphs 1, 2, and 3.
PREFIX ns: <http://mysparql.ai/ns#> SELECT * WHERE { GRAPH ns:graph1 { ?s ?p ?o } GRAPH ns:graph2 { ?s ?p ?o } GRAPH ns:graph3 { ?s ?p ?o } }
Only book1 can be found in all 3 graphs.
As seen in a previous example, the GRAPH keyword can be used to bind a variable to each named graph in the RDF Dataset. However, this time we are going to use the bound variable to filter the graphs, and not FROM NAMED clause, as per the following two examples:
PREFIX ns: <http://mysparql.ai/ns#> SELECT ?g ?s ?p ?o WHERE { { GRAPH ?g { ?s ?p ?o } } } ORDER BY ?g ?s
PREFIX ns: <http://mysparql.ai/ns#> SELECT ?g ?s ?p ?o WHERE { { GRAPH ?g { ?s ?p ?o } } FILTER (?g IN (ns:graph2 , ns:graph3)) } ORDER BY ?g ?s
Union
You can explicitly union graphs and therefore alter the default “merge” behaviour. The following example returns all books from all graphs, including books that are repeated in different graphs.
PREFIX ns: <http://mysparql.ai/ns#> SELECT * WHERE { { GRAPH ns:graph1 { ?s ?p ?o } } UNION { GRAPH ns:graph2 { ?s ?p ?o } } UNION { GRAPH ns:graph3 { ?s ?p ?o } } }
The following example shows a query that unions graphs 1 and 2 into the default graph and joins on the named graph 3.
PREFIX ns: <http://mysparql.ai/ns#> SELECT ?g ?s ?p ?o FROM ns:graph1 FROM ns:graph2 FROM NAMED ns:graph3 WHERE { { ?s ?p ?o } # default graph { GRAPH ?g { ?s ?p ?o } } # named graph } ORDER BY ?g ?o
The following example shows a query that unions graph 1 and 2 into the default graph and unions again on the named graph 3. Note that ?g variable doesn’t get bound to the default graph, but only named graphs.
PREFIX ns: <http://mysparql.ai/ns#> SELECT ?g ?s ?p ?o FROM ns:graph1 FROM ns:graph2 FROM NAMED ns:graph3 WHERE { { ?s ?p ?o } UNION { GRAPH ?g { ?s ?p ?o } } } ORDER BY ?g ?o
This query returns the counts only for the named graphs listed in the FROM NAMED clause.
PREFIX ns: <http://mysparql.ai/ns#> SELECT ?g (count(*) as ?size) FROM NAMED ns:graph1 FROM NAMED ns:graph2 FROM NAMED ns:graph3 WHERE { GRAPH ?g { ?s ?p ?o } } GROUP BY ?g ORDER BY asc(?size)
The following query returns the count for all existing named graphs on the database by using a Stardog extension.
SELECT ?g (count(*) as ?size) FROM NAMED stardog:context:named WHERE { GRAPH ?g { ?s ?p ?o } } GROUP BY ?g ORDER BY asc(?size)
Considering that the option query.all.graphs=false, or if you don't want to rely on it, you could use the following Stardog extension.
SELECT (count(*) as ?size) FROM stardog:context:default WHERE { ?s ?p ?o }
The default graph shown above is from the Northwind sample database.
The following query returns the count of triples in the default and all named graphs using the stardog:context:all Stardog extension.
stardog:context:all
SELECT ?g (count(*) as ?size) FROM NAMED stardog:context:all WHERE { GRAPH ?g { ?s ?p ?o } } GROUP BY ?g ORDER BY asc(?size)
The following query is equivalent to the previous one, however it does not rely on the Stardog extension, which makes it compatible with other triplestore vendors.
SELECT ?g (count(*) as ?size) WHERE { { GRAPH ?g { ?s ?p ?o } } UNION { ?s ?p ?o BIND("default" AS ?g) } } GROUP BY ?g ORDER BY asc(?size)
Search for book1 in the default graph.
SELECT ?s ?p ?o WHERE { { ?s ?p ?o } FILTER ( (CONTAINS (STR(?s), ?searchString)) || (CONTAINS (STR(?p), ?searchString)) || (CONTAINS (STR(?o), ?searchString)) ) BIND("book1" AS ?searchString) } ORDER BY ?s
Book1 was not found in the default graph, because it was added to the named graph graph1. In fact, all books in these demonstrations were added to named graphs.
Note that the variable ?searchString is assigned before it’s been used in the filter, despite the BIND coming after the FILTER in the code.
?searchString
BIND
FILTER
However, setting the query context to stardog:context:all in the Stardog Studio drop-down **(**through the SPARQL Protocol) makes all triples in named graphs available on the default graph.
The query below can be used to achieve the same result. It will search for book1 across all graphs, named and default and union the results.
SELECT ?g ?s ?p ?o WHERE { { GRAPH ?g { ?s ?p ?o } } UNION { ?s ?p ?o BIND("default" AS ?g) } FILTER ( (CONTAINS (STR(?s), ?searchString)) || (CONTAINS (STR(?p), ?searchString)) || (CONTAINS (STR(?o), ?searchString)) ) BIND("book1" AS ?searchString) } ORDER BY ?g ?s
Setting context to stardog:context:all in the Stardog Studio drop-down **(**through the SPARQL Protocol) makes all triples available on the default graph. Therefore, the query won’t return any data from named graphs.
Stardog has a database property called "Query All Graphs" query.all.graphs, which provides the same behaviour as the stardog:context:all, but set at the database level.
query.all.graphs
Note that this setting is required to be set to “true” when working with some visualisations and query tools.
To be more specific, given the RDF Dataset as a structure of two parts (the default and named part) here is how the query.all.graphs affects the query behaviour: With false, the default dataset will be <context:**default**, context:named>. With true, the default dataset will be <context:**all**, context:named>. This option applies only when the query does not use any FROM or FROM NAMED and also the dataset is not set through the SPARQL Protocol (which happens when you select the graph in that drop-down list in Stardog Studio).
false
<context:**default**, context:named>
true
<context:**all**, context:named>
Setting query.all.graphs=true in Stardog can be handy when you want to provide an easy way to execute a triple pattern query over all stored RDF statements in the database. This is the default behaviour in GraphDB (Refer to Querying Graphs in GraphDB section), and allows you to query all the data without having to worry about where they are, reducing the complexity of queries. It also allows visualisation tools to have full visibility of the data available in the RDF database. Note that you can still access data in named graphs individually, if needed to, using the methods described in this article.
query.all.graphs=true
Note that the query below does not use the GRAPH keyword to reference named graphs. Triples from the named graphs are now available on the default graph.
However, when the GRAPH is used, duplicates will appear, as the same triples are available through the default and named graphs.
SELECT ?g ?s ?p ?o WHERE { { GRAPH ?g {?s ?p ?o} } UNION { ?s ?p ?o BIND("default" AS ?g) } FILTER ( (CONTAINS (STR(?s), ?searchString)) || (CONTAINS (STR(?p), ?searchString)) || (CONTAINS (STR(?o), ?searchString)) ) BIND("book1" AS ?searchString) } ORDER BY ?g ?s
By setting the query context to stardog:context:all in the Stardog Studio drop-down **(**through the SPARQL Protocol) seems to correct this behaviour, as all data is made available through the default graph only.
Amazon Neptune associates every triple with a named graph. The default graph is defined as the union of all named graphs.
If you submit a SPARQL query without explicitly specifying a graph via the GRAPH keyword or constructs such as FROM NAMED, Neptune always considers all triples in your DB instance. For example, the following query returns all triples from a Neptune SPARQL endpoint:
SELECT * WHERE { ?s ?p ?o }
Triples that appear in more than one graph are returned only once.
Specifying the Named Graph for Loading, Inserts, or Updates
If you don’t specify a named graph when loading, inserting, or updating triples, Neptune uses the fallback named graph defined by the URI http://aws.amazon.com/neptune/vocab/v01/DefaultNamedGraph.
http://aws.amazon.com/neptune/vocab/v01/DefaultNamedGraph
GraphDB constructs the default dataset as follows:
There are two reasons for this behaviour:
SELECT ?g { GRAPH ?g { ?s ?p ?o } }
⠀ Examples
Querying the default graph
Note that book1 was added to the named graphs 1, 2, and 3, but not to the default graph, in the beginning of this demonstration. Also note that there are no book1 duplicates in the default graph.
Querying the default and named graphs
Note that there are occurrences of book1 in each of the named graphs, as well as the default graph. That is the expected behaviour in GraphDB, as explained previously.
Run one statement at a time to clear the graphs used in this demonstration.
PREFIX ns: <http://mysparql.ai/ns#> CLEAR GRAPH ns:graph1PREFIX ns: <http://mysparql.ai/ns#> CLEAR GRAPH ns:graph2PREFIX ns: <http://mysparql.ai/ns#> CLEAR GRAPH ns:graph3
Visualisation and natural language query tools (and query builders) can offer great options to explore an RDF Graph Database.
You can also explore these concepts using the Explore and Visualize tabs, which provide interactive graph browsing and visualization capabilities.
Below are two examples of tools that require all named graphs to be available in the default graph for them to work properly. This is the default behaviour in GraphDB and can be accomplished in Stardog by setting the query.all.graphs=true database property.
The following visualisation was created using metaphacts.
Sparklis is a natural language query builder and offers a very intuitive way of exploring an RDF Graph Database. The tool generates the SPARQL query for you automatically.
Sparklis lets you explore an RDF dataset through a SPARQL endpoint without the need to read or write SPARQL queries.