SPARQL 1.1 Property Paths の紹介

これは [SPARQL Advent Calendar 2015](http://qiita.com/advent-calendar/2015/sparql) の15日目の記事です。

[SPARQL 1.1 Query Language : Property Paths](http://www.w3.org/TR/sparql11-query/#propertypaths) という仕様があるのですが、これを使うと SPARQL の表現が簡単になったり、いままではできなかったような検索方法が使えるようになったりします。この記事ではそんな Property Paths を少しだけ紹介します。

# 1. シナリオ

@kochizufan さんの [SPARQLで旧日本海軍艦船の命名由来となった地名をリストアップしてみた](http://qiita.com/kochizufan/items/5f75edf8ed0024b51962) からお題をお借りしました。

# 2. SPARQL

こんなSPARQLが

```
PREFIX dbpedia-owl:  <http://dbpedia.org/ontology/>
PREFIX dcterms: <http://purl.org/dc/terms/>
PREFIX category-ja: <http://ja.dbpedia.org/resource/Category:>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>

select distinct ?a ?b where {
  ?a dcterms:subject/skos:broader* category-ja:日本海軍の艦船 ; ^dbpedia-owl:wikiPageDisambiguates ?x .
  ?b a dbpedia-owl:Place ; ^dbpedia-owl:wikiPageDisambiguates ?x .
}
```

こんな結果を返します

```
a	b
http://ja.dbpedia.org/resource/東艦	http://ja.dbpedia.org/resource/東京都
http://ja.dbpedia.org/resource/比叡_(戦艦)	http://ja.dbpedia.org/resource/比叡山
http://ja.dbpedia.org/resource/比叡_(コルベット)	http://ja.dbpedia.org/resource/比叡山
http://ja.dbpedia.org/resource/石川_(練習艦)	http://ja.dbpedia.org/resource/石川県
http://ja.dbpedia.org/resource/長門_(戦艦)	http://ja.dbpedia.org/resource/長門市
http://ja.dbpedia.org/resource/長門型戦艦	http://ja.dbpedia.org/resource/長門市
http://ja.dbpedia.org/resource/鹿島_(戦艦)	http://ja.dbpedia.org/resource/鹿児島県
http://ja.dbpedia.org/resource/鹿島_(練習巡洋艦)	http://ja.dbpedia.org/resource/鹿児島県
http://ja.dbpedia.org/resource/最上_(重巡洋艦)	http://ja.dbpedia.org/resource/最上川
http://ja.dbpedia.org/resource/最上_(通報艦)	http://ja.dbpedia.org/resource/最上川
http://ja.dbpedia.org/resource/最上_(重巡洋艦)	http://ja.dbpedia.org/resource/最上町
http://ja.dbpedia.org/resource/最上_(通報艦)	http://ja.dbpedia.org/resource/最上町
http://ja.dbpedia.org/resource/大和_(戦艦)	http://ja.dbpedia.org/resource/大和市
http://ja.dbpedia.org/resource/大和型戦艦	http://ja.dbpedia.org/resource/大和市
http://ja.dbpedia.org/resource/大和_(スループ)	http://ja.dbpedia.org/resource/大和市
http://ja.dbpedia.org/resource/日向_(戦艦)	http://ja.dbpedia.org/resource/日向市

（以下省略）

```

2015/12/13 時点、 <http://ja.dbpedia.org/sparql> で実行すると 348件の結果が得られます。実行時間は体感で1秒以下といったところでしょうか。

# 3. 解説

## クエリーの組み立て

Wikipedia で **曖昧さ回避** というページを見かけるかと思います。地名と戦艦名が似通っている場合は共通の曖昧さ回避ページがあるだろう、ということで、**曖昧さ回避ページを共有する地名と戦艦** を検索してみました。

## Inverse Path

**曖昧さ回避ページを持つ地名** を取得する場合、普通はこんな感じで書くかと思います。


```
PREFIX dbpedia-owl:  <http://dbpedia.org/ontology/>

select distinct ?a where {
  ?a a dbpedia-owl:Place .
  ?b dbpedia-owl:wikiPageDisambiguates ?a .
}
```

Property Paths の **InversePath** という記法を使うと次のように書くことができます。


```
PREFIX dbpedia-owl:  <http://dbpedia.org/ontology/>

select distinct ?a where {
  ?a a dbpedia-owl:Place ; ^dbpedia-owl:wikiPageDisambiguates ?b .
}

```

述語の前に **^** をつけることで、主語と目的語の位置を入れ替えることができる、というものです。

## SequencePath と ZeroOrMorePath

カテゴリ [日本海軍の艦船](http://ja.dbpedia.org/page/Category:%E6%97%A5%E6%9C%AC%E6%B5%B7%E8%BB%8D%E3%81%AE%E8%89%A6%E8%88%B9) は結構たくさんのサブカテゴリを持っているので、すべての艦船を検索しようとすると通常は **UNION** を使った長いクエリーが必要になってしまいます。

SequencePath と ZeroOrMorePath を使うことで、次のように書くことができます。

```
?a dcterms:subject/skos:broader* category-ja:日本海軍の艦船 .
```

これは

1. **?a dcterms:subject ?b** のような **?b** が存在し
2. **?b** は **category-ja:日本海軍の艦船** そのものであるか、あるいは **skos:broader** を任意回数たどることで **category-ja:日本海軍の艦船** に到達する

といったパターンにマッチするというものです。述語をスラッシュでつなげているのが **SequencePath** 、述語の最後にアスタリスクをつけているのが **ZeroOrMorePath** です。

**skos:broader** で定義されたような階層型カテゴリを芋づる式に探索する場合に便利な方法です。

# 4.まとめ

* SPARQL 1.1 Query Language : Property Paths を少しだけ紹介しました
* ここで紹介したもの以外にも何種類かあるので、気になったら調べてみましょう

# 追記

@sasau さんの [日本の野球選手の出身高校を列挙するクエリの例](http://qiita.com/sasau/items/d5cb1b4cf552489cbb31) に **ZeroOrOne** を導入すると：

```
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
PREFIX dcterms: <http://purl.org/dc/terms/>
PREFIX prop-ja: <http://ja.dbpedia.org/property/>
PREFIX category-ja: <http://ja.dbpedia.org/resource/Category:>
PREFIX dbpedia-owl: <http://dbpedia.org/ontology/>
PREFIX template-ja: <http://ja.dbpedia.org/resource/Template:>

SELECT DISTINCT ?label ?team_label WHERE {
  ?person
    dcterms:subject/skos:broader? category-ja:日本の野球選手 ;
    dbpedia-owl:team ?team ;
    rdfs:label ?label .
  ?team
    dbpedia-owl:wikiPageRedirects?/prop-ja:wikiPageUsesTemplate template-ja:日本の高等学校 ;
    rdfs:label ?team_label .
}

```

オリジナルのクエリーと意味がちょっと違うところもありますが[^1]、述語のあとの **?** が **ZeroOrOne** です。これによって「日本の野球選手カテゴリとその直下のカテゴリに所属する選手」や「ひとつのリダイレクトページを挟むかもしれない場合」といった場合を簡潔に書くことができます。
**ZeroOrMore** や **OneOrMore** は探索が深くなってしまう傾向があるので、条件を限定できる場合には **ZeroOrOne** のほうが応答性能がいいかもしれません。使い分けましょう。

[^1]: オリジナルでは「日本の野球選手」「MLBの日本人選手」「在日外国人の野球選手」「日系人の野球選手」を UNION していますが、ここでは説明のために「日本の野球選手」とその直下のカテゴリに属する選手を対象としています。女子選手等も含めてより多くの選手が選択されていることに注意。