SPARQLって結局何なの?実際にRDFデータに問い合わせしてみて勉強しよう!

0. この記事の目的

「SPARQL」に関して書いてある記事等には、こういうコードが、特に説明もなく登場したりしますよね。

PREFIX saas: <https://example.com/ontology/saas#>

SELECT ?workspace ?workspaceName
WHERE {
    <https://example.com/data/tenant-abc>
        saas:containsWorkspace ?workspace .

    ?workspace saas:name ?workspaceName .
}

PREFIXSELECTWHERE?workspace。見た感じSQLに似ている気もしますが、確信が持てないまま読み進めるのは、なかなかつらいものです。

この記事は、その手前の部分を埋めるための記事です。SPARQLだけに絞って、それが何であり、どうやって実際に手元で動かすのかを、説明します!

※この記事は、RDF・RDFS・OWL・Turtleについて説明済みの前提で書きます。まだの方は、先にRDF・RDFS・OWL・Turtleの違いを整理して、ファイルで理解するオントロジー入門を読んでおいてください。


1. SPARQLとは何か

一言でいうと、SPARQLは次のようなものです。

SPARQL:RDFグラフに問い合わせるための検索言語

正式名称はSPARQL Protocol and RDF Query Languageです。読み方は「スパークル」です。

プログラミングやデータベースに触れたことがある方には、次のたとえが分かりやすいと思います。

SQL    :テーブルに対して問い合わせる
SPARQL :RDFグラフに対して問い合わせる

RDF・RDFS・OWL・Turtleの違いを整理して、ファイルで理解するオントロジー入門で説明した技術を、もう一度並べてみます。

RDF
    情報をトリプルとして表す

RDFS・OWL
    データが何を意味するかを定義する

SHACL
    データが期待する形になっているかを検証する

SPARQL
    RDFグラフから、必要な情報を取り出す

RDFで情報を表し、RDFS・OWLで意味を定義し、SHACLで形を検証し、SPARQLで情報を取り出す。この記事は、最後のSPARQLの部分を担当します。


2. SPARQLも、Turtleに似た書き方をする

SPARQLのクエリは、Turtleのトリプルパターンとよく似た見た目をしています。これは偶然ではなく、意図的にそう設計されています。

ex:yamada ex:worksFor ex:abc .

これは、Turtleで書いた、確定した事実です。

?employee ex:worksFor ex:abc .

これは、SPARQLで書いた、問い合わせの条件です。

違いは、主語の部分がex:yamadaという具体的な値ではなく、?employeeという変数になっている点だけです。

実際のデータ
    ex:yamada ex:worksFor ex:abc .

問い合わせの条件
    ?employee ex:worksFor ex:abc .

?で始まる名前は変数です。「ここに当てはまる値を教えてほしい」という、いわば穴あき問題です。


3. 最初のクエリを書く

SPARQLのSELECTクエリは、次の3つの部分でできています。

PREFIX ex: <https://example.com/company#>

SELECT ?employee
WHERE {
    ?employee a ex:Employee .
}

順番に見ていきます。

PREFIX ex: <https://example.com/company#>

これは、Turtleの@prefixと同じ役割です。ex:を、このIRIの省略形として使う、という宣言です。書き方が少し違うだけで(@が無い、末尾に.が無い)、意味はまったく同じです。

SELECT ?employee

?employeeという変数の値を、結果として返してください」という指定です。

WHERE {
    ?employee a ex:Employee .
}

?employeeが、ex:Employeeという型を持つ、というパターンに一致するものを探してください」という条件です。

aは、rdf:typeの省略記法です。これもTurtleと同じ書き方です。

このクエリは、日本語にすると次のような問い合わせです。

Employeeという型を持つものを、すべて教えてください

4. データを用意する

具体的な例で確認するために、次のデータを用意します。RDF・RDFS・OWL・Turtleの違いを整理して、ファイルで理解するオントロジー入門の山田・鈴木・ABC社に、もう1人、田中を加えます。

