SPARQL BIND inside UNION is too slow - sparql

Given an IMDb ID, i want to get a list of directors and actors for that movie from Wikidata.
The problem is, I want to UNION both the director and actor query into a single column while also providing a new column with the role of director or actor.
Pretty easy query overall: first I get the movie entity from the IMDb ID, then I get all the directors from that movie followed by getting all the actors from that movie and UNION them together while filling a new column (?role) with the role.
This is what I have:
PREFIX p: <http://www.wikidata.org/prop/>
PREFIX ps: <http://www.wikidata.org/prop/statement/>
PREFIX wdt: <http://www.wikidata.org/prop/direct/>
SELECT ?person ?personLabel ?role ?imdb WHERE
{
?movie wdt:P345 "tt0110912" .
{ ?movie p:P57 ?cast .
?cast ps:P57 ?person .
BIND("director" as ?role) .
} UNION {
?movie p:P161 ?cast .
?cast ps:P161 ?person .
BIND("actor" as ?role) . }
?person wdt:P345 ?imdb .
OPTIONAL { ?cast prov:wasDerivedFrom ?ref . }
SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
}
GROUP BY ?person ?personLabel ?role ?imdb
ORDER BY DESC(?role)
LIMIT 100
This works and gives the result I want, problem is it takes about 10secs. If I remove the BINDs its instant speed, but I don't get a column with the roles.

I'd write this using values instead of bind and union. The idea is that you're saying when the properties are one thing, then ?role is one thing, and when the properties are another, ?role is another. The easy way to do that with values is something like:
select ?owner ?pet ?petType {
values (?hasPet ?petType) {
(:hasCat "cat")
(:hasDog "dog")
}
?owner ?hasPet ?pet
}
In your case, this would be:
PREFIX p: <http://www.wikidata.org/prop/>
PREFIX ps: <http://www.wikidata.org/prop/statement/>
PREFIX wdt: <http://www.wikidata.org/prop/direct/>
SELECT ?person ?personLabel ?role ?imdb WHERE
{
?movie wdt:P345 "tt0110912" .
values (?p ?ps ?role) {
(p:P161 ps:P161 "actor")
(p:P57 ps:P57 "director")
}
?movie ?p ?cast .
?cast ?ps ?person .
?person wdt:P345 ?imdb .
OPTIONAL { ?cast prov:wasDerivedFrom ?ref . }
SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
}
GROUP BY ?person ?personLabel ?role ?imdb
ORDER BY DESC(?role)
LIMIT 100
When I run this at query.wikidata.org, it produces 35 results almost instantly.

I guess that BIND leads to some problems with the query optimizer. You can try as an alternative to bind the role outside of the UNION clause, i.e.
PREFIX p: <http://www.wikidata.org/prop/>
PREFIX ps: <http://www.wikidata.org/prop/statement/>
PREFIX wdt: <http://www.wikidata.org/prop/direct/>
SELECT ?person ?personLabel ?role ?imdb WHERE
{
?movie wdt:P345 "tt0110912" .
?person wdt:P345 ?imdb .
{
?movie p:P57 ?c1 . ?c1 ps:P57 ?person .
?movie p:P57 ?cast .
} UNION {
?movie p:P161 ?c2 . ?c2 ps:P161 ?person .
?movie p:P161 ?cast .
}
BIND(IF(bound(?c1), "director", "actor") as ?role)
OPTIONAL { ?cast prov:wasDerivedFrom ?ref . }
SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
}
GROUP BY ?person ?personLabel ?role ?imdb
ORDER BY DESC(?role)
LIMIT 100
(If you do not the ?ref variable, you can omit the triple patterns to retrieve the ?cast in the UNION clauses.)

Related

Problem with basics queries form dbpedia with sparql

