Search code examples
sparqlrdfowlontologyprotege

Literal as subject in SPARQL CONSTRUCT query


I am struggling creating a correct SPARQL query which will produce the right format so that I can open in it Protege. Our ontology is about cocktails, we want to have all DBPedia cocktails in our database, including the ingredients (dbp:ingredients) and recipe (dbp:prep). Getting the cocktail in the database works fine, but the ingredients and recipe not. I have now the following query:

CONSTRUCT {?drink dct:subject ?category.
?drink dbp:prep ?recipe.
?drink dbp:ingredients ?ingredients.
?drink rdf:type owl:NamedIndividual .
?category rdf:type owl:Class.
dct:subject rdf:type owl:ObjectProperty.
dbp:prep rdf:type owl:ObjectProperty.
dbp:ingredient rdf:type owl:Objectproperty.
}
WHERE {
?drink dct:subject ?category.
?drink dbp:prep ?recipe.
?drink dbp:ingredients ?ingredients.}

Since ?ingredients and ?recipe are now not declared, it does not show in the individuals tab in Protege. But when I add this to the CONSTRUCT part of the query:

?recipe rdf:type owl:NamedIndividual.
?ingredients rdf:type owl:NamedIndividual.

I get an error:

Virtuoso RDF01 Error Bad variable value in CONSTRUCT: "*5 cL vodka *10 cL orange juice" (tag 246 box flags 0) is not a valid subject, only object of a triple can be a literal

I think because the prep and ingredients on dbpedia are just a string, no linked data. However, how do I make this work so that it does show in Protege?


Solution

  • It's not possible to have a literal as subject of an RDF triple. Instead, creating a resource for recipe and ingredients + attaching the string values as rdfs:comment (or maybe rdfs:label) could be a workaround. It works like this:

    CONSTRUCT {
    ?drink dct:subject ?category.
    ?drink dbp:prep ?recipe.
    ?drink dbp:ingredients ?ingredients.
    ?drink rdf:type owl:NamedIndividual .
    ?category rdf:type owl:Class.
    dct:subject rdf:type owl:ObjectProperty.
    dbp:prep rdf:type owl:ObjectProperty.
    dbp:ingredients rdf:type owl:Objectproperty.
    # add string values as rdfs:comment
    ?recipe rdfs:comment ?recipe_str .
    ?ingredients rdfs:comment ?ingredients_str
    }
    WHERE {
    ?drink dct:subject ?category.
    ?drink dbp:prep ?recipe_str.
    ?drink dbp:ingredients ?ingredients_str.
    BIND(URI(CONCAT("http://dbpedia.org/resource/recipe", MD5(STR(?recipe_str)))) as ?recipe)
    BIND(URI(CONCAT("http://dbpedia.org/resource/ingredients", MD5(STR(?ingredients_str)))) as ?ingredients)
    }
    

    Note, it would somehow fail if the recipe (resp. ingredients) is already a resource. It doesn't hold for dbp:prep and dbp:ingredients on DBpedia, but in general, if you're not sure and you have some rdf:Property which in fact allows for both resources and literals, you need to handle this properly, e.g. by using the IF-ELSE construct:

    BIND(IF(isLiteral(?recipe_str), URI(CONCAT("http://dbpedia.org/resource/recipe", MD5(STR(?recipe_str)))), ?recipe_str) as ?recipe)
    BIND(IF(isLiteral(?ingredients_str), URI(CONCAT("http://dbpedia.org/resource/ingredients", MD5(STR(?ingredients_str)))), ?ingredients_str)  as ?ingredients)
    

    and you also would have to omit the rdfs:comment triples then indeed...