# data.ttl
@prefix ex: <https://example.com/company#> .

ex:yamada a ex:Employee ;
    ex:employeeId "E001" ;
    ex:worksFor ex:abc ;
    ex:manages ex:suzuki .

ex:suzuki a ex:Employee ;
    ex:employeeId "E002" ;
    ex:worksFor ex:abc .

ex:tanaka a ex:Employee ;
    ex:employeeId "E003" ;
    ex:worksFor ex:abc .

ex:abc a ex:Organization .

山田はABC社に勤務し、鈴木を管理しています。鈴木と田中も、同じくABC社に勤務しています。


5. 具体例でクエリを書く

5.1 山田の勤務先を取得する

PREFIX ex: <https://example.com/company#>

SELECT ?org
WHERE {
    ex:yamada ex:worksFor ?org .
}

主語(ex:yamada)と述語(ex:worksFor)を固定し、目的語だけを変数にしています。結果は、ex:abcです。

5.2 ABC社に勤務する全員を取得する

今度は逆に、主語を変数にします。

PREFIX ex: <https://example.com/company#>

SELECT ?employee
WHERE {
    ?employee ex:worksFor ex:abc .
}

このクエリは、「ex:worksFor ex:abcという関係の、主語側に来るものをすべて教えてください」という意味です。データを逆方向にたどっていることになります。

RDFのトリプルには、あらかじめ決まった「たどってよい方向」はありません。主語を固定して目的語を探すこともできますし、目的語を固定して主語を探すこともできます。

5.3 複数の条件をつなげる

山田が管理している人の、社員番号を取得したいとします。1つのトリプルパターンだけでは表せません。

PREFIX ex: <https://example.com/company#>

SELECT ?subordinateId
WHERE {
    ex:yamada ex:manages ?subordinate .
    ?subordinate ex:employeeId ?subordinateId .
}

WHEREの中に、2つのトリプルパターンが並んでいます。

1つ目:ex:yamada ex:manages ?subordinate .
2つ目:?subordinate     ex:employeeId ?subordinateId .

1つ目のパターンで?subordinateに当てはまった値が、2つ目のパターンの主語としてそのまま使われます。同じ変数名を使うことで、2つのパターンが連動します。

SQLに慣れている方には、これがJOINに相当する、と考えると分かりやすいと思います。1つ目のパターンで?subordinateex:suzukiに決まり、2つ目のパターンによってex:suzukiemployeeIdである"E002"が引き出されます。


6. 実際に問い合わせを実行してみる

ここまでは、クエリの書き方の説明でした。ここからは、実際にこのクエリを動かしてみます。

6.1 準備

pip install rdflib 'pyparsing>=3.1'

rdflibは、RDFをPythonで扱うための、最も基本的なライブラリです。これをインストールすると、sparqlqueryというコマンドラインツールも一緒に使えるようになります。

6.2 データとクエリをファイルに保存する

4.のデータをdata.ttlに、5.1のクエリをquery.rqに保存します。

# query.rq
PREFIX ex: <https://example.com/company#>

SELECT ?org
WHERE {
    ex:yamada ex:worksFor ?org .
}

6.3 実行する

同じフォルダにdata.ttlquery.rqを置いた状態で、次のコマンドを実行します。

sparqlquery data.ttl -qf query.rq -f csv

-qfのあとにクエリファイル、-f csvで結果をCSV形式(見やすい表形式)で出力するよう指定しています。

結果は、次のように表示されます。

org
https://example.com/company#abc

1行目が列名(SELECTで指定した変数名)、2行目以降が実際の値です。この出力は、実際にこの記事のとおりに実行して得られたものです。

6.4 もう1つ試してみる

query.rqの内容を、5.2のクエリに書き換えて、もう一度実行してみます。

# query.rq
PREFIX ex: <https://example.com/company#>

