SPARQL이란?
RDF기반 Jena에서 사용하는 쿼리입니다.
기본적으로 이 SPARQL을 사용하기 위해선 RDF의 트리플에 대해서 알고 있어야 합니다.
잘 모르겠다면 아래 두 링크를 확인해주시길 바랍니다.
https://narup.tistory.com/2?category=880408
https://narup.tistory.com/3?category=880408
SPARQL 쿼리는 기본적으로 트리플 패턴으로 사용자가 찾고자 하는 결과를 찾아옵니다. 트리플, 즉 주어부(subject), 술어부(predicate), 목적부(object)를 구성하는 패턴에 따라 결과를 매칭하게 됩니다.
SPARQL에서 쿼리의 유형은 제일 앞에 나오게 됩니다. 여기서는 당연히 SELECT에 대하여 설명을 하고 있으니 SELECT가 먼저 나옵니다.
그 다음 찾는 값은 데이터베이스에서 컬럼명이 나오는데 SPARQL에서는 변수라고 지칭하는 문자를 사용합니다. 이 변수들은 특수문자인 ‘?’ 혹은 ‘$’를 사용하여 표현하며 대부분은 ‘?’을 사용하여 표현합니다. 따라서 기본적인 모습은 ‘SELECT ?변수’ 로 만들어집니다.
SPARQL 쿼리 분석
SELECT ?id WHERE { ?id rdf:type <_c1d5c0d08f8011e08e4d00247eb1f55z> . } |
위 구문을 해석해보면 RDF type과 일치하는 ID를 찾는다는 것이다.
* RDF의 태그 안에는 type이 무조건 들어 있다.
<cim:ACLineSegment rdf:ID="_c1d5c0d08f8011e08e4d00247eb1f55z">
이 태그를 예로 들면 type이 _c1d5c0d08f8011e08e4d00247eb1f55z과 같은 것의 ID를 반환하라 라는 것이다.
WHERE이 트리플 즉 주어부, 술어부, 목적부로 구성이 되어 있다는 점을 생각하자.
현재 주어부는 ?id이다. 모르는 상태다. 알고 있는 것은 술어부가 rdf:type인 것. 목적부가 <>안에 있는 링크이다.
* 아, 참고하면 좋은 것이 WHERE절의 트리플을 끝낸다는 의미로 맨 끝에 .을 무조건 찍어야 한다.
SELECT로 가져오는 것은 ?id이다.
즉, rdf:type이 <_c1d어쩌구>인 주어부를 출력하라! 라는 구문인 것이다.
SELECT ?id ?name WHERE { ?id rdf:type <http://lod.nl.go.kr/ontology/Author> . ?id <http://xmlns.com/foaf/0.1/name> ?name . } |
다른 쿼리를 살펴보자. 한줄 한줄 천천히 해석해보면 된다.
WHERE의 첫줄인 ?id, 주어부를 모르는 상태이다. 술어부는 Rdf:type이고 목적부는 <lod.nl.go.kr>이다.
Type이 lod.nl.go.kr인 주어부를 가져오라라는 의미이다.
SPARQL 쿼리에서 WHERE에 일치하는 것은 1개가 아닌 n개라는 점 명심. 변수가 ?id라서 1개가 들어가는 것이 아니라 여러 개가 들어간다라는 점이다. 변수가 아닌 컬럼으로 생각하는 것이 편하다. 임의의 컬럼. 임의의 ID컬럼을 생성해 조건에 일치하는 주어부를 ID컬럼에 출력한다라는 이야기.
그 다음 줄을 보면 주어부가 ?id이고, 술어부가 xmlns.com이고, 목적부가 ?name이다.
모르는 변수가 2개네?
아니다. 앞 줄에서 type = lod.nl.go.kr과 일치하는 주어부를 id에 넣어 놓았다.
즉, 주어부가 type = lod.nl.go.kr과 일치하는 ID와, 술어부가 xmlns.com인 조건에 일치하는 목적부를 모두 가져오라는 쿼리이다.
복잡하다.
복잡하면 한줄 한줄 천천히 풀이하면 된다. 첫줄의 조건과 일치하는 컬럼을 ID에 넣었다.
Id1, id2, id5, id7, … 여러 개 있을 것이다.
이 id1에는 술어부 -> 목적부로 이루어진 태그가 1개가 될 수도 있고, 10개가 될 수도 있다.
<cim:BaseVoltage rdf:ID="BV_400_0_NL">
<cim:IdentifiedObject.name>400.0 kv</cim:IdentifiedObject.name>
<cim:BaseVoltage.nominalVoltage>400.0</cim:BaseVoltage.nominalVoltage>
</cim:BaseVoltage>
이것처럼 말이다.
뽑아낸 ID들 중에서 술어부가 xmlns.com인 것을 찾아서 목적부 컬럼 ?name에 넣으라는 이야기다!
RDF 그래프를 통한 SPARQL 해석
위와 같은 형태의 RDF 파일이 있다.
1) Subject에 해당 하는 값 가져오기
SELECT ?x WHERE { ?x <http://www.w3.org/2001/vcard-rdf/3.0#FN> "John Smith" } |
l 결과
---------------------------------
| x |
=================================
| <http://somewhere/JohnSmith/> |
---------------------------------
l ?로 시작하는 변수도 아니고, 술어부에 해당하는 predicate도 아닌 목적부에 해당하는 Object에 임의의 값을 넣어서 조회를 하고 싶을 경우에는 “”으로 Object값을 묶어 주어야 한다.
2) X와 fname에 해당하는 값 가져오기(where은 S->P->O의 형태로 되어있다. P가 주소로 되어 있는데, 이 P와 일치하는 구성(방향성,그래프)을 가진 S(=x)와 O(=fname)을 가져오는 것임.)
SELECT ?x ?fname WHERE {?x <http://www.w3.org/2001/vcard-rdf/3.0#FN> ?fname} |
l 결과
----------------------------------------------------
| x | name |
====================================================
| <http://somewhere/RebeccaSmith/> | "Becky Smith" |
| <http://somewhere/SarahJones/> | "Sarah Jones" |
| <http://somewhere/JohnSmith/> | "John Smith" |
| <http://somewhere/MattJones/> | "Matt Jones" |
----------------------------------------------------
3) Subject의 값이 Predicate가 Family고 Object가 Smith인 노드의 값 => y(Blank Node)이고, subject가 y이고 Predicate가 Given인 Object의 값 Rebecca, John임. 따라서 givenName값은 Rebecca, John.
SELECT ?givenName WHERE { ?y <http://www.w3.org/2001/vcard-rdf/3.0#Family> "Smith" . ?y <http://www.w3.org/2001/vcard-rdf/3.0#Given> ?givenName . } |
l 결과
-------------
| givenName |
=============
| "John" |
| "Rebecca" |
-------------
4) PREFIX라는 것을 사용해서 Predicate에 항상 링크가 들어갈 수가 없으니까 주로 사용하는 링크를 enum과 같이 특정 변수로 정의해서 사용 하는 것. 3번과 결과는 동일.
PREFIX vcard: <http://www.w3.org/2001/vcard-rdf/3.0#>
SELECT ?givenName WHERE { ?y vcard:Family "Smith" . ?y vcard:Given ?givenName . } |
5) Blank Node. 그래프와 아래 쿼리를 보면 쿼리 첫줄의 ?y vcard:Family “Smith”와 일치하는 노드가 비어있다. 이 빈 노드를 Blank Node라고 하고 조회, 사용은 할 수 있지만 특정한 값이 존재하지 않는 노드를 뜻한다. Y를 출력할 경우 blank인 b가 나옴.
PREFIX vcard: <http://www.w3.org/2001/vcard-rdf/3.0#>
SELECT ?y ?givenName WHERE { ?y vcard:Family "Smith" . ?y vcard:Given ?givenName . } |
l 결과
--------------------
| y | givenName |
====================
| _:b0 | "John" |
| _:b1 | "Rebecca" |
--------------------
실제 RDF파일을 통한 SPARQL 분석
다른 예시의 RDF/XML파일 형태가 있다고 가정하고,
<rdf:RDF
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cim="http://iec.ch/TC57/2013/CIM-schema-cim16#"
xmlns:md="http://iec.ch/TC57/61970-552/ModelDescription/1#">
<cim:ACLineSegment rdf:ID="_c1d5c0f08f8011e08e4d00247eb1f55e">
<cim:Conductor.length>23.0</cim:Conductor.length>
<cim:ACLineSegment.r>0.5203</cim:ACLineSegment.r>
<cim:IdentifiedObject.name>N2X1</cim:IdentifiedObject.name>
<cim:Equipment.aggregate>false</cim:Equipment.aggregate>
<cim:ConductingEquipment.BaseVoltage rdf:resource="#BV_220_0_NL"/>
<cim:ACLineSegment.bch>2.1012401091865922E-5</cim:ACLineSegment.bch>
<cim:ACLineSegment.x0>0.0</cim:ACLineSegment.x0>
<cim:ACLineSegment.x>71.00038</cim:ACLineSegment.x>
<cim:ACLineSegment.b0ch>0.0</cim:ACLineSegment.b0ch>
<cim:ACLineSegment.r0>0.0</cim:ACLineSegment.r0>
</cim:ACLineSegment>
</rdf:RDF>
쿼리는 아래와 같다
PREFIX cim: < http://iec.ch/TC57/2013/CIM-schema-cim16#> PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> SELECT DISTINCT ?s ?p WHERE { ?s ?p “N2X1” . } |
출력 결과
?s 변수 = Subject에 해당하는 ID인
file:///C:/workplace/Source/RDF/RDF_Test/#_c1d5c0f08f8011e08e4d00247eb1f55e 가 나오고,
?p 변수 = predicate에 해당하는 태그 값인 http://iec.ch/TC57/2013/CIM-schema-cim16#IdentifiedObject.name가 나온다.(cim PREFIX값도 포함해서 나온 결과)
쿼리는 아래와 같다면? 위의 쿼리에서 조건이 하나 추가되었다.
PREFIX cim: < http://iec.ch/TC57/2013/CIM-schema-cim16#> PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> SELECT DISTINCT ?s ?p WHERE { ?s ?p “N2X1” . ?s cim:Conductor.length ?o . } |
출력 결과
?s 변수 = Subject에 해당하는 ID인
file:///C:/workplace/Source/RDF/RDF_Test/#_c1d5c0f08f8011e08e4d00247eb1f55e 가 나오고,
?o 변수 = Object에 해당하는 출력 값이 23.0이 나온다.
Eclipse Jena 에서의 SPARQL 사용
Java Jena에서 SPARQL을 사용하려면 RDF형식으로 이루어진 파일이 있어야 한다.
SPARQL은 RDF Model을 기반으로 처리되는 쿼리이다.
public static void getReturnQuery() {
try{
storeDesc = new StoreDesc(LayoutType.LayoutTripleNodesHash, DatabaseType.SQLServer);
jdbcConn = getConnection();
sdbConn = new SDBConnection(jdbcConn);
store = StoreFactory.create(storeDesc, sdbConn);
// Model baseModel = SDBFactory.connectDefaultModel(store); // Store를 기준으로 모델을 가져옴.
Model baseModel = ModelFactory.createDefaultModel();
FileInputStream file = new FileInputStream(new File("D:\\130604_NL_EQ111.xml"));
String base = "http://iec.ch/TC57/2013/CIM-schema-cim16";
baseModel.read(file, base);
String queryString = "PREFIX cim: <http://iec.ch/TC57/2013/CIM-schema-cim16#> " +
"PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>" +
"SELECT DISTINCT ?s ?p " +
"WHERE {" +
"?s ?p \"N2X1\" ." +
"}";
Query query = QueryFactory.create(queryString);
QueryExecution qe = QueryExecutionFactory.create(query, baseModel);
ResultSet rs = qe.execSelect();;
while(rs.hasNext())
{
QuerySolution qs = rs.next();
String s1 = qs.get("s").toString();
String s2 = qs.get("p").toString();
System.out.println(s1 + " / " + s2);
jdbcConn.close();
sdbConn.close();
store.close();
}
}catch(Exception e){
e.printStackTrace();
}
}
StroeDesc, jdbcConn, sdbConn, store는 SDB와 관련된 코드이다.
데이터베이스에서 스키마를 읽고 이를 SDBFactory를 통해 Model화해서 가져오는 것이라 주석을 달아놓았고, 파일로 가져오는 경우 ModelFactory를 통해 기본적인 모델을 구성해서 RDF 파일을 Model에 read시키는 것으로 SPARQL을 사용할 수 있게 된다.
작성한 쿼리를 queryString에 담고 QueryFactory.create()를 통해 Query객체에 담는다.
query객체와 해당 모델을 인자로 QueryExecution에 담아 ResultSet이라는 이터레이터를 통해 실행한 쿼리에 대한 데이터를 읽는다.
쿼리에 지정했던 ?s, ?p 변수를 Select했기 때문에, QuerySolution에서 가져오는 데이터를 get메소드를 통해 지정해서 가져와야 한다.
RDF파일을 기준으로 읽어드린 결과는 아래와 같다.
http://iec.ch/TC57/2013/CIM-schema-cim16#_c1d5c0f08f8011e08e4d00247eb1f55e / http://iec.ch/TC57/2013/CIM-schema-cim16#IdentifiedObject.name
정리하자면,
1) 모델로 파일을 읽는다.
2) 쿼리를 작성해 String에 담는다.
3) Query객체에 작성한 쿼리를 QueryFactory로 담는다.
4) QueryExecution에 model과 query를 담는다.
5) ResultSet에 쿼리 실행 결과를 담는다.
6) 이터레이터를 통해 ResultSet을 조회
7) QuerySolution에 조회한 결과값을 담기.
8) get으로 결과값을 꺼내와서 출력.
별건 아닌데 절차가 참 복잡하다.
'Java > RDF&Jena' 카테고리의 다른 글
[RDF] Parse error: Bad character in IRI(space) 해결 방법 (0) | 2020.03.27 |
---|---|
[Jena] Apache Jena Fuseki 리눅스 설치 방법 (0) | 2020.03.27 |
Jena SDB 사용 예제 (0) | 2020.03.20 |
RDF - Jena 예제 및 주로 사용하는 함수 정리 (0) | 2020.03.12 |
RDF - Eclipse Apache Jena Xml 파일 읽어오기 (0) | 2020.03.10 |