SPARQLとは

SPARQL(SPARQL Protocol and RDF Query Language、読み方は「スパークル」)は、RDFデータに対するクエリ言語です。W3Cによって標準化されており、現在の最新バージョンはSPARQL 1.1です。

リレーショナルデータベースに対するSQLと同様に、SPARQLはRDFデータストア(トリプルストア)に対するクエリ言語として機能します。RDFグラフの中から特定のパターンに合致するデータを検索し、抽出するためのパワフルなツールです。

SPARQLクエリは、SPARQLエンドポイント(HTTPでアクセスできるSPARQLの実行環境)に対して送信します。多くのLODデータセットがSPARQLエンドポイントを公開しており、Webブラウザ上のクエリエディタからインタラクティブにクエリを実行できます。

筆者はこれまで、Wikidata、ジャパンサーチ、Dydra、Virtuosoなど、さまざまなSPARQLエンドポイントに対してクエリを実行してきました。本章では、これらの実践経験をもとに、SPARQLの基本構文を体系的に解説します。

最初のSPARQLクエリ

まず、最もシンプルなSPARQLクエリを見てみましょう。以下はWikidata Query Service(https://query.wikidata.org/)で実行できるクエリです。

SELECT ?item ?itemLabel
WHERE {
  ?item wdt:P31 wd:Q6256 .
  SERVICE wikibase:label { bd:serviceParam wikibase:language "ja,en" . }
}
LIMIT 10

このクエリは「国(Q6256)のインスタンスであるアイテムを10件取得する」という意味です。各要素を分解して見ていきましょう。

  • SELECT ?item ?itemLabel: 取得したい変数を指定する。? で始まる単語が変数
  • WHERE { ... }: 検索条件(グラフパターン)を記述するブロック
  • ?item wdt:P31 wd:Q6256 .: 「?itemのインスタンス(P31)が国(Q6256)である」というトリプルパターン
  • SERVICE wikibase:label { ... }: Wikidata固有のラベル取得サービス
  • LIMIT 10: 結果を10件に制限する

SELECT文の基本構造

SPARQLのSELECTクエリは、以下の基本構造を持ちます。

PREFIX prefix: <URI>

SELECT 変数リスト
WHERE {
  トリプルパターン
}
修飾子(LIMIT, ORDER BY, OFFSET 等)

PREFIX宣言

SPARQLでもTurtleと同様にプレフィックス宣言が使えます。ただし、構文が少し異なります。Turtleでは @prefix:(コロン)を使いますが、SPARQLでは PREFIX キーワードを使い、末尾のピリオドは不要です。

PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX dcterms: <http://purl.org/dc/terms/>
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX schema: <http://schema.org/>

変数

SPARQLでは、? または $ で始まる単語が変数として扱われます。一般的には ? が使用されます。

SELECT ?name ?birthDate
WHERE {
  ?person foaf:name ?name .
  ?person schema:birthDate ?birthDate .
}

トリプルパターン

WHERE句の中に記述するトリプルパターンは、Turtleのトリプルと同じ構造ですが、URIやリテラルの位置に変数を使うことができます。

# 固定のトリプルパターン(結果が存在するかどうかの確認に使う)
WHERE {
  <http://dbpedia.org/resource/Tokyo> rdfs:label ?label .
}

# 変数を含むトリプルパターン
WHERE {
  ?city rdf:type <http://dbpedia.org/ontology/City> .
  ?city rdfs:label ?label .
  ?city <http://dbpedia.org/ontology/country> <http://dbpedia.org/resource/Japan> .
}

RDFストアの基本的な情報取得

SPARQLクエリを書く際、まず対象となるRDFストアにどのようなデータが格納されているかを把握することが重要です。筆者はRDFストアのトリプル数を数えるで、トリプル数をカウントする基本的なクエリを紹介しています。

# トリプル総数を数える
SELECT (COUNT(*) AS ?count)
WHERE {
  ?s ?p ?o .
}

さらに、述語(プロパティ)ごとのトリプル数や主語・目的語の共起関係を把握することで、データの構造を理解できます。この手法についてはRDFストアのトリプル数を数える2: 共起頻度で詳しく解説しています。

DBpediaでの実践クエリ

DBpediaのSPARQLエンドポイント(https://dbpedia.org/sparql)で試せるクエリ例を紹介します。

日本の都市を取得する

PREFIX dbo: <http://dbpedia.org/ontology/>
PREFIX dbr: <http://dbpedia.org/resource/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>

SELECT ?city ?label
WHERE {
  ?city rdf:type dbo:City .
  ?city dbo:country dbr:Japan .
  ?city rdfs:label ?label .
  FILTER (lang(?label) = "ja")
}
LIMIT 20

このクエリのポイントは以下のとおりです。

  • ?city rdf:type dbo:City: 変数 ?city の型が dbo:City(都市)であるもの
  • ?city dbo:country dbr:Japan: その都市の国が日本であるもの
  • FILTER (lang(?label) = "ja"): ラベルの言語が日本語であるものだけに絞り込む

FILTER

FILTER は、検索条件に追加の制約を加えるために使います。トリプルパターンだけでは表現できない条件(文字列の比較、数値の範囲、正規表現など)を指定できます。

比較演算子

# 人口が100万以上の都市
SELECT ?city ?label ?population
WHERE {
  ?city rdf:type dbo:City .
  ?city dbo:country dbr:Japan .
  ?city rdfs:label ?label .
  ?city dbo:populationTotal ?population .
  FILTER (lang(?label) = "ja")
  FILTER (?population > 1000000)
}
ORDER BY DESC(?population)

文字列関数

# ラベルに「山」を含むリソース
SELECT ?item ?label
WHERE {
  ?item rdfs:label ?label .
  ?item rdf:type dbo:Mountain .
  FILTER (lang(?label) = "ja")
  FILTER (CONTAINS(?label, "山"))
}
LIMIT 20

正規表現

# 「〜寺」で終わるラベルを持つ寺院
SELECT ?temple ?label
WHERE {
  ?temple rdf:type dbo:BuddhistTemple .
  ?temple rdfs:label ?label .
  FILTER (lang(?label) = "ja")
  FILTER (REGEX(?label, "寺$"))
}
LIMIT 20

主要なフィルター関数

関数説明
CONTAINS(str, pattern)文字列に部分文字列が含まれるかFILTER(CONTAINS(?label, "東京"))
STRSTARTS(str, prefix)文字列が指定接頭辞で始まるかFILTER(STRSTARTS(?label, "日本"))
STRENDS(str, suffix)文字列が指定接尾辞で終わるかFILTER(STRENDS(?label, "大学"))
REGEX(str, pattern)正規表現にマッチするかFILTER(REGEX(?label, "^東.*区$"))
lang(literal)リテラルの言語タグを取得FILTER(lang(?label) = "ja")
STR(term)URIやリテラルを文字列に変換FILTER(CONTAINS(STR(?uri), "tokyo"))
BOUND(var)変数に値がバインドされているかFILTER(BOUND(?date))
isIRI(term)IRIかどうかを判定FILTER(isIRI(?obj))
isLiteral(term)リテラルかどうかを判定FILTER(isLiteral(?obj))
YEAR(date)日付から年を抽出FILTER(YEAR(?date) > 1900)

OPTIONAL

OPTIONAL は、「存在すれば取得するが、存在しなくても結果から除外しない」パターンを指定するために使います。SQLのLEFT JOINに相当する機能です。

PREFIX dbo: <http://dbpedia.org/ontology/>
PREFIX dbr: <http://dbpedia.org/resource/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>

SELECT ?person ?name ?birthDate ?deathDate
WHERE {
  ?person rdf:type dbo:Writer .
  ?person dbo:birthPlace dbr:Japan .
  ?person rdfs:label ?name .
  FILTER (lang(?name) = "ja")
  ?person dbo:birthDate ?birthDate .
  OPTIONAL { ?person dbo:deathDate ?deathDate . }
}
ORDER BY ?birthDate
LIMIT 20

このクエリでは、作家の死亡日(dbo:deathDate)は OPTIONAL で指定されています。死亡日のデータがない作家(存命の作家など)も結果に含まれ、?deathDate は空(unbound)になります。

DISTINCT

DISTINCT は、重複する結果行を除去します。RDFデータでは同じ情報が異なる言語で記述されている場合があるため、重複除去が必要になることがよくあります。

SELECT DISTINCT ?city ?label
WHERE {
  ?city rdf:type dbo:City .
  ?city dbo:country dbr:Japan .
  ?city rdfs:label ?label .
  FILTER (lang(?label) = "ja")
}

ORDER BY

ORDER BY は、結果を指定した変数の値で並べ替えます。ASC()(昇順)と DESC()(降順)を指定できます。デフォルトは昇順です。

# 人口の多い順に並べ替え
SELECT ?city ?label ?population
WHERE {
  ?city rdf:type dbo:City .
  ?city dbo:country dbr:Japan .
  ?city rdfs:label ?label .
  ?city dbo:populationTotal ?population .
  FILTER (lang(?label) = "ja")
}
ORDER BY DESC(?population)
LIMIT 10

LIMIT / OFFSET

LIMIT は取得する結果の件数を制限し、OFFSET は先頭からスキップする件数を指定します。ページネーションの実装に使えます。

# 最初の10件
SELECT ?item ?label
WHERE { ... }
LIMIT 10
OFFSET 0

# 次の10件(11件目〜20件目)
SELECT ?item ?label
WHERE { ... }
LIMIT 10
OFFSET 10

SPARQLエンドポイントは、デフォルトで結果数に上限を設けている場合が多いため、大量の結果が予想される場合は LIMIT を明示的に指定して、段階的にデータを取得するのが良い習慣です。

COUNT と集約

結果の件数を数えたい場合は、COUNT 集約関数を使います。

# 日本の都市の数をカウント
SELECT (COUNT(?city) AS ?count)
WHERE {
  ?city rdf:type dbo:City .
  ?city dbo:country dbr:Japan .
}

(COUNT(?city) AS ?count) は、?city にバインドされた値の数を数え、その結果を ?count という変数名で返すことを意味します。

BIND

BIND は、式の結果を新しい変数にバインド(代入)するために使います。

PREFIX dbo: <http://dbpedia.org/ontology/>
PREFIX dbr: <http://dbpedia.org/resource/>

SELECT ?person ?name ?birthYear ?age
WHERE {
  ?person rdf:type dbo:Writer .
  ?person dbo:birthPlace dbr:Japan .
  ?person foaf:name ?name .
  ?person dbo:birthDate ?birthDate .
  BIND(YEAR(?birthDate) AS ?birthYear)
  BIND(2024 - YEAR(?birthDate) AS ?age)
  FILTER(lang(?name) = "ja")
}
ORDER BY ?birthYear
LIMIT 20

クエリの実行方法

Webベースのクエリエディタ

多くのSPARQLエンドポイントは、Webブラウザから直接クエリを実行できるインターフェースを提供しています。

また、筆者が公開しているSnorqlを使えば、複数のSPARQLエンドポイントを手軽に切り替えて探索できます。詳しくはSnorql — 複数の SPARQL エンドポイントを手軽に探索できるブラウザ UI を公開しましたをご覧ください。

YASGUIも広く使われているSPARQLエディタです。筆者はジャパンサーチのSPARQLエンドポイントをYasguiで使ってみるメディア芸術データベースのSPARQLエンドポイントをYasguiで使ってみるで、YASGUIの活用方法を紹介しています。

curlコマンドによるアクセス

プログラムからSPARQLエンドポイントにアクセスする場合は、HTTPリクエストを送信します。

# Wikidata SPARQL エンドポイントにクエリを送信
curl -G https://query.wikidata.org/sparql \
  --data-urlencode "query=SELECT ?item ?itemLabel WHERE { ?item wdt:P31 wd:Q6256 . SERVICE wikibase:label { bd:serviceParam wikibase:language \"ja\" . } } LIMIT 5" \
  -H "Accept: application/sparql-results+json"

Pythonからのアクセス

PythonではSPARQLWrapperライブラリを使うと便利です。筆者はGoogle Colabを用いたジャパンサーチRDFストアに対するSPARQLの実行例で、Google ColabからSPARQLWrapper を使ってジャパンサーチにクエリを実行する方法を詳しく解説しています。

from SPARQLWrapper import SPARQLWrapper, JSON

sparql = SPARQLWrapper("https://query.wikidata.org/sparql")
sparql.setQuery("""
    SELECT ?item ?itemLabel
    WHERE {
      ?item wdt:P31 wd:Q6256 .
      SERVICE wikibase:label { bd:serviceParam wikibase:language "ja,en" . }
    }
    LIMIT 10
""")
sparql.setReturnFormat(JSON)
results = sparql.query().convert()

for result in results["results"]["bindings"]:
    print(result["itemLabel"]["value"])

grlcを使ったAPI化

SPARQLクエリをGitHubリポジトリに置いてREST APIとして公開できるgrlcという仕組みもあります。筆者はgrlc (git repository linked data API constructor)を試すで、ジャパンサーチのSPARQLクエリをAPI化する方法を紹介しています。

まとめ

本章では、SPARQLの基本構文を学びました。重要なポイントを振り返ります。

  • SPARQLはRDFデータに対するクエリ言語で、SQLに似た構文を持つ
  • SELECT文で取得する変数を指定し、WHERE句にトリプルパターンを記述する
  • FILTERで文字列比較、数値比較、正規表現などの追加条件を指定できる
  • OPTIONALで「存在すれば取得」するパターンを指定できる
  • ORDER BYLIMITOFFSETで結果の並べ替えとページネーションが可能
  • BINDで計算結果を新しい変数に代入できる
  • SPARQLエンドポイントにHTTPリクエストを送ることで、プログラムからもクエリを実行できる

次章では、サブクエリ、プロパティパス、連合クエリなど、SPARQLの高度な機能を学びます。

関連記事