ウィキデータ:SPARQLチュートリアル

This page is a translated version of the page Wikidata:SPARQL tutorial and the translation is 76% complete.
Outdated translations are marked like this.

WDQS こと Wikidata Query Service は、Wikidata のコンテンツへの洞察を提供する強力なツールです。このガイドでは WDQS の使い方を説明します。interactive tutorial by Wikimedia Israel も参照してください。

自分で SPARQL クエリを書く前に、{{Item documentation}}やその他の一般的な SPARQL クエリのテンプレートを調べて、その中にすでに書きたいクエリが含まれていないかどうか確認してください。

はじめる前に

このガイドはとても長く威圧的に見えるかもしれませんが、どうか敬遠しないでください。SPARQLの基本を学ぶだけでも、かなりのことができるようになります。#初めてのクエリまでで読むのを止めたとしても、興味深いクエリをたくさん書くための知識としては十分です。さらにこのチュートリアルの各セクションを読めば、よりパワフルなクエリを書くことができるでしょう。

WikidataやSPARQL、WDQSについて、これまでまったく聞いたことがない方のために、これらの用語について簡単に説明しておきましょう。

SPARQLの基本

シンプルなSPARQLクエリは次のようなものです。

SELECT ?a ?b ?c
WHERE
{
  x y ?a.
  m n ?b.
  ?b f ?c.
}

SELECT節にはクエリの結果として得たい変数を列挙します(変数はクエスチョン・マークで始まります)。WHERE節はそれらの変数に対する制約からなり、ほとんどの場合はトリプルの形をとります。Wikidata(および類似の知識データベース)のすべての情報は、トリプルの形で保存されています。クエリが実行されると、クエリサービスは得たいトリプルの制約を満たすような変数の値を知識データベース上に見つけようと試みます。そしてそのような変数の組み合わせが見つかるごとに、それをひとつの結果として返します。

トリプルは主語述語目的語を持つ文のように読むことができます(トリプルがピリオドで終わるのはそのためです)。

SELECT ?fruit
WHERE
{
  ?fruit hasColor yellow.
  ?fruit tastes sour.
}

このクエリの回答は、例えば “lemon” です。Wikidataでは、ほとんどのプロパティは “has” の性質を持つプロパティです。そのためこのクエリは以下のように書いてもいいでしょう。

SELECT ?fruit
WHERE
{
  ?fruit color yellow.
  ?fruit taste sour.
}

これは?fruit has color ‘yellow’ と読めます(?fruit is the color of ‘yellow’ ではありません — parent/child のような対になるプロパティのためにこれは心に留めておいてください)。

しかしながら、WDQSの説明のためには、これはあまりいい例ではありませんでした。taste(〜な味がする)は主観的なので、Wikidataにはこれに対応するプロパティがないのです。代わりに、たいていの場合においては曖昧さのない、parent/child(親/子)の関係について考えてみましょう。

初めてのクエリ

バロック音楽の作曲家、ヨハン・ゼバスティアン・バッハの、すべての子供を列挙したいものと考えてみてください。上のクエリで見たような擬似要素を使うとして、あなたならどんなクエリを書きますか?

こんな感じのクエリを書いたのではないでしょうか。

SELECT ?child
WHERE
{
  #  child "has parent" Bach
  ?child parent Bach.
  # (注:‘#’ より後ろはすべてはコメントで、WDQSには無視されます。)
}

あるいはこんなのや、

SELECT ?child
WHERE
{
  # child "has father" Bach 
  ?child father Bach. 
}

こんなのかもしれませんね。

SELECT ?child
WHERE
{
  #  Bach "has child" child
  Bach child ?child.
}

最初のふたつのトリプルは、?childは parent/father に Bach を持っていなければならない、と言っています。3つめのは、Bach はその子供に?childを持っていなければならない、と言っています。ここでは2つめのを採用することにしましょう。

では、これを適切なWDQSクエリに変換するためには、あと何が必要でしょうか。ウィキデータでは、項目やプロパティを特定するために使われているのは、例えば「父親」(プロパティ)とか、「バッハ」(項目)とかいった、人間にわかりやすい名前ではありません。(これにはちゃんとした理由があります。「ヨハン・ゼバスティアン・バッハ」はドイツの画家の名前でもありますし、「バッハ」は人物のであるだけでなく、フランスの自治体や、火星のクレーターなどの名前でもあり、これらを指しているかもしれないからです。) 代わりに、ウィキデータの項目とプロパティには、識別子が割り当てられています。ある項目の識別子を見つけるには、項目を検索して、(例えば説明文などから)それらしいものが見つかったら、その Q ナンバーをコピーします。プロパティの識別子を見つけるのにも同じことを行いますが、単に検索窓に探している言葉を入力するのではなく、「P:探している言葉」と入力すれば、プロパティに限定して検索を行います。この検索の結果として、あの有名な作曲家のヨハン・ゼバスティアン・バッハは Q1339 であり、そしてある人物の父親を指定するプロパティは P:P22 であることがわかります。

最後に忘れてはいけないのが、接頭辞をつけることです。単純なWDQSトリプルでは、項目にはwd:を、そしてプロパティにはwdt:を、その頭につけなければいけません。(ただしこれは値が決まっているときだけにしてください。変数に接頭辞をつけてはいけません。)

これをまとめると、初めての正しいWDQSクエリが出来上がります。

SELECT ?child
WHERE
{
# ?child  father   Bach
  ?child wdt:P22 wd:Q1339.
}
Try it!

「Try it!」のリンクをクリックすると、WDQSのページでクエリが実行されます。結果はどうなりましたか?

child
wd:Q57225
wd:Q76428

うーんこれはちょっとがっかりですね。識別子しか表示されていません。識別子をクリックすれば該当のWikidataのページを見ることはできるのですが(そこには人にわかりやすいラベルもあるのですが)、検索結果を見るのにもうちょっといい方法はないのでしょうか?

はい、実はこれがあるんですね!(修辞疑問文ってすごいと思いませんか?)次の魔法の一文を WHERE 節のどこかに追加すると、

SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }

するとクエリ中のすべての変数に対して、自動的にひとつの変数が追加されます。例えば?fooという変数には?fooLabelという変数が追加され、この変数には?fooを満たす項目のラベルの値が保持されます。この変数をSELECT節に追加すれば、項目とともにそのラベルも得ることができます。

SELECT ?child ?childLabel
WHERE
{
# ?child  father   Bach
  ?child wdt:P22 wd:Q1339.
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

このクエリを実行してみてください。すると項目番号だけでなく、色々と子供の名前が表示されるはずです。

child childLabel
wd:Q57225 Johann Christoph Friedrich Bach
wd:Q76428 Carl Philipp Emanuel Bach

自動補完

でもこのSERVICEから始まるコードを覚えとくのはしんどそうですよね? それにクエリを書くたびに検索を繰り返すのもうんざりですよね。幸いなことに、WDQSにはこれに対する大いなる解決策、つまり「自動補完」が用意されています。クエリエディタ query.wikidata.org でクエリを書いている途中に、どこででもいいので Ctrl+Space を押すと、そこで入力するのにふさわしいコードの候補が表示されます。そこで適当な候補を up/down の矢印キーで選んでから、Enter キーを押して確定してください。

たとえば、SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }と毎回毎回入力する代わりに、SERVとだけ入力して、Ctrl+Space キーを押せばいいんです。すると一番上の候補に、ラベル用のこの魔法の一文がバッチリ表示されているものと思います。準備は整いました!あとは Enter キーを押してこれを確定させるだけです。(入力されたコードのフォーマットは少しばかり違っているかもしれませんが、問題ありません。)

