. . . . . . . . . . . . . . . "2017-06-13T05:40:08.824565"^^ . "2017-06-13T05:40:08.824565"^^ . . . "2017-06-13T05:40:08Z" . . "3cd194e839d2df161698cb0705ea842d" . "%VOSWARNING%\n\n\n%META:TOPICINFO{author=\"KingsleyIdehen\" date=\"1161195828\" format=\"1.1\" version=\"1.1\"}%\n%META:TOPICPARENT{name=\"VOSRDF\"}%\n%VOSNAV%\n---+ Mapping Relational Data to RDF in Virtuoso\n\nOrri Erling (Program Manager, OpenLink Virtuoso), Ivan Mikhailov (Lead Developer, OpenLink Virtuoso).\n\n---++ Abstract\n\nThis paper discusses Virtuoso's new declarative Meta Schema Language for \nmapping SQL Data to RDF Ontologies. This functionality, commonly referred \nto as \"Linked Data Views\", enables the exposure of pre-existing ODBC or JDBC \naccessible relational data as Virtual RDF Graphs thereby exposing said \ndata to SPARQL query access directly or via Virtuoso's in-built support \nfor SPARQL within SQL (SPASQL). The entire process results in RDF Data \nsets and optimized data access without physical regeneration of RDF Data \nSets from SQL Data. \n\nConsidering that almost all of the present generation of Web 2.0 and \nEnterprise applications run atop relational databases, the need for \nefficiently exposing existing data to the burgeoning Semantic Web is \nobvious. For instance, it now possible to dynamically generate RDF Data \nSets for popular ontologies such as SIOC, SKOS, FOAF, and ATOM/OWL without \ndisruption to the existing database infrastructure that drives existing \nWeb 1.0 or Web 2.0 solutions. The same applies to Enterprises seeking to \nexploit many of the collaborative prowess of the Web 2.0 application \nprofiles. As part of this paper we provide a simple example of how the \nWeblog Data Space within the OpenLink Data Spaces collective is mapped \nto the AtomOWL Ontology en route to making the Weblog Data Space data \naccessible to SPARQL. \n\n\n---++ Introduction\n\nThe Virtuoso RDF meta schema is a built-in feature of Virtuoso's SPARQL \nto SQL translator. It recognizes triple patterns that refer to graphs \nfor which an alternate representation is declared and translates these \ninto SQL accordingly. The main purpose of this is mapping SPARQL queries \nonto existing relational databases.\n\nThere exists previous work from many parties for rendering relational data \nas RDF and opening it to SPARQL access. We can mention D2RQ, SPASQL, Squirrel \nRDF and others. The Virtuoso effort differs from these mainly in the following:\n\n * Integration with a triple store. Virtuoso can process a query for which \nsome triple patterns will go to local or remote relational data and some to \nlocal physical RDF triples.\n * Integration with SQL. Since SPARQL and SQL share the same run time and \nquery optimizer, the query compilation decisions are always made with the \nbest knowledge of the data and its location. This is especially important \nwhen mixing triples and relational data or when dealing with relational data \ndistributed across many outside databases.\n * No limits on SPARQL. It remains possible to make queries with unspecified \ngraph or predicate against mapped relational data, even though these may \nsometimes be inefficient.\n * Coverage of the whole relational model. Multi-part keys, etc., are \nsupported in all places.\n\n---++ Rationale\n\nSince most of the data that is of likely use for the emerging semantic web is \nstored in relational databases, the argument for exposing this to SPARQL access \nis clear. We note that historically, SQL access to relational data has essentially \nnever been given to the public outside of the organization. If programmatic access \nto corporate IS has been available to the public, it has been through dynamic web \npages or more recently web services. There are reasons of performance, security, \nmaintainability and so forth for this.\n\nThe culture of the emerging semantic web is however taking a different turn. Since \nRDF and OWL offer a merge-able and query-able model for heterogeneous data, it is \nmore meaningful and maintainable to expose selected data for outside query than \nit would be with SQL. Advances in hardware make this also less of a performance \nissue than it would have been in the client-server database era.\n\nIn the context of Virtuoso, since Virtuoso is originally a virtual/federated \ndatabase, incorporating SPARQL to relational mapping is an evident extension of \nthe product's mission as a multi-protocol, multi-platform connector between \ninformation systems.\n\n---++ Graph Patterns, Value and IRI Classes\n\nIn the simplest sense, any relational schema can be rendered into RDF by converting \nall primary keys and foreign keys into IRI's, assigning a predicate IRI to each \ncolumn, and an rdf:type predicate for each row linking it to a RDF class IRI \ncorresponding to the table. Then a triple with the primary key IRI as subject, \nthe column IRI as predicate and the column's value as object is considered to \nexist for each column that is neither part of a primary or foreign key.\n\nStrictly equating a subject value to a row and each column to a predicate is often \ngood but is too restrictive for the general case. For example, multiple triples \nwith the same object and predicate can exist. Or a single subject can get \nsingle-valued properties from multiple tables or in some cases stored procedures.\n\n#BasicLanguageElements\nThe basic elements of the Virtuoso RDF meta schema are:\n#IriClass\n * IRI Class - This declares that a key column or set of key columns gets converted \ninto a URI in a certain way. When declaring that a table's primary key is converted \ninto a URI according to one IRI class, one usually declares that all foreign keys \nreferring to this class also get converted into an IRI as per this same class.\n#ValueClass\n * Value Class - This is a mechanism for unit conversions, for converting \nenumerations into IRI's or any other scalar to scalar conversion. The difference \nbetween a value class and an IRI class is that a value resulting from converting \na relational column according to a value class is not necessarily an IRI joinable \nto the subject or predicate of a triple.\n#QuadPattern\n * Quad pattern - This declares that a quad with any combination of specified or \nunspecified G, S, P or O is stored in a non-default way. The patterns are grouped \ntogether in Linked Data Views. A quad pattern consists of the following:\n * Subject - This can be an IRI class followed by one of more columns of a \ntable. The IRI class specifies how the column values are combined into an IRI \nstring and how an IRI string is decomposed back into the key value. Alternately, \nthis can be a constant IRI. \n * Predicate - This is most often a constant IRI but can also be an IRI class \nas with the subject.\n * Object - This can be an IRI class applied to columns, a value class applied \nto a column or a literal IRI or scalar.\n * Condition - This is an optional SQL search condition. If the search condition \nis true when applied to a row of the table designated in the subject, predicate and \nobject patterns, then the triple is considered to exist, otherwise not.\n * Optional RDF type of the subject. If the subject is known to be of a type \ndisjoint from this type, then the quad pattern will not apply. We note that when S, \nP, or O refers to columns of a table, all of these should be to the same table. For \ncomplex joins, the table in question can also be a SQL view joining multiple tables. \nThus the search condition of the quad pattern does not have to be used as a join \ncondition between different tables.\n * Graph - This is most often a constant IRI but can also be an IRI class as \nwith the subject. It is very common for many quad patterns of one SQL schema to \nshare one constant graph, but one meta schema may consist of quad patterns with \nany number of different graph declarations.\n * Quad Storage -- This is a named set of quad patterns. This is used for \ncompartmentalizing the RDF to SQL mapping. This allows specifying that a SPARQL \nquery will be executed using only quad patterns of the given quad storage. \nDeclarations of IRI classes, value classes and quad patterns are shared between \nall quad storages of an RDF meta schema but every quad storage contains only a \nsubset of all available quad patterns. Two quad storages are always defined: a \n'virtrdf:default' one usually consists of everything (all relational mappings \nplus a table of physical quads) and an 'virtrdf:empty' storage that refers to \nsingle table of physical quads).\n\nA special case of IRI or value class is the identity class, which is simply \nmarked by table.column. For this to occur as S or P, the column must be of the \nIRI ID type. For an O, the column may be of any type. \n\n#SiocMappingExample1\n---+++ Example - Mapping Virtuoso Database Users and Roles to Accounts in the SIOC Ontology\n\nHere we map the Virtuoso sys_users and sys_role_grant tables to \nthe SIOC ontology. The tables are in part declared as follows, some columns are \nomitted for brevity:\n\n\ncreate table sys_users (u_id int primary key, u_name varchar, u_is_role int, u_e_mail, u_home varchar, u_dav_enable int...)\ncreate table sys_role_grants (gi_super int, gi_sub int, gi_direct, primary key (gi_super, gi_sub, gi_direct));\n\n\nThe column gi_super is the role and gi_sub is \nthe principal to which the role is granted. Both refer to u_id \nin sys_users. A row of sys_users is either a role or an account, \nas indicated by u_is_role. All role memberships, including \nindirect ones are listed in sys_role_grants, the \ngi_direct flag shows whether this is a directly granted role.\n\nWe can map this into the sioc:User and sioc:Usergroup RDF classes. These are then \nrelated by the mutually inverse predicates sioc:member_of and sioc:has_member. \nAdditionally, a group can have other groups as members, although not circularly.\n\nTo define this mapping, we need the following group of declarations that form \none big statement:\n\n\nsparql\npragma prefix oplsioc: \npragma prefix sioc: \n\n\n\n-- We put all the SIOC names in the original SIOC namespace. The names that pertain \nto the mapping go into the oplsioc namespace.\n\n\ndrop graph iri(\"^{URIQADefaultHost}^/dataspace\") .\n\n\n-- We have the notation ^{URIQADefaultGraph}^ to refer to the default host declared \nfor the server on which the mapping is being loaded. This allows IRI's to be \ndereferenceable on the host itself. For example, the IRI's declared for the various \nkeys can go to dynamic web pages giving information on the entities being described. \nIn addition, the URIQA protocol server uses this host name to convert relative URIs \nof local web pages into absolute URIs that can be used by browsers.\n\n\ncreate iri class oplsioc:user_iri\n \"http://^{URIQADefaultHost}^/sys/user?id=%d\" (in uid integer not null) .\n\ncreate iri class oplsioc:group_iri\n \"http://^{URIQADefaultHost}^/sys/group?id=%d\" (in gid integer not null) .\ncreate iri class oplsioc:membership_iri\n \"http://^{URIQADefaultHost}^/sys/membersip?super=%d&sub=%d\"\n (in super integer not null, in sub integer not null) .\n\ncreate iri class oplsioc:dav_iri \"http://^{URIQADefaultHost}^%s\" (in path varchar) .\n\ncreate iri class oplsioc:grantee_iri using\n function DB.DBA.RDF_DF_GRANTEE_ID_URI (in id integer) returns varchar ,\n function DB.DBA.RDF_DF_GRANTEE_ID_URI_INVERSE (in id_iri varchar) returns integer .\n\nmake oplsioc:user_iri subclass of oplsioc:grantee_iri .\nmake oplsioc:group_iri subclass of oplsioc:grantee_iri .\n\n\n\n-- Here we declare the entities that we will see in RDF. These are the user and \nusergroup and their abstract superclass called grantee. Note that the GI_SUB \ncolumn of sys_role_grants can refer to either a user or a group \nand we have decided to give them different IRI's. This is not absolutely necessary \nsince we can tell from the id whether something is a user or group but this may be \ngood for readability and serves to illustrate features of the mapping.\n\nWe can map the group membership simply as a many to many without attributes. For \nsome queries, though, we will need to see this relationship as an entity with its \nown attributes, for example whether the grant is direct and whether it is re-grantable. \n(the sql with admin option).\n\nFor the case where we are interested in the attributes of the relationship, we define \nthe iri class for membership, although this is not part of SIOC, hence the oplsioc \nnamespace.\n\n\nalter quad storage virtrdf:DefaultQuadStorage\n {\n create virtrdf:SysUsers as graph iri (\"http://^{URIQADefaultHost}^/dataspace\") option (exclusive)\n {\n oplsioc:user_iri (DB.DBA.SYS_USERS.U_ID)\n a sioc:user where (^{alias}^.U_IS_ROLE = 0) ;\n sioc:email U_E_MAIL ;\n sioc:login U_NAME ;\n oplsioc:home oplsioc:dav_iri (U_HOME) where (^{alias}^.U_DAV_ENABLE = 1) ;\n oplsioc:name U_FULL_NAME where (^{alias}^.U_IS_ROLE = 0) .\n oplsioc:group_iri (DB.DBA.SYS_USERS.U_ID)\n a sioc:role where (^{alias}^.U_IS_ROLE = 1) .\n oplsioc:group_iri (DB.DBA.SYS_ROLE_GRANTS.GI_SUPER)\n sioc:has_member oplsioc:grantee_iri (GI_SUB) ;\n oplsioc:group_of_membership\n oplsioc:membership_iri (GI_SUPER, GI_SUB) .\n oplsioc:grantee_iri (DB.DBA.SYS_ROLE_GRANTS.GI_SUB)\n sioc:has_function oplsioc:group_iri (GI_SUPER) ;\n oplsioc:member_of\n oplsioc:membership_iri (GI_SUPER, GI_SUB) .\n oplsioc:membership_iri (DB.DBA.SYS_ROLE_GRANTS.GI_SUPER, GI_SUB)\n oplsioc:is_direct GI_DIRECT ;\n rdf:type oplsioc:grant .\n }\n }\n;\n\n\n-- Now we declare the actual mapping. In the graph /dataspace of the default \nhost of the server, the above rules will apply.\n\nThe first rule declare that ?s rdf:type sioc:User is true of any \nIRI matching the user_iri (sys_users.u_id) \npattern. This means that:\n * the constant part of the IRI is as defined for the user_iri \nIRI class.\n * there is a row of DB.DBA.SYS_USERS with U_ID \nequal to the number extracted form the IRI as per the format string in the create \niri class statement, i.e. ^{URIQADefaultHost}^/user?id=%d.\n * The rule is applicable U_IS_ROLE column of the row should \nbe equal to zero.\n\nAfter this, we map some scalar properties, for example email. In defining repeating \npredicates for the same subject, the subject does not have to be repeated if the \nsemicolon is used as delimiter, just as with the SPARQL syntax for triple patterns. \nSince U_E_MAIL is just a string, no special mapping is needed. If \nwe wanted it as a mailto:// IRI, we could define an IRI class that would convert \nthe string between the SPARQL and SQL values.\n\nThe rule with group_iri (SYS_USERS.U_ID) subject is \nalmost identical to the first rule with user_iri (SYS_USERS.U_ID) subject.\n\nThen we declare the many-to-many between roles and users. The predicates are the \nSIOC has_function and has_member. They are \nidentical, except that the subject and object are reversed. The group is always \na group, the member is either a group or user, hence the use of the \ngrantee_iri IRI class. The mapping functions of this IRI class \nwill have to consult the SYS_USERS table to decide which kind \nof IRI to make:\n\n\ncreate function DB.DBA.RDF_DF_GRANTEE_ID_URI (in id integer)\n{\n declare is_role integer;\n is_role := coalesce ((select top 1 U_IS_ROLE from DB.DBA.SYS_USERS where U_ID = id));\n if (is_role is null)\n return NULL;\n else if (is_role)\n return sprintf ('http://%s:/sys/group?id=%d', get_uriqa_default_host(), id);\n else\n return sprintf ('http://%s:/sys/user?id=%d', get_uriqa_default_host(), id);\n}\n;\n\ncreate function DB.DBA.RDF_DF_GRANTEE_ID_URI_INVERSE (in id_iri varchar)\n{\n declare parts any;\n parts := sprintf_inverse (id_iri, concat ('http://', get_uriqa_default_host(), '/sys/user?id=%d', 0);\n if (parts is not null)\n { -- The syntax of given IRI matches to the format pattern for IRIs of user accounts\n if (not exists (select top 1 1 from DB.DBA.SYS_USERS where U_ID = parts[0] and not U_IS_ROLE))\n return NULL; -- The ID specified is not valid user ID\n return parts[0]; -- Both syntax and the ID value are OK\n }\n parts := sprintf_inverse (id_iri, concat ('http://', get_uriqa_default_host(), '/sys/group?id=%d', 0);\n if (parts is not null)\n {\n if (not exists (select top 1 1 from DB.DBA.SYS_USERS where U_ID = parts[0] and U_IS_ROLE))\n return NULL;\n return parts[0];\n }\n return NULL;\n}\n;\n\n\nThe clause options (exclusive) instructs the SPARQL compiler that all \nquads of the graph 'http://^{URIQADefaultHost}^/dataspace' are described \nby this set of declared quad patterns. Even if the table of physical quads contains \nquads with graph 'http://^{URIQADefaultHost}^/dataspace' they may be \nignored by the SPARQL query. Any group of quad patterns or a single quad pattern can \nbe declared exclusive. Exclusive clauses may conflict. Consider an exclusive group of \npatterns for .../dataspace graph and an single exclusive quad pattern for \nsioc:email predicate (say, one might have a table DB.DBA.E_MAILS with G, \nS and O columns of triples and a sioc:email predicate is assumed for all \nquads stored there). Consider a query\n\nsparql select * where { graph { ?s sioc:email ?o } }\n\nthat contains a triple pattern that matches both .../dataspace and \nsioc:email mapping rules. Each rule prevents the compiler from using \nanother rule. To resolve ambiguities, a quad storage may specify the order of quad \npatterns so that the compiler will use the first suitable quad pattern and ignore \nthe rest.\n\n---++ Sample Queries \n\n---+++ Two Triple Patterns As A Single Table Lookup\n\nSelect all accounts that have both full names and home DAV collections.\n\nsparql\nprefix sioc: \nprefix oplsioc: \nselect ?fullname ?home\nwhere {\n graph {\n ?uid sioc:name ?fullname .\n ?uid oplsioc:home ?home } }\n\nfullname home\nVARCHAR VARCHAR\n_____________________________________________________\n\nIvan Mikhailov http://localhost/DAV/home/iv_an_ru/\nOrri Erling http://localhost/DAV/home/oerling/\n\n\nThe generated SQL code contains only one table in FROM clause and its WHERE clause \ncontains texts of all WHERE clauses of relevant quad patterns; duplicate conditions \nare removed.\n\nSELECT \"t0\".\"U_FULL_NAME\" AS \"fullname\",\n case (isnull (\"t0\".\"U_HOME\")) when 0 then sprintf ('http://localhost%s', \"t0\".\"U_HOME\") else NULL end AS \"home\"\nFROM DB.DBA.SYS_USERS AS \"t0\"\nWHERE (\"t0\".\"U_FULL_NAME\" is not null)\n AND (\"t0\".U_HOME is not null) and (\"t0\".U_DAV_ENABLE = 1)\n AND (\"t0\".U_IS_ROLE = 0)\nOPTION (QUIETCAST)\n\n\n---+++ Simple Join of Three Tables\n\nSelect all pairs of grantees and their roles, return names instead of integer IDs: \n\nsparql\nprefix sioc: \nprefix oplsioc: \nselect ?super ?sub\nwhere {\n graph {\n ?superid sioc:has_member ?subid .\n ?superid sioc:id ?super .\n ?subid sioc:id ?sub .\n } }\n\nsuper sub\nVARCHAR NOT NULL VARCHAR NOT NULL\n____________________________________\n\nSPARQL_UPDATE SPARQL_SELECT\n\n\nThe query is translated into the following SQL:\n\nSELECT \"t1\".\"U_NAME\" AS \"super\",\n \"t2\".\"U_NAME\" AS \"sub\"\nFROM DB.DBA.SYS_ROLE_GRANTS AS \"t0\",\n DB.DBA.SYS_USERS AS \"t1\",\n DB.DBA.SYS_USERS AS \"t2\"\nWHERE \"t0\".\"GI_SUPER\" = \"t1\".\"U_ID\" /* superid */\n AND \"t0\".\"GI_SUB\" = \"t2\".\"U_ID\" /* subid */\nOPTION (QUIETCAST)\n\n\n---+++ Optional Clause and Filtering of Table Rows\n\n\nprefix sioc: \nprefix oplsioc: \nselect ?uid ?login ?fullname ?home\nwhere\n {\n graph \n {\n ?uid sioc:id ?login .\n ?uid sioc:name ?fullname .\n optional { ?uid oplsioc:home ?home }\n }\n }\n\nuid login fullname home\nVARCHAR VARCHAR NOT NULL VARCHAR VARCHAR\n_______________________________________________________________________________________________________________________\n\nhttp://localhost/sys/user?id=2 dav WebDAV System Administrator NULL\nhttp://localhost/sys/user?id=106 iv_an_ru Ivan Mikhailov http://localhost/DAV/home/iv_an_ru/\nhttp://localhost/sys/user?id=5 nobody Special account NULL\nhttp://localhost/sys/user?id=107 oerling Orri Erling http://localhost/DAV/home/oerling/\n\n\nThe query is translated into the following SQL:\n\nSELECT sprintf ( 'http://localhost/sys/user?id=%d' , \"t0\".\"uid\" ) AS \"uid\",\n \"t0\".\"login\",\n \"t0\".\"fullname\",\n case (isnull ( \"t1\".\"home\" )) when 0 then sprintf ( 'http://localhost%s' , \"t1\".\"home\" ) else NULL end AS \"home\"\nFROM (\n SELECT \"t00\".\"U_ID\" AS \"uid\",\n \"t00\".\"U_NAME\" AS \"login\",\n \"t01\".\"U_FULL_NAME\" AS \"fullname\"\n FROM DB.DBA.SYS_USERS AS \"t00\",\n (SELECT \"t01-int\".\"U_ID\", \"t01-int\".\"U_FULL_NAME\"\n FROM DB.DBA.SYS_USERS AS \"t01-int\"\n WHERE (\"t01-int\".\"U_FULL_NAME\" is not null)\n AND (\"t01-int\".U_IS_ROLE = 0)\n ) AS \"t01\"\n WHERE \"t00\".\"U_ID\" = \"t01\".\"U_ID\"\n ) AS \"t0\"\n LEFT OUTER JOIN (\n SELECT \"t10\".\"U_ID\" AS \"uid\", \"t10\".\"U_HOME\" AS \"home\"\n FROM (\n SELECT \"t10-int\".\"U_ID\", \"t10-int\".\"U_HOME\" FROM DB.DBA.SYS_USERS AS \"t10-int\"\n WHERE (\"t10-int\".\"U_HOME\" is not null)\n AND (\"t10-int\".U_DAV_ENABLE = 1)\n ) AS \"t10\") AS \"t1\"\n ON ( \"t0\".\"uid\" = \"t1\".\"uid\" )\nOPTION (QUIETCAST)\n\n\n---+++ Mix Of Relational Data and Plain RDF Quads\n\n\nsparql\nprefix sioc: \nprefix oplsioc: \nselect ?home ?id ?login\nwhere {\n graph ?g { ?id oplsioc:home ?home } .\n optional {\n graph {\n ?id sioc:id ?login }\n } }\n\nhome id login\nVARCHAR VARCHAR VARCHAR NOT NULL\n_______________________________________________________________________________\n\nhttp://localhost/DAV/home/iv_an_ru/ http://localhost/sys/user?id=106 iv_an_ru\nhttp://localhost/DAV/home/oerling/ http://localhost/sys/user?id=107 oerling\n\n\nThe context of the first triple pattern does not specify graph, so triples with \noplsioc:home predicate may be retrieved from both the default storage of RDF quads \n(table RDF_QUADS) and mapping of relational data (table SYS_USERS). The graph of \nthe second triple pattern matches 'exclusive' group of quad patterns so the compiled \nquery will not try to locate sioc:id predicates in RDF_QUADS table and will make no \nsecond union. Thus the intermediate result of the compilation is like this:\n\n(sys_users as t00 union all rdf_quad as t10) left outer join sys_users as t1)\n\nThe optimizer will convert a join of unions into a union of joins so the final query is like\n\n(sys_users as t00 left outer join sys_users as t01) union all (rdf_quad as t10 left outer join sys_users as t11)\n\nand can be executed efficiently:\n\n\nSELECT DB.DBA.RDF_SQLVAL_OF_LONG (\"t00\".\"home\") AS \"home\",\n sprintf ('http://localhost/sys/user?id=%d', \"t00\".\"id\") AS \"id\",\n \"t01\".\"login\"\nFROM (\n SELECT \"t00a\".\"U_ID\" AS \"id\",\n case (isnull (\"t00a\".\"U_HOME\")) when 0 then DB.DBA.RDF_MAKE_IID_OF_QNAME (sprintf ('http://localhost%s', \"t00a\".\"U_HOME\")) else NULL end AS \"home\"\n FROM DB.DBA.SYS_USERS AS \"t00a\"\n WHERE (\"t00a\".\"U_HOME\" is not null) AND (\"t00a\".U_DAV_ENABLE = 1)\n ) AS \"t00\"\n LEFT OUTER JOIN (\n SELECT \"t01-t1\".\"U_ID\" AS \"id\", \"t01-t1\".\"U_NAME\" AS \"login\"\n FROM DB.DBA.SYS_USERS AS \"t01-t1\") AS \"t01\"\n ON ( \"t00\".\"id\" = \"t01\".\"id\")\nUNION ALL\nSELECT DB.DBA.RDF_SQLVAL_OF_LONG (\"t10\".\"home\"),\n DB.DBA.RDF_SQLVAL_OF_LONG (\"t10\".\"id\"),\n \"t11\".\"login\"\nFROM (\n SELECT \"t10a\".\"S\" AS \"id\",\n DB.DBA.RDF_LONG_OF_OBJ (\"t10a\".\"O\") AS \"home\"\n FROM DB.DBA.RDF_QUAD AS \"t10a\"\n WHERE \n \"t10a\".\"P\" = DB.DBA.RDF_MAKE_IID_OF_QNAME_SAFE (UNAME'http://www.openlinksw.com/schemas/oplsioc#home')) AS \"t10\"\n LEFT OUTER JOIN (\n SELECT \"t11a\".\"U_ID\" AS \"id\", \"t11a\".\"U_NAME\" AS \"login\"\n FROM DB.DBA.SYS_USERS AS \"t11a\") AS \"t11\"\n ON ( \"t10\".\"id\" = DB.DBA.RDF_MAKE_IID_OF_QNAME (DB.DBA.RDF_DF_GRANTEE_ID_URI (\"t11\".\"id\")))\nOPTION (QUIETCAST)\n\n\n\n---++ Simplifying Queries\n \nIn principle, we take each triple pattern from a SPARQL query and expand it into \na union of all the quad mapping rule expansions that apply to it. If we have an \nunspecified graph and unspecified ?s ?p ?o, we indeed get the union of everything. \n\nIn practice, the graph and the predicates are most often given. This will \nconsiderably limit the number of applicable quad patterns. We further assume \nall IRI classes to be disjoint, unless exceptions are declared with the \ncreate ... subclass ... statement shown in the example with \nuser_iri and grantee_iri. Thus many joins \ncan be known to be empty at compile time.\n\nIn the simplest case, when we have two triple patterns with the same subject and \nthe subject is mapped to a relational table, we get a join of the table with \nitself, with pk = pk. This self-join can however be optimized away. After this, \nthere will be condition on multiple columns and normal SQL index and join type \nselection will apply.\n\nAs a last step, after pruning the union terms that are known not to be relevant, \nthe unions are taken to the top, so that we have a union of joins and not a join \nof unions. This is easier for the SQL compiler to optimize.\n\nWe note that throughout this process, some expansions of triple patterns can refer \nto relational tables and some to the default RDF_QUAD table or other native RDF \nstorage tables. This is not a problem at any stage. Thus a mixed representation \nwhere some things are obtained from local or remote relational stores and some \nthings are stored locally as physical triple is entirely feasible.\n\n---++ SQL Optimizer Support for Meta Schema\n\nAn equality of two IRI's can become an AND of key column equalities. When this \ntakes place between SPARQL triple patterns mapped to the relational tables, the \ncolumns that make up the URI are simply compared pair-wise, as in any regular \nrelational join. However, it is also possible that a URI composed from relational \ncolumns is compared with an IRI_ID stored in a physical triple. In this case, we \nhave a comparison between and S, P or O of a triple and a function over one or \nmore columns of a relational table.\n\nIf the relational table is first in join order, there is no problem. The function \nis evaluated and the result is used to retrieve the triple. However, if the triple \nis first in join order, we must be able to decompose the IRI into its constituent \nparts, that is, if the IRI is such that it can be returned by the key to IRI \nfunction corresponding to the columns. \n\nThe Virtuoso SQL compiler has a notion of inverse functions. This allows both join \norders to be tried, in one case converting the key part(s) into an IRI ID and in \nthe other case the IRI_ID into the key part(s).\n\nAlso, when a relational key is mapped into an IRI, making an IRI_ID for the text \nof the IRI is actually necessary only when there actually is a triple in the triple \nstore that references this IRI. Thus, if joining a relational key to triples, the \nkey to IRI_ID function can return NULL if no IRI_ID \nexists. The IRI_ID will be made only when an actual triple is created. \nWhen clients consume IRI's anyway as strings, the existence or non-existence of a \nphysical IRI ID is entirely transparent. To facilitate this situation, the compiler \nrecognizes the comparison of an IRI column to a string-valued function or variable. \nIt will automatically insert a 'get IRI_ID for string if exists' \nfunction into the compilation. \n\nWe note that when we cast primary keys to IRI's and pass them back and forth, we may \noften get situations where key_to_iri (t1.k1, t1.k2) = \nkey_to_iri (t2.k1, t2.k2) where t1 and t2 are different correlation \nnames for a pk and fk tables and k1, k2 are the parts of a 2 part key. This is noticed \nby the SQL compiler and optimized into t1.k1 = t2.k1 and t1.k2 = t2.k2. Thus no function \nindices or the like are needed. It suffices that the key to iri functions are declared \nas having inverses and as being bijective with the sinv_create_inverse \nfunction. See the documentation for this.\n\nAlso, when joining native RDF triples and relational data, we get situations like \nrdf_quad.S = key_to_iri (t1.k1, t1.k2). We note that there may not \nbe an IRI ID corresponding to the key_to_iri function's result. The \nkey_to_iri function returns a string, whereas the S,P,O,G of the \nRDF_QUAD table are IRI ID's referenced RDF_URL. The SQL compiler recognizes this and \nputs a cast there, one that will convert the string returned by the \nkey_to_iri function into an IRI ID if there indeed is such an ID and \nNULL otherwise. Furthermore, because all key_to_iri functions have \ninverses, the condition can be rewritten as\n\n\niri_to_key_1 (rdf_iri_id_qname (rdf_quad.S)) = t1.k1 and iri_to_key_2 (rdf_iri_id_qname (rdf_quad.S)) = t1.k2.\n\n\nThis allows both join orders to be used with all index usage options, again without \nrequiring function indices or the like.\n\nWe note that all these optimizations will serve equally well whether the relational \ntables are local to Virtuoso or not. The native RDF tables will have to be local due \nto their use of the native IRI_ID data type.\n\n---++ Using Meta Schema for Triple Storage Optimization\n\nIf we know that some predicates are always single-valued within a certain graph, we \ncan use a relational-like table with the subject as primary key and the objects of \nsingle-valued predicates as columns. Then we can make a single-column index on each \nof the columns.\n\nOracle has used SQL materialized views for creating such data structures on the fly. \nTheir name for this is SPMJV for Subject-Predicate Matrix Join View. We can use the \nmeta schema graph patterns for implementing a similar thing at the RDF to SQL \ntransformation and RDF storage level.\n\n---++ Using Table Valued Functions for RDF on Demand\n\nData that we wish to make SPARQL query-able are not always in relational tables. For \nexample, some data can come from web services. Caching all possible outputs of a web \nservice in a relational table in the anticipation of SPARQL access does not make sense \nin the general case.\n\nAlso, data is sometimes de-normalized into non-first normal form columns, for example, \nthe tags of an article can be all stored in a single text column with a text index on \nit. Still, for purposes of RDF rendition, we wish to consider each tag as a separate \nsioc:topic triple.\n\nFor these cases, Virtuoso offers table valued functions, also known as procedure views. \nA piece of procedure code can thus be invoked from the FROM clause of a SQL select and \nits result set will be considered as that of a derived table.\n\nA graph pattern can refer to columns of procedure views just as it can refer to columns \nof actual tables. The SQL compiler will choose a reasonable join order based on cost \ninformation associated with the procedure view.\n\nFor example, consider\n\n\ncreate procedure TV_PROC (in blog_id int, in post_uid int, in tag varchar)\n{\n __cost (20, 1, 1000, 100, 5);\n /* this means that the procedure costs 20 units to invoke\n * and returns 1 row if all parameters have a non-NULL value.\n * If the 1st is not given, multiply these by 1000.\n * If the 2nd is not given, by 100, if the 3rd is not given, by 5. */\n ...\n /* if blog id is given, loop over all posts and tags.\n * If blog id and post id are given, loop over all tags of the post.\n * If all are given, give a result row if the post has the tag\n * and so on, for all the 8 combinations of null or non-null value of the three parameters */ \n}\n\ncreate procedure view BLOG_TAG_VIEW\nas TV_PROC (in TV_BLOG_ID int, in TV_POST_ID int, in TV_TAG varchar)\n(TV_BLOG_ID int, TV_POST_ID int, TV_TAG varchar);\n\nsparql\ngraph .... \n{\npost_iri (BLOG_TAG.TV_BLOG_ID, BLOG_TAG.TV_POST_ID) sioc:topic TV_TAG .\n}\n\n\nNow we can do a query like:\n\n\nselect * from where { ?me sioc:name \"Orri\" . ?me author_of ?post . ?post sioc:topic ?tag}\n\n\nThe join order will be left to right because the compiler knows that the tag \nprocedure has a very high cost factor for not specifying the two first arguments, \nso it will place the procedure call at the end, where the post id is known, instead \nof iterating over all posts and tags and then seeing which of these has \"Orri\" as \nname of author.\n\n#MappingOdsBlogToAtomOwl\n---++ Example: Mapping ODS Weblog Post Data to the AtomOWL Ontology\n\nConsider a weblog application from OpenLink Data Spaces (ODS).\n\nA blog consists of blog posts.\n\nEvery post may have comments.\n\nEvery blog is owned by a user.\n\nA blog owner is a 'database user' registered in DB.DBA.SYS_USERS even if SQL \naccess is disabled for him. A user may create as many blogs as he wishes.\n\nThe application uses a database schema that is not very convenient for the mapping \nto AtomOWL.\n\nIt is easy to form some set of subjects and predicates, because relational data are \nall in 3rd normal form.\n\nIt's not easy to form predicates exactly as described in an independent AtomOWL \nstandard and to form all IRIs to match names of existing resources.\n\nThis example demonstrates the urgent need in extending basic data mapping functionality \nto map arbitrary joins of relational tables as opposed to VIEWs only.\n\nWhen four tables form a group (e.g. user, blog, post and comment) come from different \ntables we need additional relational views.\n\n\ncreate view BLOG.DBA.SYS_BLOG_INFO_FULL as\nselect\n bi.BI_WAI_NAME, -- Blog application instance name\n u.U_ID, u.U_NAME, u.U_E_MAIL, u.U_HOME\nfrom\n BLOG.DBA.SYS_BLOG_INFO as bi\n left outer join DB.DBA.SYS_USERS as u on (U_ID = BI_OWNER)\n;\n\ncreate view BLOG.DBA.SYS_BLOG_FULL as\nselect\n b.B_POST_ID,\t-- ID of a post\n b.B_BLOG_ID,\t-- ID of a blog where the post appears\n b.B_TITLE,\t-- Post title\n b.B_CONTENT,\t-- Content of a post\n b.B_TS,\t-- Creation time\n b.B_USER_ID,\t-- Post author\n b.B_MODIFIED,\t-- Last modified\n bi.BI_WAI_NAME,\n u.U_ID, u.U_NAME, u.U_E_MAIL, u.U_HOME\nfrom\n BLOG.DBA.SYS_BLOGS as b\n left outer join BLOG.DBA.SYS_BLOG_INFO as bi on (BI_BLOG_ID = B_BLOG_ID)\n left outer join DB.DBA.SYS_USERS as u on (U_ID = BI_OWNER)\n;\n\ncreate view BLOG.DBA.SYS_BLOG_COMMENT_FULL as\nselect\n BM_ID,\t-- Id of a comment\n BM_TITLE,\t-- Comment title\n BM_COMMENT,\t-- Text of the comment\n BM_NAME,\t-- Author name\n BM_E_MAIL,\t-- Author e-mail\n BM_TS,\t-- Creation time\n BM_IS_PUB,\t-- Whether the comment message is public or private\n B_BLOG_ID, B_CONTENT, B_TITLE, B_POST_ID, B_TS, B_MODIFIED,\n BI_WAI_NAME,\n U_ID, U_NAME, U_E_MAIL, U_HOME\nfrom\n BLOG.DBA.BLOG_COMMENTS bm\n left outer join BLOG.DBA.SYS_BLOGS as b on (BM_BLOG_ID = B_BLOG_ID and BM_POST_ID = B_POST_ID)\n left outer join BLOG.DBA.SYS_BLOG_INFO as bi on (BI_BLOG_ID = B_BLOG_ID)\n left outer join DB.DBA.SYS_USERS as u on (U_ID = BI_OWNER)\n; \n\ncreate view BLOG.DBA.SYS_BLOG_CATEGORY_FULL as\nselect\n MTC_ID,\t-- ID if a category\n MTC_BLOG_ID,\t-- Id of a blog\n MTC_NAME,\t-- Category name\n BI_WAI_NAME,\t-- Blog application instance name\n U_ID, U_NAME, U_E_MAIL, U_HOME\nfrom\n BLOG.DBA.MTYPE_CATEGORIES as mtc\n left outer join BLOG.DBA.SYS_BLOG_INFO as bi on (MTC_BLOG_ID = BI_BLOG_ID)\n left outer join DB.DBA.SYS_USERS as u on (U_ID = BI_OWNER)\n;\n\ncreate view BLOG.DBA.SYS_BLOG_POST_CATEGORY_FULL as\nselect\n B_BLOG_ID, B_CONTENT, B_TITLE, B_POST_ID, B_TS, B_USER_ID, B_MODIFIED,\n MTC_ID, MTC_BLOG_ID, MTC_NAME,\n BI_WAI_NAME,\n U_ID, U_NAME, U_E_MAIL, U_HOME\nfrom\n BLOG.DBA.MTYPE_CATEGORIES as mtc\n join BLOG.DBA.SYS_BLOGS as b on (B_BLOG_ID = MTC_BLOG_ID)\n left outer join BLOG.DBA.SYS_BLOG_INFO as bi on (B_BLOG_ID = BI_BLOG_ID)\n left outer join DB.DBA.SYS_USERS as u on (U_ID = BI_OWNER)\n;\n\n\nNow for any combination of tables we have a view that consists of tables with proper \njoin conditions. Virtuoso has a good chance to create good execution plans for joins \nof such views because a 'view' in Virtuoso is expanded like a macro before any join \norder decisions are made. When a FROM clause of a SQL SELECT statement contains a view, \nthe occurrence of a view in FROM clause is replaced with the full SELECT statement of \nthe view body. So when a view P is a join of tables of P1 and P2, view Q is a join of \nQ1 and Q2 and the query is a join of P and Q then the compilation 'flattens' it and \nruns same way as if the query were a plain join of P1, P2, Q1 and Q2.\n\nThe created views use demonstrate an optimization technique where LEFT OUTER JOINS are \nused instead of INNER JOINS as a way of eliminating the generation of redundant table \njoins. Thus, If we define a VIEW as: Table A left OUTER JOIN Table B and the JOIN \ncondition has equalities for all columns matching the unique key of Table B (e.g., \nprimary key columns), and only columns of Table A are queried from the VIEW, then the \njoin with B will neither alter returned columns nor change the number of resulting \nrows, implying that they can be omitted. This let us declare a single VIEW using: \n\"COMMENTS left join BLOGS left join BLOG_INFO left join \nSYS_USERS\" avoiding the creation of redundant: \"COMMENTS left join \nBLOGS\" and \"COMMENTS left join BLOGS left join BLOG_INFO\".\n\n\nsparql\nprefix oplsioc: \nprefix sioc: \nprefix atom: \nprefix odsWeblog: \ncreate iri class odsWeblog:feed_iri \"http://^{URIQADefaultHost}^/dataspace/%U/weblog/%U\" (\n in memb varchar not null, in inst varchar not null) .\ncreate iri class odsWeblog:feed_inst_iri \"http://^{URIQADefaultHost}^/dataspace/%U/weblog-inst/%U\" (\n in memb varchar not null, in inst varchar not null) .\ncreate iri class odsWeblog:post_iri \"http://^{URIQADefaultHost}^/dataspace/%U/weblog/%U/%U\" (\n in memb varchar not null, in inst varchar not null, in post varchar not null) .\ncreate iri class odsWeblog:post_inst_iri \"http://^{URIQADefaultHost}^/dataspace/%U/weblog-post-inst/%U/%U\" (\n in memb varchar not null, in inst varchar not null, in post varchar not null) .\ncreate iri class odsWeblog:text_iri \"http://^{URIQADefaultHost}^/dataspace/%U/weblog-text/%U/%U\" (\n in memb varchar not null, in inst varchar not null, in post varchar not null) .\ncreate iri class odsWeblog:cmt_iri \"http://^{URIQADefaultHost}^/dataspace/%U/weblog/%U/%U/%d\" (\n in memb varchar not null, in inst varchar not null, in post varchar not null, in cmtid integer) .\ncreate iri class odsWeblog:cat_iri \"http://^{URIQADefaultHost}^/dataspace/%U/weblog-cat/%U/%U\" (\n in memb varchar not null, in inst varchar not null, in catid varchar not null) .\n;\n\n\nNote that the AtomOWL mapping for blogs will use IRI classes oplsioc:user_iri, \noplsioc:group_iri as they are already declared in the example above. As soon as \nviews are prepared and IRI classes are tested to ensure that composed IRIs match \nto IRIs of actual web pages the rest of quad map declaration is quite straightforward \nand every single mapping is as simple as in previous 'users and groups' use case.\n\n#MappingOdsBlogToAtomOwlExample\n\n\nsparql\nprefix oplsioc: \nprefix sioc: \nprefix atom: \nprefix odsWeblog: \ncreate quad storage virtrdf:ODS\n {\n create virtrdf:Weblog2 as graph iri (\"http://^{URIQADefaultHost}^/dataspace/weblog2\") option (exclusive)\n {\n # Feed\n odsWeblog:feed_iri (BLOG.DBA.SYS_BLOG_INFO_FULL.U_NAME, BI_WAI_NAME)\n a atom:Feed ;\n atom:feedInstance odsWeblog:feed_inst_iri (BLOG.DBA.SYS_BLOG_INFO_FULL.U_NAME, BI_WAI_NAME) .\n \n # Feed Instance\n odsWeblog:feed_inst_iri (BLOG.DBA.SYS_BLOG_INFO_FULL.U_NAME, BI_WAI_NAME)\n a atom:FeedInstance ;\n atom:title BI_WAI_NAME .\n odsWeblog:feed_inst_iri (BLOG.DBA.SYS_BLOG_CATEGORY_FULL.U_NAME, BI_WAI_NAME)\n atom:category odsWeblog:cat_iri (BLOG.DBA.SYS_BLOG_CATEGORY_FULL.U_NAME, BI_WAI_NAME, MTC_ID) .\n # The following mappings are excluded from the example: atom:contributor, atom:extension, atom:generator,\n # atom:icon, atom:link, atom:logo, atom:rights, atom:subtitle, atom:updated.\n\n # Post\n odsWeblog:post_iri (BLOG.DBA.SYS_BLOG_FULL.U_NAME, BI_WAI_NAME, B_POST_ID)\n a atom:Entry ;\n atom:entryInstance odsWeblog:post_inst_iri (BLOG.DBA.SYS_BLOG_FULL.U_NAME, BI_WAI_NAME, B_POST_ID) .\n \n # Post Instance\n odsWeblog:post_inst_iri (BLOG.DBA.SYS_BLOG_FULL.U_NAME, BI_WAI_NAME, B_POST_ID)\n a atom:EntryInstance ;\n atom:author oplsioc:user_iri (U_ID) ;\n atom:containingFeed odsWeblog:feed_inst_iri (BLOG.DBA.SYS_BLOG_FULL.U_NAME, BI_WAI_NAME) ;\n atom:content odsWeblog:text_iri (U_NAME, BI_WAI_NAME, B_POST_ID) ;\n atom:contributor oplsioc:user_iri (B_USER_ID) ;\n atom:published B_TS ;\n atom:title B_TITLE ;\n atom:updated B_MODIFIED .\n odsWeblog:post_inst_iri (BLOG.DBA.SYS_BLOG_POST_CATEGORY_FULL.U_NAME, BI_WAI_NAME, B_POST_ID)\n atom:category\n odsWeblog:cat_iri (BLOG.DBA.SYS_BLOG_POST_CATEGORY_FULL.U_NAME, BI_WAI_NAME, MTC_ID) .\n # The following mappings are excluded from the example: atom:extension, atom:link, atom:rights\n # atom:sourceFeed atom:summary.\n\n # Text\n odsWeblog:text_iri (BLOG.DBA.SYS_BLOG_FULL.U_NAME, BI_WAI_NAME, B_POST_ID)\n a atom:Text ;\n rdf:value B_CONTENT .\n # The following mappings are excluded from the example: atom:contentBase, atom:contentLang,\n # atom:contentSource, atom:contentType, atom:mimeType\n\n # Comment\n odsWeblog:cmt_iri (BLOG.DBA.SYS_BLOG_COMMENT_FULL.U_NAME, BI_WAI_NAME, B_POST_ID, BM_ID)\n a atom:EntryInstance where (^{alias}^.BM_IS_PUB = 1) .\n\n odsWeblog:cat_iri (BLOG.DBA.SYS_BLOG_CATEGORY_FULL.U_NAME, BI_WAI_NAME, MTC_ID)\n a atom:Category ;\n atom:categoryLabel MTC_NAME .\n # atom:categoryScheme and atom:categoryTerm are excluded from the example, as well as\n # a view that is specific for them.\n }\n }\n;\n\n\nThe disadvantage of current SPARQL implementation is that it can create enormous \nintermediate SQL queries. A relatively simple SPARQL query like five triple patterns \nwhere every IRI reference comes from a join of four tables results in a query with \ntens of joins and the full optimization before the first run takes seconds of CPU time. \nAdditional problem is a risk of running out of optimization time limit so the server \nwill stop optimization before it reached the optimal plan.\n\nWe are presently developing a rich syntax of quad map declarations, that allows the \napplication developer to describe join rules in quad mapping.\n\n * There will be no need in explicit CREATE VIEW statements.\n * The SPARQL optimizer will never create redundant joins.\n * The SPARQL optimizer will be able to prove more about ranges of SPARQL variables \nand better eliminate joins of triple patterns that can not produce any bindings.\n * The generator of an intermediate SQL code will create the smallest possible query \nand minimize the SQL optimization time.\n\n#MappingDemoToCustomOwlExample\n---++Mapping Virtuoso Demo Database to a Custom Ontology for the Northwind Data Model\n\nConsider a Virtuoso Demo Database.\n\nThis example uses the following tables from the Virtuoso Demo Database SQL Schema: \nCustomers, Orders, Order Details, Products, Product Categories, Employee, Region, \nCountry, Province. \n\nBearing in mind the similarities between the SQL schemas of Virtuoso's Demo database \nand Microsoft's Northwind Database (used by ACCESS and Microsoft SQL Server), we have \nchosen to broaden the scope of this example by deriving an OWL ontology from a Conceptual \nData Schema Language (CSDL) file that uses XML Schema to conceptually model the Northwind \nSchema. The aforementioned Ontology is then used as the basis for declaring mappings \nbetween the SQL Database and the OWL Ontology.\n\n---+++XML Schema for Northwind (CSDL File)\n\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n\n\n---+++OWL Ontology for Northwind (in N3)\n\n@prefix rdfs: .\n@prefix xsd: .\n@prefix virtrdf: .\n@prefix owl: .\n@prefix rdf: .\n@prefix northwind: .\n\nnorthwind: rdf:type owl:Ontology ;\n\trdfs:label \"Northwind\" ;\n\trdfs:comment \"Northwind database classes and properties\" ;\n\tvirtrdf:catName \"Northwind\" ;\n\tvirtrdf:version \"1.00\" .\nnorthwind:Product rdf:type rdfs:Class ;\n\trdfs:label \"Product\" .\nnorthwind:has_category rdf:type rdf:Property ;\n\trdfs:range northwind:Category ;\n\trdfs:domain northwind:Product ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"Category\" .\nnorthwind:has_supplier rdf:type rdf:Property ;\n\trdfs:range northwind:Supplier ;\n\trdfs:domain northwind:Product ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"Supplier\" .\nnorthwind:ProductName rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Product ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"ProductName\" .\nnorthwind:QuantityPerUnit rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Product ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"QuantityPerUnit\" .\nnorthwind:UnitPrice rdf:type rdf:Property ;\n\trdfs:range xsd:double ;\n\trdfs:domain northwind:Product ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"UnitPrice\" .\nnorthwind:UnitsInStock rdf:type rdf:Property ;\n\trdfs:range xsd:integer ;\n\trdfs:domain northwind:Product ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"UnitsInStock\" .\nnorthwind:UnitsOnOrder rdf:type rdf:Property ;\n\trdfs:range xsd:integer ;\n\trdfs:domain northwind:Product ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"UnitsOnOrder\" .\nnorthwind:ReorderLevel rdf:type rdf:Property ;\n\trdfs:range xsd:integer ;\n\trdfs:domain northwind:Product ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"ReorderLevel\" .\nnorthwind:Discontinued rdf:type rdf:Property ;\n\trdfs:range xsd:integer ;\n\trdfs:domain northwind:Product ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"Discontinued\" .\nnorthwind:Supplier rdf:type rdfs:Class ;\n\trdfs:label \"Supplier\" .\nnorthwind:CompanyName rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Supplier ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"CompanyName\" .\nnorthwind:ContactName rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Supplier ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"ContactName\" .\nnorthwind:ContactTitle rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Supplier ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"ContactTitle\" .\nnorthwind:Address rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Supplier ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"Address\" .\nnorthwind:City rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Supplier ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"City\" .\nnorthwind:Region rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Supplier ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"Region\" .\nnorthwind:PostalCode rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Supplier ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"PostalCode\" .\nnorthwind:Country rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Supplier ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"Country\" .\nnorthwind:Phone rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Supplier ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"Phone\" .\nnorthwind:Fax rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Supplier ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"Fax\" .\nnorthwind:HomePage rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Supplier ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"HomePage\" .\nnorthwind:Category rdf:type rdfs:Class ;\n\trdfs:label \"Category\" .\nnorthwind:CategoryName rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Category ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"CategoryName\" .\nnorthwind:Description rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Category ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"Description\" .\nnorthwind:Shipper rdf:type rdfs:Class ;\n\trdfs:label \"Shipper\" .\nnorthwind:CompanyName rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Shipper ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"CompanyName\" .\nnorthwind:Phone rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Shipper ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"Phone\" .\nnorthwind:Customer rdf:type rdfs:Class ;\n\trdfs:label \"Customer\" .\nnorthwind:CompanyName rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Customer ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"CompanyName\" .\nnorthwind:ContactName rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Customer ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"ContactName\" .\nnorthwind:ContactTitle rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:donorthwind:main northwind:Customer ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"ContactTitle\" .\nnorthwind:Address rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Customer ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"Address\" .\nnorthwind:City rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Customer ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"City\" .\nnorthwind:Region rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Customer ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"Region\" .\nnorthwind:PostalCode rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Customer ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"PostalCode\" .\nnorthwind:Country rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Customer ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"Country\" .\nnorthwind:Phone rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Customer ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"Phone\" .\nnorthwind:Fax rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Customer ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"Fax\" .\nnorthwind:Employee rdf:type rdfs:Class ;\n\trdfs:label \"Employee\" .\nnorthwind:LastName rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Employee ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"LastName\" .\nnorthwind:FirstName rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Employee ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"FirstName\" .\nnorthwind:Title rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Employee ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"Title\" .\nnorthwind:TitleOfCourtesy rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Employee ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"TitleOfCourtesy\" .\nnorthwind:BirthDate rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Employee ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"BirthDate\" .\nnorthwind:HireDate rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Employee ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"HireDate\" .\nnorthwind:Address rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Employee ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"Address\" .\nnorthwind:City rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Employee ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"City\" .\nnorthwind:Region rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Employee ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"Region\" .\nnorthwind:PostalCode rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Employee ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"PostalCode\" .\nnorthwind:Country rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Employee ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"Country\" .\nnorthwind:HomePhone rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Employee ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"HomePhone\" .\nnorthwind:Extension rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Employee ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"Extension\" .\nnorthwind:Notes rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Employee ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"Notes\" .\nnorthwind:ReportsTo rdf:type rdf:Property ;\n\trdfs:range xsd:integer ;\n\trdfs:domain northwind:Employee ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"ReportsTo\" .\nnorthwind:Order rdf:type rdfs:Class ;\n\trdfs:label \"Order\" .\nnorthwind:has_customer rdf:type rdf:Property ;\n\trdfs:range northwind:Customer ;\n\trdfs:domain northwind:Order ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"Customer\" .\nnorthwind:has_employee rdf:type rdf:Property ;\n\trdfs:range northwind:Employee ;\n\trdfs:domain northwind:Order ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"Employee\" .\nnorthwind:OrderDate rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Order ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"OrderDate\" .\nnorthwind:RequiredDate rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Order ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"RequiredDate\" .\nnorthwind:ShippedDate rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Order ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"ShippedDate\" .\nnorthwind:order_ship_via rdf:type rdf:Property ;\n\trdfs:range northwind:Shipper ;\n\trdfs:domain northwind:Order ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"Shipper\" .\nnorthwind:Freight rdf:type rdf:Property ;\n\trdfs:range xsd:double ;\n\trdfs:domain northwind:Order ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"Freight\" .\nnorthwind:ShipName rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Order ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"ShipName\" .\nnorthwind:ShipAddress rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Order ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"ShipAddress\" .\nnorthwind:ShipCity rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Order ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"ShipCity\" .\nnorthwind:ShipRegion rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Order ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"ShipRegion\" .\nnorthwind:ShipPostalCode rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Order ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"ShipPostalCode\" .\nnorthwind:ShipCountry rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Order ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"ShipCountry\" .\nnorthwind:OrderLine rdf:type rdfs:Class ;\n\trdfs:label \"OrderLine\" .\nnorthwind:has_order_id rdf:type rdf:Property ;\n\trdfs:range northwind:Order ;\n\trdfs:domain northwind:OrderLine;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"Order\" .\nnorthwind:has_product_id rdf:type rdf:Property ;\n\trdfs:range northwind:Product ;\n\trdfs:domain northwind:OrderLine;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"Product\" .\nnorthwind:UnitPrice rdf:type rdf:Property ;\n\trdfs:range xsd:double ;\n\trdfs:domain northwind:OrderLine;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"UnitPrice\" .\nnorthwind:Quantity rdf:type rdf:Property ;\n\trdfs:range xsd:integer ;\n\trdfs:domain northwind:OrderLine;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"Quantity\" .\nnorthwind:Discount rdf:type rdf:Property ;\n\trdfs:range xsd:double ;\n\trdfs:domain northwind:OrderLine;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"Discount\" .\nnorthwind:Country rdf:type rdfs:Class ;\n\trdfs:label \"Country\" .\nnorthwind:Name rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Country ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"Name\" .\nnorthwind:Code rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Country ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"Code\" .\nnorthwind:SmallFlagDAVResourceName rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Country ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"SmallFlagDAVResourceName\" .\nnorthwind:LargeFlagDAVResourceName rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Country ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"LargeFlagDAVResourceName\" .\nnorthwind:SmallFlagDAVResourceURI rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Country ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"SmallFlagDAVResourceURI\" .\nnorthwind:LargeFlagDAVResourceURI rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Country ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"LargeFlagDAVResourceURI\" .\nnorthwind:Lat rdf:type rdf:Property ;\n\trdfs:range xsd:double ;\n\trdfs:domain northwind:Country ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"Lat\" .\nnorthwind:Lng rdf:type rdf:Property ;\n\trdfs:range xsd:double ;\n\trdfs:domain northwind:Country ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"Lng\" .\nnorthwind:Province rdf:type rdfs:Class ;\n\trdfs:label \"Province\" .\nnorthwind:has_country_code rdf:type rdf:Property ;\n\trdfs:range northwind:Country ;\n\trdfs:domain northwind:Provinces ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"Country Code\" .\nnorthwind:ProvinceName rdf:type rdf:Property ;\n\trdfs:range xsd:string ;\n\trdfs:domain northwind:Province ;\n\trdfs:cardinality \"1\" ;\n\trdfs:label \"ProvinceName\" .\n\n\n---+++ IRI Generator (Entity ID Constructor) Declarations\n\n\nsparql\nprefix demo: \nprefix oplsioc: \nprefix sioc: \ncreate iri class demo:Category \"http://^{URIQADefaultHost}^/demo/Category?id=%d\" (in category_id integer not null) .\ncreate iri class demo:Shipper \"http://^{URIQADefaultHost}^/demo/Shipper?id=%d\" (in shipper_id integer not null) .\ncreate iri class demo:Supplier \"http://^{URIQADefaultHost}^/demo/Supplier?id=%d\" (in supplier_id integer not null) .\ncreate iri class demo:Product \"http://^{URIQADefaultHost}^/demo/Product?id=%d\" (in product_id integer not null) .\ncreate iri class demo:Customer \"http://^{URIQADefaultHost}^/demo/Customer?id=%d\" (in customer_id integer not null) .\ncreate iri class demo:Employee \"http://^{URIQADefaultHost}^/demo/Employee?id=%d\" (in employee_id integer not null) .\ncreate iri class demo:Order \"http://^{URIQADefaultHost}^/demo/Order?id=%d\" (in order_id integer not null) .\ncreate iri class demo:OrderLine \"http://^{URIQADefaultHost}^/demo/OrderLine?id=%d&prod_id=%d\" (in order_id integer not null, in product_id integer not null) .\ncreate iri class demo:Province \"http://^{URIQADefaultHost}^/demo/Province?country=%s&province=%s\" (in country_name varchar not null, in province_name varchar not null) .\ncreate iri class demo:Country \"http://^{URIQADefaultHost}^/demo/Country?country=%s\" (in country_name varchar not null) .\n;\n\n\n---+++ SQL to RDF Mapping (Quad Patterns)\n\nsparql\n\nprefix demo: \nprefix oplsioc: \nprefix sioc: \n\ncreate quad storage virtrdf:Northwind\nfrom Demo.demo.Products as products\nfrom Demo.demo.Suppliers as suppliers\nfrom Demo.demo.Shippers as shippers\nfrom Demo.demo.Categories as categories\nfrom Demo.demo.Customers as customers\nfrom Demo.demo.Employees as employees\nfrom Demo.demo.Orders as orders\nfrom Demo.demo.Order_Details as order_lines\nfrom Demo.demo.Countries as countries\nfrom Demo.demo.Provinces as provinces\n{\n create virtrdf:Demo as graph iri (\"http://^{URIQADefaultHost}^/demo\") option (exclusive)\n {\n demo:Product (products.ProductID)\n a demo:Product\n as virtrdf:Product-ProductID ;\n demo:has_category demo:Category (products.CategoryID)\n as virtrdf:Product-product_has_category ;\n demo:has_supplier demo:Supplier (products.SupplierID)\n as virtrdf:Product-product_has_supplier ;\n demo:ProductName products.ProductName\n as virtrdf:Product-name_of_product ;\n demo:QuantityPerUnit products.QuantityPerUnit\n as virtrdf:Product-quantity_per_unit ;\n demo:UnitPrice products.UnitPrice\n as virtrdf:Product-unit_price ;\n demo:UnitsInStock products.UnitsInStock\n as virtrdf:Product-units_in_stock ;\n demo:UnitsOnOrder products.UnitsOnOrder\n as virtrdf:Product-units_on_order ;\n demo:ReorderLevel products.ReorderLevel\n as virtrdf:Product-reorder_level ;\n demo:Discontinued products.Discontinued\n as virtrdf:Product-discontinued .\n demo:Supplier (suppliers.SupplierID)\n a demo:Supplier\n as virtrdf:Supplier-SupplierID ;\n demo:CompanyName suppliers.CompanyName\n as virtrdf:Supplier-company_name ;\n demo:ContactName suppliers.ContactName\n as virtrdf:Supplier-contact_name ;\n demo:ContactTitle suppliers.ContactTitle\n as virtrdf:Supplier-contact_title ;\n demo:Address suppliers.Address\n as virtrdf:Supplier-address ;\n demo:City suppliers.City\n as virtrdf:Supplier-city ;\n demo:Region suppliers.Region\n as virtrdf:Supplier-region ;\n demo:PostalCode suppliers.PostalCode\n as virtrdf:Supplier-postal_code ;\n demo:Country suppliers.Country\n as virtrdf:Supplier-country ;\n demo:Phone suppliers.Phone\n as virtrdf:Supplier-phone ;\n demo:Fax suppliers.Fax\n as virtrdf:Supplier-fax ;\n demo:HomePage suppliers.HomePage\n as virtrdf:Supplier-home_page .\n demo:Category (categories.CategoryID)\n a demo:Category\n as virtrdf:Category-CategoryID ;\n demo:CategoryName categories.CategoryName\n as virtrdf:Category-home_page ;\n demo:Description categories.Description\n as virtrdf:Category-description ;\n demo:Picture categories.Picture\n as virtrdf:Category-picture .\n demo:Shipper (shippers.ShipperID)\n a demo:Shipper\n as virtrdf:Shipper-ShipperID ;\n demo:CompanyName shippers.CompanyName\n as virtrdf:Shipper-company_name ;\n demo:Phone shippers.Phone\n as virtrdf:Shipper-phone .\n demo:Customer (customers.CustomerID)\n a demo:Customer\n as virtrdf:Customer-CustomerID ;\n demo:CompanyName customers.CompanyName\n as virtrdf:Customer-company_name ;\n demo:ContactName customers.ContactName\n as virtrdf:Customer-contact_name ;\n demo:ContactTitle customers.ContactTitle\n as virtrdf:Customer-contact_title ;\n demo:Address customers.Address\n as virtrdf:Customer-address ;\n demo:City customers.City\n as virtrdf:Customer-city ;\n demo:Region customers.Region\n as virtrdf:Customer-region ;\n demo:PostalCode customers.PostalCode\n as virtrdf:Customer-postal_code ;\n demo:Country customers.Country\n as virtrdf:Customer-country ;\n demo:Phone customers.Phone\n as virtrdf:Customer-phone ;\n demo:Fax customers.Fax\n as virtrdf:Customer-fax .\n demo:Employee (employees.EmployeeID)\n a demo:Employee\n as virtrdf:Employee-EmployeeID ;\n demo:LastName employees.LastName\n as virtrdf:Employee-last_name ;\n demo:FirstName employees.FirstName\n as virtrdf:Employee-first_name ;\n demo:Title employees.Title\n as virtrdf:title ;\n demo:TitleOfCourtesy employees.TitleOfCourtesy\n as virtrdf:Employee-title_of_courtesy ;\n demo:BirthDate employees.BirthDate\n as virtrdf:Employee-birth_date ;\n demo:HireDate employees.HireDate\n as virtrdf:Employee-hire_date ;\n demo:Address employees.Address\n as virtrdf:Employee-address ;\n demo:City employees.City\n as virtrdf:Employee-city ;\n demo:Region employees.Region\n as virtrdf:Employee-region ;\n demo:PostalCode employees.PostalCode\n as virtrdf:Employee-postal_code ;\n demo:Country employees.Country\n as virtrdf:Employee-country ;\n demo:HomePhone employees.HomePhone\n as virtrdf:Employee-home_phone ;\n demo:Extension employees.Extension\n as virtrdf:Employee-extension ;\n demo:Photo employees.Photo\n as virtrdf:Employee-photo ;\n demo:Notes employees.Notes\n as virtrdf:Employee-notes ;\n demo:ReportsTo employees.ReportsTo\n as virtrdf:Employee-reports_to .\n demo:Order (orders.OrderID)\n a demo:Order\n as virtrdf:Order-Order ;\n demo:has_customer demo:Customer (orders.CustomerID)\n as virtrdf:Order-order_has_customer ;\n demo:has_employee demo:Employee (orders.EmployeeID)\n as virtrdf:Order-order_has_employee ;\n demo:OrderDate orders.OrderDate\n as virtrdf:Order-order_date ;\n demo:RequiredDate orders.RequiredDate\n as virtrdf:Order-required_date ;\n demo:ShippedDate orders.ShippedDate\n as virtrdf:Order-shipped_date ;\n demo:order_ship_via demo:Shipper (orders.ShipVia)\n as virtrdf:Order-order_ship_via ;\n demo:Freight orders.Freight\n as virtrdf:Order-freight ;\n demo:ShipName orders.ShipName\n as virtrdf:Order-ship_name ;\n demo:ShipAddress orders.ShipAddress\n as virtrdf:Order-ship_address ;\n demo:ShipCity orders.ShipCity\n as virtrdf:Order-ship_city ;\n demo:ShipRegion orders.ShipRegion\n as virtrdf:Order-ship_region ;\n demo:ShipPostal_code orders.ShipPostalCode\n as virtrdf:Order-ship_postal_code ;\n demo:ShipCountry orders.ShipCountry\n as virtrdf:ship_country .\n demo:OrderLine (order_lines.OrderID, order_lines.ProductID)\n a demo:OrderLine\n as virtrdf:OrderLine-OrderLines ;\n demo:has_order_id demo:Order (order_lines.OrderID)\n as virtrdf:order_lines_has_order_id ;\n demo:has_product_id demo:Product (order_lines.ProductID)\n as virtrdf:order_lines_has_product_id ;\n demo:UnitPrice order_lines.UnitPrice\n as virtrdf:OrderLine-unit_price ;\n demo:Quantity order_lines.Quantity\n as virtrdf:OrderLine-quantity ;\n demo:Discount order_lines.Discount\n as virtrdf:OrderLine-discount .\n demo:Country (countries.Name)\n a demo:Country\n as virtrdf:Country-Name ;\n demo:Code countries.Code\n as virtrdf:Country-Code ;\n demo:SmallFlagDAVResourceName countries.SmallFlagDAVResourceName\n as virtrdf:Country-SmallFlagDAVResourceName ;\n demo:LargeFlagDAVResourceName countries.LargeFlagDAVResourceName\n as virtrdf:Country-LargeFlagDAVResourceName ;\n demo:SmallFlagDAVResourceURI countries.SmallFlagDAVResourceURI\n as virtrdf:Country-SmallFlagDAVResourceURI ;\n demo:LargeFlagDAVResourceURI countries.LargeFlagDAVResourceURI\n as virtrdf:Country-LargeFlagDAVResourceURI ;\n demo:Lat countries.Lat\n as virtrdf:Country-Lat ;\n demo:Lng countries.Lng\n as virtrdf:Country-Lng .\n demo:Province (provinces.CountryCode, provinces.Province)\n a demo:Province\n as virtrdf:Province-Provinces ;\n demo:has_country_code demo:Country (provinces.CountryCode)\n as virtrdf:has_country_code ;\n demo:Province provinces.Province\n as virtrdf:Province-Province .\n }\n}\n;\n\n\n---++ Conclusions \n\nWe have described how we can arbitrarily map relational database schemas to RDF ontologies \nen route to generating virtual RDF Data Sets (Graphs) that are then accessible to SPARQL \nQueries from within SQL or via the SPARQL Query Protocol. All of this is achieved without \ncompromising the inherent flexibility of the RDF data model or the SPARQL Query Language.\n\nIt should also be noted that all of the functionality demonstrated also applies Virtual DBMS \nfunctionality realm of Virtuoso. Thus, you can now map of 3rd party ODBC or JDBC accessible \nSQL data to RDF on the fly. Likewise, you can also use the same Virtual DBMS layer to map \ndata exposed via local or 3rd party SOAP or REST based Web Services to RDF.\n\nIn addition to providing immense power and flexibility at the data mapping level, we have \nalso paid great attention to the low level optimization of Virtuoso's underlying RDF storage \nengine (Triple or Quad Store).\n\n---++ Future Research & Development\nFuture areas of research and development include: \n\n * Supporting declaration of data representation conditioned by the inferred domain of \nthe triple pattern subject.\n * Optimized mapping of joins of relational tables without creating redundant views.\n * Supporting disjunction.\n * Federating SPARQL queries across multiple SPARQL endpoints.\n\n---++Other SQL to RDF Mapping Projects\n\n * [[http://sites.wiwiss.fu-berlin.de/suhl/bizer/D2RQ/][D2RQ Project]]\n * [[http://jena.sourceforge.net/SquirrelRDF/][Squirrel Project]]\n * [[http://www.w3.org/2005/05/22-SPARQL-MySQL/XTech][SPASQL Project]]\n\n---++References\n[[http://www.w3.org/DesignIssues/RDB-RDF][Relational Databases & The Semantic Web]] - Tim Berners-Lee\n\n\nCategoryRDF CategoryAtomOWL CategoryTutorial CategorySQL CategoryOntology CategorySIOC CategoryVOS CategoryVirtuoso CategoryODS\n\n%VOSCOPY%" . . . . "VOSSQLRDF" . . . . "2017-06-13T05:40:08Z" . . . "VOSSQLRDF" . "VOSSQLRDF" . . . . .