03/11
RDFの検索とデータセット
神崎 正英
RDFグラフの検索
RDFのデータを利用するためには、データの中から求めるサブセットを取得する(検索する)手段が必要です。グラフをたどるだけでは、発見はあっても、求めるものを確実に得ることは難しいからです。
RDFトリプルとデータベース
RDFグラフに対する検索を単純に考えれば、グラフをデータベースなどに収めて条件に合致するものを照会することになります。グラフは主語―述語―目的語のトリプルの集合ですから、これらの要素をデータベースのフィールドとみなして扱うのが最もシンプルな方法でしょう。
たとえば前回のJSON-LD記述例で取り上げたグラフなら、次のようなテーブルにグラフが格納されます。
S | P | O |
---|---|---|
http://example.org/photo/2008/0208.jpg | http://...contentLocation | http://...ミューザ川崎 |
http://example.org/photo/2008/0208.jpg | http://...dateCreated | 2008-02-08 |
... | ... | ... |
写真に関するメタデータを全部取り出すなら
例1
SELECT * FROM tablename WHERE S='http://example.org/photo/2008/0208.jpg';
といったSQLクエリで、データ全体からこのURIを主語としたサブセットが取得できます。この写真の作成日を知りたければ、条件句に and P='http://schema.org/dateCreated'
を加えることで絞込が可能です。
もちろんこれでは各要素がURIなのかリテラルなのか区別できませんし、データ型も言語コードも保持できませんから、テーブルはもう少し精密なものが必要です。それに、この方法では「写真に写っているコンサートの演奏者」などと考えたとたんにクエリは複雑になり、どんどん手に負えなくなってしまうでしょう。
RDFグラフのパターン
RDFのデータはグラフ構造を持つので、特定のデータベーステーブルを前提にするよりも、グラフの形(パターン)として取り出したい条件を考えるほうが自然です。ある写真のメタデータを表すパターンとは、次のような具合です(以下の図ではURIは接頭辞を使って短縮表記します)。
「写真に写っているコンサートの演奏者」という条件も、グラフのパターンなら簡単に表現できます。
RDFデータのグラフ全体の中から、こうしたパターンに当てはまるトリプルを取り出すことができれば、グラフの構造を利用した検索が可能になります。このようなグラフのパターンの検索方法を標準化したものがSPARQLです。SPARQLは2008年に最初のバージョンがW3C勧告となり、2013年にはSPARQL 1.1 Query Languageが勧告されています。
SPARQLとグラフパターン
SPARQLは、グラフのパターンをトリプルパターンの組み合わせとして表現します。トリプルパターンはTurtleとほぼ同じ構文を用い、主語、述語、目的語のうち値が未知であるものを変数とします。変数には?
で始まる名前を用います。
前節のトリプルパターンをSPARQLで記述してみましょう。
例2
<http://example.org/photo/2008/0208.jpg>?p
?o
.
このトリプルパターンを用いてRDFデータベースから写真のメタデータを取り出すためには、パターンを{}
で囲んでキーワードWHERE
に続けて条件句とし、その前に結果を取得するキーワードSELECT
と対象変数名を空白区切りで列挙します。変数名の代わりに*
を指定すると、パターンに含まれる変数全てを取得することができます。
例3
SELECT * WHERE
{
<http://example.org/photo/2008/0208.jpg> ?p ?o .
}
SQLとよく似た構文ですね。このSPARQLクエリで照会すると次のような結果が得られます。
?p | ?o |
---|---|
http://schema.org/about | http://example.org/concert/2008j |
http://schema.org/name | ...演奏会のステージ |
http://schema.org/dateCreated | 2008-02-08 |
http://schema.org/contentLocation | http://ja.dbpedia.org/resource/ミューザ川崎 |
http://www.w3.org/1999/02/22-rdf-syntax-ns#type | http://schema.org/Photograph |
トリプルパターンを列挙することでグラフパターンを表現できます。グラフパターンを構成する複数のトリプルパターンはピリオド(.
)で区切ります。また接頭辞を用いる場合は先頭にPREFIX
句を置いて宣言します(Turtleとは違って@
は用いず、宣言行末にピリオドも記述しません)。
例4
PREFIX
s: <http://schema.org/>
SELECT ?o ?who WHERE {
<http://example.org/photo/2008/0208.jpg> ?p ?o .
?o s:performer ?who .
}
上のクエリからは次のような結果が得られます。
?o | ?who |
---|---|
http://example.org/concert/2008j | http://ja.dbpedia.org/resource/シュトゥットガルト放送交響楽団 |
http://example.org/concert/2008j | http://ja.dbpedia.org/resource/ロジャー・ノリントン |
クエリには変数?p
も記述されていますが、SELECT
で取得する変数を明示しているので、結果集合には含まれません。
制約条件とフィルタ
条件を絞り込むには、変数の代わりに具体的な値を指定します。たとえばさまざまな写真メタデータを収録しているRDFデータベースを対象にしている場合、被写体のタイプをs:MusicEvent
とすれば、コンサート写真のみを取り出すことができます。
例5
PREFIX s: <http://schema.org/> SELECT * WHERE { ?photo ?p ?o ; s:about [ a s:MusicEvent ] . }
このグラフパターンは読み取れるでしょうか? 前回とりあげたTurtle構文と同じく、[]
は空白ノード、a
はrdf:type
を表します。SPARQLクエリのグラフパターンにおいては、空白ノードは値を取得しない変数という位置付けで、実際のグラフで対応部分がURIであっても構いません。念のため上のクエリのグラフパターンを図で確認しておきましょう。
値を直接指定するのではなく、グラフパターンで取り出した変数の値に対して条件を加えて絞り込む方法もあります。この絞り込みはFILTER
句を用いて行ないます。たとえば2008年に撮影された写真のメタデータのみを取り出す場合は、まず撮影日を変数?date
で取り出した上で、その値を制約します。
例6
PREFIX s: <http://schema.org/>
SELECT * WHERE {
?photo ?p ?o ;
s:dateCreated ?date .
FILTER
( REGEX(?date, "^2008") )
}
ここでは日付を単純リテラルとして扱い、正規表現で先頭が"2008"であるものという条件を与えて絞り込んでいます。
SPARQLでは、文字列本体が同じでもデータ型や言語タグの有無によって異なるリテラル値として扱われることに注意してください。上記のクエリでは、撮影日がデータ型xsd:date
のリテラルとして記述されているものは取得できません。データ型や言語タグの有無が不明な場合は、STR()
関数を用いて値の文字列のみを取り出してからフィルタをかけます。
例7
FILTER ( REGEX(STR(?date), "^2008") )
日付データがxsd:date
型ならば、SPARQL 1.1ではyear(?date)
を用いて年部分を取り出して比較することもできます。
例8
FILTER ( year(?date) = "2008" )
このほかFILTERでは、比較演算子を用いたり!=
で条件に一致しないものを取り出したりすることも可能です。
複数のグラフパターン
トリプルパターンの集合としてのグラフパターンは、常に「XかつY(AND)」の条件を表現しますが、「XもしくはY(OR)」という条件を設定したいこともあります。この場合は、XとYを表現するグラフパターンを{}
で囲んでグループ化し(グループグラフパターン)、その関係をキーワードで表します。
UNIONを用いたOR検索
複数条件(グラフパターン)のいずれかに該当するものを調べる場合は、グループグラフパターンをUNION
で連結します。たとえば音楽イベントとソーシャルイベントのメタデータがある場合、グラフパターンはそれぞれ次のように表現できます。
例9
?evt ?p ?o ; a s:MusicEvent .
例10
?evt ?p ?o ; a s:SocialEvent .
グラフパターンを{}
で囲んでグループ化し、UNION
で連結したものをWHERE
句内に置けばOR検索を実行できます。
例11
PREFIX s: <http://schema.org/>
SELECT * WHERE {
{
?evt ?p ?o ;
a s:MusicEvent .
} UNION
{
?evt ?p ?o ;
a s:SocialEvent .
}
}
2つのグループグラフパターンにおいてトリプルパターン?evt ?p ?o
は共通ですから、これをまとめて外に出してしまうこともできます。
例12
PREFIX s: <http://schema.org/> SELECT * WHERE { ?evt ?p ?o . { ?evt a s:MusicEvent . } UNION { ?evt a s:SocialEvent . } }
これは、WHERE
句全体がひとつの大きなグループグラフパターンとなり、その中に2つのグループグラフパターンがあるというわけです。このように、グループグラフパターンは入れ子にして扱うことができます。
必須ではない条件を表すOPTIONAL
写真が位置情報を持っていればそれを用いて地図上に表示できるので、次のようなクエリでの写真メタデータと緯度経度データの取得を考えましょう。
例13
PREFIX s: <http://schema.org/> SELECT * WHERE { ?photo ?p ?o ; s:location ?place . ?place s:geo [ s:latitude ?lat ; s:longitude ?long ] . }
このクエリで緯度経度を含めたメタデータを取り出せるのですが、「位置情報があれば地図も表示するけれど、なければ地図なしで写真メタデータのみを表示する」という場合、このままではうまく行きません。図を見ると分かるように、位置情報がないデータはこのグラフパターンに当てはまらなくなってしまうからです。
ある条件は必須でない(オプション)としたい場合は、その条件を表すグラフパターンを{}
でグループ化し、OPTIONAL
キーワードで親パターンに加えます。
例14
PREFIX s: <http://schema.org/>
SELECT * WHERE {
?photo ?p ?o ;
s:location ?place .
OPTIONAL
{
?place s:geo [
s:latitude ?lat ;
s:longitude ?long
] .
}
}
このクエリを使えば、位置情報のない写真については?lat
、?long
の値は空として結果に含んだメタデータが得られます。
DBpediaを検索してみる
SPARQLは標準化されたクエリ言語なので、データセットや提供サイトにかかわりなく利用することができます(SPARQL検索を提供しているURLをSPARQLエンドポイント
と呼びます)。ただし検索のためにはグラフパターンを用いてクエリを構築しなければならないので、データセットがどんなグラフ構造なのかを知っていないと適切なパターンを記述できません。
はじめて利用するSPARQLエンドポイントでクエリを組み立てる方法はいくつかありますが、よく使われるのはデータがどんな型(クラス)として提供されているかをまず調べ、そこを手がかりにしてプロパティを調べていくという手順です。
たとえばDBpedia Japaeseのエンドポイントで得られるグラフを見てみましょう。まず次のクエリで、DBpedia Japaeseはどんな型のデータを提供してくれるのかを調べます。
例15
SELECT DISTINCT ?type WHERE { ?s a ?type }
DISTINCT
は重複する値を一つにまとめるためのキーワードです。これを用いることで、異なるクラスのみが一覧されるようになります。
次に結果の中からひとつの型(クラス)を選び、その型を持つリソースがどんなプロパティによって記述されているのかを調べてみます。ここではhttp://schema.org/Museum
を選んでみましょう。
例16
SELECT DISTINCT ?p WHERE { ?s a <http://schema.org/Museum> ; ?p ?o. }
結果として300近くのプロパティが表示されます。そこからいくつかのプロパティを選んで記述したのが次のクエリです。
例17
PREFIX dbp: <http://ja.dbpedia.org/property/> SELECT * WHERE { ?s a <http://schema.org/Museum> ; dbp:名称 ?name ; dbp:所在地 ?location ; dbp:収蔵作品数 ?numitem ; dbp:竣工 ?open . }
おや? これでは何もデータがヒットしません。美術館メタデータがこれらのプロパティを全て備えているわけではないからですね。提供されないかもしれないプロパティは、OPTIONAL
とする必要があります。
例18
PREFIX dbp: <http://ja.dbpedia.org/property/> SELECT * WHERE { ?s a <http://schema.org/Museum> ; dbp:名称 ?name ; dbp:所在地 ?location . OPTIONAL{ ?s dbp:収蔵作品数 ?numitem . } OPTIONAL{ ?s dbp:竣工 ?open . } }
これで2500件ほどの美術館データが得られます。一度に処理しきれない時は、WHERE句の{}
のあとにLIMIT
を置いて最大取得件数を指定します。取得しきれなかったレコードは、OFFSET
と組み合わせることで続きを取り出すことができます。
例19
PREFIX dbp: <http://ja.dbpedia.org/property/> SELECT * WHERE { ... }LIMIT
100OFFSET
200
結果はHTMLのテーブルとしてだけでなく、XML形式結果フォーマットやJSON形式結果フォーマットでも取得できるので、直接プログラムで処理することも可能です。
RDFグラフ名とデータセット
SPARQLエンドポイントなどのRDFデータベースは、いくつかの種別にわかれたグラフが収録されている場合があります。これらを区別するために、グラフにURIで名前を与える(名前付きグラフ)ことができるようになっています。
たとえば国立国会図書館のWeb NDLAには、個人や団体名、統一タイトル、地名などを含む「名称典拠」と、書籍の主題(キーワード)を示すための「件名典拠」の2つの大きな区分があります。Web NDLAではこの2つを名前付きグラフとし、それぞれにURIを与えています。
SPARQLではGRAPH
キーワードを用いてこのグラフURIを取得することができます。Web NDLAのエンドポイントで次のクエリを実行してみましょう。
例20
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
SELECT * WHERE {
GRAPH
?g {
?auth rdfs:label "コスモス" .
OPTIONAL {
?auth foaf:primaryTopic [ a ?type ] .
}
}
}
?g
の値として得られるのがグラフURIです。「コスモス」というラベルをもつ典拠は4つあり、そのうち3つはhttp://id.ndl.go.jp/auth/ndlna
というグラフ(名称典拠)、1つはhttp://id.ndl.go.jp/auth/ndlsh
というグラフ(件名典拠)に属していることが分かります。
RDF 1.1の仕様では、RDFグラフの集まりであるRDFデータセットの概念を定義しており、SPARQLクエリはこのデータセットを対象に実行されます。RDFデータセットには名前URIのない既定グラフが1つあり、名前付きグラフは任意の数(0以上)含めることができます。既定グラフは空(トリプルがない)でも構わないので、実質的には名前付きグラフだけでデータセットが構成されることもあります。
データの来歴と名前付きグラフ
複数のSPARQLエンドポイントから得られたデータを組み合わせて情報を提供するようなアプリケーションを構築する場合、それぞれのデータをもともとどこから取得したか(来歴)を把握しておくことは重要です。SPARQLクエリの結果はトリプルではなく値の集合となってしまうので、変数名が同じだと元データの区別がつかなくなり、思わぬ矛盾や不整合に行き当たることがあるからです。来歴の確認のためにも、複数エンドポイントを利用する場合はグラフURIも合わせて取得しておくとよいでしょう。
来歴は、SPARQLクエリの結果だけでなくRDFグラフ全般についても重要な情報です。RDFは誰もが何について何を述べてもよい(Anyone can say anything about anything)世界なので、同じURIを主語にしたトリプル/グラフがいろいろなところで記述される可能性があります。これらを集めることでグラフが連結され、ばらばらだった情報がつながるのがRDFの大きな力ですが、来歴をきちんと管理していないと、どの情報(トリプル)が正確なのか、情報が古くなっていないのかといったことが分からなくなってしまうのです。
この来歴管理のため、SPARQLでの検索だけでなくRDFデータそのものにもグラフURIを利用することができます。これについては、回を改めて検討してみます。