8 RDF Views: Relational Data as RDF

You can create and use RDF views over relational data in Oracle Spatial and Graph RDF Semantic Graph.

Relational data is viewed as virtual RDF triples using one of the two forms of RDB2RDF mapping described in W3C documents on Direct Mapping and R2RML mapping:

8.1 Why Use RDF Views on Relational Data?

Using RDF views on relational data enables you to integrate data available from different sources.

You can exploit the advantages of relational data without the need for physical storage of the RDF triples that correspond to the relational data. Before RDF views were included in RDF Semantic Graph in Oracle Database 12c Release 1 (12.1), you needed to write custom SQL queries or use non-standard mappings and physically store the generated RDF triples in an RDF model.

The simplest way to create a mapping of relational data to RDF data is by calling the SEM_APIS.CREATE_RDFVIEW_MODEL procedure to create an RDF view and supplying the list of tables or views whose content you would like to be viewed as RDF. This provides a direct mapping of those relational tables or views.

To get a more customized mapping, you can write an R2RML mapping document (in RDF using Turtle, for example) to specify the desired mapping, load the mapping document (after converting it to N-Triple format) into a staging table (for the table definition, see Bulk Loading Semantic Data Using a Staging Table), and then call the SEM_APIS.CREATE_RDFVIEW_MODEL procedure to create an RDF view by supplying the name of the staging table.

8.2 API Support for RDF Views

Subprograms are included in the SEM_APIS package for creating, dropping, and exporting (that is, materializing the content of) RDF views.

An RDF view is created as an RDF model, but the RDF model physically contains only metadata. The actual data is still stored in the relational tables for which the RDF view has been created. (The SEM_APIS subprograms are documented in SEM_APIS Package Subprograms.)

For the examples in the rest of this section, assume that the following relational tables exist in the invoker's schema:

CREATE TABLE dept (
  deptno NUMBER CONSTRAINT pk_DeptTab_deptno PRIMARY KEY,
  dname VARCHAR2(30),
  loc VARCHAR2(30)
);
 
CREATE TABLE emp ( 
  empno NUMBER PRIMARY KEY,
  ename VARCHAR2(30),
  job VARCHAR2(20),
  deptno NUMBER REFERENCES dept (deptno)
);

Note that if these tables are in a different schema (for example, SCOTT) than the invoker's, when specifying the names of these tables, you need to use schema-qualified table names: "SCOTT"."DEPT" and "SCOTT"."EMP".

8.2.1 Creating an RDF View with Direct Mapping

Example 8-1 creates an RDF view model using direct mapping of two tables, EMP and DEPT, with a base prefix of http://empdb/. The (virtual) RDF terms are generated according to A Direct Mapping of Relational Data to RDF, W3C Recommendation (http://www.w3.org/TR/rdb-direct-mapping/).

Example 8-1 Creating an RDF View with Direct Mapping

BEGIN
  sem_apis.create_rdfview_model(
    model_name => 'empdb_model',
    tables => SYS.ODCIVarchar2List('EMP', 'DEPT'),
    prefix => 'http://empdb/',
    options => 'KEY_BASED_REF_PROPERTY=T'
  );
END;
/

To see the properties that are generated, enter the following statement (which assumes that the objects are created in the schema of a user named TESTUSER):

SELECT DISTINCT p
  FROM TABLE(SEM_MATCH(
    '{?s ?p ?o}',
    SEM_Models('empdb_model'),
    NULL,
    NULL,
    NULL));
 
P                                                                               
--------------------------------------------------------------------------------
http://empdb/TESTUSER.EMP#DEPTNO                                                
http://empdb/TESTUSER.DEPT#LOC                                                  
http://empdb/TESTUSER.EMP#JOB                                                   
http://empdb/TESTUSER.DEPT#DEPTNO                                               
http://empdb/TESTUSER.EMP#ENAME                                                 
http://www.w3.org/1999/02/22-rdf-syntax-ns#type                                 
http://empdb/TESTUSER.DEPT#DNAME                                                
http://empdb/TESTUSER.EMP#EMPNO                                                 
http://empdb/TESTUSER.EMP#ref-DEPTNO                                            
 