Why can get results with this query:
SELECT *
WHERE {
?person rdfs:label "Kirk Douglas"#en;
dbo:birthPlace ?place. # With dbp: too
}
but not with another one:
SELECT *
WHERE {
?person rdfs:label "Kirk Douglas"#en;
dbo:starring ?film.
}
I'm following tags in https://dbpedia.org/page/Kirk_Douglas
Some tip to understand it.
Thx!
In dbr:Kirk_Douglas you can read:
dbo:birthPlace dbr:Amsterdam_(city),_New_York
...
is dbo:starring of dbr:The_Light_at_the_Edge_of_the_World
dbr:Cast_a_Giant_Shadow
dbr:Two-Fisted_Tales_(film)
...
where is dbo:starring of is a way of simulating an inverse property for dbo:starring.
Indeed, in dbo:starring you can read:
rdfs:domain dbo:Work
...
rdfs:range dbo:Actor
This means that you shouldn't build triples like ?actor dbo:starring ?work, while ?work dbo:starring ?actor is a valid triple.
So your query should be something like:
SELECT *
WHERE {
?person rdfs:label "Kirk Douglas"#en .
?film dbo:starring ?person .
}

In Wikidata SPARQL, how can I return other fields?

In my Wikidata query, I actually have 2 problems.
Date of Birth + Gender + Description is not returned
GROUP BY does not aggregate based on the name (Bad Aggregate Error)
SELECT ?person ?label ?dob ?gender ?description
WHERE
{
?person wdt:P31 wd:Q5;
rdfs:label ?label.
FILTER(STRSTARTS(?label, "Michael Bloom")).
}
GROUP BY ?person ?label
LIMIT 5
Try it here
You must query all fields for them to being returned (SPARQL can't guess where to get them), with the exception of labels and descriptions when using Wikibase Label Service.
All fields in your SELECT must be in the GROUP BY clause, with the exception of the use of the GROUP_CONCAT function that will concatenate all values into one field.
Bad news: searching humans by other than their exact label in Wikidata Query Service is hard, as you will often hit the timeout of 60 seconds. A solution, as shown by AKSW, is to use Wikibase Mediawiki API and do an entity search.
Here is an example of your query rewritten:
SELECT ?person ?personLabel ?personDescription (GROUP_CONCAT(DISTINCT ?dob ; SEPARATOR = ' / ') AS ?dob) (GROUP_CONCAT(DISTINCT ?gender ; SEPARATOR = ' / ') AS ?gender)
WHERE {
SERVICE wikibase:mwapi {
bd:serviceParam wikibase:api "EntitySearch" .
bd:serviceParam wikibase:endpoint "www.wikidata.org" .
bd:serviceParam mwapi:search "Michael Bloom" .
bd:serviceParam mwapi:language "en" .
?person wikibase:apiOutputItem mwapi:item .
}
?person wdt:P31 wd:Q5 .
OPTIONAL { ?person wdt:P569 ?dob }
OPTIONAL { ?person wdt:P21 ?gender }
SERVICE wikibase:label { bd:serviceParam wikibase:language "en" . }
}
GROUP BY ?person ?personLabel ?personDescription

Aggregate properties

I'm developing my own Fuseki endpoint from some DBpedia data.
I'm in doubt on how to aggregate properties related to a single resource.
SELECT ?name ?website ?abstract ?genre ?image
WHERE{
VALUES ?s {<http://dbpedia.org/resource/Attack_Attack!>}
?s foaf:name ?name ;
dbo:abstract ?abstract .
OPTIONAL { ?s dbo:genre ?genre } .
OPTIONAL { ?s dbp:website ?website } .
OPTIONAL { ?s dbo:image ?image } .
FILTER LANGMATCHES(LANG(?abstract ), "en")
}
SPARQL endpoint: http://dbpedia.org/sparql/
This query returns 2 matching results. They are different just for the dbo:genre value. There is a way I can query the knowledge base and retrieving a single result with a list of genres?
#chrisis's query works well on the DBpedia SPARQL Endpoint, which is based on Virtuoso.
However, if you are using Jena Fuseki, you should use more conformant syntax:
PREFIX dbo: <http://dbpedia.org/ontology/>
PREFIX dbp: <http://dbpedia.org/property/>
SELECT
?name
(SAMPLE(?website) AS ?sample_website)
(SAMPLE(?abstract) AS ?sample_abstract)
(SAMPLE(?image) AS ?sample_image)
(GROUP_CONCAT(?genre; separator=', ') AS ?genres)
WHERE {
VALUES (?s) {(<http://dbpedia.org/resource/Attack_Attack!>)}
?s foaf:name ?name ;
dbo:abstract ?abstract .
OPTIONAL { ?s dbo:genre ?genre } .
OPTIONAL { ?s dbp:website ?website } .
OPTIONAL { ?s dbo:image ?image} .
FILTER LANGMATCHES(LANG(?abstract ), "en")
} GROUP BY ?name
The differences from the #chrisis's query are:
Since GROUP_CONCAT is an aggregation function, it might be used with GROUP BY only;
Since GROUP BY is used, all non-grouping variables should be aggregated (e.g. via SAMPLE);
GROUP_CONCAT syntax is slightly different.
In Fuseki, these AS in the projection are in fact superfluous: see this question and comments.
Yes, the GROUP_CONCAT() function is what you want.
SELECT ?name ?website ?abstract (GROUP_CONCAT(?genre,',') AS ?genres) ?image
WHERE{
<http://dbpedia.org/resource/Attack_Attack!> a dbo:Band ;
foaf:name ?name;
dbo:abstract ?abstract .
OPTIONAL{ <http://dbpedia.org/resource/Attack_Attack!> dbo:genre ?genre } .
OPTIONAL{ <http://dbpedia.org/resource/Attack_Attack!> dbp:website ?website} .
OPTIONAL{ <http://dbpedia.org/resource/Attack_Attack!> dbo:image ?image} .
FILTER LANGMATCHES(LANG(?abstract ), "en")
}

Sparql of DbPedia based upon name not subject

I am trying to query dbpedia to get some people data and I don't have subjects just names of the people I want to query and their birth/death dates.
I am trying to do a query along these lines. I want the name, birth date, death date and thumbnail of everyone with the surname Presley. What I then intend to do is loop through the results returned and find the best match for Elvis Presley 1935-1977 which is the data I have.
PREFIX dbo: <http://dbpedia.org/ontology/>
SELECT ?Name ?thumbnail ?birthDate ?deathDate WHERE {
{
dbo:name ?Name ;
dbo:birthDate ?birthDate ;
dbo:birthDate ?deathDate ;
dbo:thumbnail ?thumbnail ;
FILTER contains(?Name#en, "Presley")
}
What is the best way to construct my sparql query?
UPDATE:
I have put together this query which seems to work to some extent but I don't entirely understand it, and I can't figure out the contains, but it does at least run and return results.
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX dbo: <http://dbpedia.org/ontology/>
SELECT ?subject ?thumbnail ?birthdate ?deathdate WHERE {
{
?subject rdfs:label "Elvis Presley"#en ;
dbo:thumbnail ?thumbnail ;
dbo:birthDate ?birthdate ;
dbo:deathDate ?deathdate ;
a owl:Thing .
}
UNION
{
?altName rdfs:label "Elvis Presley"#en ;
dbo:thumbnail ?thumbnail ;
dbo:birthDate ?birthdate ;
dbo:deathDate ?deathdate ;
dbo:wikiPageRedirects ?s .
}
}
Some entities might not have all of that information, so it's better to use optional. You can use foaf:surname to check for surname directly.
select * where {
?s foaf:surname "Presley"#en
optional { ?s dbo:name ?name }
optional { ?s dbo:birthDate ?birth }
optional { ?s dbo:deathDate ?death }
optional { ?s dbo:thumbnail ?thumb }
}

How to SPARQL optional

I have to use this Spaql query to retrive information about a person, my problem is to break the optional construct up into multiple optional constructs.
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX dbo: <http://dbpedia.org/ontology/>
PREFIX dbpprop: <http://dbpedia.org/property/>
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
SELECT DISTINCT ?label ?abstract ?placeOfBirth
?birthPlace ?birthDate ?page ?thumbnail
WHERE {
<http://dbpedia.org/resource/Ismail_Kadare> rdfs:label ?label ;
dbo:abstract ?abstract ;
foaf:page ?page .
OPTIONAL {
<http://dbpedia.org/resource/Ismail_Kadare> dbpprop:placeOfBirth ?placeOfBirth ;
dbpprop:birthPlace ?birthPlace ;
dbo:birthDate ?birthDate ;
dbo:thumbnail ?thumbnail .
}
FILTER (LANG(?label) = 'en')
FILTER (LANG(?abstract) = 'en')
}
LIMIT 1
Splitting the OPTIONAL pattern into parts
The pattern
<http://dbpedia.org/resource/Ismail_Kadare> dbpprop:placeOfBirth ?placeOfBirth ;
dbpprop:birthPlace ?birthPlace ;
dbo:birthDate ?birthDate ;
dbo:thumbnail ?thumbnail .
is shorthand for four triple patterns:
<http://dbpedia.org/resource/Ismail_Kadare> dbpprop:placeOfBirth ?placeOfBirth .
<http://dbpedia.org/resource/Ismail_Kadare> dbpprop:birthPlace ?birthPlace .
<http://dbpedia.org/resource/Ismail_Kadare> dbo:birthDate ?birthDate .
<http://dbpedia.org/resource/Ismail_Kadare> dbo:thumbnail ?thumbnail .
Instead of OPTIONAL { …first pattern… }, you just need to use four optional blocks, one for each of the four triple patterns:
optional { <http://dbpedia.org/resource/Ismail_Kadare> dbpprop:placeOfBirth ?placeOfBirth }
optional { <http://dbpedia.org/resource/Ismail_Kadare> dbpprop:birthPlace ?birthPlace }
optional { <http://dbpedia.org/resource/Ismail_Kadare> dbo:birthDate ?birthDate }
optional { <http://dbpedia.org/resource/Ismail_Kadare> dbo:thumbnail ?thumbnail }
Other issues
It's worth nothing that language matching is a bit more complicated than string matching, so rather than
FILTER (LANG(?label) = 'en')
FILTER (LANG(?abstract) = 'en')
you should really be using
filter(langMatches(lang(?label),'en'))
filter(langMatches(lang(?abstract),'en'))
which allows you to retrieve results that use different language tags that are all English.
select distinct and limit 1 aren't both necessary
Notice that select distinct ensures that you don't have any duplicate rows in your results. However, limit 1 means that you'll only have one result at most anyhow, so there won't be any duplicates to remove.
Standard Namespaces
It looks like you're querying against DBpedia, so it might be worthwhile to use the same namespace prefixes that the public endpoint defines, so that you can copy and paste queries and experiment more easily. Doing that (and using a values ?x { dbpedia:Ismail_Kadare } to avoid some typing, we end up with this query:
select ?label ?abstract ?placeOfBirth ?birthPlace ?birthDate ?page ?thumbnail
where {
values ?x { dbpedia:Ismail_Kadare }
?x rdfs:label ?label ;
dbpedia-owl:abstract ?abstract ;
foaf:page ?page .
optional { ?x dbpprop:placeOfBirth ?placeOfBirth }
optional { ?x dbpprop:birthPlace ?birthPlace }
optional { ?x dbpedia-owl:birthDate ?birthDate }
optional { ?x dbpedia-owl:thumbnail ?thumbnail }
filter langMatches(lang(?label),'en')
filter langMatches(lang(?abstract),'en')
}
limit 1
The DBpedia endpoint won't return anything for that query, but that's because http://dbpedia.org/resource/Ismail_Kadare doesn't have a foaf:page property, not because the query is malformed. I don't know whether you're actually running this against DBpedia or not, so that may or not matter.