1. 程式人生 > >RDF查詢語言SPARQL

RDF查詢語言SPARQL

對知識圖譜有興趣的讀者可以關注我的知乎專欄,主要介紹知識圖譜的相關概念、技術,也包含一些具體實踐。

前面我們已經介紹過了語義網技術棧中的RDF,RDFS/OWL。這次我們介紹最後一個核心技術標準——SPARQL(RDF,OWL和SPARQL稱為語義網的三大核心技術)。RDF本質上是一種資料模型,那麼我們如何在RDF上進行查詢呢?類似使用SQL查詢關係資料庫,我們使用SPARQL查詢RDF格式的資料。本文先簡單介紹一下SPARQL的歷史,然後結合我們實踐篇的資料舉幾個具體的例子。

一、SPARQL

SPARQL即SPARQL Protocol and RDF Query Language的遞迴縮寫,專門用於訪問和操作RDF資料,是語義網的核心技術之一。W3C的RDF資料存取小組(RDF Data Access Working Group, RDAWG)對其進行了標準化。在2008年,SPARQL 1.0成為W3C官方所推薦的標準。2013年釋出了SPARQL 1.1。相對第一個版本,其支援RDF圖的更新,提供更強大的查詢,比如:子查詢、聚合操作(像我們常用的count)等等。

從SPARQL的全稱我們可以知道,其由兩個部分組成:協議和查詢語言。

  1. 查詢語言很好理解,就像SQL用於查詢關係資料庫中的資料,XQuery用於查詢XML資料,SPARQL用於查詢RDF資料。
  2. 協議是指我們可以通過HTTP協議在客戶端和SPARQL伺服器(SPARQL endpoint)之間傳輸查詢和結果,這也是和其他查詢語言最大的區別。

一個SPARQL查詢本質上是一個帶有變數的RDF圖,以我們之前提到的羅納爾多RDF資料為例:

<http://www.kg.com/person/1> <http://www.kg.com/ontology/chineseName> "羅納爾多·路易斯·納薩里奧·德·利馬"
^^string.

我們把屬性值用變數代替(SPARQL中,用問號加變數名的方式來表示一個變數。),即:

<http://www.kg.com/person/1> <http://www.kg.com/ontology/chineseName> ?x.

SPARQL查詢是基於圖匹配的思想。我們把上述的查詢與RDF圖進行匹配,找到符合該匹配模式的所有子圖,最後得到變數的值。就上面這個例子而言,在RDF圖中找到匹配的子圖後,將”羅納爾多·路易斯·納薩里奧·德·利馬”和“?x”繫結,我們就得到最後的結果。簡而言之,SPARQL查詢分為三個步驟:

  1. 構建查詢圖模式,表現形式就是帶有變數的RDF。
  2. 匹配,匹配到符合指定圖模式的子圖。
  3. 繫結,將結果繫結到查詢圖模式對應的變數上。

二、例子

以實踐篇的RDF電影資料為例,我們介紹如何利用SPARQL查詢:

  1. 所有的RDF三元組。
  2. 周星馳出演了哪些電影?
  3. 英雄這部電影有哪些演員參演?
  4. 鞏俐參演的評分大於7的電影有哪些?

如何查詢所有資料?參照我們在第一個部分介紹的查詢過程,查詢所有資料即我們沒有任何已知值,SPO三元組每個都是未知變數。對應的SPARQL查詢語言為:

PREFIX : <http://www.kgdemo.com#>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX owl: <http://www.w3.org/2002/07/owl#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX vocab: <http://localhost:2020/resource/vocab/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX map: <http://localhost:2020/resource/#>
PREFIX db: <http://localhost:2020/resource/>

SELECT * WHERE {
  ?s ?p ?o
}

SPARQL的部分關鍵詞:

  • SELECT, 指定我們要查詢的變數。在這裡我們查詢所有的變數,用*代替。
  • WHERE,指定我們要查詢的圖模式。含義上和SQL的WHERE沒有區別。
  • FROM,指定查詢的RDF資料集。我們這裡只有一個圖,因此省去了FROM關鍵詞。
  • PREFIX,用於IRI的縮寫。

下面是該語句的部分查詢結果:

s                    p             o

db:genre/12 [http]  :genreName  "冒險"
db:genre/12 [http]  rdf:type    :Genre
db:genre/14 [http]  :genreName  "奇幻"
db:genre/14 [http]  rdf:type    :Genre
db:genre/16 [http]  :genreName  "動畫"
db:genre/16 [http]  rdf:type    :Genre
db:genre/18 [http]  :genreName  "劇情"
db:genre/18 [http]  rdf:type    :Genre
db:genre/27 [http]  :genreName  "恐怖"
db:genre/27 [http]  rdf:type    :Genre

“周星馳出演了哪些電影”:

