SPARQL — язык запросов к RDF

(8 глав написал, осталось только одну доделать)

Материал для самостоятельного изучения:

informal intro

Здоровенькi були. Сегодня будем строить запросы к RDF документам на языке SPARQL.

1. Инструменты

Итак, сначала об инструменте (скачать, или взять у меня нужно все перечисленное). Хотя, в принципе, дистрибутивы Jena, Arq и Joseki являются самодостаточными.
(Естественно, для того, чтобы что-то работало должна быть установлена Java).

2. Элементы языка SPARQL

Вот вы наверное думаете, сложный язык и все такое. Но если подумать: что такое RDF — множество неупорядоченных троек

Субъект Предикат Объект

И вы, в серьез можете думать, что язык запросов к множеству неупорядоченных троек может быть сложным?

Нет в Semantic Web ничего сложного. Сколько бы не говорили, и в SPARQL — нет ничего сложного :-)

2.1. IRI

IRI — очередная придумка W3C на тему URI.
В SPARQL имеется возможность указывать IRI двумя способами:

Prefixed names
Используя ключевое слово PREFIX и уже знакомого нам «:»

Примерчик (уже был, ну да ладно):

PREFIX  vCard: <http://www.w3.org/2001/vcard-rdf/3.0#>
SELECT $y $x
WHERE
{
    $x vCard:FN $y.
    FILTER regex($y, "Jo", "i")
}

Relative IRIs
Используя символы «<» и «>»

Это самые обыкновенные ссылки, если типа <http://somewhere/JohnSmith/>, то все понятно объяснять не надо.

А если типа <JohnSmith/>, то смотрим на то, что стоит рядом с ключевым словом BASE и можем мысленно приложить это слева к нашему IRI.

Пример:

BASE <http://somewhere/>
PREFIX  vCard: <http://www.w3.org/2001/vcard-rdf/3.0#>
SELECT $x
WHERE
{
    <SarahJones/> vCard:FN $x.
}

При запросе (к 1.rdf) выводит:

-----------------
| x             |
=================
| "Sarah Jones" |
-----------------

2.2. Литералы

Здесь ничего нового нет.
Литералы (не зависимо от типа) указываются в двойных "" или одинарных '' кавычках.
Также к литералам можно прикручивать тег языка: через @:

"текст на русском"@ru

Тип указываем посредствам ^^ (по умолчанию — plain text).
Пример:

PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
SELECT $x
WHERE
{
    <http://example.org/item01> $x "123"^^<xsd:integer> .
}

Короткой записью для литерала "42"^^<http://www.w3.org/2001/XMLSchema#integer> является просто 42

На сегодняшний день литералом может быть только объект, но RDF Work Group рассматривает возможность того, чтобы субъект тоже мог быть литералом. Возможно в грядущей спецификации RDF мы это увидим, а возможно и нет (я, например, не хотел бы).

2.3. Переменные

Переменные в SPARQL имеют глобальную область действия. Они отмечаются при помощи $ или ?. Причем, $a и ?a — это одно и то же.

2.4. Blank nodes («промежуточные узлы»)

Blank nodes — пришли из RDF. В общем, на русский термин blank node можно перевести примерно как «промежуточный узел».
приведу пример RDF/XML, blank node обозначается посредством атрибута rdf:nodeID:

<?xml version="1.0"?>
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
         xmlns:dc="http://purl.org/dc/elements/1.1/"
         xmlns:ex="http://example.org/stuff/1.0/">
  <rdf:Description rdf:about="http://www.w3.org/TR/rdf-syntax-grammar"
		   dc:title="RDF/XML Syntax Specification (Revised)">
    <ex:editor rdf:nodeID="abc"/>
  </rdf:Description>

  <rdf:Description rdf:nodeID="abc"
                   ex:fullName="Dave Beckett">
    <ex:homePage rdf:resource="http://purl.org/net/dajobe/"/>
  </rdf:Description>
</rdf:RDF>

Промежуточный узел — это локальный ресурс, который не виден за пределами документа.
Существует два способа разметки промежуточных узлов (в SPARQL):

# ксати так вставляются комментарии :-)

# первый способ: посредством _:

_:b57 :p "v" .
_:b57 :q "w" .


# второй способ: посредством []

:x :q [ :p "v" ] .

# что эквивалентно двум триплетам:

:x  :q _:b57 .
_:b57 :p "v" .

Необходимость в промежуточных узлах очевидна. Но для наглядности рассмотрим жизненный пример:
Определен предикат haveDad (имеет отца).
Допустим нам необходимо узнать деда Миши, тогда запрос может выглядеть примерно так:

PREFIX som: <http://somesite.ru/>
SELECT $x
WHERE
{
    <Misha> som:haveDad [ som:haveDad $x ] .
}

Я использовал следующие данные:

<?xml version="1.0"?>
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
         xmlns:som="http://somesite.ru/"
         >

    <rdf:Description rdf:about="Alex">
        <som:haveDad rdf:resource="Misha" />
    </rdf:Description>

    <rdf:Description rdf:about="Misha">
        <som:haveDad rdf:resource="Dima" />
    </rdf:Description>

    <rdf:Description rdf:about="Dima">
        <som:haveDad rdf:resource="Ivan" />
    </rdf:Description>

</rdf:RDF>

И получил результат: дедом Миши является Иван:

--------------------------------------
| x                                  |
======================================
| <file:///D:/bin/Arq-1.3/work/Ivan> |
--------------------------------------