自動補完では検索も可能です。wd:wdt:のようなWikidataの節頭辞を入力したあとに、続けて何か文字を入力し、Ctrl+Space キーを押すと、Wikidataをこの文字で検索し、検索結果を候補として表示します。wd:なら項目を検索しますし、wdt:ならプロパティを検索します。たとえば、Johann Sebastian Bach (Q1339)father (P22)を検索して探す代わりに、wd:Bachwdt:fathとだけ入力して、表示された候補の中から正しいものを選ぶだけでいいのです。(この機能は、wd:Johann Sebastian Bachのようにテキスト中にスペースがある場合でも有効です。)

より進んだトリプルのパターン

そういうわけで、いまはヨハン・ゼバスティアン・バッハのすべての子供をみたところでした。より具体的にいうと、父親としてヨハン・ゼバスティアン・バッハを持つすべての項目を得たところです。しかしバッハには妻が2人いましたので、これらの項目は2つの異なる母親を持っています。もしヨハン・ゼバスティアン・バッハの、最初の妻であるマリア・バルバラ・バッハ (Q57487) との間の子供だけをみたい場合は、どうしたらいいでしょうか? 上で書いたクエリをもとに、このクエリを書いてみましょう。

できましたか? オッケー、では解答をみていくことにしましょう。いちばんシンプルな方法は、制約に2つめのトリプルを追加することです。

