H_13. SPARQL 사용하기
몇개월 전에 특정 사이트에 추가하기 위한 필요에 의해 작성하였던 SPARQL 사용설명을 공개합니다.
SPARQL(SPARQL Protocol and RDF Query Language)은 W3C의 표준으로서 RDF 쿼리 언어입니다. 데이터베이스에서 정보를 찾거나 입력하고자 할 경우 Query를 사용하듯이 RDF로 표현된 데이터를 찾기 위해서 SPARQL 이라는 언어를 사용합니다.
그럼 이 SPARQL을 하나씩 살펴보겠습니다.
1. SPARQL의 유형
데이터베이스에 질의하는 쿼리는 흔히들 사용하는 SELECT문 이외에도 UPDATE, INSERT, DELETE 문들이 있습니다. 마찬가지로 SPARQL에도 쓰임새는 약간 다르지만 이러한 유형들이 존재합니다. 이 유형들은 SELECT, ASK, CONSTRUCT, DESCRIBE입니다. 이중에서 흔히 사용하는 유형은 SELECT입니다.
가. SELECT
SELECT는 사용자가 찾고자 하는 데이터를 찾는 용도로 사용합니다. 마치 데이터베이스에서 SELECT문으로 질의를 하는 것과 같습니다.
나. ASK
ASK는 사용자가 찾고자 하는 쿼리의 결과가 있는지 없는지를 boolean(true 또는 false) 값으로 알려줍니다. SELECT문은 쿼리를 결과를 사용자에게 가져다주는 반면에 ASK는 결과가 있으면 true를, 결과가 아무것도 존재하지 않으면 false를 가져다주는 점이 큰 차이점입니다.
다. CONSTRUCT
CONSTRUCT는 사용자가 찾고자 하는 쿼리를 하면서 동시에 자신이 원하는 템플릿을 입력해줌으로 쿼리의 결과가 사용자가 원하는 템플릿대로 나오도록 할 때 사용합니다. 이는 single RDF Graph라고 하는데 쉽게 말하면 내가 원하는 트리플 모양대로 결과를 반환해 달라는 것입니다.
라. DESCRIBE
DESCRIBE는 사용자가 찾고자 하는 정보에 대해서 그 정보와 연결된 모든 트리플을 반환받고자 할 때 사용합니다.
이상 4가지의 유형에 대해서 간단하게 설명을 하였습니다. 그러면 이제 제일 많이 사용하는 SELECT를 가지고 좀 더 자세히 살펴보도록 하겠습니다.
2. SPARQL 만들기
가. SELECT 사용하기
SPARQL의 SELECT는 어떻게 사용하는 것일까요? 우선 데이터베이스의 SELECT문을 예로 살펴보겠습니다.
<DBQuery> | SELECT id, name FROM author WHERE birthYear=1933 ORDER BY id DESC LIMIT 3 OFFSET 4 |
위의 문장에서와 같이 질의를 하는 대상, 조건, 정렬조건, 결과건수제한 그리고 찾는 값에 대한 정보들이 있습니다.
SPARQL에서도 마찬가지로 질의하는 대상, 조건, 정렬조건, 결과건수제한 그리고 찾는 값에 대한 정보들을 SPARQL에 표현을 하게 됩니다.
먼저 자세히 알아보기 전에 기억해야 할 것은 SPARQL 쿼리는 기본적으로 트리플 패턴으로 사용자가 찾고자 하는 결과를 찾아옵니다. 트리플, 즉 주어부(subject), 술어부(predicate), 목적부(object)를 구성하는 패턴에 따라 결과를 매칭하게 됩니다.
SPARQL에서 쿼리의 유형은 제일 앞에 나오게 됩니다. 여기서는 당연히 SELECT에 대하여 설명을 하고 있으니 SELECT가 먼저 나오겠죠.
그 다음 찾는 값은 데이터베이스에서 컬럼명이 나오는데 SPARQL에서는 변수라고 지칭하는 문자를 사용합니다. 이 변수들은 특수문자인 ‘?’ 혹은 ‘$’를 사용하여 표현하며 대부분은 ‘?’을 사용하여 표현합니다. 따라서 기본적인 모습은 ‘SELECT ?변수’ 로 만들어집니다.
나. WHERE 사용하기
질의하는 대상은 데이터베이스에서는 FROM 절을 사용하여 표현하지만 SPARQL에서는 데이터베이스의 조건에 해당하는 WHERE를 사용하는 점이 다릅니다. 이는 위에서 설명한 트리플 패턴이라는 것으로 결과를 찾는다고 설명하였는데 여기에서 사용이 됩니다. 데이터베이스에서는 author 이라는 테이블에서 조건에 맞는 컬럼값을 가져오라는 질의이지만, SPARQL에서는 그 author 라는 정보가 트리플로 표현되어 있기 때문에 그 패턴을 WHERE절에 작성해 주는 것입니다.
그럼 여기까지 SPARQL로 표현해보면 아래와 같습니다.
<Query 1> | SELECT ?id WHERE { ?id rdf:type <http://lod.nl.go.kr/ontology/Author> . } |
각각에 대한 설명은 아래와 같습니다.
구분 | 설명 |
SELECT | 질의 유형 |
?id | 찾는 변수(특수문자를 앞에 꼭 붙여야 함) |
WHERE | 찾는 조건 입력 부분 |
?id | 트리플이 주어부에 해당하는 것으로 SELECT 문에서도 사용하고 있는 변수 |
rdf:type | 트리플의 술어부에 해당하는 것으로 클래스 타입이 무엇인지를 지칭함) |
트리플의 목적부에 해당하는 것으로 Author를 가리키는 URI |
이때 WHERE에서는 { }를 사용하여 그 안에 트리플 패턴을 정의하고 있다는 것을 기억해야 합니다. 그리고 하나의 트리플 패턴이 끝날 때에는 마침표(.)를 통해 트리플이 끝났다는 것을 명시해 주어야 합니다.
다. 패턴 추가하기
이번에는 <DBQuery>에서처럼 name을 찾는 SPARQL을 만들어보겠습니다.
데이터베이스에서는 간단하게 컬럼명을 적어주면 되지만, SPARQL에서는 트리플 패턴이라고 말씀드렸듯이 WHERE절에 그 패턴을 지정해 주어야 합니다.
name을 찾기 위해서는 찾는 변수 부분에 ?name을 추가하고 WHERE 절에 패턴을 추가해야 합니다.
국립중앙도서관의 저자정보에는 이름을 <http://xmlns.com/foaf/0.1/name>을 사용하여 트리플을 생성하였기에 이 정보를 가지고 패턴을 만들어보면 아래와 같습니다.
<Query 2-1> | SELECT ?id ?name WHERE { ?id rdf:type <http://lod.nl.go.kr/ontology/Author> . ?id <http://xmlns.com/foaf/0.1/name> ?name . } |
변수는 ?name 으로 SELECT에 추가하였는데 변수들 구분하기 위해서는 공백만 있으면 됩니다. (데이터베이스처럼 콤마(,)를 통해 구분하지 않습니다)
<Query 2-1>은 저자 중에서 id와 name을 찾는 쿼리입니다. 이를 좀 더 간단하게 표현할 수도 있습니다. 공통된 subject를 가지는 트리플 패턴에서는 공통인 subject를 하나만 사용하고 predicate와 object를 각각 표현하며 그 구분은 세미콜론(;) 으로 합니다.
<Query 2-2> | SELECT ?id ?name WHERE { ?id rdf:type <http://lod.nl.go.kr/ontology/Author> ; <http://xmlns.com/foaf/0.1/name> ?name . } |
<Query 2-2>는 세미콜론으로 공통된 subject를 가지는 트리플 패턴을 생성한 것이며 항상 마칠 때에는 콤마를 사용해야 합니다.
라. PREFIX 사용하기
이제는 <Query 2-2>가 그 전보다 좀 간결해 졌지만 여기서 더 간결하게 생성할 수도 있습니다. <Query 2-2>를 살펴보면 <>를 사용하여 표현하는 부분이 있습니다. 이는 URI를 표현할 때 ‘<’ 와 ‘>’ 문자를 사용하여 그 안에 자원(Resource)이라고 하는 URI를 입력하기 때문입니다. 그런데 또 살펴보면 rdf:type 라는 것이 있습니다. 자원의 URI를 표현하기 위해서는 <> 문자를 사용하여 표현한다고 했는데 이것은 형태가 다른 부분이라고 생각할 수 있습니다만, 이는 PREFIX를 사용하여 표현한 동일하지만 다른 형태의 표현입니다.
rdf:type를 위와 같이 표현하면 <http://www.w3.org/1999/02/22-rdf-syntax-ns#t ype> 이렇게 표현이 가능합니다. PREFIX를 사용하면 URI를 길게 쓰지 않고 짧게 사용할 수 있기 때문에 간결한 표현이 가능합니다. 그럼 PREFIX를 사용하여 <Query 2-2>를 다시 표현해면 <Query 2-3>과 같습니다.
<Query 2-3> | PREFIX nlon: <http://lod.nl.go.kr/ontology/> PREFIX foaf: <http://xmlns.com/foaf/0.1/> SELECT ?id ?name WHERE { ?id rdf:type nlon:Author ; foaf:name ?name . } |
이제 WHERE에서 URI를 길게 쓰지 않고 PREFIX를 사용하여 간결하게 입력할 수 있게 된 것입니다. 다른 한 가지 의문은 rdf:type는 PREFIX 선언하는 부분에 rdf가 없는데 어떻게 된 것인지 의문을 가질 수 있습니다. 이는 SPARQL를 처리하는 엔진에서 디폴트로 선언을 해주고 있기 때문에 국립중앙도서관의 SPARQL Endpoint에서는 rdf를 굳이 선언하지 않아도 내부적으로 선언을 하고 있기 때문에 별도의 선언 없이 사용할 수 있습니다.
마. FILTER 사용하기
데이터베이스에서와 마찬가지로 SPARQL에서도 특정 조건에 맞는 정보를 찾아 볼 수가 있습니다. 이때 사용하는 것이 FILTER입니다. 데이터베이스 질의에서는 WHERE 절에서 비교, 수식 등과 같은 조건을 통해 자신이 원하는 정보를 찾아오듯이 SPARQL에서는 WHERE 절 안에 FILTER를 만들어서 조건을 입력하도록 되어 있습니다. FILTER에는 다양한 연산을 사용하여 조건에 대한 제한을 사용할 수 있으며 여기에서는 심플한 것을 소개하도록 하겠습니다. 보다 다양한 연산은 3장을 참고하시기 바랍니다.
<DBQuery>에서는 조건이 birthYear가 1933으로 제한하고 있습니다. 이를 SPARQL에서 표현하기 위해서는 우선 birthYear라는 변수가 WHERE절의 패턴에서 정의가 되어야 합니다. 왜냐하면 WHERE절에서 트리플의 패턴이 어떤 것인지 정의되어 있지 않으면 FILTER에서도 적용이 되지 않기 때문입니다. 이를 적용하면 아래와 같습니다.
<Query 3> | PREFIX nlon: <http://lod.nl.go.kr/ontology/> PREFIX foaf: <http://xmlns.com/foaf/0.1/> SELECT ?id ?name WHERE { ?id rdf:type nlon:Author ; foaf:name ?name ; nlon:birthYear ?birth . } |
<Query 3>의 의미를 풀어보면 ‘?id는 Author라는 집합에 속해 있고, ?id의 foaf:name은 ?name이며, ?id의 nlon:birthYear는 ?birth이다’ 라는 세 가지 패턴을 모두 만족하고 있는 집합에서 ?id와 ?name을 가져오라는 것입니다.
이 쿼리에 birthYear가 1933년생인 ?id를 가져오라는 조건을 추가해보겠습니다. 이 조건은 FILTER를 사용하여 그 안에 조건을 입력하게 됩니다.
<Query 4> | PREFIX nlon: <http://lod.nl.go.kr/ontology/> PREFIX foaf: <http://xmlns.com/foaf/0.1/> SELECT ?id ?name WHERE { ?id rdf:type nlon:Author ; foaf:name ?name ; nlon:birthYear ?birth . FILTER(?birth=1933) } |
FILTER 안에는 ‘(’와 ‘)’를 사용하여 조건을 입력해야 합니다. 그리고 조건을 제한하고자 하는 변수 ?birth와 그 조건을 명시함으로서 FILTER를 완성하게 됩니다.
바. ORDER BY 사용하기
검색한 결과에 대해서 때로는 정렬이 필요할 때가 있습니다. 이를 위해서 사용하는 것이 ORDER BY입니다. 이는 데이터베이스의 질의와 유사하게 사용됩니다.
먼저 사용되는 곳부터 살펴보면 SPARQL의 WHERE을 닫은 뒤에 ORDER BY를 사용합니다.
<DBQuery> 에서와 같이 id로 정렬을 하도록 만든 SPARQL는 아래와 같습니다.
<Query 5> | PREFIX nlon: <http://lod.nl.go.kr/ontology/> PREFIX foaf: <http://xmlns.com/foaf/0.1/> SELECT ?id ?name WHERE { ?id rdf:type nlon:Author ; foaf:name ?name ; nlon:birthYear ?birth . FILTER(?birth=1933) } ORDER BY ?id |
정렬 순서는 디폴트로 ASC(오름차순)가 적용되어 있기 때문에 별도로 입력하지 않아도 적용이 됩니다. 하지만 역순으로 정렬을 하고자 할 경우에는 약간의 변화가 필요합니다. ORDER BY 이후에 DESC(내림차순)을 쓰고 변수를 사용해야 합니다. 이때 변수 앞뒤로 괄호(())를 사용하여 변수를 그 안에 넣어야 합니다. 결론적으로 ASC나 DESC 모두 ORDER BY ASC(?변수) 혹은 ORDER BY DESC(?변수) 형태의 SPARQL 입니다
<Query 6> | PREFIX nlon: <http://lod.nl.go.kr/ontology/> PREFIX foaf: <http://xmlns.com/foaf/0.1/> SELECT ?id ?name WHERE { ?id rdf:type nlon:Author ; foaf:name ?name ; nlon:birthYear ?birth . FILTER(?birth=1933) } ORDER BY DESC (?id) |
정렬은 하나에 대해서만 가능한 게 아니라 다중 정렬도 가능합니다. 다중 정렬을 사용하는 방법은 ORDER BY 이후에 정렬 순서에 따라 변수를 열거하여 질의를 할 수 있습니다. 이때 변수를 열거하는 경우에는 공백을 사용하여 열거하며 정렬순서를 어떤 방식으로 할 것인지에 따라 ASC나 DESC를 사용하여 변수를 열거하게 됩니다.
아래의 <Query 7>은 다중 정렬을 적용한 예시로서 첫 번째 정렬 방식은 name 오름차순으로 하고 두 번째는 id 내림차순으로 정렬을 하겠다는 의미로 사용하였습니다.
<Query 7> | PREFIX nlon: <http://lod.nl.go.kr/ontology/> PREFIX foaf: <http://xmlns.com/foaf/0.1/> SELECT ?id ?name WHERE { ?id rdf:type nlon:Author ; foaf:name ?name ; nlon:birthYear ?birth . FILTER(?birth=1933) } ORDER BY ?name DESC (?id) |
사. LIMIT 사용하기
SELECT를 사용하여 정보를 검색하다보면 예기치 않게 많은 수의 결과가 반환될 때도 있습니다. 이럴 경우에 결과의 수를 제한함으로서 원하는 결과를 찾을 수 있도록 설정할 수 있습니다. 결과의 개수를 제한할 때는 LIMIT를 사용하여 매칭이 된 결과 중 사용자가 원하는 개수만큼만 가져오도록 설정을 할 수 있습니다. LIMIT가 사용되는 위치는 ORDER BY 가 끝나고 뒤에 사용하든지 ORDER BY 가 사용되지 않으면 WHERE가 끝나는 뒤에 사용하면 됩니다. <Query 8> 은 <Query 6>의 결과에 개수 제한을 걸어 10개의 결과만 가져오도록 작성한 것입니다.
<Query 8> | PREFIX nlon: <http://lod.nl.go.kr/ontology/> PREFIX foaf: <http://xmlns.com/foaf/0.1/> SELECT ?id ?name WHERE { ?id rdf:type nlon:Author ; foaf:name ?name ; nlon:birthYear ?birth . FILTER(?birth=1933) } ORDER BY DESC (?id) LIMIT 10 |
아. OFFSET 사용하기
지금까지 조건을 만들고 정렬도 하고 개수 제한도 해보았습니다. 마지막으로는 검색한 결과를 가져올 때 몇 번째 정보부터 가져올 것인지를 정해보도록 하겠습니다. 검색한 결과가 아무런 제한이 없다면 1,000건이 있다고 가정할 때 500번째부터 10개만 가져와서 보고 싶을 경우에 사용이 가능합니다. 이때 사용하는 것이 OFFSET입니다. OFFSET을 사용하면 전체 1,000건의 정보 중에서 500번째 것부터 가져오도록 제한을 둘 수가 있습니다.
아래의 <Query 9>는 120번째부터 10개의 정보를 가져오도록 제한을 두었습니다.
<Query 9> | PREFIX nlon: <http://lod.nl.go.kr/ontology/> PREFIX foaf: <http://xmlns.com/foaf/0.1/> SELECT ?id ?name WHERE { ?id rdf:type nlon:Author ; foaf:name ?name ; nlon:birthYear ?birth . FILTER(?birth=1933) } ORDER BY DESC (?id) LIMIT 10 OFFSET 120 |
자. Query 형태
지금까지 SPARQL에서 사용하는 기본적은 것들을 살펴보았습니다. 2장에서 설명한 내용은 아주 기본적인 것들이며 대략적인 형태는 아래와 같습니다. 이 중에서 FROM(NAMED)는 약간의 혼동이 생길 수 있음으로 이 설명에서는 제외하였으며 나머지 생소한 용어는 [3장 알아두면 유용한 SPARQL]에서 자세히 다루고 있습니다.
PREFIX SELECT / CONSTRUCT / ASK / DESCRIBE (DISTINCT / REDUCED) FROM (NAMED) WHERE Graph Pattern / OPTIONAL / FILTER / UNION / GRAPH ORDER BY LIMIT OFFSET |
3. 알아두면 유용한 SPARQL
이 장에서는 앞 장에서 설명한 내용보다 좀 더 어렵지만 유용한 SPARQL을 설명하겠습니다.
가. DISTINCT
아래의 <Query 10>은 책이라는 집합에 포함되어 있으며 저작자가 있는 책과 저작자를 찾는 쿼리입니다. 지금까지 사용했던 쿼리와 다른 점은 SELECT 뒤에 변수를 사용하지 않고 ‘*’를 사용한 점입니다. ‘*’ 문자를 사용하면 WHERE 절에서 나오는 모든 변수를 결과로 반환할 때 사용할 수 있습니다.
<Query 10> | PREFIX foaf: <http://xmlns.com/foaf/0.1/> PREFIX bibo: <http://purl.org/ontology/bibo/> PREFIX dcterms: <http://purl.org/dc/terms/> PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> SELECT * WHERE { ?book a bibo:Book ; rdfs:label ?label ; ?person foaf:name ?name . } |
<Query 10>의 결과는 book, label, person, name 이 한꺼번에 100건이 결과로 반환됩니다. 이 중에서 name만을 확인해보고 싶은데 사용자가 검색한 결과 중에서 중복된 결과를 제거하여 결과를 반환받고자 할 경우에 사용합니다. DISTINCT는 SELECT 뒤에 사용하며 그 다음에 중복을 제거할 변수를 사용합니다. 이를 반영한 쿼리는 <Query 11>과 같습니다.
<Query 11-1> | PREFIX foaf: <http://xmlns.com/foaf/0.1/> PREFIX bibo: <http://purl.org/ontology/bibo/> PREFIX dcterms: <http://purl.org/dc/terms/> PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> SELECT DISTINCT ?name WHERE { ?book a bibo:Book ; rdfs:label ?label ; ?person foaf:name ?name . } |
WHERE 절을 자세히 살펴보면 ‘a' 라는 문자가 눈에 들어옵니다. 이는 rdf:type과 같은 용도로 사용하는 것으로서 어떤 집합에 속한 것을 찾을 때 사용합니다. 2장의 쿼리들에서 사용했던 rdf:type 대신에 a로 바꾸어 사용해도 동일한 결과를 가져다줍니다. 결과적으로 <Query 11-1>과 <Query 11-2>는 동일한 쿼리입니다.
<Query 11-2> | PREFIX foaf: <http://xmlns.com/foaf/0.1/> PREFIX bibo: <http://purl.org/ontology/bibo/> PREFIX dcterms: <http://purl.org/dc/terms/> PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> SELECT DISTINCT ?name WHERE { ?book rdf:type bibo:Book ; rdfs:label ?label ; ?person foaf:name ?name . } |
나. UNION
수학에서 합집합의 개념으로 여러 가지 패턴으로 검색되는 결과들을 SPARQL에서는 합집합으로 반환받을 수 있습니다. <Query 12>는 이름이 ‘고은’인 사람과 출생년이 1990년인 사람들을 합집합하여 결과를 달라는 의미로 사용하고 있습니다. 이 때 사용하는 것이 UNION입니다.
<Query 12> | PREFIX nlon: <http://lod.nl.go.kr/ontology/> PREFIX foaf: <http://xmlns.com/foaf/0.1/> SELECT * WHERE { { ?id foaf:name "고은"@ko . } UNION { ?id nlon:birthYear ?birth . FILTER (?birth=1990) } } |
UNION을 사용할 때에는 WHERE절과 별개로 {} 문자를 사용하여 트리플 패턴들을 묶어 줘야 합니다. 즉, WHERE { {패턴A} UNION {패턴B} } 형태입니다. UNION을 사용하지 않았을 때와의 차이점은 합집합과 교집합의 차이입니다. 다시 말하면 UNION을 사용하여 쿼리를 실행하면 이름이 고은이 사람과 1990년에 태어난 사람 모두를 결과로 반환하는 반면에 UNION을 사용하지 않고 쿼리를 실행하면 이름이 고은이면서 1990년에 태어난 사람을 결과로 반환하는 차이가 생깁니다.
다. OPTIONAL
국립중앙도서관의 저자정보에는 생년과 몰년이 존재합니다. 이 중에서 1960년 이후에 태어난 사람을 검색하고 싶은데 몰년까지 알고 싶은 경우도 있습니다. <Query 13>은 이 경우에 사용하는 쿼리입니다.
<Query 13> | PREFIX nlon: <http://lod.nl.go.kr/ontology/> PREFIX foaf: <http://xmlns.com/foaf/0.1/> SELECT * WHERE { ?id foaf:name ?name ; nlon:birthYear ?birth ; nlon:deathYear ?death . FILTER(?birth>1960) } |
하지만 이 쿼리의 경우에는 1960년 이후에 태어난 사람이지만 데이터 상으로 몰년이 존재하지 않기 때문에 결과에 포함되지 않는 경우도 발생합니다. 왜냐하면 앞 절에서 설명하였듯이 교집합으로 검색을 하기 때문입니다. 다시 말하면 데이터 상에 생년이 존재하고 몰년도 존재하는 데이터만 검색 결과로 반환이 됩니다. 하지만 원하는 결과는 몰년이 존재하든지 존재하지 않던지 1960년 이후에 태어난 사람들 모두를 결과로 받고 싶을 경우에 OPTIONAL을 사용하여 쿼리를 만들 수 있습니다.
<Query 14> | PREFIX nlon: <http://lod.nl.go.kr/ontology/> PREFIX foaf: <http://xmlns.com/foaf/0.1/> SELECT * WHERE { ?id foaf:name ?name ; nlon:birthYear ?birth . FILTER(?birth>1960) OPTIONAL { ?id nlon:deathYear ?death . } } |
OPTIONAL은 데이터베이스의 Join 연산과 같은 기능을 합니다. 찾고자 하는 트리플 패턴은 {}를 사용하여 묶어주어 사용하며 여러 개의 OPTIONAL을 사용할 수도 있습니다.
<Query 14>는 생년이 1960년 이후에 태어난 사람을 기본적으로 반환하되 그 사람의 몰년이 존재여부에 상관없이 다 가져오라는 의미로 사용합니다. 따라서 데이터 상으로 몰년이 명시되지 않은 사람과 몰년이 명시된 사람 모두가 결과로 반환되어집니다.
OPTIONAL의 연산은 연산 시에 공통 변수의 여부에 따라서 연산하는 방식이 다르게 됩니다. 공통 변수라 함은 <Query 14>에서 ?id 에 해당하는 것으로 각각의 패턴에서 공통적으로 사용되는 변수를 뜻합니다. 공통 변수를 가지고 있는 경우에는 Left outer Join 연산을 수행하며, 공통 변수를 가지고 있지 않은 경우에는 Full Join 연산을 수행합니다.
라. COUNT
지금까지 SPARQL을 통해 검색 결과를 가져오는 여러 가지 방법들에 대해서 설명하였습니다. 이번에는 검색된 결과의 개수를 알아보는 쿼리를 설명하겠습니다. 검색된 결과가 몇 건인지를 알아보기 위해서는 COUNT를 사용합니다. 이는 SELECT 뒤에 위치하며 괄호를 사용하여 변수를 묶어 사용해야 합니다. 이때 위에서 설명한 ‘*’를 사용해도 무방합니다.
<Query 15> | PREFIX nlon: <http://lod.nl.go.kr/ontology/> PREFIX foaf: <http://xmlns.com/foaf/0.1/> SELECT COUNT(*) WHERE { ?id foaf:name ?name ; nlon:birthYear ?birth . FILTER(?birth>1960) OPTIONAL { ?id nlon:deathYear ?death . } } |
<Query 15>는 위의 <Query 14> 검색 결과를 개수를 알아보기 위한 쿼리입니다. COUNT 부분을 살펴보면 전체를 표현하는 ‘*’를 사용하고 있는데 생년에 해당하는 birth로 변경할 수도 있습니다. 즉, COUNT(?birth) 로 변경할 수 있습니다. 또한 3장 첫 부분에 설명하였던 DISTINCT를 같이 사용할 수도 있는데 이를 사용하면 전체 중에서 중복된 항목은 제거한 개수를 반환하게 됩니다.
<Query 16> | PREFIX nlon: <http://lod.nl.go.kr/ontology/> PREFIX foaf: <http://xmlns.com/foaf/0.1/> SELECT COUNT(DISTINCT ?birth) WHERE { ?id foaf:name ?name ; nlon:birthYear ?birth . FILTER(?birth>1960) OPTIONAL { ?id nlon:deathYear ?death . } } |
마. GROUP BY
SPARQL을 사용하다보면 데이터베이스처럼 하나의 변수에 대하여 그룹으로 묶어서 결과를 가져오는 것이 필요해 질 수도 있습니다. 이럴 때 사용하는 것이 GROUP BY입니다. GROUP BY에 의해 정의된 변수를 통해 같은 값들이 하나의 그룹으로 형성되어 결과가 반환이 됩니다. <Query 17>은 1980년 이후에 태어난 사람을 찾는 쿼리에 생년을 그룹으로 묶어서 각각의 연도에 몇 명의 저자가 있는지를 확인하는 쿼리로 사용이 됩니다. GROUP BY의 변수는 birth 가 사용되었으므로 birth별로 그룹이 형성되고 각각의 그룹에 존재하는 id의 개수를 COUNT 로 반환하도록 하고 있습니다.
<Query 17> | PREFIX nlon: <http://lod.nl.go.kr/ontology/> PREFIX foaf: <http://xmlns.com/foaf/0.1/> SELECT ?birth COUNT(?id) WHERE { ?id foaf:name ?name ; nlon:birthYear ?birth . FILTER(?birth>1980) } GROUP BY ?birth |
바. FILTER의 여러 가지 사용법
SPARQL을 사용하면서 조건들을 명시할 때 유용한 것 중에 하나가 FILTER입니다. FILTER에는 앞 장에서 예시로 든 논리 비교뿐 만 아니라 다양한 함수와 기능들이 존재합니다. 이 중에 많이 사용되는 몇 가지를 선택하여 그에 대한 사용법을 설명하도록 하겠습니다. 보다 자세한 내용은 http://www.w3.org/TR/rdf-sparql-query/ 에서 확인해 보실 수 있습니다.
a. STR
트리플로 표현된 데이터 중에서 language tag나 XSDDataype 이 무엇인지 표현하고 있는 경우도 있습니다. 예를 들면 아래의 <Triple 1>처럼 표현된 트리플이 있을 경우 FILTER를 통해 ‘고은’을 검색하고자 한다면 language tag 인 ‘ko’를 신경 써야 하는 부분이 생기게 됩니다.
<Triple 1> | nlk:KAC201011569 foaf:name "고은"@ko . |
language tag를 신경쓰지 않고 <Query 18>과 같이 FILTER를 통해 name이 '고은’인 사람을 검색하도록 하면 language tag로 인해 제대로 검색이 이루어지지 않는 경우가 발생합니다.
<Query 18> | PREFIX foaf: <http://xmlns.com/foaf/0.1/> SELECT * WHERE { ?id foaf:name ?name . FILTER(?name="고은“) } |
이를 해결하기 위해서는 몇 가지 방법이 존재하지만 여기서는 STR을 통해 해결해 보도록 하겠습니다.
<Query 19> | PREFIX foaf: <http://xmlns.com/foaf/0.1/> SELECT * WHERE { ?id foaf:name ?name . FILTER(STR(?name)="고은") } |
<Query 19>에서는 변수에 해당하는 name을 STR 안에 넣어줌으로서 lexical form을 반환하도록 요청하였습니다. 결과적으로 language tag는 제외하고 string값만 가져와 ‘고은’이라는 string과 비교하도록 처리하고 있습니다.
또 다른 해결 방법은 FILTER를 통해 비교를 할 때 language tag를 직접 붙여서 비교를 하도록 함으로써 해결이 가능합니다.
<Query 20> | PREFIX foaf: <http://xmlns.com/foaf/0.1/> SELECT * WHERE { ?id foaf:name ?name . FILTER(?name="고은"@ko) } |
또한 STR은 Resource의 URI를 단순 string으로 바꿔 처리하는 것에도 사용할 수 있습니다. <Query 21>은 어떤 서지의 저자 아이디가 KAC201011569로 끝나는 서지와 저자를 찾는 쿼리로서 URI 형태인 변수 c를 string으로 변경하여 substring(afn:substring) 하도록 하고 있습니다. 이 같은 경우에 URI를 string으로 바꾸는 역할도 STR이 하고 있습니다.
<Query 21> | PREFIX dcterms: <http://purl.org/dc/terms/> PREFIX afn: <http://jena.hpl.hp.com/ARQ/function#> SELECT * WHERE { ?s dcterms:creator ?c . FILTER(afn:substring(str(?c),29)='KAC201011569') } |
b. LANG
Literal로 표현된 값의 language tag를 반환하는 기능도 사용할 수 있습니다. LANG를 사용하면 Literal의 language tag만을 가져오게 됩니다. <Query 22>은 KAC201011569 라는 아이디를 가진 사람의 이름 중에 language tag 가 korean인 이름을 가져오는 쿼리입니다.
<Query 22> | PREFIX foaf: <http://xmlns.com/foaf/0.1/> PREFIX nlk: <http://lod.nl.go.kr/resource/> SELECT ?name WHERE { nlk:KAC201011569 foaf:name ?name. FILTER(LANG(?name)='ko') } |
앞절의 STR과 LANG는 FILTER안에서 뿐만 아니라 SELECT 뒤에서도 사용이 가능합니다. <Query 23>은 KAC201011569 라는 아이디를 가진 사람의 이름의 language tag를 반환해 달라는 쿼리로 사용됩니다.
<Query 23> | PREFIX foaf: <http://xmlns.com/foaf/0.1/> PREFIX nlk: <http://lod.nl.go.kr/resource/> SELECT LANG(?name) WHERE { nlk:KAC201011569 foaf:name ?name. } |
c. REGEX
SPARQL에서는 정규 표현식(Regular Expression, regex)을 지원하고 있습니다. 정규 표현식을 통해 텍스트를 검색하거나 혹은 치환, 문자열 추출 등과 같은 기능을 사용하실 수 있습니다. 여기에서는 정규 표현식 자체에 대한 설명은 하지 않으니 자세한 설명은 정보 검색을 통해 확인해 주시기 바랍니다.
SPARQL에서 정규 표현식을 사용하기 위해서는 REGEX를 사용합니다. REGEX는 FILTER와 같이 사용하며 간단한 사용법은 <Query 24>와 같습니다.
<Query 24> | PREFIX foaf: <http://xmlns.com/foaf/0.1/> PREFIX nlk: <http://lod.nl.go.kr/resource/> SELECT * WHERE { ?id foaf:name ?name. FILTER REGEX(STR(?name), '고은') } |
<Query 24>에서는 어떤 사람의 이름을 찾는 쿼리로서 이름에 ‘고은’이라는 문자가 들어간 모든 사람을 찾도록 설정하고 있습니다. 여기에서 사용하고 있는 REGEX의 형태는 REGEX(대상 문자열, 패턴) 형태입니다. REGEX의 첫 번째 인자로 대상 문자열이기 때문에 name을 문자열로 바꾸기 위해 STR을 사용하고 있습니다. 그리고 대상 문자열에서 ‘고은’이라는 단어가 들어간 모든 이름을 찾도록 하고 있습니다.