9 rows selected.

Example 8-2 Using CONFORMANCE=T

Example 8-2 is essentially the same as Example 8-1, but it uses the CONFORMANCE=T option (see the options parameter description for SEM_APIS.CREATE_RDFVIEW_MODEL). Notice in the output that the schema name is not included in the list of properties; for example, the first output record in Example 8-2 is http://empdb/DEPT#LOC, whereas its counterpart generated by Example 8-1 is http://empdb/TESTUSER.DEPT#LOC.

BEGIN
  sem_apis.create_rdfview_model(
    model_name => 'empdb_model',
    tables => SYS.ODCIVarchar2List('EMP', 'DEPT'),
    prefix => 'http://empdb/',
    options => 'CONFORMANCE=T'
  );
END;
/

SELECT DISTINCT p
  FROM TABLE(SEM_MATCH(
    '{?s ?p ?o}',
    SEM_Models('empdb_model'),
    NULL,
    NULL,
    NULL));
 
P                                                                               
--------------------------------------------------------------------------------
http://empdb/DEPT#LOC                                                           
http://empdb/EMP#ref-DEPTNO                                                     
http://empdb/EMP#ENAME                                                          
http://empdb/DEPT#DEPTNO                                                        
http://empdb/EMP#JOB                                                            
http://empdb/EMP#EMPNO                                                          
http://www.w3.org/1999/02/22-rdf-syntax-ns#type                                 
http://empdb/DEPT#DNAME                                                         
http://empdb/EMP#DEPTNO                                                         
 
9 rows selected.

8.2.2 Creating an RDF View with an R2RML Mapping

If you wanted to create an RDF view using the two tables EMP and DEPT, but with your own customizations, you could create an R2RML mapping document specified using Turtle, such as the following:

@prefix rr: <http://www.w3.org/ns/r2rml#>.
@prefix xsd: <http://www.w3.org/2001/XMLSchema#>.
@prefix ex: <http://example.com/ns#>.
 
ex:TriplesMap_Dept
    rr:logicalTable [ rr:tableName "DEPT" ];
    rr:subjectMap [
        rr:template "http://data.example.com/department/{DEPTNO}";
        rr:class ex:Department;
    ];
    rr:predicateObjectMap [
        rr:predicate ex:deptNum;
        rr:objectMap [ rr:column "DEPTNO" ; rr:datatype xsd:integer ];
    ];
    rr:predicateObjectMap [
        rr:predicate ex:deptName;
        rr:objectMap [ rr:column "DNAME" ];
    ];
    rr:predicateObjectMap [
        rr:predicate ex:deptLocation;
        rr:objectMap [ rr:column "LOC" ];
    ].
 
ex:TriplesMap_Emp
    rr:logicalTable [ rr:tableName "EMP" ];
    rr:subjectMap [
        rr:template "http://data.example.com/employee/{EMPNO}";
        rr:class ex:Employee;
    ];
    rr:predicateObjectMap [
        rr:predicate ex:empNum;
        rr:objectMap [ rr:column "EMPNO" ; rr:datatype xsd:integer ];
    ];
    rr:predicateObjectMap [
        rr:predicate ex:empName;
        rr:objectMap [ rr:column "ENAME" ];
    ];
    rr:predicateObjectMap [
        rr:predicate ex:jobType;
        rr:objectMap [ rr:column "JOB" ];
    ];
    rr:predicateObjectMap [
        rr:predicate ex:worksForDeptNum;
        rr:objectMap [ rr:column "DEPTNO" ; rr:dataType xsd:integer ];
    ];
    rr:predicateObjectMap [
        rr:predicate ex:worksForDept;
        rr:objectMap [ 
          rr:parentTriplesMap ex:TriplesMap_Dept ; 
          rr:joinCondition [ rr:child "DEPTNO"; rr:parent "DEPTNO" ]]].