PREFIX : <http://www.kgdemo.com#>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX owl: <http://www.w3.org/2002/07/owl#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX vocab: <http://localhost:2020/resource/vocab/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX map: <http://localhost:2020/resource/#>
PREFIX db: <http://localhost:2020/resource/>

SELECT ?n WHERE {
  ?s rdf:type :Person.
  ?s :personName '周星馳'.
  ?s :hasActedIn ?o.
  ?o :movieTitle ?n
}

部分結果:

n

"功夫"
"琉璃樽"
"英雄本色"
"少林足球"
"西遊記第壹佰零壹回之月光寶盒"
"長江七號"
"西遊記大結局之仙履奇緣"
"建國大業"
"審死官"
"龍在天涯"
"大內密探零零發"

就我們這個例子而言,可以不要“?s rdf:type :Person”,這裡只是讓查詢圖更具體(在擁有複雜關係的RDF圖中,可能會存在不同的類擁有相同的屬性名。比如,貓和狗名字的屬性名都是”name”,我們想查詢一隻叫湯姆的貓;如果不指定型別,返回結果可能也包含一隻叫湯姆的狗)。圖模式中,每個RDF用英文句號進行分割。

“英雄這部電影有哪些演員參演”:

PREFIX : <http://www.kgdemo.com#>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX owl: <http://www.w3.org/2002/07/owl#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX vocab: <http://localhost:2020/resource/vocab/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX map: <http://localhost:2020/resource/#>
PREFIX db: <http://localhost:2020/resource/>

SELECT ?n WHERE {
  ?s rdf:type :Movie.
  ?s :movieTitle '英雄'.
  ?a :hasActedIn ?s.
  ?a :personName ?n
}

結果:

n

"李連杰"
"梁朝偉"
"張曼玉"
"章子怡"
"甄子丹"

“鞏俐參演的評分大於7的電影有哪些”:

PREFIX : <http://www.kgdemo.com#>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX owl: <http://www.w3.org/2002/07/owl#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX vocab: <http://localhost:2020/resource/vocab/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX map: <http://localhost:2020/resource/#>
PREFIX db: <http://localhost:2020/resource/>

SELECT ?n WHERE {
  ?s rdf:type :Person.
  ?s :personName '鞏俐'.
  ?s :hasActedIn ?o.
  ?o :movieTitle ?n.
  ?o :movieRating ?r.
FILTER (?r >= 7)
}

結果:

n

"2046"
"Memoirs of a Geisha"
"荊軻刺秦王"
"大紅燈籠高高掛"
"霸王別姬"
"活著"
"唐伯虎點秋香"
"秋菊打官司"
"菊豆"
"Hong gao liang"
"畫魂"
"風月"
"Piao Liang Ma Ma"
"The Hand"

這裡我們用到了FILTER關鍵詞,可以對變數取值進行約束。

SPARQL更詳細的語法和功能這裡就不再多做介紹。讀者可以參考W3C的文件或者SPARQL查詢的例子,也有專門的書來講解SPARQL 1.1(Learning SPARQL: Querying and Updating with SPARQL 1.1)

另外多提一點,關於知識圖譜,有一個非常重要的概念,即開放世界假定(Open-world assumption,OWA)。這個假定的意思是當前沒有陳述的事情是未知的,或者說知識圖譜沒有包含的資訊是未知的。怎麼理解?首先我們要承認知識圖譜無法包含所有完整的資訊。以我們這個電影資料的例子而言,很明顯,它的資料十分殘缺。即使我們擁有一個十分完整的電影知識圖譜,包含了當下所有的電影、演員等資訊,在現實世界中,資訊也是動態變化和增長的。即,我們要承認知識圖譜的資訊本身就是殘缺的。有了這個前提,我們來思考例子中的第二個SPARQL語句:

周星馳出演了上述查詢結果中的電影。基於我們構建的電影知識圖譜,提問:周星馳出演了《臥虎藏龍》嗎?根據OWA,我們得到的答案是“不知道”,相反,如果是封閉世界假定(Closed-world assumption),我們得到的答案是“沒有出演”。

我們在設計本體和開發相關應用的時候需要考慮開放世界假定。舉個簡單的例子,基於知識圖譜的問答系統,使用者提問“周星馳出演了《臥虎藏龍》嗎?”,合適的回答是“不知道”而不是“沒有出演”。直覺上這和一個人向另一個人提這個問題一樣,如果我們知道問題答案,我們會給出肯定的回答,不知道的話,我們往往傾向於回覆“我不知道”,“我不太清楚”,“我查檢視”,而不是信誓旦旦地回答“沒有出演”。畢竟,大多數人都有“自知之明”,知道自己總有不瞭解的東西。從這個角度上說,人和知識圖譜類似,我們都存在於OWA的世界中。

三、總結

本文主要介紹了SPARQL及其基本用法,希望能讓讀者對SPARQL有個初步的認識。下一篇文章是實踐篇,介紹如何利用D2RQ建立SPARQL endpoint並在我們的資料上進行相關查詢。

參考資料