2.5. Шаблоны

Подробнее о шаблонах смотрите в разделе «5. Шаблоны».

3. Мои первые SPARQL запросы

Все 4 нижеперечисленных SPARQL запроса, по-сути, являются одинаковыми. Обратите внимание на синтаксис:

PREFIX  dc: <http://purl.org/dc/elements/1.1/>
SELECT  ?title
WHERE   { <http://example.org/book/book1> dc:title ?title }

PREFIX  dc: <http://purl.org/dc/elements/1.1/>
PREFIX  : <http://example.org/book/>
SELECT  $title
WHERE   { :book1  dc:title  $title }

BASE    <http://example.org/book/>
PREFIX  dc: <http://purl.org/dc/elements/1.1/>
SELECT  $title
WHERE   { <book1>  dc:title  ?title }

Dublin Core (http://purl.org/dc/elements/1.1/) принято записывать как dc. Но, можно сделать и такой запрос:

BASE    <http://example.org/book/>
PREFIX  dcore: <http://purl.org/dc/elements/1.1/>
SELECT  $title
WHERE   { <book1>  dcore:title  ?title }

Вообще, не имеет значения как называть пространства имен (в смысле не обязательно называть их так же, как и в других документах). Т.е. в данных префикс может быть «dc», а в запросе «dcore» и наоборот.

3.1. Формат входных данных

Вы уже знаете, что помимо RDF/XML существуют и другие языки, реализующие RDF Concept.
В спецификации SPARQL данные в большинстве случаев описывались с помощью формата Turtle. Он позволяет быстро и наглядно записывать триплеты. Вот пример его использования:

@prefix dc:   <http://purl.org/dc/elements/1.1/> .
@prefix :     <http://example.org/book/> .
:book1  dc:title  "SPARQL Tutorial" .
:book2  dc:title  "SPARQL вторая книга, типа того" .

Тут, по-моему, все понятно и объяснять ничего не надо.

Этот формат, естественно, поддерживается и в Arq. Просто создайте файл с расширением «.ttl» (вместо «.rdf») и пишите туда данные в формате Turtle.

Потом необходимо вызвать созданный нами q.bat, например так:

q.bat D:/bin/Arq-1.3/work/5.ttl D:/bin/Arq-1.3/work/5.rq text D:\bin\Arq-1.3\work\5.txt

Кстати, если запрос из предыдущего пункта применить к нашим данным, Arq выдает результат:

---------------------
| title             |
=====================
| "SPARQL Tutorial" |
---------------------

3.2. Формат результата

Как вы уже заметили, мы используем Arq и результаты выводим в виде текста. На практике, в большинстве случаев, это будет не текст, а XML. Для этого вызываем q.bat вот таким образом:

q.bat D:/bin/Arq-1.3/work/5.ttl D:/bin/Arq-1.3/work/5.rq xml D:\bin\Arq-1.3\work\5.xml

Подробнее о формате результата SPARQL запроса в виде XML читайте на сайте W3C: SPARQL Query Results XML Format

3.3. Пример №1

Данные с применением сокращенной формы записи триплетов и промежуточных узлов _:a и _:b

@prefix foaf:    <http://xmlns.com/foaf/0.1/> .

_:a  foaf:name   "Johnny Lee Outlaw" .
     foaf:mbox   <mailto:outlaw@example.com> .

_:b  foaf:name   "A. N. Other" .
     foaf:mbox   <mailto:other@example.com> .

SPARQL-запрос с применением сокращенной формы записи шаблонов триплетов:

PREFIX foaf:   <http://xmlns.com/foaf/0.1/>
SELECT ?mbox
WHERE
  { ?x foaf:name "Johnny Lee Outlaw" .
       foaf:mbox ?mbox }

Результат:

-------------------------------
| mbox                        |
===============================
| <mailto:outlaw@example.com> |
-------------------------------

3.4. Пример №2

Рассмотрим куски 2-х запросов:

{ _:x :p ?v .
  FILTER (?v < 3) .
  _:x :q ?w .
}

и

{ _:x :p ?v .
  _:x :q ?w .
  FILTER (?v < 3) .
}

На самом деле это — одно и то же. И в результате будут лежать только те данные, которые удовлетворяют всем трем:
двум шаблонам и одному фильтру. (О фильтрах мы поговорим чуть позже).

3.5. Пример №3

Данные:

@prefix foaf:  <http://xmlns.com/foaf/0.1/> .

_:a  foaf:name   "Johnny Lee Outlaw" .
_:a  foaf:mbox   <mailto:jlow@example.com> .
_:b  foaf:name   "Peter Goodguy" .
_:b  foaf:mbox   <mailto:peter@example.org> .

SPARQL запрос:

PREFIX foaf:   <http://xmlns.com/foaf/0.1/>
SELECT ?name ?mbox
WHERE
  { ?x foaf:name ?name .
    ?x foaf:mbox ?mbox }

Результат (в таблице две колонки: name и mbox):

----------------------------------------------------
| name                | mbox                       |
====================================================
| "Peter Goodguy"     | <mailto:peter@example.org> |
| "Johnny Lee Outlaw" | <mailto:jlow@example.com>  |
----------------------------------------------------

3.6. Пример №4

Данные:

@prefix foaf:  <http://xmlns.com/foaf/0.1/> .

_:a  foaf:name   "Alice" .
_:b  foaf:name   "Bob" .

SPARQL запрос:

PREFIX foaf:   <http://xmlns.com/foaf/0.1/>
SELECT ?x ?name
WHERE  { ?x foaf:name ?name }

Результат:

------------------
| x    | name    |
==================
| _:b0 | "Bob"   |
| _:b1 | "Alice" |
------------------

Здесь следует обратить внимание на то, как промежуточные узлы отображаютя в результате. Если в данных они имели одно имя, в результате могут иметь совершенно другое.

4. Использование общеупотребимых конструкций RDF

4.1. Классы

Ключевое слово «a» используется как предикат в шаблонах триплетов, и является альтернативой rdf:type (http://www.w3.org/1999/02/22-rdf-syntax-ns#type):

  ?x  a  :Class1 .
  [ a :appClass ] :p "v" .

# то же самое, что и
  
  ?x    rdf:type  :Class1 .
  _:b0  rdf:type  :appClass .
  _:b0  :p        "v" .

В связи с появлением языка OWL использование классов (при помощи предиката rdf:type или ключевого слова «a») становится все более употребимым.

Не путайте пожалуйста datatype (задается посредством ^^), которое определяет тип литерала.
И rdf:type (в SPARQL задается посредством предиката «a»), которое определяет класс ресурса.
Это разные вещи.

Кстати, ключевое слово «a» есть и в Turtle. Т.е.:

:someres  rdf:type  :appClass .

# можно записать как

:someres  a  :appClass .

Причем при выводе «a» преобразуется в <http://www.w3.org/1999/02/22-rdf-syntax-ns#type>

4.2. Reification Vocabulary

Вот пример Turtle документа, который использует Reification Vocabulary:

@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix dc:  <http://purl.org/dc/elements/1.1/> .
@prefix :    <http://example/ns#> .

_:a   rdf:subject   <http://example.org/book/book1> .
_:a   rdf:predicate dc:title .
_:a   rdf:object    "SPARQL" .
_:a   :saidBy       "Alice" .

_:b   rdf:subject   <http://example.org/book/book1> .
_:b   rdf:predicate dc:title .
_:b   rdf:object    "SPARQL Tutorial" .
_:b   :saidBy       "Bob" .

Reification Vocabulary определяет предикаты rdf:subject, rdf:predicate и rdf:object.

Т.е. мы как бы говорим:

# "Alice" сказала:
<http://example.org/book/book1> dc:title "SPARQL".

# А "Bob" сказал:
<http://example.org/book/book1> dc:title "SPARQL Tutorial".

Но если мы пошлем SPARQL запрос

PREFIX dc: <http://purl.org/dc/elements/1.1/>

SELECT ?book ?title
WHERE
{ ?book dc:title ?title }

к предыдущему документу, то результат будет пустой:

----------------
| book | title |
================
----------------

Потому что в действительности в RDF-графе нет ни одного триплета, где dc:title являлся бы предикатом.
Есть только два триплета, где он выступает в роли объекта:

_:a   rdf:predicate dc:title .
_:b   rdf:predicate dc:title .

Запрос к подобным документам должен выглядеть примерно так:

PREFIX rdf:  <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX dc:   <http://purl.org/dc/elements/1.1/>
PREFIX :     <http://example/ns#>

SELECT ?book ?title
WHERE
{
    ?t rdf:subject    ?book  .
    ?t rdf:predicate  dc:title .
    ?t rdf:object     ?title .

# если мы хотим узнать, — что сказал "Bob"
    ?t :saidBy       "Bob" .
}

Результат:

-------------------------------------------------------
| book                            | title             |
=======================================================
| <http://example.org/book/book1> | "SPARQL Tutorial" |
-------------------------------------------------------

5. Шаблоны

5.1. Классическая форма шаблона триплетов

Список из субъектов, предикатов и объектов

Шаблон триплетов записывается в виде списка, элементом которого является последовательность Субъект Предикат Объект, в конце которой ставится точка «.»

    ?x  foaf:name  ?name .
    ?y  ?property  ?mbox .
    ?y  foaf:name  "Misha" .

Причем, и вместо Субъекта, и вместо Предиката, и вместо Объекта, — может стоять переменная.

5.2. Другие синтаксические формы

Как в Turtle, так и в SPARQL триплеты можно представлять и в других синтаксических формах:

Список из предикатов и объектов, используя ;

    ?x  foaf:name  ?name ;
        foaf:mbox  ?mbox .

# тоже самое, что и

    ?x  foaf:name  ?name .
    ?x  foaf:mbox  ?mbox .

Список из объектов, используя ,
   ?x foaf:nick  "Alice" , "Alice_" .
	
# тоже самое, что и

   ?x  foaf:nick  "Alice" .
   ?x  foaf:nick  "Alice_" .
Эти две синтаксические формы можно комбинировать:
   ?x  foaf:name ?name ; foaf:nick  "Alice" , "Alice_" .

# тоже самое, что и

   ?x  foaf:name  ?name .
   ?x  foaf:nick  "Alice" .
   ?x  foaf:nick  "Alice_" .

5.3. Шаблоны групповых графов

Шаблон группового графа в SPARQL обозначается при помощи фигурных скобок: {}

PREFIX foaf:    
SELECT ?name ?mbox
WHERE  {
          ?x foaf:name ?name .
          ?x foaf:mbox ?mbox .
       }

Можно представить в виде 2 графов (каждый по одному триплету):

PREFIX foaf:    
SELECT ?name ?mbox
WHERE  {
          { ?x foaf:name ?name . }
          { ?x foaf:mbox ?mbox . }
       }

5.4. Выборка по необязательным шаблонам

Ключевое слово OPTIONAL использует следующий синтаксис:

pattern OPTIONAL { pattern }

Приведем пример.

Данные:

@prefix foaf:       <http://xmlns.com/foaf/0.1/> .
@prefix rdf:        <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .

_:a  rdf:type        foaf:Person .
_:a  foaf:name       "Alice" .
_:a  foaf:mbox       <mailto:alice@example.com> .
_:a  foaf:mbox       <mailto:alice@work.example> .

_:b  rdf:type        foaf:Person .
_:b  foaf:name       "Bob" .

SPARQL запрос, использующий необязательный шаблон (применяя ключевое слово OPTIONAL):

PREFIX foaf: <http://xmlns.com/foaf/0.1/>
SELECT ?name ?mbox
WHERE  { ?x foaf:name  ?name .
         OPTIONAL { ?x  foaf:mbox  ?mbox }
       }

И результат:

-----------------------------------------
| name    | mbox                        |
=========================================
| "Bob"   |                             |
| "Alice" | <mailto:alice@work.example> |
| "Alice" | <mailto:alice@example.com>  |
-----------------------------------------

Если бы мы не использовали ключевое слово OPTIONAL, то "Bob" вообще бы не попал в результат.

Еще о синтаксисе OPTIONAL:

pattern OPTIONAL { pattern } OPTIONAL { pattern }

то же самое, что и

{ pattern OPTIONAL { pattern } } OPTIONAL { pattern }

5.5. Объединение шаблонов

Рабочая группа по SPARQL пока не завершила работу над возможностью объединения шаблонов.

Шаблоны в SPARQL объединяются при помощи ключевого слова UNION

Синтаксис:

{ pattern } UNION { pattern }

Пример.

Данные:

@prefix dc10:  <http://purl.org/dc/elements/1.0/> .
@prefix dc11:  <http://purl.org/dc/elements/1.1/> .

_:a  dc10:title     "SPARQL Query Language Tutorial" .

_:b  dc11:title     "SPARQL Protocol Tutorial" .

_:c  dc10:title     "SPARQL" .
_:c  dc11:title     "SPARQL (updated)" .

SPARQL запрос:

PREFIX dc10:  <http://purl.org/dc/elements/1.0/>
PREFIX dc11:  <http://purl.org/dc/elements/1.1/>

SELECT ?title
WHERE  { { ?book dc10:title  ?title } UNION { ?book dc11:title  ?title } }
Результат:
------------------------------------
| title                            |
====================================
| "SPARQL"                         |
| "SPARQL Query Language Tutorial" |
| "SPARQL (updated)"               |
| "SPARQL Protocol Tutorial"       |
------------------------------------

Можно немного изменить запрос:

PREFIX dc10:  <http://purl.org/dc/elements/1.0/>
PREFIX dc11:  <http://purl.org/dc/elements/1.1/>

SELECT ?title10 ?title11
WHERE  { { ?book dc10:title  ?title10 } UNION { ?book dc11:title  ?title11 } }

И результат будет представлен в более наглядной форме:

-----------------------------------------------------------------
| title10                          | title11                    |
=================================================================
| "SPARQL"                         |                            |
| "SPARQL Query Language Tutorial" |                            |
|                                  | "SPARQL (updated)"         |
|                                  | "SPARQL Protocol Tutorial" |
-----------------------------------------------------------------

5.6. Фильтры

Подробнее смотрите в разделе «9. Фильтры».

6. Набор данных RDF

RDF выражает информацию в виде графа, который представляется в виде триплетов из субъектов, предикатов и объектов. Однако в большинстве случаев массив данных RDF содержит множество графов. Поэтому приложение должно иметь возможность сделать запрос, который затрагивает информацию из более чем одного графа.

SPARQL зпрос выполняется по отношению к набору данных RDF, который представляет из себя множество графов.
В наборе данных может быть только один граф, который не имеет имени — неименованный граф.
Все остальные графы (их может быть несколько, а может и не быть вовсе) являются именованными, их имя задается посредством IRI.

Определение:

Набор данных RDF — это множество
{ G, (<u1>, G1), (<u2>, G2), ... (<un>, Gn) },

где G, Gi  — графы; (i ∈ 1..n)
<ui> — является IRI (причем, каждый <ui> является уникальным). (i ∈ 1..n)

G называется неименованным графом (default graph)
(<ui>, Gi) называется именованным графом (named graph). (i ∈ 1..n)

Именованных графов может и не быть.

Пример.

# Неименованный граф
@prefix dc: <http://purl.org/dc/elements/1.1/> .

<http://example.org/bob>    dc:publisher  "Bob" .
<http://example.org/alice>  dc:publisher  "Alice" .
# Именованный граф: http://example.org/bob
@prefix foaf: <http://xmlns.com/foaf/0.1/> .

_:a foaf:name "Bob" .
_:a foaf:mbox <mailto:bob@oldcorp.example.org> .
# Именованный граф: http://example.org/alice
@prefix foaf: <http://xmlns.com/foaf/0.1/> .

_:a foaf:name "Alice" .
_:a foaf:mbox <mailto:alice@work.example.org> .

В этом примере неименованный граф содержит IRI именованных графов. Триплеты неименованных графов не распологаются непосредственно в именованном графе.

6.1. Запросы к наборам данных RDF

Чтобы применить шаблоны к именованным графам используйте ключевое слово GRAPH вместе с IRI именованного графа или переменной.

В следующих примерах будем использовать 2 именованных графа:

# Named graph: file:///d:/bin/Arq-1.3/work/aliceFoaf.ttl
@prefix  foaf:  <http://xmlns.com/foaf/0.1/> .
@prefix  rdf:    <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix  rdfs:   <http://www.w3.org/2000/01/rdf-schema#> .

_:a  foaf:name     "Alice" .
_:a  foaf:mbox     <mailto:alice@work.example> .
_:a  foaf:knows    _:b .

_:b  foaf:name     "Bob" .
_:b  foaf:mbox     <mailto:bob@work.example> .
_:b  foaf:nick     "Bobby" .
_:b  rdfs:seeAlso  <http://example.org/foaf/bobFoaf> .

<http://example.org/foaf/bobFoaf>
     rdf:type      foaf:PersonalProfileDocument .
# Named graph: file:///d:/bin/Arq-1.3/work/bobFoaf.ttl
@prefix  foaf:  <http://xmlns.com/foaf/0.1/> .
@prefix  rdf:    <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix  rdfs:   <http://www.w3.org/2000/01/rdf-schema#> .

_:z  foaf:mbox     <mailto:bob@work.example> .
_:z  rdfs:seeAlso  <http://example.org/foaf/bobFoaf> .
_:z  foaf:nick     "Robert" .
<http://example.org/foaf/bobFoaf>
     rdf:type      foaf:PersonalProfileDocument .

Итак, создадим простенький SPARQL-запрос, который позволит нам узнать какой у Боба ник по мнению Элис (именованый граф file:///d:/bin/Arq-1.3/work/aliceFoaf.ttl) и по мнению самого Боба (именованый граф file:///d:/bin/Arq-1.3/work/bobFoaf.ttl):

# d:/bin/Arq-1.3/work/alicebob.rq
PREFIX foaf: <http://xmlns.com/foaf/0.1/>

SELECT ?src ?bobNick
WHERE
  {
    GRAPH ?src
    { ?x foaf:mbox <mailto:bob@work.example> .
      ?x foaf:nick ?bobNick
    }
  }

Теперь надо заставить Arq возвратить нам результат запроса именно по отношению к этим двум именованым графам. Для этого создадим новый файл alicebob.bat в папке D:\bin\Arq-1.3

set ARQROOT=D:\bin\Arq-1.3

bat\sparql.bat --namedGraph file:///d:/bin/Arq-1.3/work/aliceFoaf.ttlnoenter
               --namedGraph file:///d:/bin/Arq-1.3/work/bobFoaf.ttlnoenter
               --query file:///d:/bin/Arq-1.3/work/alicebob.rqnoenter
               --results textnoenter
               > d:\bin\Arq-1.3\work\alicebob.txt

Значок noenter означает, что переносить текст на следующую строку нельзя. Я отформатировал текст с переносом строк только для удобства.

Вот результат (d:\bin\Arq-1.3\work\alicebob.txt):

----------------------------------------------------------
| src                                         | bobNick  |
==========================================================
| <file:///d:/bin/Arq-1.3/work/aliceFoaf.ttl> | "Bobby"  |
| <file:///d:/bin/Arq-1.3/work/bobFoaf.ttl>   | "Robert" |
----------------------------------------------------------

Изменим запрос alicebob.rq, так чтобы он применял шаблон только к графу file:///d:/bin/Arq-1.3/work/bobFoaf.ttl:

# d:/bin/Arq-1.3/work/alicebob.rq
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX local: <file:///d:/bin/Arq-1.3/work/>

SELECT ?bobNick
WHERE
  {
    GRAPH local:bobFoaf.ttl
    {
        ?x foaf:mbox <mailto:bob@work.example> .
        ?x foaf:nick ?bobNick
    }
  }

и получаем результат:

------------
| bobNick  |
============
| "Robert" |
------------

Ключевое слово GRAPH (с помощью него формируют запрос, который применяет шаблон к именованым графам) можно комбинировать с обычными шаболнами (запрос к неименованному графу). Например так:

# d:/bin/Arq-1.3/work/alicebob.rq
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX : <http://somesite.org/>

SELECT ?src ?bobNick
WHERE
  {
    ?x :somePredicate "someliteral" .
    GRAPH ?src
    {
        ?x foaf:mbox <mailto:bob@work.example> .
        ?x foaf:nick ?bobNick
    }
  }

Вызывать Arq необходимо так (например):

set ARQROOT=D:\bin\Arq-1.3

bat\sparql.bat --data file:///d:/bin/Arq-1.3/work/somedata.ttlnoenter
               --namedGraph file:///d:/bin/Arq-1.3/work/aliceFoaf.ttlnoenter
               --namedGraph file:///d:/bin/Arq-1.3/work/bobFoaf.ttlnoenter
               --query file:///d:/bin/Arq-1.3/work/alicebob.rqnoenter
               --results textnoenter
               > d:\bin\Arq-1.3\work\alicebob.txt

Содержимое файла file:///d:/bin/Arq-1.3/work/somedata.ttl является неименованным графом.
С ним и только с ним сверяют шаблон

    ?x :somePredicate "someliteral" .
Шаблоны триплетов
        ?x foaf:mbox <mailto:bob@work.example> .
        ?x foaf:nick ?bobNick .

применяются только к file:///d:/bin/Arq-1.3/work/aliceFoaf.ttl и file:///d:/bin/Arq-1.3/work/bobFoaf.ttl.

6.2. Указание набора данных RDF

До этого мы указывали местонахождение набора данных RDF при запуске самого Arq. Однако, с помощью ключевых слов FROM и FROM NAMED можно указать источники данных прямо в запросе.

На этот раз я использовал сервер Apache, чтобы файлы доставались не с file:, а с http:. (Просто для того, чтобы все условия были приближены к сетевым).

Первый документ содержит информацию о моем телефоне и группе в Университете, в которой я учусь:

# http://localhost/misha1.ttl

@prefix contact: <http://localhost/contact.owl#> .
@prefix : <http://localhost/> .

:Misha contact:telephone "+79025890925" ;
       contact:group "PRI" .

Второй документ содержит информацию о моем домашнем телефоне, который, кстати, недавно сменили

# http://localhost/misha2.ttl

@prefix contact: <http://localhost/contact.owl#> .
@prefix : <http://localhost/> .

:Misha contact:telephone "261719" .

Однако в военкомате меня причесляют к моей прошлой группе, в которой я давно уже не учусь:

# http://localhost/voenkomat.ttl

@prefix contact: <http://localhost/contact.owl#> .
@prefix : <http://localhost/> .

:Misha contact:group "MOAIS" .

Есть много людей, которые не знают моего нового домашнего номера телефона. В следующем документе записан мой старый номер:

# http://localhost/popsy.ttl

@prefix contact: <http://localhost/contact.owl#> .
@prefix : <http://localhost/> .

:Misha contact:telephone "655087" .

Теперь создадим SPARQL запрос с использованием FROM и FROM NAMED:

# http://localhost/pry.rq
PREFIX contact: <http://localhost/contact.owl#>
PREFIX local: <http://localhost/>
SELECT $res $group $tel
FROM local:misha1.ttl
FROM local:misha2.ttl
FROM NAMED local:voenkomat.ttl
FROM NAMED local:popsy.ttl
WHERE
{
    {
        { <http://localhost/Misha> contact:telephone $tel . }
        UNION
        { <http://localhost/Misha> contact:group $group . }
    }
    UNION
    {
        GRAPH $res
        {
            { <http://localhost/Misha> contact:telephone $tel . }
            UNION
            { <http://localhost/Misha> contact:group $group . }
        }
    }
}

Что прежде всего привлекает — у нас будет два неименованных графа? Нет. Неименованный граф может быть только один. Просто 2 документа (misha1.ttl и misha2.ttl) «склеятся» в один неименовынный граф.

Т.е. у нас получится один неименованный граф: путем склеивания документов misha1.ttl и misha2.ttl

И два именованных графа:

<http://localhost/voenkomat.ttl> — IRI именованного графа из документа voenkomat.ttl

<http://localhost/popsy.ttl> — IRI именованного графа из документа popsy.ttl

Теперь вызываем Arq (источники данных указывать уже не нужно):

set ARQROOT=D:\bin\Arq-1.3

bat\sparql.bat --query http://localhost/pry.rq
               --results text
               > d:\bin\Arq-1.3\work\result.txt

А вот и результат:

--------------------------------------------------
| res                 | group   | tel            |
==================================================
|                     |         | "261719"       |
|                     |         | "+79025890925" |
|                     | "PRI"   |                |
| local:voenkomat.ttl | "MOAIS" |                |
| local:popsy.ttl     |         | "655087"       |
--------------------------------------------------

Т.е. в наглядной форме показано в каких графах — какой мой телефон и какая моя группа записаны. То, что поле res осталось пустым в некоторых строках означает, что информация бралась из неименованного графа.

Если вы заметили, то
contact:telephone — короткая запись для http://localhost/contact.owl#telephone;
contact:group — короткая запись для http://localhost/contact.owl#group

В contact.owl я мог бы, например, определить, что свойство group может выступать (в триплете) в качестве предиката только один раз с одним субъектом. (Т.е. определить, что некоторая персона может обучаться только в одной группе).

Однако, я даже не создавал файл contact.owl, т.е. ни как не использовал возможности OWL. О том, почему я так сделал напишу чуть позже. А пока — продолжаем изучать SPARQL.

7. Модификаторы последовательности решения

В SPARQL 5 модификаторов последовательности решений:

Projection

Distinct (используя ключевое слово DISTINCT)

Order (используя ключевые слова ORDER BY, ASC и DESC)

Limit (используя ключевое слово LIMIT)

Offset (используя ключевое слово OFFSET)

7.1. Projection

Projection — самый простой модификатор последовательности решений (даже не имеет специального ключевого слова). Он просто отсекает часть каждого решения.

Пример.

Данные:

# http://localhost/projection.ttl

@prefix : <http://localhost/> .

:Anya  :email <mailto:nenapishu@mail.ru> .
:Misha :email <mailto:mishundic@mail.ru> .
:Lesha :email <mailto:zerohunter@mail.ru> .

Запрос:

# http://localhost/projection.rq

BASE <http://localhost/>
SELECT $y
FROM <http://localhost/projection.ttl>
WHERE
{
    $x <email> $y .
}

Последовательность решений выглядит примерно так:

1. $x = <http://localhost/Lesha>, $y = <mailto:zerohunter@mail.ru>
2. $x = <http://localhost/Anya>, $y = <mailto:nenapishu@mail.ru>
3. $x = <http://localhost/Misha>, $y = <mailto:mishundic@mail.ru>

Однако, применяя модификатор Projection, мы указали, что нас интересует только переменная $y, поэтому в результате в каждом решении значение переменной $x вообще не указано:

-------------------------------
| y                           |
===============================
| <mailto:zerohunter@mail.ru> |
| <mailto:mishundic@mail.ru>  |
| <mailto:nenapishu@mail.ru>  |
-------------------------------

Если у Вас еще остались вопросы по модификатору Projection, Вы можете прочитать о нем еще немного в главе посвещенной форме результата Select.

7.2. Distinct

Синтаксис:

SELECT DISTINCT ... 

Использование ключевого слова DISTINCT указывает, что каждое решение (т.е. в табличном представлении — каждая строка) в ответе на запрос будет уникальной.

Пример:

# http://localhost/specialists.ttl

@prefix sp: <http://localhost/properties/> .
@prefix   : <http://localhost/> .

:Misha sp:name "Misha" ;
       sp:profession "web developer" ;
       sp:city "Ul'sk" .

:Natasha sp:name "Natasha" ;
         sp:profession "tester" ;
         sp:city "Moskow" .

:Ascar sp:name "Ascar" ;
       sp:profession "tester" ;
       sp:city "Ul'sk" .
		  
:Ilia sp:name "Ilia" ;
      sp:profession "web developer" ;
      sp:city "Ul'sk" .

Теперь построим SPARQL-запрос, который позволит нам понять в каком городе специалисты каких профессий работают:

BASE <http://localhost/>
PREFIX sp: <http://localhost/properties/>
SELECT $city $profession
FROM <http://localhost/specialists.ttl>
WHERE
{
	$a sp:profession $profession ;
	   sp:city $city .
}

Получаем результат:

------------------------------
| city     | profession      |
==============================
| "Ul'sk"  | "web developer" |
| "Ul'sk"  | "tester"        |
| "Moskow" | "tester"        |
| "Ul'sk"  | "web developer" |
------------------------------

Первая и последняя строчка одинаковы. Если нам не важно сколько специалистов работает в конкретном городе, то можем применить ключевое слово DISTINCT:

BASE <http://localhost/>
PREFIX sp: <http://localhost/properties/>
SELECT DISTINCT $city $profession
FROM <http://localhost/specialists.ttl>
WHERE
{
	$a sp:profession $profession ;
	   sp:city $city .
}

Получаем результат:

------------------------------
| city     | profession      |
==============================
| "Ul'sk"  | "web developer" |
| "Ul'sk"  | "tester"        |
| "Moskow" | "tester"        |
------------------------------

7.3. Order

С помощью модификатора последовательности решения Order результат можно отсортировать.

Синтаксис:

ORDER BY param1  param2 ...

где parami — это

$x или ASC($x) или DESC($x)

где $x — некоторая переменная.

Причем, в данном контексте:
$x — это короткая запись для ASC($x)

ASC($x) (англ. ascending — возрастать) — сортирует результат по возрастанию переменной $x.

DESC($x) (англ. descending — убывать) — сортирует результат по убыванию переменной $x.

Допустим, мы хотим отсортировать города по возрастанию, а профессии по убыванию.

Сортировка по убыванию и возрастанию выполняется путем вызова оператора <, а он, как известно, определен и на строках.

Итак, строим запрос:

BASE <http://localhost/>
PREFIX sp: <http://localhost/properties/>
SELECT DISTINCT $city $profession
FROM <http://localhost/specialists.ttl>
WHERE
{
	$a sp:profession $profession ;
	   sp:city $city .
}
ORDER BY $city DESC($profession)

Получили то, что хотели:

------------------------------
| city     | profession      |
==============================
| "Moskow" | "tester"        |
| "Ul'sk"  | "web developer" |
| "Ul'sk"  | "tester"        |
------------------------------

7.4. Limit

Синтаксис:
LIMIT n

Ключевое слово LIMIT задает максимальное значение решений, которые будут в результате (т.е. максимальное количество строк).

Пример:

BASE <http://localhost/>
PREFIX sp: <http://localhost/properties/>
SELECT DISTINCT $city $profession
FROM <http://localhost/specialists.ttl>
WHERE
{
	$a sp:profession $profession ;
	   sp:city $city .
}
ORDER BY $city DESC($profession)
LIMIT 2

Результат:

------------------------------
| city     | profession      |
==============================
| "Moskow" | "tester"        |
| "Ul'sk"  | "web developer" |
------------------------------

7.5. Offset

Синтаксис:
OFFSET n

позволяет не показывать в результате первые n решений.

В предыдущем примере мы получили в результате — 2 решения. Допустим мы хотим получить еще решения, но при этом не хотим, чтобы их было больше 3. Также, не хотим получать уже известные нам решения.

Тогда необходимо выполнить следующий SPARQL запрос:

BASE <http://localhost/>
PREFIX sp: <http://localhost/properties/>
SELECT DISTINCT $city $profession
FROM <http://localhost/specialists.ttl>
WHERE
{
	$a sp:profession $profession ;
	   sp:city $city .
}
ORDER BY $city DESC($profession)
LIMIT 3
OFFSET 2

Результат:

------------------------
| city    | profession |
========================
| "Ul'sk" | "tester"   |
------------------------

8. Формы результата

SPARQL имеет четыре формы результата.

SELECT — возвращает все или часть переменных, связанных в шаблонах запроса.

CONSTRUCT — возвращает RDF граф, построенный путем замещения переменных в Construct-шаблоне запроса.

DESCRIBE — возвращает RDF граф, который описывает найденный результат.

ASK — возвращает true, если хоть что-то найдено, иначе — false.

Если форма результата SELECT или ASK, то результат запроса будет в формате SPARQL Variable Binding Results XML Format или просто текст (в Arq).

Если форма результата CONSTRUCT или DESCRIBE, то результат запроса будет в формате RDF.

8.1. Select

Синтаксис с модификатором Projection:

SELECT $a $b ...

Форма результата SELECT просто возвращает значения переменных ($a $b ...) из всех найденых решений.

Синтаксис без модификатора Projection:

SELECT *

SELECT * возвращает значения всех переменных из всех найденых решений.

8.2. Construct

Синтаксис:

CONSTRUCT
{
    pattern
}

где pattern — некоторый Construct-шаблон.

Форма результата Construct возвращает RDF граф, который задается специальным шаблоном. Т.е. для каждого очередного решения в Construct-шаблон вместо переменных подставляются uri, промежуточные узлы или литералы.

Если такую подстановку осуществить невозможно (например, — триплет, в котором субъектом или предикатом является литерал), то триплет, естественно, не включается в результирующий граф.

Пример:

SPARQL запрос (все к тем же данных):

# http://localhost/newgraph.rq
BASE <http://localhost/>
PREFIX uv: <http://ulskforever.org/vocabulary/>
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX sp: <http://localhost/properties/>
CONSTRUCT
{
    $person foaf:firstName $name ;
            uv:name $name .
}
FROM <http://localhost/specialists.ttl>
WHERE
{
    $person sp:name $name .
}

Этим запросом мы хотим создать граф, в котором вместо свойства sp:name, которое определено на нашем локальном сервере будут использованы аналогичные
свойство FOAF (friend of a friend) foaf:firstName
и свойство некоего Ul'sk Vocabulary uv:name

Теперь надо правильно запустить Arq:

set ARQROOT=D:\bin\Arq-1.3

bat\sparql.bat --query http://localhost/newgraph.rq
               --results N3 > d:\bin\Arq-1.3\work\result.ttl

Я выбрал результат в виде N3; если кто хочет RDF, — надо ставить --results RDF

А вот и результат (Arq даже отформатировал все красиво):

@prefix sp:      <http://localhost/properties/> .
@prefix uv:      <http://ulskforever.org/vocabulary/> .
@prefix foaf:    <http://xmlns.com/foaf/0.1/> .
@prefix :        <http://localhost/> .

:Misha
      uv:name       "Misha" ;
      foaf:firstName  "Misha" .

:Ilia
      uv:name       "Ilia" ;
      foaf:firstName  "Ilia" .

:Ascar
      uv:name       "Ascar" ;
      foaf:firstName  "Ascar" .

:Natasha
      uv:name       "Natasha" ;
      foaf:firstName  "Natasha" .

8.3. Describe

Синтаксис:

DESCRIBE param1  param2 ...

где parami — это переменная или iri

Форма результата Describe возвращает RDF граф, который описывает ресурсы resourcei.

где resourcei — это

parami, если parami — iri

значение parami, взятое из очередного решения, если parami — переменная.

Пример:

# http://localhost/newgraph.rq
BASE <http://localhost/>
PREFIX sp: <http://localhost/properties/>
DESCRIBE $person <Natasha>
WHERE
{
    $person sp:name $name ;
            sp:profession "web developer" .
}

Результат:

@prefix sp:      <http://localhost/properties/> .
@prefix :        <http://localhost/> .

:Misha
      sp:city       "Ul'sk" ;
      sp:name       "Misha" ;
      sp:profession  "web developer" .

:Ilia
      sp:city       "Ul'sk" ;
      sp:name       "Ilia" ;
      sp:profession  "web developer" .

:Natasha
      sp:city       "Moskow" ;
      sp:name       "Natasha" ;
      sp:profession  "tester" .

Результирующий граф описывает три ресурса:

<http://localhost/Misha>

<http://localhost/Ilia>

<http://localhost/Natasha>

Причем, первые два iri взяты из набора решений.
А <http://localhost/Natasha> взято непосредственно из самого запроса и никак не зависит от набора решений.

Кстати, Arq не поддерживает возможность использования в одном запросе ключевых слов DESCRIBE и FROM. Поэтому пришлось вызывать по старинке (с указанием источника данных):

set ARQROOT=D:\bin\Arq-1.3

bat\sparql.bat --query http://localhost/newgraph.rqnoenter
               --data http://localhost/specialists.ttlnoenter
               --results N3 > d:\bin\Arq-1.3\work\result.ttl

8.4. Ask

Синтаксис:

ASK
{
    pattern
}

где pattern — обычный шаблон (подробнее смотрите в разделе «5. Шаблоны»).

Принцип действия SPARQL-процессора при использовании Ask запроса простой:

Если находится хотябы одно решение, то в результате возвращается — true, иначе — false.

Пример

# http://localhost/ask.rq
BASE <http://localhost/>
PREFIX sp: <http://localhost/properties/>
ASK
{
    $person sp:name "Natasha" ;
            sp:city "Moskow" .
}

Результат:

Ask => Yes

Кстати, Arq не поддерживает возможность использования в одном запросе ключевых слов ASK и FROM. Поэтому вызывать необходимо с указанием источника данных:

set ARQROOT=D:\bin\Arq-1.3

bat\sparql.bat --query http://localhost/ask.rqnoenter
               --data http://localhost/specialists.ttlnoenter
               --results text > d:\bin\Arq-1.3\work\result.txt

9. Фильтры

Hosted by uCoz