SELECT ?employee
WHERE {
    ?employee ex:worksFor ex:abc .
}
ORDER BY ?employee
sparqlquery data.ttl -qf query.rq -f csv
employee
https://example.com/company#suzuki
https://example.com/company#tanaka
https://example.com/company#yamada

鈴木・田中・山田の3人が、ABC社に勤務している全員として返ってきました。


7. DISTINCTとORDER BY

SPARQLのクエリには、DISTINCTORDER BYもよく登場します。どちらも、SQLと同じ意味です。

ORDER BY ?変数
    結果を、その変数の値で並べ替える

DISTINCT
    同じ組み合わせの結果が複数出たとき、重複を1つにまとめる

ORDER BYは、先ほどの6.4のクエリでも使いました。?employeeのIRI文字列の順番(アルファベット順)で並んでいたのは、ORDER BY ?employeeを指定していたためです。

DISTINCTは、次のように使います。

PREFIX ex: <https://example.com/company#>

SELECT DISTINCT ?org
WHERE {
    ?employee ex:worksFor ?org .
}

SELECTのすぐ後ろにDISTINCTを書きます。3人の社員が同じex:abcに勤務しているため、DISTINCTが無ければex:abcが3回、あれば1回だけ返ります。手元で両方を実行して、違いを確認してみてください。


8. もう少し複雑な問い合わせについて

ここまでで説明したのは、SELECTWHERE・トリプルパターンの組み合わせ・ORDER BYDISTINCTという、SPARQLの中でも基本的な部分です。

さらに発展的なクエリには、次のような機能も登場します。

OPTIONAL
    無くてもエラーにせず、あれば結果に含める

FILTER
    値を比較して、条件に合わないものを除外する

たとえば、OPTIONALは次のように使います。

SELECT ?employee ?org
WHERE {
    ?employee a ex:Employee .
    OPTIONAL { ?employee ex:worksFor ?org . }
}

これは、「Employeeはすべて返すが、worksForが書かれていない場合は、?orgを空(未束縛)のままにする」という意味です。OPTIONALを使わずに?employee ex:worksFor ?org .を普通に書くと、worksForが無いEmployeeはそもそも結果に出てきません。

FILTERは、次のように使います。

SELECT ?employee ?id
WHERE {
    ?employee ex:employeeId ?id .
    FILTER (?id != "E001")
}

これは、「社員番号が"E001"ではない人だけを返す」という意味です。

この記事では、OPTIONALFILTERの説明までは踏み込みません。ここで大事なのは、次の1点だけです。

基本のSELECT/WHEREだけでは、
「無い場合の扱い」や「値による絞り込み」を
うまく表現できない場面がある。

そのために OPTIONAL と FILTER がある。

これだけ知っておけば、OPTIONALFILTERが出てきても、「ああ、無い場合の扱いや絞り込みをしているんだな」と読めるはずです。


まとめ

最後に、この記事の内容を整理します。

SPARQLとは
    RDFグラフに問い合わせるための検索言語

PREFIX
    TurtleのPREFIXと同じ、IRIの省略形の宣言

SELECT ?変数
    結果として返す変数を指定する

WHERE { トリプルパターン }
    ?変数を含むトリプルパターンで、条件を書く

同じ変数名でつなげる
    複数のトリプルパターンを連動させる(SQLのJOINに近い)

ORDER BY / DISTINCT
    結果の並べ替え / 重複の除去

OPTIONAL / FILTER
    より複雑な問い合わせのための機能(この記事では扱わない)

実際に問い合わせを動かす手順は、次の3ステップでした。

1. pip install rdflib 'pyparsing>=3.1'
2. データを data.ttl に、クエリを query.rq に保存する
3. sparqlquery data.ttl -qf query.rq -f csv を実行する

ここまで読めば、突然出てくるSPARQLの例も、初見のコードではなくなっているはずです。ぜひ、ハマってしまった記事に戻って読んでみてください!なんとなくわかるようになってる気がします!

こちらからは以上です。