SPARQL 提供了四种不同形式的查询:SELECT、ASK、DESCRIBE 和 CONSTRUCT。我将通过几个查询来说明每种查询类型的不同形式、各种语法技巧、变化形式和查询的用途。这些查询有很多共性。在大多数情况下,我都使用 SELECT 形式来介绍,因为这可能是最常用的查询类型。
SELECT 查询形式用于标准查询。以标准 SPARQL XML 结果格式返回查询结果。本节中多数查询都要用到 SELECT 查询。ASK 的结果是 yes/no,没有具体内容,后面的 清单 33 将会展示。DESCRIBE 用于提取本体和实例数据的一部分。CONSTRUCT 根据查询图的结果生成 RDF,在 下载 部分的代码中可以找到一些例子。
为了节约空间,本教程中的例子将只保留前两个结果,除非必须看到全部内容。多数查询返回的结果都多于两个。如果您痛恨那些罗罗嗦嗦的手册和书籍,我和您一样。本教程会尽量避免这种情况。本教程中重复的一个地方是查询的前缀。查询应该能够立即使用,因此它们必须是自成体系的。您可以直接将其拖到查询表单中执行。
清单 22 中的查询获取所有的注释并按照时间先后返回。这一节我们看看典型的 SPARQL 查询语法。下一节 将讨论三元组库用于查找和查询匹配的三元组的算法。
清单 22. 按日期顺序返回注释的查询
PREFIX : <http://aabs.purl.org/ont/journal#> SELECT ?notes WHERE { ?e a :JournalEntry . ?e :notes ?notes . ?e :date ?date . } ORDER BY ?date |
每个 SPARQL SELECT 查询都包括一组按顺序排列的部分。第一部分是序言,包括可选的 BASE 定义和一些前缀定义。其后的 SELECT 部分以 SELECT
开始,描述搜索哪个图的可选的数据集部分,后面用 WHERE 子句表达描述目标结果的图模式。WHERE 子句之后是一些结果修饰符:Order 子句、Limit 子句或者 Offset 子句。这些修饰符将在后面介绍。
结果如清单 23 所示。
清单 23. 清单 22 的查询结果
notes "Today I wrote some more content for the great new SPARQL tutorial ... "Today I learnt about insane asylums" |
要使返回结果按倒序排列,可通过选择顺序修饰符 ASC()
、DESC()
实现。下面的命令可以使结果按相反的顺序出现:ORDER BY DESC(?date)
。
如果还想在日期排序中根据其他变量排序,可以在 ORDER BY 子句中添加其他变量:ORDER BY DESC(?date) ASC(?notes)
。
如果需要把结果限制到前 5 个,可使用 LIMIT 运算符(如清单 24 所示)。
清单 24. 限制返回的结果数量
SELECT ?notes WHERE { ?e a :JournalEntry . ?e :notes ?notes . ?e :date ?date . } ORDER BY ?date LIMIT 5 |
如果准备跳过某些结果,可使用 OFFSET 修饰符(如清单 25 所示)。
清单 25. 跳过部分结果
SELECT ?notes WHERE { ?e a :JournalEntry . ?e :notes ?notes . ?e :date ?date . } ORDER BY ?date LIMIT 5 OFFSET 150 |
如果进行过 SQL 开发,应该很熟悉这些修饰符。
使用图模式获取结果的过程非常简单。多数三元组库都在内存或者数据库中保存三元组,可按照主语、谓语和宾语查询。SPARQL 在查询图模式中用一组三元组表示。首先假设图模式中只有一个三元组。查询可能提供具体的主语 URI。这样的话,三元组库可以忽略没有该主语的所有三元组。然后再筛选掉与图模式提供 的谓语不匹配的所有三元组。最后,如果 SPARQL 提供了具体的宾语,还可进一步排除不匹配的三元组。
如果查询的主语、谓语或宾语中使用了变量,则不排除那些不匹配的三元组,三元组库将其全部保留,因为可能和变量匹配。上例中第一个三元组是 ?e a :JournalEntry .
。a
是 rdfs:type
的简写,因此三元组库可以忽略所有谓语不是 rdfs:type
的三元组。然后再筛选掉宾语不是 :JournalEntry
的三元组。余下的就是可以作为 ?e
结果的三元组。
上述查询的图模式包含多个三元组,因此三元组库在完成之前还需要对其他三元组做同样的处理。如果一个变量出现在多个位置,则可以使用所有那些值相同的三元组的交集。上例中所有匹配的三元组必须有一个匹配的主语。如果不符合,则丢弃,结果中只保留剩下的三元组。变量匹配可能有多种方式,所以会有多个结果。三元组库的最后一步是根据结果集需要的变量创建结果集。
在搜索的最后,三元组库将得到包含 ?e
、?notes
、?date
的结果集,这些都是查询中定义的变量。如果 SELECT 查询的形式为 “SELECT ?date ?notes”,则不返回 ?e,虽然在查询中很重要。
上述过程是实际过程的简化形式。有多种方法可以加快这个过程(比如一次进行多项匹配)。完成的任务将是一样的。
清单 22 中的查询可以创建一个简单的微型博客,按照日期顺序显示注释。和 twitter 差不多。这个查询采用相同的形式,按照日期顺序返回所有的预订列表。如果准备组织新的项目团队,这样的查询是必需的,以便筛掉已经预定的雇员。
在清单 26 中,必须连接多个类的数据:EmployeeBooking
、Customer
和 User
。SQL 表示连接表的语法非常笨拙。所幸的是 SPARQL 不会这样。它根本就是为这类查询而设计的,因此非常简单。您需要定义一个图匹配模式,定义需要返回的每个类的属性。在三元组世界中,连接的结果是另一个三元组。清单 26 展示了这个查询。
清单 26. 按日期返回所有的预约信息
PREFIX u: <http://aabs.purl.org/ont/users#> PREFIX j: <http://aabs.purl.org/ont/journal#> PREFIX b: <http://aabs.purl.org/ont/avail#> SELECT ?dn ?custName ?startDate ?endDate WHERE { ?booking a b:EmployeeBooking ; b:startDate ?startDate ; b:endDate ?endDate ; b:employee ?dl ; b:with ?cust . ?cust a b:Customer ; b:name ?custName . ?emp a u:User ; u:domainLogin ?dl ; u:displayName ?dn . } ORDER BY ?startDate |
首先定义了三个名称空间前缀:u
、j
和 b
,分别用于用户、日志和预约。然后告诉 Joseki 您需要和变量 ?dn、?custName、?startDate 以及 ?endDate 的匹配。?dn 仅仅是 “display name” 的简写形式,也是查询运行后包含的内容。
在基本的图模式中定义了预约(称为 ?booking)及其起始和结束日期(?startDate 和 ?endDate)。意思是说在 b:EmployeeBooking
类型的图中定义了一些实例。只要发现 b:Employeebooking
类型的匹配,就一定有 b:startDate
和 b:endDate
类型的属性。与这些属性匹配的内容可以放在变量 ?startDate 和 ?endDate 中。
查询结果定义规定必须取得雇员的 u:displayName
和与其联系的客户的 b:name
。查询必须定义谁以及这些属性是什么,以便获得正确的匹配。因此定义 ?emp 和 ?cust 实例及其属性。查询只需要将其链接起来,以引入将预约、用户和客户类的实例链接到一起的三元组。EmployeeBooking
的对象属性 b:employee
链接一个特定的用户实例。它的 b:with
属性则链接到 Customer
类。
添加 'b:with ?cust'
三元组(主语仍然是 ?booking),这可以告诉 SPARQL 只需要返回和 ?booking 返回结果匹配的 ?cust(如表 1 所示)。这就是 SPARQL 中的连接。:User
类也是如此。
表 1. 与 ?booking 匹配的 ?cust 的连接结果
dn | custName | startDate | endDate |
---|---|---|---|
"Andrew Matthews" | "IBM" | "2008-03-01T09:00:00" ^^xsdt:dateTime | "2008-03-08T09:00:00" ^^xsdt:dateTime |
"John Connor" | "IBM" | "2008-03-01T09:00:00" ^^xsdt:dateTime | "2008-03-08T09:00:00" ^^xsdt:dateTime |
连接很简单吧?只需要声明两个类的实例之间存在关系,SPARQL 只返回存在这种关系的匹配。
这个查询和前面的查询非常相似。清单 27 中的查询使用了默认前缀,因为它只处理一个本体中的 URI。因为可以假定该类和对象属性的 URI,因此可以使用分号(:)作为前缀。
清单 27. 使用默认前缀的查询
PREFIX : <http://aabs.purl.org/ont/users#> SELECT DISTINCT ?dl ?dn WHERE { ?u a :User ; :domainLogin ?dl ; :displayName ?dn . } ORDER BY ?dn |
表 2 显示了 清单 27 中查询的结果。
表 2. 默认前缀查询的结果
dl | dn |
---|---|
"someDomain/andrew.matthews" | "Andrew Matthews" |
"someDomain/john.connor" | "John Connor" |
"someDomain/john.doe" | "John Doe" |
"someDomain/sarah.connor" | "Sarah Connor" |
作为对公司内其他雇员的一项服务,可以按照作者附加到日志条目上的标签进行聚合。从而为 RDF 或者 Java 开发提供专门的流。希望了解和 RDF 有关的工作的用户可以订阅这样的流,从而得到量身定做的提要。
这个查询很有意思,因为它引入了 FILTER 关键字。允许用表达式来定义匹配变量的属性。该查询假设您只对那些包含关键字 SPARQL 的注释感兴趣。
清单 28 中的查询仅仅取得所有日志条目的注释部分 — 不想看到大量的文本。这是使用 FILTER 关键字的第一个查询。筛选提供了一种非图形化的方法来定义匹配结果特性。下面的例子使用正则表达式匹配包含单词 SPARQL 的条目。
清单 28. 检索注释中包含单词 “today” 的所有日志条目
PREFIX : <http://aabs.purl.org/ont/journal#> SELECT ?notes WHERE { ?e a :JournalEntry . ?e :notes ?notes . FILTER regex(?notes, "today") } ORDER BY ?date |
该查询使用了默认名称空间前缀分号(:)。该查询仅指一个本体中定义的实体,因此谓语和类型没有歧义。FILTER 表达式使用正则表达式说明需要包含单词 SPARQL 的日志条目。结果有两个,如清单 29 所示。
清单 29. 结果
notes "Went to work on the SPARQL tutorial today. I seem to have a terminator fixation." "Today I wrote some more content for the great new SPARQL tutorial that I've been preparing. I used some Turtle, and I defined a simple ontology for defining journal entries. This is an example of one of those entries!" |
正则表达式是可用的函数和运算符之一。它来自 XPath 和 XQuery 系统。FILTER 表达式支持中缀和前缀运算符以及括号的一般语法,因此清单 30 也是有效的。
清单 30. 使用 FILTER 限制图匹配的更多例子
FILTER (?t = "RDF" || ?t = "OWL" || ?t = "cash") FILTER ( (?start > "2008-03-05T09:00:00"^^xsdt:dateTime && ?start < "2008-03-07T09:00:00"^^xsdt:dateTime)|| (?start < "2008-03-05T09:00:00"^^xsdt:dateTime && ?end > "2008-03-05T09:00:00"^^xsdt:dateTime) ) |
运算符、函数的完整列表以及类型匹配请参阅 SPARQL 文档。
如果需要组建一个团队(这也是本教程中的例子),需要一份具备将要使用的技术知识的人员清单。要找到这些人,需要提供要寻找的所有技能或技术,看看谁和这些技能匹配。这个查询的意义在于它使用了 UNION 运算符。UNION 可以合并单独查询的结果。可以用它将每种技术的查询结果合并起来。
这个例子获取编写的日志条目中标记有 RDF、OWL 或 SPARQL 的所有用户的显示名称。UNION 运算符允许合并可替换的图模式的结果。
一般来说,返回结果前必须匹配所有的图模式。但是 UNION 只需要满足任何一个子图模式即可。在清单 31 所示查询中,任何能够匹配 {?e j:tag "RDF".}
、{?e j:tag "OWL".}
或 {?e j:tag "SPARQL".}
的结果都是可接受的。它相当于 OR 运算符,后面将看到 || 运算符也能实现同样的效果。
清单 31. 获取具备匹配技能的用户
PREFIX u: <http://aabs.purl.org/ont/users#> PREFIX j: <http://aabs.purl.org/ont/journal#> SELECT DISTINCT ?dn WHERE { ?u a u:User; u:displayName ?dn . ?e a j:JournalEntry; j:user ?u . { {?e j:tag "RDF".} UNION {?e j:tag "OWL".} UNION {?e j:tag "SPARQL".} } } |
通常,定义所处理的本体的名称空间以及感兴趣的变量。然后定义希望用户匹配的属性。这里定义了希望连接的两个实例(?u 表示用户,?e 表示日志条目)。为了建立连接,在日志条目中描述 j:user
属性。实例及其之间的关系被定义之后,另一个图模式根据附加到日志条目的标记将匹配项合并到一起(如表 3 所示)。
表 3. 连接日志实例和用户类的实例
dn |
---|
"Andrew Matthews" |
"John Doe" |
表示该查询的另一种方法是使用 FILTER。
清单 31 中的简单查询将日志实例和用户类实例连接起来。该查询(清单 32)引入了一个字符串文字作为三元组模式 <?e j:tag "RDF">
的宾语。它的意思很清楚,但是如果没有读过本教程开始部分关于 RDF 和 RDF 的介绍,现在最好读一读。
清单 32. 该查询使用 FILTER 实现和清单 31 中的 UNION 同样的结果
PREFIX u: <http://aabs.purl.org/ont/users#> PREFIX j: <http://aabs.purl.org/ont/journal#> SELECT DISTINCT ?dn WHERE { ?u a u:User; u:displayName ?dn . ?e a j:JournalEntry; j:user ?u ; j:tag ?t . FILTER (?t = "RDF" || ?t = "OWL" || ?t = "SPARQL") } |
筛选器的逻辑意义和前面的基于 UNION 的查询一样,但如果不熟悉多图模式的话可能更容易理解。
很多查询可能需要对数据进行概括,或者得到许多问题(比如 “Do we have someone working at X this week?”)的是或者否的简要答案。SPARQL 可使用 ASK 查询类型解决这类问题。
清单 33 中的查询是否有人在 3月 18 日开始在 IBM 工作。不需要关心是谁,只想知道有没有。
清单 33. 询问是否有人在 3 月 18 日开始在 IBM 工作
PREFIX b: <http://aabs.purl.org/ont/avail#> PREFIX u: <http://aabs.purl.org/ont/users#7> PREFIX xsdt: <http://www.w3.org/2001/XMLSchema#> ASK { ?x a u:User; u:displayName ?dn; u:domainLogin ?dl. ?c a b:Customer; b:name "IBM". ?b a b:EmployeeBooking; b:with ?c; b:employee ?dl; b:startDate "2008-03-18T09:00:00"^^xsdt:dateTime. } |
ASK 查询只需要返回是否找到了结果,返回结果基本上不需要占用带宽。该查询的格式类似于 SELECT。但是没有定义返回变量,因为不需要返回变量。相反,它返回 true 或者 false 表示查询(如果使用 SELECT)有没有返回数据(如清单 34 所示)。
清单 34. ASK 查询返回 true
ASK => true |
通过上面这些查询示例,您应该对 SPARQL 有一定的了解了。本文未进行详细讨论,可以通过 下载 部分的示例代码了解从这个日志示例导出数据的各种方法。