Then, load your R2RML mapping (converted into N-Triples format) into a staging table, such as SCOTT.R2RTAB, and grant the SELECT privilege for this table to MDSYS.

Next, call SEM_APIS.CREATE_RDFVIEW_MODEL, as in Example 8-3.

Example 8-3 Creating an RDF View with an R2RML Mapping

BEGIN
  sem_apis.create_rdfview_model(
    model_name => 'empdb_model',
    tables => NULL,
    r2rml_table_owner => 'SCOTT',
    r2rml_table_name => 'R2RTAB'
  );
END;
/

8.2.3 Dropping an RDF View

An RDF view can be dropped using the SEM_APIS.DROP_RDFVIEW_MODEL procedure, as shown in Example 8-4.

Example 8-4 Dropping an RDF View

BEGIN
  sem_apis.drop_rdfview_model(
    model_name => 'empdb_model'
  );
END;
/

8.2.4 Exporting Virtual Content of an RDF View into a Staging Table

The content of an RDF view is virtual; that is, the RDF triples corresponding to the underlying relational data, as mapped by direct mapping or R2RML mapping, are not materialized and stored anywhere. You may, however, want to materialize and store these virtual RDF triples in an RDF model for your testing purposes. The SEM_APIS.EXPORT_RDFVIEW_MODEL subprogram lets you store the RDF triples of an RDF view in a staging table. The staging table can then be used for loading into an RDF model.

Example 8-5 materializes (in N-Triples format) the content of RDF view empdb_model into the staging table SCOTT.RDFTAB.

Example 8-5 Exporting an RDF View

BEGIN
  sem_apis.export_rdfview_model(
    model_name => 'empdb_model',
    rdf_table_owner => 'SCOTT',
    rdf_table_name => 'RDFTAB'
  );
END;

8.3 Example: Using an RDF View with Direct Mapping

This topic provides an example of using an RDF view with direct mapping.

Example 8-6 shows a simple workflow using an RDF view with direct mapping. In it, you:

  1. Create two relational tables (EMP and DEPT).

  2. Insert data into the tables.

  3. Create an RDF view model (empdb_model) using direct mapping of the two tables.

  4. Query the RDF view using SPARQL in a SEM_MATCH-based SQL query.

Example 8-7 shows the output of the statements in Example 8-6.

Example 8-6 Using an RDF View with Direct Mapping

-- Use the following relational tables.
 
CREATE TABLE dept (
  deptno NUMBER CONSTRAINT pk_DeptTab_deptno PRIMARY KEY,
  dname VARCHAR2(30),
  loc VARCHAR2(30)
);
 
CREATE TABLE emp ( 
  empno NUMBER PRIMARY KEY,
  ename VARCHAR2(30),
  job VARCHAR2(20),
  deptno NUMBER REFERENCES dept (deptno)
);
 
-- Insert some data.
 
INSERT INTO dept (deptno, dname, loc)
  VALUES (1, 'Sales', 'Boston');
INSERT INTO dept (deptno, dname, loc)
  VALUES (2, 'Manufacturing', 'Chicago');
INSERT INTO dept (deptno, dname, loc)
  VALUES (3, 'Marketing', 'Boston');
 
INSERT INTO emp (empno, ename, job, deptno)
  VALUES (1, 'Alvarez', 'SalesRep', 1);
INSERT INTO emp (empno, ename, job, deptno)
  VALUES (2, 'Baxter', 'Supervisor', 2);
INSERT INTO emp (empno, ename, job, deptno)
  VALUES (3, 'Chen', 'Writer', 3);
INSERT INTO emp (empno, ename, job, deptno)
  VALUES (4, 'Davis', 'Technician', 2);
 