SELECT ?child ?childLabel
WHERE
{
  ?child wdt:P22 wd:Q1339.
  ?child wdt:P25 wd:Q57487.
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

英語に直すと、これは次のように読むことができます。

Child has father Johann Sebastian Bach.

Child has mother Maria Barbara Bach.

少しばかり不自然に聞こえますね。自然言語であれば、これは次のように略されるでしょう。

Child has father Johann Sebastian Bach and mother Maria Barbara Bach.

実は、SPARQLでも同様に、同じ省略を表現することが可能です。ピリオドの代わりにセミコロン (;) でトリプルを終えると、別の述語 ー 目的語の組を追加することができます。これにより上のクエリを次のように略すことが可能です。

SELECT ?child ?childLabel
WHERE
{
  ?child wdt:P22 wd:Q1339;
         wdt:P25 wd:Q57487.
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

結果は同じですが、クエリ中の繰り返しは減らすことができました。

では、ここまでの結果に加えて、作曲家かつピアニストである子供にしか興味がないとしたらどうでしょうか。これに適したプロパティと項目は、occupation (P106)composer (Q36834)、そしてpianist (Q486748)です。

私の解答は次のようになります。

SELECT ?child ?childLabel
WHERE
{
  ?child wdt:P22 wd:Q1339;
         wdt:P25 wd:Q57487;
         wdt:P106 wd:Q36834;
         wdt:P106 wd:Q486748.
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

ここでは;を使った省略が、2つの必要な職業を追加するために、 さらに2回使われています。しかし、お気づきかもしれませんが、ご覧のようにここにもまだ繰り返しがあります。このコードだとあたかもこう言っているかのようです。

Child has occupation composer and occupation pianist.

これを私たちは普通、こう略します。

Child has occupation composer and pianist.

SPARQLにはこのためのシンタックスも同様にあります。;が述語と目的語の組をトリプルに追加することを(主語を再利用することによって)可能にしたのと同じように、,を使うことによって、別の目的語をトリプルに追加することが(主語と述語の双方を再利用することによって)可能になります。これによりクエリは次のように省略することができます。

SELECT ?child ?childLabel
WHERE
{
  ?child wdt:P22 wd:Q1339;
         wdt:P25 wd:Q57487;
         wdt:P106 wd:Q36834,
                  wd:Q486748.
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

注:インデントやその他の空白は、実のところ重要ではありません。クエリがより読みやすくなるように、インデントしただけです。上のクエリは次のように書くことも可能です。

SELECT ?child ?childLabel
WHERE
{
  ?child wdt:P22 wd:Q1339;
         wdt:P25 wd:Q57487;
         wdt:P106 wd:Q36834, wd:Q486748.
  # 両方の職業を1行で
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

あるいは、だいぶ読みにくくはなりますが、次のようなのも可能です。

SELECT ?child ?childLabel
WHERE
{
  ?child wdt:P22 wd:Q1339;
  wdt:P25 wd:Q57487;
  wdt:P106 wd:Q36834,
  wd:Q486748.
  # インデントなし。これは ; と , の識別が難しくなる
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

幸いにもWDQSエディタは自動的に行をインデントしてくれますので、通常はこれを気にする必要はありません。

さあ、ではここまでをまとめてみましょう。クエリは文章のように組み立てられることを見てきました。ある主語についてのトリプルは、それぞれがピリオドで終わります。同じ主語についての複数の述語は、セミコロンで区切られます。そして、同じ主語と述語に対する複数の目的語は、コンマで区切って列挙することが可能です。

SELECT ?s1 ?s2 ?s3
WHERE
{
  ?s1 p1 o1;
      p2 o2;
      p3 o31, o32, o33.
  ?s2 p4 o41, o42.
  ?s3 p5 o5;
      p6 o6.
}

ここでSPARQLで使用可能なもうひとつの省略形を紹介したいと思います。いまいちど仮定のお話におつきあい願うことになりますが…

本当はバッハの子供には関心はなかったとしましょう。(いや、これはひょっとすると、仮定の話ではなかったかもしれませんね!)子供ではなく、その「孫」に(仮定的に)興味があったのでした。ここで厄介な問題がひとつあります。孫は父親がバッハの子供なのかもしれないし、母親がバッハの子供なのかもしれない。どちらでもありうるのです。父親と母親は別々のプロパティなので、これは面倒です。代わりにぐるっと、関係を逆転させてしまいましょう。ウィキデータには「子」というプロパティもあって(P:P40 です)、これは親が主語になって親から子への関係性になるのと同時に、子供の性別を区別しないのです。これを念頭に、バッハの孫を返すクエリを書いてみましょう。書けましたか?

私の解答はこうです。

SELECT ?grandChild ?grandChildLabel
WHERE
{
  wd:Q1339 wdt:P40 ?child.
  ?child wdt:P40 ?grandChild.
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

自然言語に直せば、これは次のように読むことができます。

Bach has a child ?child.

?child has a child ?grandChild.

ここでも、上の文章を短縮することを提案します。その上で、SPARQLではどうしたら同様の省略をすることができるかお見せしたいと思います。子供に関心がない場合には、実際にはどうするか考えてみてください。孫のことを話すときにしか変数を使わない、というのが答えです。そこで上の文は次のように略すことができるはずです。

Bach has as child someone who has a child ?grandChild.

ここではバッハの子供が誰かを言う代わりに、単に “someone” と言いました。それが誰であるかには関心がないからです。しかし someone 'who' と言っておくことで、それについて引き続き言及することができます。つまり関係節を作ることができ、その節の中で “someone” について何かをいうことができるということです。(たとえば、he or she “has a child ?grandChild” という風に。)ある意味で、 “someone” は変数なのです。ただしこの修飾節の中でのみ有効な特別な変数で、さらに、それに明示的に言及することはしない変数です(“someone who is this and does that” とは言いますが、“someone who is this and someone who does that” とは言いません。それだと someone は別々の違う「誰か」になるからです。)

SPARQLでは、これを次のように書くことができます。

SELECT ?grandChild ?grandChildLabel
WHERE
{
  wd:Q1339 wdt:P40 [ wdt:P40 ?grandChild ].
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

変数のあった場所に角括弧の組を使うことができ ([])、これが無名変数として機能します。角括弧の中では述語 - 目的語の組を指定することができ、これは通常のトリプルの後に;を置いた場合と同様です。しかしここでの暗黙的な主語は、角括弧が示すところの無名変数になります。(注:;の後とこれもまた同様に、セミコロンを置けばさらに述語 - 目的語の組を追加することができますし、コンマを置けば述語を共有する目的語をさらに追加することができます。)

これでトリプル・パターンについては終わりです! SPARQLについてさらに見ていきますが、自然言語との類似性が高いパートを終えるに当たって、その関係をもう一度まとめておきたいと思います。

自然言語 SPARQL
Juliet loves Romeo. ピリオド juliet loves romeo.
接続詞(節の) Romeo loves Juliet and kills himself. セミコロン romeo loves juliet; kills romeo.
接続詞(名詞の) Romeo kills Tybalt and himself. コンマ romeo kills tybalt, romeo.
関係節 Juliet loves someone who kills Tybalt. 角括弧 juliet loves [ kills tybalt ].

インスタンスとクラス

先に私は、ほとんどのWikidataプロパティは “has” 関係だといいました。has child(子供を持つ)、has father(父親を持つ)、has occupation(職業を持つ)、といったように。しかし時には(実際のところはしばしば)、それがなんで「ある」か(what something is)を話す必要があります。しかし実はそこにも2種類の関係があるのです。

  • Gone with the Wind is a film.(『風と共に去りぬ』は映画である
  • A film is a work of art.(映画は芸術作品である

『風と共に去りぬ』は一つの特定の映画です。そこには特定の監督(ヴィクター・フレミング)がおり、具体的な上映時間(238分)があり、キャストメンバー表(クラーク・ゲーブル、ヴィヴィアン・リー、…)があり、といった具合です。

一方で「映画」は一般的な概念です。個々の映画は監督や上映時間やキャストを持つことができますが、「映画」という概念それ自体は、どんな特定の監督も上映時間もキャストも持ちません。そして映画は芸術作品であり(a film is a work of art)、芸術作品は通常その作者を持ちますが、「映画」という概念自体は作者を持ちません。この概念の特定の「実例(インスタンス)」のみが、それを持ちます。

この違いゆえに、Wikidataには “is”(〜である)を表すプロパティが2つ、つまり instance of (P31)subclass of (P279) があるのです。『風と共に去りぬ』は「映画」というクラスの特定のインスタンスです。「映画」というクラスは、より一般的なクラスである「芸術作品」の下位クラス(より具体的なクラス。特殊化)です。

この違いをはっきりさせるためには、ふたつの違った動詞を使ってみることが役に立ちます。それは「〜である」と「一種の〜である」です。もし「一種の〜である」を使うことができる場合は(例えば映画は「一種の芸術作品である」のように)、下位クラス、つまりより広いクラスを特殊化したものについて話していることになるので、subclass of (P279)を使うべきです。もし「一種の〜である」が使えない場合は(例えば『風と共に去りぬ』は「一種の映画である」という文章は意味を成しません)、特定の実例について話していることになるので、instance of (P31)を使うべきです。

ではSPARQLクエリを書く際にこれが意味することは何でしょうか。それは、「具体的な個々の芸術作品すべて」を検索したい時には、それが「芸術作品」に直接分類されているものをすべて検索したところで、それだけでは十分ではない、ということです。

SELECT ?work ?workLabel
WHERE
{
  ?work wdt:P31 wd:Q838948. # 芸術作品に分類されているもの
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

私がこれを書いている2016年10月の時点では、上のクエリが返すのは2,815件の結果だけです。実際の芸術作品がこれより多いのは明らかです。問題は、このクエリの結果には、「芸術作品」ではなく「映画」にだけ分類されている『風と共に去りぬ』のような、そういった項目が欠けていることです。「映画」は「芸術作品」の下位クラスではあるのですが、SPARQLで検索するときには、これを考慮に入れて検索するよう明示的に指示しなければなりません。

ひとつの可能な解決策としては、すでに触れた [] 構文を使うことがあります。『風と共に去りぬ』は「芸術作品」の下位クラスである何らかのクラスに分類される、と表現するのです(練習のために実際にこのクエリを書いてみてください)。しかしこれにも問題があります。

  1. 今度は「芸術作品」に直接分類されている項目が含まれなくなります。
  2. また「芸術作品」の下位クラスであってもそれ以外の下位クラスに分類されている項目は含まれないままです。例えば「白雪姫」は「アニメ映画」に分類されていますが、これは「芸術作品」の下位クラスである「映画」のさらに下位クラスです。この場合、「下位クラス」であるという言明を2回繰り返さないと検索で到達することができません。しかし実際はこれは3回でもあり得ますし、4回や5回、いや何回でもあり得ます。

解決策はこうです。 ?item wdt:P31/wdt:P279* ?class ——これが意味するのは、itemはある一つのクラスclassに分類されるが、そのクラスはclassだけでなくその下位クラスであってもよく、さらにその下の下の、どれだけ下のクラスでもあってもよい、ということです。

SELECT ?work ?workLabel
WHERE
{
  ?work wdt:P31/wdt:P279* wd:Q838948. # 芸術作品のあらゆる下位クラスに分類されているもの
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

(このクエリを実行することはお勧めしません。WDQSは(何とかかろうじて)このクエリを処理することはできますが、結果を表示をする際に、件数が多すぎてブラウザが落ちる可能性があるからです。)

というわけで、すべての芸術作品、すべてのビル、すべての居留地を検索する方法がわかりました。魔法の呪文 wdt:P31/wdt:P279* を、適切な分類と一緒に使えばよいのです。このクエリではまだ説明していないSPARQLのワザも少しばかり使っていますが、ごく正直にいえば、それらのワザが役立つのはこの時ぐらいのものです。なのでWDQSを効果的に使うためにこの呪文の働きを理解する必要はありません。知りたい方のためにこの後すぐに説明しますが、でも次のセクションは飛ばして、wdt:P31/wdt:P279* だけ覚えておくか、必要な時にここからコピペしてもよいのです。

プロパティパス

プロパティパスは、ふたつの項目をつなぐ複数のプロパティの経路(パス)を、ごく簡潔に書き記すための方法です。最もシンプルなパスは単一のプロパティからなります。これは通常のトリプル(3つ組)をつくります。

?item wdt:P31 ?class.

パスを構成する要素(プロパティ)を追加するにはスラッシュ (/) を使います。

?item wdt:P31/wdt:P279/wdt:P279 ?class.

これは以下の二つのいずれとも等しくなります。

?item wdt:P31 ?temp1.
?temp1 wdt:P279 ?temp2.
?temp2 wdt:P279 ?class.
?item wdt:P31 [ wdt:P279 [ wdt:P279 ?class ] ].

練習として、先に出てきた「バッハの孫」のクエリを、この構文を使って書き直してみましょう。

アスタリスク (*) をパス要素の後につけると、その要素のゼロ回以上の繰り返しが可能であることを意味します。

?item wdt:P31/wdt:P279* ?class.
# means:
?item wdt:P31 ?class
# or
?item wdt:P31/wdt:P279 ?class
# or
?item wdt:P31/wdt:P279/wdt:P279 ?class
# or
?item wdt:P31/wdt:P279/wdt:P279/wdt:P279 ?class
# or ...

もしパスに他の要素がなければ、?a something* ?b?b?a他の要素を挟まずにすぐ後ろに来ても良いことを表します。

プラス (+) はアスタリスクに似ていますが、1回以上の繰り返しを意味します。次のクエリはバッハの子孫をすべて探します:

SELECT ?descendant ?descendantLabel
WHERE
{
  wd:Q1339 wdt:P40+ ?descendant.
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

もしここでプラスの代わりにアスタリスクを使うと、クエリの結果はバッハ自身も含んでしまいます。

疑問符 (?) はアスタリスクやプラスに似ていますが、0回か1回の繰り返しを意味します。

バーティカルバー (|) をスラッシュの代わりに使ってパスを区切ることもできます。この場合、「または」を意味します。パスはどちらか片方のプロパティを使いますが、組み合わされることはありません。「または」は常に1要素のパスにマッチします。

丸括弧 (()) でパスの要素や今まで紹介してきた構文の要素 (/|*+?) を囲むこともできます。次のようにして、別のやり方でバッハの子孫を探すことができます:

SELECT ?descendant ?descendantLabel
WHERE
{
  ?descendant (wdt:P22|wdt:P25)+ wd:Q1339.
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

「子」のプロパティを使う代わりに、「父」と「母」のプロパティを使いました。このパスは2人の母と1人の父、4人の父、父母母父というように、任意の組み合わせにマッチします。 (もちろん、バッハは誰かの母ではないので、最後の要素は常に父になります。)

修飾子

(最初に良い知らせです。このセクションでは新しく学ぶSPARQLの構文はありません。やったね。深呼吸して落ち着いて臨めば、きっとここは朝飯前です。いいですね?)

今までのところは、主語・プロパティ・目的語といった、単純な文についてのみ話してきました。しかし、ウィキデータの文はそれだけでなく、修飾子 (qualifier) と 出典 (references) を持つことができます。例えば、モナリザ (Q12418) には、made from material (P186)の文が3つあります。

  1. oil paint (Q296955)は、主要な材料です。
  2. poplar wood (Q291034)には、applies to part (P518)painting support (Q861259)の修飾子がついています。これは、モナリザが描かれている面の材質です。
  3. wood (Q287)には、applies to part (P518)stretcher (Q1737943)start time (P580) 1951の修飾子がついています。これは、作品に後で付け足された部分の材料です。

塗装面についての情報を持つすべての絵画作品を求めたいと仮定しましょう。つまり、made from material (P186) 文を持つすべての絵画作品で、かつその文には修飾子 applies to part (P518)painting support (Q861259) があるということです。これにはどうしたらいいでしょうか。単一のトリプルで表現できる以上の情報がそこにはあります。

答えは「トリプルを増やす」です(経験から言えば、Wikidata における解決策のほとんどは「項目を増やす」であり、WDQS でそれに該当するのが「トリプルを増やす」です。出典、数値の精度、単位付きの値、地理座標など、ここでは触れないすべてにおいて、これは当てはまります)。これまで、文のトリプルの接頭辞として wdt: を使ってきました。これは文の目的語を直接指し示します。しかしそれ以外の接頭辞もあります。p: は目的語を指し示すのではなく、「ある文というノード」を指し示します。そしてこのノードが他のトリプルの主語になります。接頭辞 ps: (property statement の略です) は文を目的語として指し示します。接頭辞 pq: (property qualifier) は修飾子を指し示し、prov:wasDerivedFrom は出典というノード(ここでは触れません)を指し示します。

抽象的な説明が続いてしまいました。モナリザを例に具体的な例を示します。

wd:Q12418 p:P186 ?statement1.    # Mona Lisa: material used: ?statement1
?statement1 ps:P186 wd:Q296955.  # value: oil paint

wd:Q12418 p:P186 ?statement2.    # Mona Lisa: material used: ?statement2
?statement2 ps:P186 wd:Q291034.  # value: poplar wood
?statement2 pq:P518 wd:Q861259.  # qualifier: applies to part: painting surface

wd:Q12418 p:P186 ?statement3.    # Mona Lisa: material used: ?statement3
?statement3 ps:P186 wd:Q287.     # value: wood
?statement3 pq:P518 wd:Q1737943. # qualifier: applies to part: stretcher bar
?statement3 pq:P580 1951.        # qualifier: start time: 1951 (pseudo-syntax)

[] シンタックスで ?statement 変数を置き換えることにより、次のように大幅に省略することができます。

wd:Q12418 p:P186 [ ps:P186 wd:Q296955 ].

wd:Q12418 p:P186 [
            ps:P186 wd:Q291034;
            pq:P518 wd:Q861259
          ].

wd:Q12418 p:P186 [
            ps:P186 wd:Q287;
            pq:P518 wd:Q1737943;
            pq:P580 1951
          ].

この知識を活かして、塗装面についての情報を持つすべての絵画作品を求めるクエリを書くことができますか?

私の解答はこうです。

SELECT ?painting ?paintingLabel ?material ?materialLabel
WHERE
{
  ?painting wdt:P31/wdt:P279* wd:Q3305213;
            p:P186 [ ps:P186 ?material; pq:P518 wd:Q861259 ].
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

最初に、?paintingpainting (Q3305213) の実例として、もしくはそのサブクラスとして限定します。次に p:P186 文により素材の情報を抽出します。このとき、その文は、applies to part (P518)painting support (Q861259) 修飾子を持っている文に限定します。

ORDERLIMIT

さて、SPARQLの機能の紹介にもどります。

これまで私達はマッチするすべての結果を取得するクエリを学んできました。ですが、数件だけとってきたいというシチュエーションはよくあるはずです。典型的には、一番突出している値をとってくるケース、例えば最も古い、最も若い、最も早い、最も新しい、最も人口が多い、最も低い融点、最も子供の人数が多い、最もよく使われた物質、などです。これらの共通項は結果が何らかの値で「順位付け」されているということです。したがって私達は一番最初の結果 (順位付けで一番になるような結果) だけ考えたいということです。

これはWHERE {}の中括弧の後に書く2つの節によって達成されます。それがORDER BYLIMITです。

ORDER BY somethingは、somethingによって結果を並び替えます。somethingは任意の式です。今の所、私達が知っているのは単なる変数 (?something) だけですが、あとで他の式も見ることにします。この式はソートの順序を指定するためにASC()DESC()で囲むことができます (それぞれ、昇順 ascending と降順 descending に対応します)。 (もしどちらも指定しなかった場合、デフォルトでは昇順にソートされます。つまり、ASC(something)somethingと一緒です。)

LIMIT countは結果のリストをcount件に制限します。countには任意の自然数を指定することができます。例えば、LIMIT 10は結果を10件に制限し、LIMIT 1は一つの結果を返すようにします。

(ORDER BYをつけずにLIMITを使うこともできます。この場合、結果は並び替えされていないので、マッチする結果の内どのような結果がヒットするかは保証されません。これはあなたがマッチする件数の上限を知っている場合や、個々の結果には興味がなくマッチする中で一部の結果にだけ興味がある場合には気にならないものでしょう。どちらのケースでも、LIMITはクエリの処理時間を大きく縮めます。それは、指定された件数の結果をWDQSが見つけると、検索を止めても良くなるからです。)

エクササイズの時間です!最も人口の多い10の国を返すクエリを作成してみてください。(国はsovereign state (Q3624078)で、人口のプロパティはP:P1082です。)人口のある国を検索することから始めて、ORDER BY句とLIMIT句を追加できます。

私の解答はこうです。

SELECT ?country ?countryLabel ?population
WHERE
{
  ?country wdt:P31/wdt:P279* wd:Q3624078;
           wdt:P1082 ?population.
  SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
}
ORDER BY DESC(?population)
LIMIT 10
Try it!

「最も人口の多い」国が必要な場合は、最初の結果が最も高い値を持つ国になるように、人口の「降順」で順序付ける必要があることに注意してください。

演習

ここまでで基礎の多くをカバーしましたので、ここで練習の時間をとりましょう。(急いでいる場合は、このセクションをスキップできます。)

アーサー・コナン・ドイルの本

サー・アーサー・コナン・ドイルのすべての本を返すクエリーを作成します。

化学元素

すべての化学元素とその元素記号および原子番号を、原子番号順に返すクエリーを作成します。

ミシシッピ川に流れ込む川

ミシシッピ川に直接流れ込むすべての川を返すクエリーを作成します。(主な課題は、正しいプロパティを見つけることです…)

ミシシッピ川に流れ込む川 その2

ミシシッピ川に直接または間接的に流れ込むすべての川を返すクエリを記述します。

OPTIONAL

上記の演習では、サー・アーサー・コナン・ドイルのすべての本を検索しました。

SELECT ?book ?bookLabel
WHERE
{
  ?book wdt:P50 wd:Q35610.
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

しかしこれは少々退屈です。本に関するデータは他にもたくさんあるのに、タイトルだけを表示するなんて。そこでクエリを加工して、title (P1476)illustrator (P110)publisher (P123)、およびpublication date (P577)も含むようにしてみましょう。

最初に書いてみたのはこんな感じになりました。

SELECT ?book ?title ?illustratorLabel ?publisherLabel ?published
WHERE
{
  ?book wdt:P50 wd:Q35610;
        wdt:P1476 ?title;
        wdt:P110 ?illustrator;
        wdt:P123 ?publisher;
        wdt:P577 ?published.
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

クエリーを実行してみます。私がこれを書いた時点では、返ってきた結果は2つだけでした。少々残念な結果ですね。なぜこうなったのでしょうか。前は100冊以上の本が見つかったのに。

その理由は、このクエリに一致するためには、結果(本)がここで列挙したすべてのトリプルに一致する必要があるためです。結果にはタイトル、イラストレーター、出版社、および出版日のすべてが含まれていなければなりません。これらのプロパティの一部が含まれていても、すべてが含まれていなければ、一致しません。しかしそれでは本来ここで欲しかったものにはなりません。まず欲しいのはすべての本のリストなのですから。追加のデータが利用可能であればそれも含めたいのですが、そのことで結果のリストを制限したくはないのです。

解決策は、これらのトリプルが「オプション」であることをWDQSに伝えることです。

SELECT ?book ?title ?illustratorLabel ?publisherLabel ?published
WHERE
{
  ?book wdt:P50 wd:Q35610.
  OPTIONAL { ?book wdt:P1476 ?title. }
  OPTIONAL { ?book wdt:P110 ?illustrator. }
  OPTIONAL { ?book wdt:P123 ?publisher. }
  OPTIONAL { ?book wdt:P577 ?published. }
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

これにより、該当する文が存在する場合は追加の変数(?title?publisherなど)が得られますが、文が存在しない場合でも結果は破棄されません。単に変数が設定されないだけです。

注意:ここでは、個別のOPTIONAL句を使用することが非常に重要です。すべてのトリプルを1つの句に入れると、次のようになります -

SELECT ?book ?title ?illustratorLabel ?publisherLabel ?published
WHERE
{
  ?book wdt:P50 wd:Q35610.
  OPTIONAL {
    ?book wdt:P1476 ?title;
          wdt:P110 ?illustrator;
          wdt:P123 ?publisher;
          wdt:P577 ?published.
  }
  SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
}
Try it!

- ほとんどの結果には追加情報が含まれていません。これは、複数のトリプルを持つオプション句が一致するのは、これらのトリプルをすべて満たすことができる場合のみであるためです。つまり、ブックにタイトル、イラストレーター、発行者および発行日がある場合、オプション句は一致し、これらの値は適切な変数に割り当てられます。ただし、ブックにタイトルがあってもイラストレーターがいない場合などは、オプション句全体が一致せず、結果は破棄されませんが、4つの変数はすべて空のままです。

式、FILTERBIND

このセクションは、かなり広範で多様なトピックを扱っているため、他のセクションよりも少し整理されていないように思われるかもしれません。基本的な概念は、私たちが今まで無差別に選択して返してきた値を使って何かをしたいということです。そして、「式」は、値に対するこれらの操作を表現する方法です。式には多くの種類があり、それを使ってできることはたくさんありますが、まず、基本的なデータ型から始めましょう。

データ型

SPARQLの各値には型があり、その型によって、その値の種類と、その値を使って何ができるかがわかります。最も重要な型は次のとおりです。

  • 項目。Douglas Adams (Q42)に対するwd:Q42など。
  • ブール型。とりうる値はtruefalseの2つです。ブール型の値である真偽値そのものは文には使われませんが、多くの式は真偽値を返します。例えば、2 < 3 (true)や"a" = "b" (false)など。
  • 文字列型。テキストを構成する。文字列リテラルは二重引用符で括って書かれます。
  • 単一言語文字列。言語タグ付きの文字列です。リテラルでは言語タグを文字列の後ろに@記号をつけて付加します。例:"Douglas Adams"@en
  • 数値型。整数(1)もしくは小数(1.23)。
  • 日付型。日付リテラルはISO 8601で書かれた日付文字列に^^xsd:dateTimeを付加することで書くことができます(大文字と小文字を区別します。^^xsd:datetimeは不正です)。例:"2012-10-29"^^xsd:dateTime

演算子

+-*/を使用して数値を加算、減算、乗算、または除算したり、<>=<=>=を使用してそれらを比較したりできます。不等式テスト≠は!=と記述されます。比較は他のタイプにも定義されます。たとえば、"abc"<"abd"は真(字句比較)であり、"2016-01-01"^^xsd:dateTime>"2015-12-31"^^xsd:dateTimeおよびwd:Q4653!=wd:Q283111も同様です。また、ブール条件は&&(論理および:a&&bは、abの両方が真の場合に真)および(論理または:a bは、abのいずれか(または両方)が真の場合に真)と組み合わせることができます。

FILTER

  情報 FILTERに代わるより高速な方法として、MINUSを参照することもできます。exampleを参照してください。

FILTER(condition).は、結果をフィルタリングするためにSPARQLクエリに挿入できる句です。括弧内には、ブール型の任意の式を入れることができ、式がtrueを返す結果のみが使用されます。

たとえば、2015年に生まれたすべての人間のリストを取得するには、まずすべての人間とその生年月日を取得します -

SELECT ?person ?personLabel ?dob
WHERE
{
  ?person wdt:P31 wd:Q5;
          wdt:P569 ?dob.
  SERVICE wikibase:label { bd:serviceParam wikibase:language "en". } 
}

-そして、それをフィルタして、生年月日の年が2015である結果のみを返します。これを行うには、2つの方法があります。YEAR関数を使用して日付の年を抽出し、それが2015であることをテストします-

FILTER(YEAR(?dob) = 2015).

-または、日付が2015年1月1日(を含む)から2016年1月1日(を含まない)の間であることを確認します。

FILTER("2015-01-01"^^xsd:dateTime <= ?dob && ?dob < "2016-01-01"^^xsd:dateTime).

最初の方が簡単だと思いますが、2番目の方がはるかに高速であることがわかったので、それを使用しましょう。

SELECT ?person ?personLabel ?dob
WHERE
{
  ?person wdt:P31 wd:Q5;
          wdt:P569 ?dob.
  FILTER("2015-01-01"^^xsd:dateTime <= ?dob && ?dob < "2016-01-01"^^xsd:dateTime).
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". } 
}
Try it!

FILTERのもう1つの使用法は、ラベルに関連しています。ラベルサービスは、変数のラベルを表示するだけの場合に非常に便利です。しかし、ラベルを使って何かをしたい場合-例えば、「Mr.」で始まるかどうかをチェックしたい場合-それは機能しないことがわかります。

SELECT ?human ?humanLabel
WHERE
{
  ?human wdt:P31 wd:Q15632617.
  SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
  #This FILTER does not work!
  FILTER(STRSTARTS(?humanLabel, "Mr. ")).
}
Try it!

このクエリはfictional human (Q15632617)のすべてのインスタンスを検索し、そのラベルが"Mr."で始まるかどうかをテストします(STRSTARTSは"string starts[with]"の略で、STRENDSCONTAINSもあります)。これが機能しない理由は、ラベルサービスがクエリ評価の非常に遅い段階で変数を追加するためです。?humanLabelでフィルタをかけようとした時点では、ラベルサービスはまだその変数を作成していません。

幸いなことに、ラベルサービスはアイテムのラベルを取得する唯一の方法ではありません。ラベルは、述語rdfs:labelを使用して、通常のトリプルとしても保存されます。もちろん、これは英語のラベルだけでなく、すべてのラベルを意味します。英語のラベルだけが必要な場合は、ラベルの言語に基づいてフィルタする必要があります。

FILTER(LANG(?label) = "en").

LANG関数は単一言語文字列の言語を返します。ここでは、英語のラベルのみを選択します。完全なクエリは次のとおりです。

SELECT ?human ?label
WHERE
{
  ?human wdt:P31 wd:Q15632617;
         rdfs:label ?label.
  FILTER(LANG(?label) = "[AUTO_LANGUAGE]").
  FILTER(STRSTARTS(?label, "Mr. ")).
}
Try it!

?human rdfs:label?labelトリプルを持つラベルを取得し、それを英語のラベルに制限し、それが「Mr.」で始まるかどうかをチェックします。

正規表現でFILTERを使用することもできます。次に例を示します。

SELECT ?item ?itemLabel ?bblid
WHERE {  
    ?item wdt:P2580 ?bblid .
    SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en" }  
    FILTER(!REGEX(STR(?bblid), "[\\.q]")) 
}
Try it!

IDのフォーマット制約が[A-Za-z][-.0-9A-Za-z]{1,}の場合:

SELECT ?item ?itemLabel ?bblid
WHERE {  
    ?item wdt:P2580 ?bblid .
    SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en" }  
    FILTER(!REGEX(STR(?bblid), "^[A-Za-z][-.0-9A-Za-z]{1,}$"))
}
Try it!

このように、特定の要素を除外することができます。

FILTER ( ?item not in ( wd:Q4115189,wd:Q13406268,wd:Q15397819 ) )

フィルタを適用して、入力されていない要素を含めることができます。

FILTER ( NOT EXISTS { ?item  wdt:P21 [] } )


BINDBOUNDIF

これらの3つの機能は、組み合わせて使用されることが多いので、最初に3つすべてを説明し、次にいくつかの例を示します。

BIND(expression AS?variable).句を使用して、式の結果を変数(通常は新しい変数ですが、既存の変数を上書きすることもできます)に割り当てることができます。

BOUND(?variable)は、変数が値にバインドされているかどうかをテストします(trueまたはfalseを返します)。これは主に、OPTIONAL句で導入された変数に役立ちます。

IF(condition,thenExpression,elseExpression)は、conditiontrueと評価された場合はthenExpressionと評価され、conditionfalseと評価された場合はelseExpressionと評価されます。 つまり、IF(true,"yes","no")"yes"と評価され、IF(false,"great","terrible")"terrible"と評価されます。

BINDは、いくつかの計算の結果を新しい変数にバインドするために使用できます。これは、より大きな計算の中間結果である場合もあれば、クエリの直接の結果である場合もあります。例えば、死刑の被害者の年齢を取得するには:

SELECT ?person ?personLabel ?age
WHERE
{
  ?person wdt:P31 wd:Q5;
          wdt:P569 ?born;
          wdt:P570 ?died;
          wdt:P1196 wd:Q8454.
  BIND(?died - ?born AS ?ageInDays).
  BIND(?ageInDays/365.2425 AS ?ageInYears).
  BIND(FLOOR(?ageInYears) AS ?age).
  # or, as one expression:
  #BIND(FLOOR((?died - ?born)/365.2425) AS ?age).
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

BINDは、読みやすさを向上させるために、定数値を変数に単純にバインドするためにも使用できます。たとえば、すべての女性聖職者を検索するクエリは次のようになります。

SELECT ?woman ?womanLabel
WHERE
{
  ?woman wdt:P31 wd:Q5;
         wdt:P21 wd:Q6581072;
         wdt:P106 wd:Q42603.
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

は次のように書き直すことができます:

SELECT ?woman ?womanLabel
WHERE
{
  BIND(wdt:P31 AS ?instanceOf).
  BIND(wd:Q5 AS ?human).
  BIND(wdt:P21 AS ?sexOrGender).
  BIND(wd:Q6581072 AS ?female).
  BIND(wdt:P106 AS ?occupation).
  BIND(wd:Q42603 AS ?priest).
  ?woman ?instanceOf ?human;
         ?sexOrGender ?female;
         ?occupation ?priest.
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

?womanから?priest.までのクエリの意味のある部分は、おそらくより読みやすくなりました。しかし、そのすぐ前にある大きなBINDブロックはかなり気が散るので、このテクニックは控えめに使用されるべきです。(WDQSユーザーインターフェイスでは、wd:Q123またはwdt:P123のような任意の用語の上にマウスを移動して、エンティティのラベルと説明を表示することもできます。したがって、?femaleは、その機能を無視した場合にのみwd:Q6581072よりも読みやすくなります。)

IF式は、BOUNDで構築された条件式とともに使用されることがよくあります。たとえば、一部の人間を表示するクエリがあり、そのラベルを表示するだけでなく、ラベルがある場合はpseudonym (P742)を表示し、ペンネームが存在しない場合にのみラベルを使用するとします。そのためには、OPTIONAL句でペンネームを選択し(オプションである必要があります。ペンネームのない結果をスローアウトしたくはありません)、BIND(IF(BOUND(…)を使用して、ペンネームまたはラベルを選択します。

SELECT ?writer ?label
WHERE
{
  # French writer born in the second half of the 18th century
  ?writer wdt:P31 wd:Q5;
          wdt:P27 wd:Q142;
          wdt:P106 wd:Q36180;
          wdt:P569 ?dob.
  FILTER("1751-01-01"^^xsd:dateTime <= ?dob && ?dob < "1801-01-01"^^xsd:dateTime).
  # get the English label
  ?writer rdfs:label ?writerLabel.
  FILTER(LANG(?writerLabel) = "en").
  # get the pseudonym, if it exists
  OPTIONAL { ?writer wdt:P742 ?pseudonym. }
  # bind the pseudonym, or if it doesn’t exist the English label, as ?label
  BIND(IF(BOUND(?pseudonym),?pseudonym,?writerLabel) AS ?label).
}
Try it!

この方法で使用できるその他のプロパティには、nickname (P1449)posthumous name (P1786)、およびtaxon common name (P1843)が含まれます。これらは、何らかの「フォールバック」が意味をなすものです。

BOUNDFILTERと組み合わせて、いくつかのOPTIONALブロックの少なくとも1つが満たされていることを確認することもできます。たとえば、月に行ったすべての宇宙飛行士と、Apollo 13 (Q182252)のメンバー(十分に近いですよね?)を取得しましょう。この制限は単一のプロパティパスとして表現できないため、「月のミッションのメンバー」に対して1つのOPTIONAL節と、「アポロ13のメンバー」に対して別の節が必要です。ただし、これらの条件のうち少なくとも1つが真である結果のみを選択します。

SELECT ?astronaut ?astronautLabel
WHERE
{
  ?astronaut wdt:P31 wd:Q5;
             wdt:P106 wd:Q11631.
  OPTIONAL {
    ?astronaut wdt:P450 ?mission.
    ?mission wdt:P31 wd:Q495307.
  }
  OPTIONAL {
    ?astronaut wdt:P450 wd:Q182252.
    BIND(wd:Q182252 AS ?mission).
  }
  FILTER(BOUND(?mission)).
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

COALESCE

COALESCE関数は、前述のフォールバックのBIND(IF(BOUND(?x),?x,?y)AS?z).パターンの省略形として使用できます。この関数はいくつかの式を受け取り、エラーなしで評価された最初の式を返します。 たとえば、上記の「pseudonym」フォールバック

BIND(IF(BOUND(?pseudonym),?pseudonym,?writerLabel) AS ?label).

より簡潔に書くことができます。

BIND(COALESCE(?pseudonym, ?writerLabel) AS ?label).

また、?writerLabelが定義されていない場合に別のフォールバックラベルを追加することも簡単です。

BIND(COALESCE(?pseudonym, ?writerLabel, "<no label>") AS ?label).

グループ化

これまで見てきたクエリはすべて、何らかの条件を満たすすべての項目を求めるクエリでした。場合によっては、項目に関する文(絵画についてその画材、アーサー・コナン・ドイルの本についてそのタイトルとイラストレーター、など)を追加しました。

しかしすべての結果を網羅した長いリストは不要なことも珍しくありません。代わりに必要なのは、次のような質問に対する答えかもしれないのです。

  • カンバス、ポプラの木…等に描かれた絵画作品の数はいくつか
  • 各国ごとにその国内の都市のうちで人口が最も多いのはどの都市か
  • 各メーカごとにその社で作られる銃の合計は何挺か
  • 平均して最も長い本を出版している出版社はどこか

都市の人口

ここでは、2番目の質問を見てみましょう。すべての都市とその人口および国を国順にリストするクエリを作成するのは、非常に簡単です。

SELECT ?country ?city ?population
WHERE
{
  ?city wdt:P31/wdt:P279* wd:Q515;
        wdt:P17 ?country;
        wdt:P1082 ?population.
}
ORDER BY ?country
Try it!

(注:このクエリは「多くの」結果を返すため、ブラウザに問題を引き起こす可能性があります。LIMIT句を追加することをお勧めします。)

結果を国ごとに並べ替えているので、ある国に属するすべての都市は、結果の中で1つの連続したブロックを形成します。そのブロック内で最も高い人口を見つけるために、ブロックを「グループ」と考え、グループ内のすべての人口値を1つの値、つまり最大値に「集約」したいと思います。これは、WHEREブロックの下のGROUP BY句と、SELECT句の集約関数(MAX)で行われます。

SELECT ?country (MAX(?population) AS ?maxPopulation)
WHERE
{
  ?city wdt:P31/wdt:P279* wd:Q515;
        wdt:P17 ?country;
        wdt:P1082 ?population.
}
GROUP BY ?country
Try it!

ORDER BYGROUP BYに置き換えました。これにより、同じ?countryを持つすべての結果が1つの結果にグループ化されます。これは、SELECT句も変更する必要があることを意味します。前のままの句SELECT ?country ?city ?populationにしていた場合、どの?city?populationが返されるのでしょうか。ひとつのグループには多くの結果が含まれることを忘れないでください。それらはすべて同じ?countryを持つので、それを選択することはできますが、?city?populationに関しては、結果ごとに異なる可能性があります。WDQSにこれらの値の中からどれを選択するかを指示する必要があるのです。これが「集約関数」の仕事です。この例ではMAXを使用しました。この関数は、すべての?population値の中から、グループごとに最大の値を選択してくれます。(この値にはAS構文で新しい名前を付ける必要がありますが、これは細かい話になります。)

これがグループクエリを記述するための一般的なパターンです。まず必要なデータ(グループ化されておらず、「グループ」ごとに多くの結果がある)を返す通常のクエリを記述してください。次にGROUP BY句を追加します。最後に、SELECT句の変数のうち、グループ化されないすべての変数に集約関数を追加してください。

画材

別の質問で試してみましょう。絵画の作品数をそこで使われた画材別に知りたいとしたら、どのようなクエリを書けばよいでしょうか。最初に、単にすべての絵画作品を、その作品の画材とともに返すクエリを作成します(made from material (P186)文にapplies to part (P518)painting support (Q861259)修飾子をつけたものだけを使用するように注意してください)。

SELECT ?material ?painting
WHERE
{
  ?painting wdt:P31/wdt:P279* wd:Q3305213;
            p:P186 [ ps:P186 ?material; pq:P518 wd:Q861259 ].
}
Try it!

次に、?materialGROUP BY句を適用し、さらにSELECT句でそれ以外の変数(?painting)に集約関数を適用します。ここでは絵画の作品数に関心がありますので、集約関数にCOUNTを使用します。

SELECT ?material (COUNT(?painting) AS ?count)
WHERE
{
  ?painting wdt:P31/wdt:P279* wd:Q3305213;
            p:P186 [ ps:P186 ?material; pq:P518 wd:Q861259 ].
}
GROUP BY ?material
Try it!

ここでひとつ問題なのは、画材のラベルが取得されていないために、結果の解釈に少し不便であることです。以下のように単にラベル変数を追加しただけでは、エラーが発生します。

SELECT ?material ?materialLabel (COUNT(?painting) AS ?count)
WHERE
{
  ?painting wdt:P31/wdt:P279* wd:Q3305213;
            p:P186 [ ps:P186 ?material; pq:P518 wd:Q861259 ].
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
GROUP BY ?material
Try it!

Bad aggregate

"Bad aggregate"は、グループクエリを操作するときにおそらく頻繁に遭遇するエラーメッセージです。これが意味するのは、SELECT句の変数のひとつに集約関数が必要なのに存在しないか、または集約関数を期待されていない変数に集約関数があるかの、いずれかです。今回の場合は、WDQSは、?materialのグループごとに?materialLabelの値は複数存在するかもしれないと考えます(そうではないことは私たちにはわかっているのですが)。そのため、この変数に適用すべき集約関数が指定されていないよと苦情を述べているのです。

ひとつの解決策は、複数の変数にまたがってグループ化してしまうことです。GROUP BY句に複数の変数を列挙した場合、それらの変数の組み合わせがひとつのグループになるので、それに対して返される結果に含まれるこれら変数の値はひとつに決まります。このため、これらの変数はすべて、集約関数なしでSELECT句に指定することができるようになります。今回の場合は、?material?materialLabelのふたつを以下のようにグループ化します。

SELECT ?material ?materialLabel (COUNT(?painting) AS ?count)
WHERE
{
  ?painting wdt:P31/wdt:P279* wd:Q3305213;
            p:P186 [ ps:P186 ?material; pq:P518 wd:Q861259 ].
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
GROUP BY ?material ?materialLabel
Try it!

これでほぼクエリは出来上がりですが、もう1つだけ改善点があります。最もよく使用されている画材が最初に表示されるようにしたいということです。幸いなことに、SELECT句で新たに集約された変数(ここでは?count)は、ORDER BY句で使うことが許されています。そのため、改善するのは以下のように非常に簡単です。

SELECT ?material ?materialLabel (COUNT(?painting) AS ?count)
WHERE
{
  ?painting wdt:P31/wdt:P279* wd:Q3305213;
            p:P186 [ ps:P186 ?material; pq:P518 wd:Q861259 ].
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
GROUP BY ?material ?materialLabel
ORDER BY DESC(?count)
Try it!

練習として、別のクエリもやってみましょう。

メーカー別の銃

各メーカーが生産している銃のメーカーごとの総数はいくつでしょうか?

ページ数別の出版社

各出版社が出版している本の出版社ごとの平均ページ数は何ページでしょうか?(平均にはAVG関数が使えます)

HAVING

最後のクエリに関して補足です。実際にクエリを実行してみると、トップの出版社の平均ページ数は、2番目の出版社の10倍以上という法外な数字であることに気づくと思います。少し調べてみるとその訳がわかるのですが、この出版社(UTET (Q4002388))が出版している本でページ数の文(number of pages (P1104))があるのは、1冊だけ(Grande dizionario della lingua italiana (Q3775610))しかありません。このことが結果を少しばかり歪めているのです。このような外れ値を取り除くために、少なくとも2冊以上、ページ数の文(number of pages (P1104))のある本を出版している出版社だけを、Wikidataから選ぶようにしてみてはどうでしょうか。

そのようにするにはどうしたらいいでしょうか? 通常、結果に限定を加えるためにはFILTER節を使います。しかし今回はひとつひとつの結果に基づいてではなく、グループ化された結果(本の冊数)に基づいて限定を加えたいのです。 これを実現するには、 GROUP BY節のすぐ後にHAVING節を置き、FILTERの時と同じように式を取ればよいのです。

SELECT ?publisher ?publisherLabel (AVG(?pages) AS ?avgPages)
WHERE
{
  ?book wdt:P123 ?publisher;
        wdt:P1104 ?pages.
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
GROUP BY ?publisher ?publisherLabel
HAVING(COUNT(?book) > 1)
ORDER BY DESC(?avgPages)
Try it!

集約関数の概要

以下は集約関数(集計関数)の簡単な概要です。

  • COUNT: 要素の数。単に結果の数を知りたい場合はCOUNT(*)と書くことができます。
  • SUM, AVG: すべての要素の合計(SUM)と平均(AVG)。要素は数値でないと意味のある結果は得られません。
  • MIN, MAX: すべての要素の最小値(MIN)と最大値(MAX)。これらはすべてのデータ型で有効です。数値の場合は数の大小で、文字列とその他のデータ型の場合は辞書順でソートします。
  • SAMPLE: 任意の要素を返します。これは結果がひとつだけだとわかっている場合、もしくはどれもよいので結果をひとつ得られればよい場合に便利です。
  • GROUP_CONCAT: すべての要素を連結します。例えば、その項目に対して複数の文があるようなプロパティ、例えばある人物の職業などの情報を、ひとつの結果に丸ごと含めて得たい場合などに便利です。それぞれの職業は再グループ化して連結されるので、結果には複数の記述としてではなく、ただ1つの変数として表すことができます。より詳しく知りたい方は、SPARQL specificationを調べてみてください。

加えてこれらすべての関数において、結果の重複を除外するためにDISTINCT修飾子を付加することができます。例えば結果が2件返ってきたが?var変数の値がいずれも同じだった場合、COUNT(?var)2を返しますが、COUNT(DISTINCT ?var)1を返します。クエリが同じ項目を複数回返す可能性があるときは、DISTINCTを使わなければならないことが多いでしょう。例えばクエリに?item wdt:P31/wdt:P279* ?classという文があり、かつ?itemから?classへ複数のパスが存在する場合が、そのような例になります。このときそれぞれのパスから新しく結果が返されますが、これらすべての結果の値は同一です。なおクエリでグループを使用していない場合は、クエリを単なるSELECTではなく、SELECT DISTINCTから始めることによっても、結果の重複を除外することができます。

wikibase:ラベルと集約

2つ以上の国で市民権を持つすべての学者をWikidat内に検索するような、以下のようなクエリでは、これらの国の名前は?citizenships列に表示されません。

select ?person ?personLabel (group_concat(?citizenshipLabel;separator="/") as ?citizenships) {
  # find all academics
  ?person wdt:P106 wd:Q3400985 ;   
          wdt:P27  ?citizenship .
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". }
} group by ?person ?personLabel having (count(?citizenship) > 2)
Try it!

?citizenshipsを表示するためには、次のように、明示的にこれら?personLabelと?citizenshipLabelの名前をwikibase:label サービスの呼び出しで挙げなければなりません。

  SERVICE wikibase:label { 
    bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". 
    ?citizenship rdfs:label ?citizenshipLabel .
    ?person      rdfs:label ?personLabel .
  }

以下のクエリは期待通りの動作をします。

select ?person ?personLabel (group_concat(?citizenshipLabel;separator="/") as ?citizenships) {
  ?person wdt:P106 wd:Q3400985 ;
          wdt:P27  ?citizenship .
  SERVICE wikibase:label { 
    bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". 
    ?citizenship rdfs:label ?citizenshipLabel .
    ?person      rdfs:label ?personLabel .
  }
} group by ?person ?personLabel having (count(?citizenship) > 2)
Try it!

VALUES

以下のように項目のリストに基づいて項目を選ぶことができます。

SELECT ?item ?itemLabel ?mother ?motherLabel WHERE {
  VALUES ?item { wd:Q937 wd:Q1339 }
  OPTIONAL { ?item wdt:P25 ?mother. }
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". }
}
Try it!

また特定のプロパティの値のリストに基づいて項目を選ぶこともできます。

SELECT ?item ?itemLabel ?mother ?motherLabel ?ISNI WHERE {
  VALUES ?ISNI { "000000012281955X" "0000000122764157" }
  ?item wdt:P213 ?ISNI.
  OPTIONAL { ?item wdt:P25 ?mother. }
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". }
}
Try it!

VALUESはさらに、一対の変数(または三つ組みの変数)に対して、とりうる値の列挙を構築することもできます。例えばこの節の最初の « value » 例で列挙された人物に対して、(既知の)カスタムラベルを使用したいものとしましょう。これは « values » 節を次のように使用することによって可能です。VALUES (?item ?customItemLabel) { (wd:Q937 "Einstein") (wd:Q1339 "Bach") } これによって、常に?itemが結果においてwd:Q937という値を持ち、?customItemLabel自体の値はEinsteinであることが保証され、同時に常に?itemは結果においてwd:Q1339という値を持ち?customItemLabelの値はBachであることが保証されます。

SELECT ?item ?customItemLabel ?mother ?motherLabel WHERE {
  VALUES (?item ?customItemLabel) { (wd:Q937 "Einstein") (wd:Q1339 "Bach") }
  OPTIONAL { ?item wdt:P25 ?mother. }
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". }
}
Try it!

さらには…

このガイドはここで終了です。しかしSPARQLはこれで終了ではありません。まだここでお示ししていないことがたくさんあります。これが完璧なガイドになると約束はしなかったはずです。ここまでを習得すれば、すでにWDQSについては多くのことを知っていて、とても強力なクエリを書くことができるに違いありません。しかしそれでもさらに学びたいのならば、以下に参考にできるものをいくつか挙げておきます。

  • サブクエリ。波括弧を使えば、別にもうひとつ完全なクエリを加えることができます({ SELECT ... WHERE { ... } LIMIT 10 }のように)。その結果は外部クエリ中に一覧されます。(SQLに馴染みがあるのなら、この概念については少しばかり再考する必要があるでしょう。SPARQLサブクエリは純粋に「ボトムアップ」なものであり、SQLの相関サブクエリで可能なようには、外部クエリからの値を使うことはできません。)
  • MINUSは何らかのグラフパターンに合致「しない」結果を選択することを可能にします。FILTER NOT EXISTSにほぼ等しいですが(違いの例についてはSPARQL specを参照してください)、少なくともWDQSにおいては、 通常かなり遅くなります。

上記を含めたその他のトピックについてはSPARQL specificationを主に参照してください。

また、WikibooksのSPARQL tutorialdata.worldによるこのチュートリアルも参照してください。

もちろん、それでもまだWikidataの一部が欠けています。例えば、参照、数値精度 (100±2.5)、単位付きの値 (2キログラム)、地理座標、サイトリンク、プロパティ宣言、などです。これらがどうトリプルとしてモデル化されているかはmw:Wikibase/Indexing/RDF Dump Formatを参照してください。

関連項目