1

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?

Romy Vos
  • 13
  • 3
  • 1
    In short, literals can not be subjects. You should declare data properties - not object properties - and make respective literals their values. It seems hard to parse these literals in order to extract objects. Probably you could query Wikidata for more structured information on cocktails (using federated queries to DBpedia, if you want to keep textual descriptions). BTW: https://stackoverflow.com/a/44227937/7879193 – Stanislav Kralin Oct 16 '17 at 11:06
  • So it is impossible to declare a Literal as an owl:NamedIndividual? Or maybe any way to convert the literal in the query so that is is possible? – Romy Vos Oct 16 '17 at 11:21
  • An IRI is used as the unique identifier of an `owl:NamedIndividual`. – UninformedUser Oct 16 '17 at 11:27
  • 1. Impossible. 2. For recipes, you could construct "stub" recipe objects and make DBpedia recipes `rdfs:label`'s or `rdfs:comment`'s of these objects. As for ingredients, you could too, but it seems to be even more horrible modelling solution... – Stanislav Kralin Oct 16 '17 at 11:29
  • You can create URIs in SPARQL by using `URI(CONCAT("http://dbpedia.org/resource/", STR(?literal)))` Indeed ,this depends on the literal value. – UninformedUser Oct 16 '17 at 11:29
  • Alright, thank you! Can I make a URI of a literal in some way (preferably inside the query)? Nevermind, see you answer! Thank you, I'm going to look into it. – Romy Vos Oct 16 '17 at 11:30
  • For a recipe this won't make sense, unless you either use some UUID or maybe the MD5 of the recipe text. And then attach the real recipe to this resource. – UninformedUser Oct 16 '17 at 11:32
  • I added an example as answer (but won't consider it as best practise, nor as a good answer) – UninformedUser Oct 16 '17 at 11:40

1 Answers1

2

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...

UninformedUser
  • 8,397
  • 1
  • 14
  • 23
  • 1
    As AKSW already provided an excellent answer, I'll just add that `MD5()` is indeed a much practice compared to the another common approach: ` BIND ( URI(concat("http://example.com/coctailingrediants/", ENCODE_FOR_URI(str(?ingredients_str)))) AS ?ingredients ) BIND ( URI(concat("http://example.com/coctailrecipe/", ENCODE_FOR_URI(str(?recipe_str)))) AS ?recipe ) }` which is risky. – Ivo Velitchkov Oct 16 '17 at 11:48
  • Ah, thanks for letting me know that there are other useful methods like `ENCODE_FOR_URI` - I never used this method, but probably for long strings this would be what you mean by risky? – UninformedUser Oct 16 '17 at 11:51