-- Create an RDF view model using direct mapping of two tables, EMP and DEPT, 
-- with a base prefix of http://empdb/.
-- Specify KEY_BASED_REF_PROPERTY=T for the options parameter.
 
BEGIN
  sem_apis.create_rdfview_model(
    model_name => 'empdb_model',
    tables => SYS.ODCIVarchar2List('EMP', 'DEPT'),
    prefix => 'http://empdb/',
    options => 'KEY_BASED_REF_PROPERTY=T'
  );
END;
/
 
-- Query an RDF view using SPARQL in a SEM_MATCH-based SQL query. 
-- The next statament is a query against an RDF view named empdb_model 
-- to find the employees who work for any department located in Boston.

SELECT emp
  FROM TABLE(SEM_MATCH(
  '{?emp emp:ref-DEPTNO ?dept . ?dept dept:LOC "Boston"}',
  SEM_Models('empdb_model'),
  NULL,
  SEM_ALIASES(
    SEM_ALIAS('dept','http://empdb/TESTUSER.DEPT#'),
    SEM_ALIAS('emp','http://empdb/TESTUSER.EMP#')
  ),
  null));

-- The preceding query is functionally comparable to this:
SELECT e.empno FROM emp e, dept d WHERE e.deptno = d.deptno AND d.loc = 'Boston';

Example 8-7 Output of Example 8-6

SQL> -- Use the following relational tables.
SQL> 
SQL> CREATE TABLE dept (
  2    deptno NUMBER CONSTRAINT pk_DeptTab_deptno PRIMARY KEY,
  3    dname VARCHAR2(30),
  4    loc VARCHAR2(30)
  5  );
 
Table created.
 
SQL> 
SQL> CREATE TABLE emp (
  2    empno NUMBER PRIMARY KEY,
  3    ename VARCHAR2(30),
  4    job VARCHAR2(20),
  5    deptno NUMBER REFERENCES dept (deptno)
  6  );
 
Table created.
 
SQL> 
SQL> -- Insert some data.
SQL> 
SQL> INSERT INTO dept (deptno, dname, loc)
  2    VALUES (1, 'Sales', 'Boston');
 
1 row created.
 
SQL> INSERT INTO dept (deptno, dname, loc)
  2    VALUES (2, 'Manufacturing', 'Chicago');
 
1 row created.
 
SQL> INSERT INTO dept (deptno, dname, loc)
  2    VALUES (3, 'Marketing', 'Boston');
 
1 row created.
 
SQL> 
SQL> INSERT INTO emp (empno, ename, job, deptno)
  2    VALUES (1, 'Alvarez', 'SalesRep', 1);
 
1 row created.
 
SQL> INSERT INTO emp (empno, ename, job, deptno)
  2    VALUES (2, 'Baxter', 'Supervisor', 2);
 
1 row created.
 
SQL> INSERT INTO emp (empno, ename, job, deptno)
  2    VALUES (3, 'Chen', 'Writer', 3);
 
1 row created.
 
SQL> INSERT INTO emp (empno, ename, job, deptno)
  2    VALUES (4, 'Davis', 'Technician', 2);
 
1 row created.
 
SQL> 
SQL> -- Create an RDF view model using direct mapping of two tables, EMP and DEPT,
SQL> -- with a base prefix of http://empdb/.
SQL> -- Specify KEY_BASED_REF_PROPERTY=T for the options parameter.
SQL> 
SQL> BEGIN
  2    sem_apis.create_rdfview_model(
  3  	 model_name => 'empdb_model',
  4  	 tables => SYS.ODCIVarchar2List('EMP', 'DEPT'),
  5  	 prefix => 'http://empdb/',
  6  	 options => 'KEY_BASED_REF_PROPERTY=T'
  7    );
  8  END;
  9  /
 
PL/SQL procedure successfully completed.
 
