Chapter 4: SPARQL 쿼리 언어
🎯 학습 목표
이 챕터를 마치면 다음을 할 수 있습니다:
- ✅ SPARQL의 4가지 쿼리 형식 (SELECT, CONSTRUCT, ASK, DESCRIBE)을 사용한다
- ✅ 고급 기능 (FILTER, OPTIONAL, UNION, BIND)을 활용한다
- ✅ Property Paths로 복잡한 관계를 쿼리한다
- ✅ Aggregation (COUNT, AVG, SUM, GROUP BY)으로 데이터를 분석한다
- ✅ Subquery와 SPARQL Federation을 사용한다
- ✅ DBpedia와 Wikidata에서 실제 데이터를 검색한다
- ✅ SPARQL 쿼리 성능을 최적화한다
📚 목차
- SPARQL이란 무엇인가?
- 기본 문법: Triple Pattern
- SELECT - 데이터 조회
- FILTER - 조건 필터링
- OPTIONAL - 선택적 매칭
- UNION - 대안 패턴
- Property Paths
- Aggregation - 집계 함수
- CONSTRUCT - 새 그래프 생성
- ASK - 존재 여부 확인
- DESCRIBE - 자원 설명
- Subquery와 Federation
- 실습: DBpedia 쿼리
- 실습: Wikidata 쿼리
- 성능 최적화
- 요약과 다음 단계
1. SPARQL이란 무엇인가?
SPARQL Protocol and RDF Query Language
SPARQL은 RDF 데이터를 쿼리하기 위한 W3C 표준 언어입니다.
비유:
- SQL : 관계형 데이터베이스 = SPARQL : RDF 그래프
SPARQL의 역사
2004: SPARQL 워킹 그룹 시작 2008: SPARQL 1.0 W3C 권고안 2013: SPARQL 1.1 (현재 표준)
SPARQL 1.1 새 기능:
- Aggregation (COUNT, AVG, SUM)
- Subquery
- Property Paths
- UPDATE (INSERT, DELETE)
- Federation (SERVICE)
SQL vs SPARQL
| 특징 | SQL | SPARQL | |------|-----|--------| | 데이터 구조 | 테이블 (행/열) | 그래프 (노드/엣지) | | 스키마 | 고정 | 유연 | | 조인 | JOIN 명시 | 자동 (변수) | | NULL | 있음 | 없음 (OPTIONAL) | | 재귀 | 제한적 | Property Paths |
예제 비교:
SQL:
SELECT name, company
FROM Person
JOIN Employment ON Person.id = Employment.person_id
JOIN Company ON Employment.company_id = Company.id
WHERE Person.age > 30;
SPARQL:
SELECT ?name ?company WHERE {
?person :name ?name ;
:age ?age ;
:worksAt ?company .
FILTER(?age > 30)
}
→ SPARQL이 더 간결! (JOIN 자동)
2. 기본 문법: Triple Pattern
Triple Pattern이란?
SPARQL의 핵심은 Triple Pattern입니다.
RDF Triple:
:홍길동 :name "홍길동" .
SPARQL Triple Pattern:
?person :name ?name .
차이:
- RDF: 구체적 값
- SPARQL: 변수 (?person, ?name)
변수 규칙
표기법:
?변수 # 물음표로 시작
$변수 # 달러로 시작 (동일)
예제:
?person # 변수
?name # 변수
:홍길동 # 구체적 URI
"홍길동" # 구체적 Literal
기본 쿼리 구조
# 네임스페이스 선언
PREFIX : <http://example.org/>
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
# 쿼리
SELECT ?변수1 ?변수2
WHERE {
# Triple Patterns
?subject ?predicate ?object .
}
첫 번째 쿼리
데이터:
@prefix : <http://example.org/> .
:홍길동 :name "홍길동" ;
:age 45 .
쿼리:
PREFIX : <http://example.org/>
SELECT ?name ?age
WHERE {
:홍길동 :name ?name ;
:age ?age .
}
결과:
| name | age |
|---------|-----|
| "홍길동" | 45 |
3. SELECT - 데이터 조회
기본 SELECT
모든 사람 조회:
PREFIX : <http://example.org/>
SELECT ?person ?name
WHERE {
?person a :Person ;
:name ?name .
}
SELECT *
모든 변수 반환:
SELECT *
WHERE {
?person :name ?name ;
:age ?age .
}
DISTINCT
중복 제거:
SELECT DISTINCT ?company
WHERE {
?person :worksAt ?company .
}
LIMIT과 OFFSET
페이지네이션:
# 처음 10개
SELECT ?person ?name
WHERE {
?person :name ?name .
}
LIMIT 10
# 11번째부터 10개
SELECT ?person ?name
WHERE {
?person :name ?name .
}
LIMIT 10
OFFSET 10
ORDER BY
정렬:
# 나이순 정렬
SELECT ?name ?age
WHERE {
?person :name ?name ;
:age ?age .
}
ORDER BY ?age
# 내림차순
ORDER BY DESC(?age)
# 다중 정렬
ORDER BY DESC(?age) ?name
복합 예제
PREFIX : <http://example.org/>
SELECT DISTINCT ?name ?age ?company
WHERE {
?person a :Person ;
:name ?name ;
:age ?age ;
:worksAt ?company .
}
ORDER BY DESC(?age)
LIMIT 10
4. FILTER - 조건 필터링
기본 FILTER
나이가 30 이상:
SELECT ?name ?age
WHERE {
?person :name ?name ;
:age ?age .
FILTER(?age >= 30)
}
비교 연산자
FILTER(?age > 30) # 초과
FILTER(?age >= 30) # 이상
FILTER(?age < 30) # 미만
FILTER(?age <= 30) # 이하
FILTER(?age = 30) # 같음
FILTER(?age != 30) # 다름
논리 연산자
# AND
FILTER(?age >= 30 && ?age <= 50)
# OR
FILTER(?age < 20 || ?age > 60)
# NOT
FILTER(!(?age = 30))
문자열 함수
1. regex (정규표현식)
# 이름이 "김"으로 시작
FILTER(regex(?name, "^김"))
# 대소문자 구분 없음
FILTER(regex(?name, "kim", "i"))
2. CONTAINS
# 이름에 "길동" 포함
FILTER(CONTAINS(?name, "길동"))
3. STRSTARTS, STRENDS
# 시작
FILTER(STRSTARTS(?name, "홍"))
# 끝
FILTER(STRENDS(?email, "@gmail.com"))
4. STRLEN
# 이름이 3글자
FILTER(STRLEN(?name) = 3)
날짜/시간 함수
# 2020년 이후
FILTER(?date >= "2020-01-01"^^xsd:date)
# 현재 연도
FILTER(YEAR(?date) = YEAR(NOW()))
# 오늘부터 7일 이내
FILTER(?date <= NOW() + "P7D"^^xsd:duration)
타입 체크
# 숫자인지 확인
FILTER(isNumeric(?value))
# 리터럴인지 확인
FILTER(isLiteral(?value))
# URI인지 확인
FILTER(isURI(?value))
# Blank Node인지 확인
FILTER(isBlank(?value))
복합 FILTER
PREFIX : <http://example.org/>
SELECT ?name ?age ?email
WHERE {
?person :name ?name ;
:age ?age ;
:email ?email .
# 30-50세
FILTER(?age >= 30 && ?age <= 50)
# 이름이 "김"으로 시작
FILTER(STRSTARTS(?name, "김"))
# Gmail 계정
FILTER(STRENDS(?email, "@gmail.com"))
}
5. OPTIONAL - 선택적 매칭
OPTIONAL이란?
SQL의 LEFT OUTER JOIN과 유사합니다.
문제:
# 이메일이 없는 사람은 결과에서 제외됨
SELECT ?name ?email
WHERE {
?person :name ?name ;
:email ?email .
}
해결:
# 이메일이 없어도 포함
SELECT ?name ?email
WHERE {
?person :name ?name .
OPTIONAL {
?person :email ?email .
}
}
결과:
| name | email |
|---------|---------------------|
| "홍길동" | "hong@example.com" |
| "김철수" | (unbound) |
| "이영희" | "lee@example.com" |
다중 OPTIONAL
SELECT ?name ?email ?phone ?address
WHERE {
?person :name ?name .
OPTIONAL { ?person :email ?email . }
OPTIONAL { ?person :phone ?phone . }
OPTIONAL { ?person :address ?address . }
}
OPTIONAL + FILTER
SELECT ?name ?age
WHERE {
?person :name ?name .
OPTIONAL {
?person :age ?age .
FILTER(?age >= 30)
}
}
주의: FILTER가 OPTIONAL 안에 있어야 함!
BOUND 함수
값이 있는지 확인:
SELECT ?name ?email
WHERE {
?person :name ?name .
OPTIONAL { ?person :email ?email . }
FILTER(BOUND(?email))
}
값이 없는 것만:
FILTER(!BOUND(?email))
6. UNION - 대안 패턴
UNION이란?
여러 패턴 중 하나라도 매칭되면 OK
예제 1: 직원 또는 계약직
SELECT ?person ?name
WHERE {
?person :name ?name .
{
?person a :Employee .
}
UNION
{
?person a :Contractor .
}
}
예제 2: 이메일 또는 전화번호
SELECT ?person ?contact
WHERE {
?person :name ?name .
{
?person :email ?contact .
}
UNION
{
?person :phone ?contact .
}
}
다중 UNION
SELECT ?person ?status
WHERE {
?person :name ?name .
{
?person a :Student .
BIND("학생" AS ?status)
}
UNION
{
?person a :Employee .
BIND("직원" AS ?status)
}
UNION
{
?person a :Retiree .
BIND("퇴직자" AS ?status)
}
}
OPTIONAL vs UNION
OPTIONAL:
- LEFT OUTER JOIN
- 왼쪽 패턴은 항상 매칭
UNION:
- OR 조건
- 어느 쪽이든 매칭 가능
7. Property Paths
Property Paths란?
복잡한 경로를 간단히 표현
기본 연산자
1. / (순차)
# A의 B의 C
?person :parent / :parent ?grandparent .
# = 다음과 동일:
?person :parent ?p .
?p :parent ?grandparent .
2. | (대안)
# 이메일 또는 전화번호
?person (:email | :phone) ?contact .
3. * (0회 이상 반복)
# 조상 (자기 자신 포함)
?person :parent* ?ancestor .
4. + (1회 이상 반복)
# 조상 (자기 자신 제외)
?person :parent+ ?ancestor .
5. ? (0회 또는 1회)
# 직접 상사 또는 본인
?person :reportsTo? ?manager .
6. ^ (역방향)
# A의 자식 = B의 부모가 A
?child ^:parent ?person .
# = 다음과 동일:
?person :parent ?child .
실전 예제
예제 1: 친구의 친구
SELECT ?person ?fof
WHERE {
:홍길동 :knows / :knows ?fof .
FILTER(?fof != :홍길동)
}
예제 2: 모든 조상
SELECT ?person ?ancestor
WHERE {
?person :name "홍길동" ;
:parent+ ?ancestor .
}
예제 3: 조직 계층
# 모든 하위 부서
SELECT ?dept ?subdept
WHERE {
:개발팀 :hasSubDepartment* ?subdept .
}
예제 4: 관리자 체인
# 최상위 관리자까지
SELECT ?person ?manager
WHERE {
?person :name "홍길동" ;
:reportsTo+ ?manager .
}
ORDER BY ?manager
예제 5: 역방향 관계
# 저자가 쓴 책
SELECT ?author ?book
WHERE {
?author :name "김영하" ;
^:writtenBy ?book .
}
Property Paths 조합
# 부모의 형제의 자식 (사촌)
?person :parent / :sibling / ^:parent ?cousin .
# 2단계 이내 친구
?person :knows{1,2} ?friend .
# 정확히 3단계 조상
?person :parent{3} ?ancestor .
8. Aggregation - 집계 함수
COUNT
전체 개수:
SELECT (COUNT(*) AS ?total)
WHERE {
?person a :Person .
}
DISTINCT COUNT:
SELECT (COUNT(DISTINCT ?company) AS ?companyCount)
WHERE {
?person :worksAt ?company .
}
SUM, AVG, MIN, MAX
SELECT
(SUM(?age) AS ?totalAge)
(AVG(?age) AS ?avgAge)
(MIN(?age) AS ?minAge)
(MAX(?age) AS ?maxAge)
WHERE {
?person :age ?age .
}
GROUP BY
회사별 직원 수:
SELECT ?company (COUNT(?person) AS ?employeeCount)
WHERE {
?person :worksAt ?company .
}
GROUP BY ?company
ORDER BY DESC(?employeeCount)
HAVING
직원이 10명 이상인 회사만:
SELECT ?company (COUNT(?person) AS ?count)
WHERE {
?person :worksAt ?company .
}
GROUP BY ?company
HAVING (COUNT(?person) >= 10)
GROUP_CONCAT
그룹의 값들을 문자열로:
SELECT ?company
(GROUP_CONCAT(?name; separator=", ") AS ?employees)
WHERE {
?person :worksAt ?company ;
:name ?name .
}
GROUP BY ?company
결과:
| company | employees |
|--------------|------------------------------|
| :데이터공작소 | "홍길동, 김철수, 이영희" |
복합 집계
PREFIX : <http://example.org/>
SELECT ?company
(COUNT(?person) AS ?count)
(AVG(?age) AS ?avgAge)
(MIN(?salary) AS ?minSalary)
(MAX(?salary) AS ?maxSalary)
WHERE {
?person :worksAt ?company ;
:age ?age ;
:salary ?salary .
}
GROUP BY ?company
HAVING (COUNT(?person) >= 5)
ORDER BY DESC(?avgAge)
9. CONSTRUCT - 새 그래프 생성
CONSTRUCT란?
SELECT는 표 형태로 결과를 반환하지만, CONSTRUCT는 새로운 RDF 그래프를 생성합니다.
기본 문법
CONSTRUCT {
# 생성할 Triple 패턴
?subject ?predicate ?object .
}
WHERE {
# 매칭할 Triple 패턴
?subject ?predicate ?object .
}
예제 1: 단순 복사
CONSTRUCT {
?person :name ?name .
}
WHERE {
?person :name ?name .
}
결과 (RDF):
:홍길동 :name "홍길동" .
:김철수 :name "김철수" .
예제 2: 변환
FOAF → Schema.org 변환:
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX schema: <http://schema.org/>
CONSTRUCT {
?person schema:name ?name ;
schema:email ?email .
}
WHERE {
?person foaf:name ?name ;
foaf:mbox ?email .
}
예제 3: 추론
간접 관계 생성:
CONSTRUCT {
?person :hasAncestor ?ancestor .
}
WHERE {
?person :parent+ ?ancestor .
}
예제 4: 요약 정보
CONSTRUCT {
?company :employeeCount ?count .
}
WHERE {
SELECT ?company (COUNT(?person) AS ?count)
WHERE {
?person :worksAt ?company .
}
GROUP BY ?company
}
실전 활용
데이터 ETL:
- 소스 시스템에서 데이터 추출
- CONSTRUCT로 변환
- 타겟 시스템에 적재
온톨로지 매핑:
- SNOMED CT → ICD-10
- FIBO → 내부 온톨로지
10. ASK - 존재 여부 확인
ASK란?
true/false만 반환 (EXISTS 확인)
기본 문법
ASK {
# 패턴
}
예제
예제 1: 사람이 존재하나?
PREFIX : <http://example.org/>
ASK {
?person a :Person .
}
결과: true 또는 false
예제 2: 특정 관계 확인
ASK {
:홍길동 :worksAt :데이터공작소 .
}
예제 3: 조건부 확인
ASK {
?person :age ?age .
FILTER(?age < 18)
}
실전 활용
1. 검증:
# 모든 책에 저자가 있나?
ASK {
?book a :Book .
FILTER NOT EXISTS {
?book :writtenBy ?author .
}
}
2. 권한 확인:
# 이 사용자가 관리자인가?
ASK {
:user123 a :Administrator .
}
3. 데이터 품질:
# 중복 ISBN이 있나?
ASK {
?book1 :isbn ?isbn .
?book2 :isbn ?isbn .
FILTER(?book1 != ?book2)
}
11. DESCRIBE - 자원 설명
DESCRIBE란?
자원에 대한 모든 정보 반환
기본 문법
DESCRIBE <uri>
예제
예제 1: 특정 자원
DESCRIBE :홍길동
결과 (구현 의존적):
:홍길동 :name "홍길동" ;
:age 45 ;
:worksAt :데이터공작소 ;
:email "hong@example.com" .
예제 2: 변수
DESCRIBE ?person
WHERE {
?person :name "홍길동" .
}
예제 3: 다중 자원
DESCRIBE ?person ?company
WHERE {
?person :worksAt ?company .
}
활용
1. 탐색:
- 자원의 전체 정보 확인
- API 개발 (GET /resource/:id)
2. 캐싱:
- 자원 정보를 한번에 가져와 캐시
3. 디버깅:
- 온톨로지 개발 중 자원 확인
12. Subquery와 Federation
Subquery
쿼리 안에 쿼리:
예제 1: 평균 이상 나이
SELECT ?name ?age
WHERE {
?person :name ?name ;
:age ?age .
{
SELECT (AVG(?age) AS ?avgAge)
WHERE {
?person :age ?age .
}
}
FILTER(?age > ?avgAge)
}
예제 2: Top 10 회사의 직원
SELECT ?person ?company
WHERE {
?person :worksAt ?company .
{
SELECT ?company
WHERE {
?person :worksAt ?company .
}
GROUP BY ?company
ORDER BY DESC(COUNT(?person))
LIMIT 10
}
}
SPARQL Federation (SERVICE)
외부 엔드포인트 쿼리:
예제: DBpedia + 로컬 데이터
PREFIX dbr: <http://dbpedia.org/resource/>
PREFIX dbo: <http://dbpedia.org/ontology/>
SELECT ?person ?name ?dbpediaInfo
WHERE {
# 로컬 데이터
?person :name ?name ;
:dbpediaURI ?dbpediaURI .
# DBpedia에서 정보 가져오기
SERVICE <http://dbpedia.org/sparql> {
?dbpediaURI dbo:abstract ?dbpediaInfo .
FILTER(LANG(?dbpediaInfo) = "en")
}
}
예제: Wikidata Federation
PREFIX wd: <http://www.wikidata.org/entity/>
PREFIX wdt: <http://www.wikidata.org/prop/direct/>
SELECT ?person ?name ?birthDate
WHERE {
?person :name ?name ;
:wikidataID ?wd .
SERVICE <https://query.wikidata.org/sparql> {
?wd wdt:P569 ?birthDate .
}
}
13. 실습: DBpedia 쿼리
DBpedia란?
Wikipedia의 구조화된 정보를 RDF로 변환한 Knowledge Graph
엔드포인트: https://dbpedia.org/sparql
🎮 SPARQL Playground 열기
URL: https://kss.ai.kr/sparql-playground
실습 1: 한국 대통령 목록
PREFIX dbo: <http://dbpedia.org/ontology/>
PREFIX dbr: <http://dbpedia.org/resource/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?president ?name ?birthDate
WHERE {
?president dbo:office dbr:President_of_South_Korea ;
rdfs:label ?name ;
dbo:birthDate ?birthDate .
FILTER(LANG(?name) = "en")
}
ORDER BY ?birthDate
실습 2: BTS 멤버
PREFIX dbo: <http://dbpedia.org/ontology/>
PREFIX dbr: <http://dbpedia.org/resource/>
SELECT ?member ?name ?birthDate
WHERE {
?member dbo:bandMember dbr:BTS_(band) ;
foaf:name ?name ;
dbo:birthDate ?birthDate .
}
ORDER BY ?birthDate
실습 3: 서울의 대학교
PREFIX dbo: <http://dbpedia.org/ontology/>
PREFIX dbr: <http://dbpedia.org/resource/>
SELECT ?university ?name ?studentCount
WHERE {
?university a dbo:University ;
dbo:city dbr:Seoul ;
rdfs:label ?name ;
dbo:numberOfStudents ?studentCount .
FILTER(LANG(?name) = "ko")
}
ORDER BY DESC(?studentCount)
LIMIT 10
실습 4: 삼성 계열사
PREFIX dbo: <http://dbpedia.org/ontology/>
PREFIX dbr: <http://dbpedia.org/resource/>
SELECT ?company ?name
WHERE {
?company dbo:parentCompany dbr:Samsung ;
rdfs:label ?name .
FILTER(LANG(?name) = "en")
}
실습 5: K-POP 그룹
PREFIX dbo: <http://dbpedia.org/ontology/>
PREFIX dbp: <http://dbpedia.org/property/>
SELECT ?group ?name ?debutYear
WHERE {
?group a dbo:Band ;
dbo:genre dbr:K-pop ;
rdfs:label ?name ;
dbp:yearsActive ?debutYear .
FILTER(LANG(?name) = "ko")
}
ORDER BY DESC(?debutYear)
LIMIT 20
14. 실습: Wikidata 쿼리
Wikidata란?
Wikipedia의 자매 프로젝트, 구조화된 데이터베이스
엔드포인트: https://query.wikidata.org/sparql
실습 6: 노벨상 수상자 (한국)
PREFIX wd: <http://www.wikidata.org/entity/>
PREFIX wdt: <http://www.wikidata.org/prop/direct/>
SELECT ?person ?personLabel ?awardLabel ?year
WHERE {
?person wdt:P27 wd:Q884 ; # 한국 국적
wdt:P166 ?award ; # 수상
wdt:P585 ?year . # 시기
?award wdt:P31 wd:Q7191 . # 노벨상
SERVICE wikibase:label {
bd:serviceParam wikibase:language "ko,en" .
}
}
ORDER BY ?year
실습 7: 가장 높은 건물 (Top 10)
PREFIX wdt: <http://www.wikidata.org/prop/direct/>
PREFIX wd: <http://www.wikidata.org/entity/>
SELECT ?building ?buildingLabel ?height ?cityLabel
WHERE {
?building wdt:P31 wd:Q18142 ; # 건물
wdt:P2048 ?height ; # 높이
wdt:P131 ?city . # 위치
SERVICE wikibase:label {
bd:serviceParam wikibase:language "ko,en" .
}
}
ORDER BY DESC(?height)
LIMIT 10
실습 8: 영화 감독과 대표작
SELECT ?director ?directorLabel ?movie ?movieLabel ?year
WHERE {
?director wdt:P106 wd:Q2526255 ; # 영화 감독
wdt:P27 wd:Q884 . # 한국인
?movie wdt:P57 ?director ; # 감독
wdt:P577 ?year . # 개봉일
SERVICE wikibase:label {
bd:serviceParam wikibase:language "ko" .
}
}
ORDER BY ?director ?year
실습 9: 프로그래밍 언어 비교
SELECT ?lang ?langLabel ?year ?paradigmLabel
WHERE {
?lang wdt:P31 wd:Q9143 ; # 프로그래밍 언어
wdt:P571 ?year ; # 탄생일
wdt:P3966 ?paradigm . # 패러다임
FILTER(?year >= "1990-01-01"^^xsd:dateTime)
SERVICE wikibase:label {
bd:serviceParam wikibase:language "en" .
}
}
ORDER BY ?year
LIMIT 20
실습 10: 대학교 순위 (졸업생 수)
SELECT ?university ?universityLabel ?alumniCount
WHERE {
?university wdt:P31 wd:Q3918 ; # 대학교
wdt:P17 wd:Q884 ; # 한국
wdt:P2196 ?alumniCount . # 졸업생 수
SERVICE wikibase:label {
bd:serviceParam wikibase:language "ko" .
}
}
ORDER BY DESC(?alumniCount)
LIMIT 10
15. 성능 최적화
최적화 팁 10가지
1. 구체적인 패턴 먼저
❌ 나쁜 예:
SELECT ?person ?name
WHERE {
?person ?p ?o . # 너무 일반적
?person :name ?name .
}
✅ 좋은 예:
SELECT ?person ?name
WHERE {
?person :name ?name . # 구체적
?person a :Person .
}
2. LIMIT 사용
항상 LIMIT 추가 (특히 테스트 시):
SELECT ?s ?p ?o
WHERE {
?s ?p ?o .
}
LIMIT 100
3. FILTER 위치
❌ 나쁜 예:
WHERE {
?person ?p ?o .
FILTER(?p = :name) # 늦은 필터링
}
✅ 좋은 예:
WHERE {
?person :name ?o . # 패턴에 직접 명시
}
4. OPTIONAL 최소화
OPTIONAL은 비용이 큼 - 꼭 필요할 때만 사용
5. Property Paths 주의
❌ 나쁜 예:
?person :knows* ?friend . # 전체 탐색
✅ 좋은 예:
?person :knows{1,3} ?friend . # 깊이 제한
6. COUNT 최적화
❌ 나쁜 예:
SELECT ?company (COUNT(?person) AS ?count)
WHERE {
?person :worksAt ?company ;
:age ?age ; # 불필요
:email ?email . # 불필요
}
GROUP BY ?company
✅ 좋은 예:
SELECT ?company (COUNT(?person) AS ?count)
WHERE {
?person :worksAt ?company . # 필요한 것만
}
GROUP BY ?company
7. DISTINCT 대신 GROUP BY
DISTINCT는 느림 - 가능하면 GROUP BY 사용
8. 인덱스 활용
Triple Store마다 다르지만, 일반적으로:
- Subject로 시작하는 패턴이 빠름
- Object로 시작하는 패턴은 느림
9. Subquery 최소화
복잡한 Subquery는 비용이 큼
10. UNION 대신 FILTER
❌ 나쁜 예:
{
?person a :Employee .
}
UNION
{
?person a :Contractor .
}
✅ 좋은 예:
?person a ?type .
FILTER(?type IN (:Employee, :Contractor))
쿼리 프로파일링
1. EXPLAIN (구현 의존적)
EXPLAIN
SELECT ?person ?name
WHERE {
?person :name ?name .
}
2. 실행 시간 측정
- SPARQL Playground의 Timer 사용
- 여러 번 실행해서 평균 측정
3. 단계별 테스트
- 작은 패턴부터 시작
- 점진적으로 확장
16. 요약과 다음 단계
핵심 정리
1. 4가지 쿼리 형식
- SELECT: 표 형태 결과
- CONSTRUCT: RDF 그래프 생성
- ASK: true/false
- DESCRIBE: 자원 설명
2. 고급 기능
- FILTER: 조건 필터링
- OPTIONAL: 선택적 매칭
- UNION: 대안 패턴
- BIND: 변수 바인딩
3. Property Paths
/: 순차|: 대안*: 0회 이상+: 1회 이상^: 역방향
4. Aggregation
- COUNT, SUM, AVG, MIN, MAX
- GROUP BY, HAVING
- GROUP_CONCAT
5. Federation
- SERVICE로 외부 엔드포인트 쿼리
- DBpedia, Wikidata 활용
6. 성능 최적화
- 구체적 패턴 먼저
- LIMIT 사용
- FILTER 위치 최적화
- Property Paths 깊이 제한
실전 체크리스트
쿼리 작성 시:
- [ ] 네임스페이스 선언
- [ ] 명확한 Triple Pattern
- [ ] LIMIT 추가 (테스트)
- [ ] FILTER 최적화
- [ ] OPTIONAL 필요성 검토
- [ ] 성능 고려
DBpedia/Wikidata 사용 시:
- [ ] 엔드포인트 확인
- [ ] 언어 필터 (LANG)
- [ ] 서비스 라벨 사용
- [ ] 타임아웃 고려
다음 챕터
Chapter 5: 추론 엔진과 온톨로지 도구
온톨로지의 마법을 경험합니다:
- RDFS vs OWL 추론 비교
- 4가지 Reasoner (Pellet, HermiT, FaCT++, ELK)
- Protégé 완전 가이드
- Python/Java 프로그래밍
- Reasoning Engine 시뮬레이터로 실습!
📝 연습 문제
문제 1: 기본 SELECT
다음 데이터에서 30세 이상인 사람의 이름을 조회하세요:
:홍길동 :name "홍길동" ; :age 45 .
:김철수 :name "김철수" ; :age 28 .
:이영희 :name "이영희" ; :age 35 .
정답:
SELECT ?name
WHERE {
?person :name ?name ;
:age ?age .
FILTER(?age >= 30)
}
문제 2: Property Paths
모든 조상을 찾는 쿼리를 작성하세요:
정답:
SELECT ?person ?ancestor
WHERE {
?person :name "홍길동" ;
:parent+ ?ancestor .
}
문제 3: Aggregation
회사별 평균 연봉을 계산하세요:
정답:
SELECT ?company (AVG(?salary) AS ?avgSalary)
WHERE {
?person :worksAt ?company ;
:salary ?salary .
}
GROUP BY ?company
ORDER BY DESC(?avgSalary)
🔗 참고 자료
W3C 표준
- SPARQL 1.1 Query: https://www.w3.org/TR/sparql11-query/
- SPARQL 1.1 Update: https://www.w3.org/TR/sparql11-update/
- SPARQL 1.1 Protocol: https://www.w3.org/TR/sparql11-protocol/
실습 엔드포인트
- DBpedia: https://dbpedia.org/sparql
- Wikidata: https://query.wikidata.org/
- Bio2RDF: https://bio2rdf.org/sparql
도구
- SPARQL Playground: https://kss.ai.kr/sparql-playground
- Yasgui: https://yasgui.triply.cc/
- Apache Jena: https://jena.apache.org/
학습 자료
- SPARQL Tutorial: https://www.w3.org/2009/Talks/0615-qbe/
- Wikidata Query Examples: https://www.wikidata.org/wiki/Wikidata:SPARQL_query_service/queries/examples