SQL> 
SQL> -- Query an RDF view using SPARQL in a SEM_MATCH-based SQL query.
SQL> -- The next statament is a query against an RDF view named empdb_model
SQL> -- to find the employees who work for any department located in Boston.
SQL> 
SQL> SELECT emp
  2    FROM TABLE(SEM_MATCH(
  3  	 '{?emp emp:ref-DEPTNO ?dept . ?dept dept:LOC "Boston"}',
  4  	 SEM_Models('empdb_model'),
  5  	 NULL,
  6  	 SEM_ALIASES(
  7  	   SEM_ALIAS('dept','http://empdb/TESTUSER.DEPT#'),
  8  	   SEM_ALIAS('emp','http://empdb/TESTUSER.EMP#')
  9  	 ),
 10  	 null));
 
EMP                                                                             
--------------------------------------------------------------------------------
http://empdb/TESTUSER.EMP/EMPNO=1                                               
http://empdb/TESTUSER.EMP/EMPNO=3                                               
 
SQL> 
SQL> -- The preceding query is functionally comparable to this:
SQL> SELECT e.empno FROM emp e, dept d WHERE e.deptno = d.deptno AND d.loc = 'Boston';
 
     EMPNO                                                                      
----------                                                                      
         1                                                                      
         3  

8.4 Combining Native RDF Data with Virtual RDB2RDF Data

You can combine native triple data with virtual RDB2RDF triple data in a single SEM_MATCH query by means of the SERVICE keyword.

The SERVICE keyword (explained in Graph Patterns: Support for SPARQL 1.1 Federated Query) is overloaded through the use of special SERVICE URLs that signify local (virtual) RDF data. The following prefixes are used to denote special SERVICE URLs:

  • Native models - oram: <http://xmlns.oracle.com/models/>

  • Native virtual models - oravm: <http://xmlns.oracle.com/virtual_models/>

  • RDB2RDF models - orardbm: <http://xmlns.oracle.com/rdb_models/>

Example 8-8 Querying Multiple Data Sets

Example 8-8 queries multiple data sets. In this query, the first triple pattern { ?x rdf:type :Person } will go against native model m1 as usual, but { ?x :name ?name } will go against the local native model m2, and { ?x :email ?email } will go against the local RDB2RDF model rdfview1.

select * from table (sem_match(
'SELECT ?x ?name ?email
 WHERE {
  ?x rdf:type :Person .
  OPTIONAL { SERVICE oram:m2 { ?x :name ?name } }
  OPTIONAL { SERVICE orardbm:rdfview1 { ?x :email ?email } }
}'
sem_models('m1'), null, null, null, null, ' '));

Overloaded SERVICE use is only allowed with a single model specified in the models argument of SEM_MATCH. Overloaded SERVICE queries do not allow multiple models or a rulebase as input. A virtual model that contains multiple models and/or entailments should be used instead for such combinations. In addition, the index_status argument for SEM_MATCH will only check the entailment contained in the virtual model passed as input in the models parameter. This means the status of entailments that are referenced in overloaded SERVICE calls will not be checked.

Example 8-9 Querying Virtual RDB2RDF Data and Native RDF Data

Example 8-9 queries two data sets: the empdb_model from Example 8-6 and a native model named people.

-- Create native model people --
create table atab (gval varchar2(4000), tri sdo_rdf_triple_s);
 
execute sem_apis.create_sem_model('people','atab','tri');
 
create table stab(RDF$STC_GRAPH varchar2(4000), RDF$STC_sub varchar2(4000),
                  RDF$STC_pred varchar2(4000), RDF$STC_obj varchar2(4000));
grant select on stab to mdsys;
grant insert on atab to mdsys;
 
insert into stab values (null, '<http://empdb/TESTUSER.EMP/EMPNO=1>', '<http://people.org/age>', '"35"^^<http://www.w3.org/2001/XMLSchema#int>');
insert into stab values (null, '<http://empdb/TESTUSER.EMP/EMPNO=2>', '<http://people.org/age>', '"39"^^<http://www.w3.org/2001/XMLSchema#int>');
insert into stab values (null, '<http://empdb/TESTUSER.EMP/EMPNO=3>', '<http://people.org/age>', '"30"^^<http://www.w3.org/2001/XMLSchema#int>');
insert into stab values (null, '<http://empdb/TESTUSER.EMP/EMPNO=4>', '<http://people.org/age>', '"42"^^<http://www.w3.org/2001/XMLSchema#int>');
commit;
 
exec sem_apis.bulk_load_from_staging_table('people','testuser','stab');
 
-- Querying multiple datasets --
SELECT emp, age
  FROM TABLE(SEM_MATCH(
    'SELECT ?emp ?age WHERE{
       ?emp peop:age ?age
       SERVICE orardbm:empdb_model { ?emp emp:ref-DEPTNO ?dept . ?dept dept:LOC "Boston" }
    }',
    SEM_Models('people'),
    NULL,
    SEM_ALIASES(
          SEM_ALIAS('dept','http://empdb/TESTUSER.DEPT#'),
          SEM_ALIAS('emp','http://empdb/TESTUSER.EMP#'),
          SEM_ALIAS('peop','http://people.org/')
        ),
    NULL));

8.4.1 Nested Loop Pushdown with Overloaded Service

Using a nested loop service can improve performance is some scenarios. Consider the following examlpe query against multiple data sets, which finds the properties of all the departments with people who are 35 years old.

SELECT emp, dept, p, o
  FROM TABLE(SEM_MATCH(
    'SELECT * WHERE{
       ?emp peop:age 35
       SERVICE orardbm:empdb_model{ ?emp emp:ref-DEPTNO ?dept . ?dept ?p ?o }
    }',
    SEM_Models('people'),
    NULL,
    SEM_ALIASES(
          SEM_ALIAS('dept','http://empdb/TESTUSER.DEPT#'),
          SEM_ALIAS('emp','http://empdb/TESTUSER.EMP#'),
          SEM_ALIAS('peop','http://people.org/')
        ),
    NULL));

To get all the results that match for given graph pattern, first the triple { ?emp peop:age 35 } is matched against model people, then the triples { ?emp emp:ref-DEPTNO ?d . ?d dept:DNAME ?dept } are matched against model empdb_model, and finally the results are joined. Assume that there is only one 35-year-old person in the model people, but there are 100,000 triples with information about departments. Obviously, a strategy that retrieves all the results is not the most efficient, and query may have poor performance because a large numberof results that need to be processed before being joined with the rest of the query.

An nested-loop service can improve performance in this case. If the hint OVERLOADED_NL=T is used, the results of the first part of the query are computed and the SERVICE pattern is executed procedurally in a nested loop once for each ?emp value from the root triple pattern. The ?emp subject variable in the SERVICE pattern is replaced with a constant from the root triple pattern in each execution. This effectively pushes the join condition down into the SERVICE clause.

The following example shows the use of the OVERLOADED_NL=T hint for the preceding query.

SELECT emp, dept, p, o
  FROM TABLE(SEM_MATCH(
    'SELECT * WHERE{
       ?emp peop:age 35
       SERVICE orardbm:empdb_model { ?emp emp:ref-DEPTNO ?dept . ?dept ?p ?o }
    }',
    SEM_Models('people'),
    NULL,
    SEM_ALIASES(
          SEM_ALIAS('dept','http://empdb/TESTUSER.DEPT#'),
          SEM_ALIAS('emp','http://empdb/TESTUSER.EMP#'),
          SEM_ALIAS('peop','http://people.org/')
        ),
    NULL,null,' OVERLOADED_NL=T '));

The hint OVERLOADED_NL=T can be specified among SEM_MATCH options or among inline comments for a given SERVICE graph.