本教程面向零基础学习者,从 SELECT 到 CONSTRUCT,从 FILTER 到 Property Path,覆盖 SPARQL 所有核心功能。所有示例都可以直接在 Fuseki Web UI 中运行。
SPARQL(读作「sparkle」,字面意思「闪亮」)是 RDF 数据的查询语言,就像 SQL 是关系数据库的查询语言一样。
概念 | SQL 对应 | SPARQL 对应 |
|---|---|---|
行 | Row | 三元组 (s, p, o) |
表 | Table | RDF Graph |
列 | Column | 属性 (Property) |
查询 | SELECT ... FROM ... WHERE | SELECT ... WHERE { ... } |
过滤 | WHERE age > 18 | FILTER (?age > 18) |
连接 | JOIN | 共用变量(自动 INNER JOIN) |
左连接 | LEFT JOIN | OPTIONAL { } |
SELECT ?name ?age
WHERE {
?person :hasName ?name .
?person :hasAge ?age .
}核心思想
SPARQL 本质就是模式匹配:你给一个图模式(Graph Pattern),引擎在图里找到所有匹配的部分,把绑定告诉你就行。
每条 SPARQL 查询由三部分组成:
PREFIX 短名: <URL> # 前缀声明
SELECT ?变量1 ?变量2 # 返回哪些列
WHERE { # 匹配条件
三元组模式
}RDF 中所有资源都用完整 URL 标识,前缀让这些 URL 可读:
PREFIX : <http://example.org/company-leave#>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>常见错误
Fuseki 不会自动加前缀。用了 rdfs:label 就必须写 PREFIX rdfs:,否则报 Unresolved prefixed name。
SPARQL 变量以 ? 或 $ 开头,习惯用问号:
?employee ?leave ?days ?statusSELECT *
WHERE {
?s ?p ?o .
}
LIMIT 10实践技巧
数据量大的时候一定要加 LIMIT,否则浏览器会卡死,服务器也可能超时。
SPARQL 的核心 —— 构造图模式,引擎在 RDF 图中找到匹配的子图。
PREFIX : <http://example.org/company-leave#>
SELECT ?p ?o
WHERE {
:Emp_Zhao ?p ?o .
}输出:赵工程师的所有属性和值(rdf:type、:salary、:reportsTo 等)。
PREFIX : <http://example.org/company-leave#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?name ?salary ?deptName
WHERE {
?e rdfs:label ?name .
?e :salary ?salary .
?e :belongsTo ?dept .
?dept :departmentName ?deptName .
}这里四个三元组模式共享变量 ?e,引擎会自动做 JOIN。
PREFIX : <http://example.org/company-leave#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?empName ?days ?status
WHERE {
?l a :LeaveRequest ;
:requestedBy ?e ;
:leaveDays ?days ;
:leaveStatus ?status .
?e rdfs:label ?empName .
}
ORDER BY ?status ?days分号简写
?l a :LeaveRequest ; :requestedBy ?e ; :leaveDays ?days 等价于三条语句,主语都是 ?l。
FILTER 筛选结果,写在大括号内。
PREFIX : <http://example.org/company-leave#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?name ?salary
WHERE {
?e rdfs:label ?name ;
:salary ?salary .
FILTER (?salary > 20000)
}
ORDER BYDESC(?salary)PREFIX : <http://example.org/company-leave#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?name ?salary
WHERE {
?e rdfs:label ?name ;
:salary ?salary .
FILTER(regex(str(?name), "工程师"))
}PREFIX : <http://example.org/company-leave#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?name ?salary ?deptName
WHERE {
?e rdfs:label ?name ;
:salary ?salary ;
:belongsTo ?dept .
?dept :departmentName ?deptName .
FILTER (
?salary >= 15000 &&
?salary <= 20000 &&
?deptName != "销售部"@zh
)
}函数 | 示例 | 说明 |
|---|---|---|
比较 | FILTER (?x > 5) | > < >= <= = != |
逻辑 | FILTER (?a && !?b) | && || ! |
字符串 | FILTER regex(str(?n), "^张") | 正则匹配 |
类型检查 | FILTER isURI(?x) | isURI / isLiteral / isBlank |
存在检查 | FILTER bound(?y) | 变量是否有绑定 |
IN 集合 | FILTER (?x IN (1, 2, 3)) | 是不是列表中的值 |
语言标签 | FILTER lang(?name) = "zh" | 按语言筛选 |
类似于 SQL 的 LEFT JOIN。如果匹配不到,变量为 unbound(空),不会过滤掉这一行。
PREFIX : <http://example.org/company-leave#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?name ?leaveLabel ?days
WHERE {
?e rdfs:label ?name .
OPTIONAL {
?l :requestedBy ?e ;
rdfs:label ?leaveLabel ;
:leaveDays ?days .
}
}结果:所有员工都会显示,没有请假的员工 leaveLabel 和 days 为空。
FILTER (!bound(?leaveLabel)) # 找出没有请假记录的员工合并多个模式的匹配结果,类似于 SQL 的 UNION ALL。
PREFIX : <http://example.org/company-leave#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?name ?typeLabel
WHERE {
{ ?e a :Manager ; rdfs:label ?name . BIND("Manager" AS ?typeLabel) }
UNION
{ ?e a :HRStaff ; rdfs:label ?name . BIND("HR" AS ?typeLabel) }
UNION
{ ?e :salary ?s ; rdfs:label ?name . FILTER (?s >= 30000) BIND("高薪" AS ?typeLabel) }
}这是 SPARQL 最强大的功能之一,可以沿着属性路径进行图遍历。
语法 | 含义 | 示例 |
|---|---|---|
:reportsTo ?m | 一步到达 | 直接上级 |
:reportsTo+ ?m | 一步或多步 | 所有上级(传递闭包) |
:reportsTo* ?m | 零步或多步 | 自己 + 所有上级 |
:reportsTo{1,3} | 1~3 步 | 三级以内上级 |
^:reportsTo ?m | 反向 | 找下属 |
:p1|:p2 ?o | 或(两种路径任选一) | 取 p1 或 p2 |
:p1/:p2 ?o | 串联(先 p1 再 p2) | 走两步 |
PREFIX : <http://example.org/company-leave#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?name ?level
WHERE {
:Emp_Zhao :reportsTo+ ?mgr .
?mgr rdfs:label ?name .
}PREFIX : <http://example.org/company-leave#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?name
WHERE {
?emp ^:reportsTo+ :TechMgr_Wang .
?emp rdfs:label ?name .
}PREFIX : <http://example.org/company-leave#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?leaver ?leaveLabel ?days
WHERE {
?l :requestedBy ?e .
?e :reportsTo+ :CEO_Zhang .
?e rdfs:label ?leaver .
?l rdfs:label ?leaveLabel ;
:leaveDays ?days .
}PREFIX : <http://example.org/company-leave#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?deptName (COUNT(?e) AS ?count)
WHERE {
?e :belongsTo ?dept .
?dept :departmentName ?deptName .
}
GROUP BY ?deptName
ORDER BYDESC(?count)PREFIX : <http://example.org/company-leave#>
SELECT
(SUM(?days) AS ?totalDays)
(AVG(?days) AS ?avgDays)
(MAX(?days) AS ?maxDays)
(MIN(?days) AS ?minDays)
WHERE {
?l :leaveDays ?days .
}PREFIX : <http://example.org/company-leave#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?empName (SUM(?days) AS ?totalDays)
WHERE {
?l :requestedBy ?e ;
:leaveDays ?days .
?e rdfs:label ?empName .
}
GROUP BY ?empName
HAVING (SUM(?days) > 20)
ORDER BYDESC(?totalDays)PREFIX : <http://example.org/company-leave#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?name ?salary
WHERE {
?e rdfs:label ?name ;
:salary ?salary .
}
ORDER BYDESC(?salary) # DESC 降序,ASC 升序
LIMIT 3 # 只取前三
OFFSET 1 # 跳过第一条(分页第二页)分页公式
LIMIT ?pageSize OFFSET ((?page - 1) * ?pageSize) —— 但 SPARQL 不支持表达式作为 LIMIT/OFFSET 参数,只能写常数。
CONSTRUCT 不是返回表格,而是构造新的 RDF 三元组。适合做数据转换或导出推理结果。
PREFIX : <http://example.org/company-leave#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
CONSTRUCT {
?e :needApproval :CEO_Zhang .
}
WHERE {
?l :requestedBy ?e ;
:leaveDays ?days .
FILTER (?days > 10)
}执行后 Fuseki 会返回构造出的 RDF(可切换 TTL / RDF/XML 格式查看)。
PREFIX : <http://example.org/company-leave#>
ASK {
:CEO_Zhang a :Manager .
}返回 true 或 false。一个 boolean,不是表格。
PREFIX : <http://example.org/company-leave#>
DESCRIBE :Emp_Zhao返回关于 :Emp_Zhao 的所有已知三元组(具体返回什么由服务端决定,不是标准化的)。
注意
INSERT/DELETE 是 SPARQL Update,必须在 Fuseki 的 UPDATE 端点(/ds/update)执行,不是在 QUERY 端点。
PREFIX : <http://example.org/company-leave#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
INSERT DATA {
:Emp_NewUser a :Employee ;
:employeeId "E099" ;
:salary 18000 ;
:reportsTo :TechMgr_Wang ;
:belongsTo :DeptTech ;
rdfs:label "新员工"@zh .
}PREFIX : <http://example.org/company-leave#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
DELETE DATA {
:Emp_NewUser rdfs:label "新员工"@zh .
}PREFIX : <http://example.org/company-leave#>
INSERT {
?l :needsCEOApproval true .
}
WHERE {
?l a :LeaveRequest ;
:leaveDays ?days .
FILTER (?days > 10)
}PREFIX : <http://example.org/company-leave#>
DELETE WHERE {
:Emp_NewUser ?p ?o .
}PREFIX : <http://example.org/company-leave#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?name ?monthlySalary ?annualSalary
WHERE {
?e rdfs:label ?name ;
:salary ?monthlySalary .
BIND (?monthlySalary * 12 AS ?annualSalary)
}PREFIX : <http://example.org/company-leave#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?name ?salary
WHERE {
VALUES ?dept { :DeptTech :DeptHR }
?e :belongsTo ?dept ;
rdfs:label ?name ;
:salary ?salary .
}PREFIX : <http://example.org/company-leave#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?name ?salary
WHERE {
SELECT ?e (AVG(?s) AS ?avgSalary)
WHERE {
?e :belongsTo :DeptTech ;
:salary ?s .
}
?e rdfs:label ?name ;
:salary ?salary .
FILTER (?salary > ?avgSalary)
}基于公司请假数据,以下是可以直接复制到 Fuseki 运行的 8 个实战查询。
PREFIX : <http://example.org/company-leave#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?name ?salary
WHERE {
?e :belongsTo :DeptTech ;
rdfs:label ?name ;
:salary ?salary .
}
ORDER BYDESC(?salary)PREFIX : <http://example.org/company-leave#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?empName
(COUNT(?l) AS ?leaveCount)
(SUM(?days) AS ?totalDays)
WHERE {
?l :requestedBy ?e ;
:leaveDays ?days .
?e rdfs:label ?empName .
}
GROUP BY ?empName
ORDER BYDESC(?totalDays)PREFIX : <http://example.org/company-leave#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?empName ?leaveLabel ?days ?risk
WHERE {
?l :requestedBy ?e ;
:leaveDays ?days ;
rdfs:label ?leaveLabel ;
:leaveStatus ?status .
?e rdfs:label ?empName .
BIND (
IF(?days > 10, "高风险",
IF(?days > 5, "中风险",
"低风险")
) AS ?risk
)
FILTER (?status = "pending"@en || ?status = "approved"@en)
}
ORDER BYDESC(?days)PREFIX : <http://example.org/company-leave#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?name ?hop
WHERE {
:Sales_Emp_Chu :reportsTo+ ?mgr .
BIND ("上级"@zh AS ?hop)
?mgr rdfs:label ?name .
}PREFIX : <http://example.org/company-leave#>
SELECT ?deptName
(COUNT(?l) AS ?count)
(SUM(?days) AS ?total)
(ROUND(AVG(?days)*10)/10 AS ?avg)
WHERE {
?l :requestedBy ?e ;
:leaveDays ?days .
?e :belongsTo ?dept .
?dept :departmentName ?deptName .
}
GROUP BY ?deptName
ORDER BYDESC(?total)PREFIX : <http://example.org/company-leave#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
CONSTRUCT {
?approver :approvedLeaveOf ?leaver .
}
WHERE {
?l :approvedBy ?approver ;
:requestedBy ?leaver .
}PREFIX : <http://example.org/company-leave#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
SELECT ?empName ?leaveLabel ?start ?end ?days
WHERE {
?l :requestedBy ?e ;
:startDate ?start ;
:endDate ?end ;
:leaveDays ?days ;
rdfs:label ?leaveLabel .
?e rdfs:label ?empName .
# 查 7 月有请假的(startDate 或 endDate 在 7 月内)
FILTER (
(?start >= "2026-07-01"^^xsd:date && ?start <= "2026-07-31"^^xsd:date) ||
(?end >= "2026-07-01"^^xsd:date && ?end <= "2026-07-31"^^xsd:date)
)
}
ORDER BY ?startPREFIX : <http://example.org/company-leave#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?empName ?leaveLabel
(COALESCE(?approverName, "待审批"@zh) AS ?approver)
WHERE {
?l :requestedBy ?e ;
rdfs:label ?leaveLabel .
?e rdfs:label ?empName .
OPTIONAL {
?l :approvedBy ?p .
?p rdfs:label ?approverName .
}
}错误信息 | 原因 | 修正 |
|---|---|---|
Unresolved prefixed name: rdfs:label | 忘了声明 PREFIX rdfs | 在查询最前面加 PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> |
Parse error: Encountered " "..." at line X | 语法错误(缺括号/句号/引号) | 检查每一行末尾的 . 和括号匹配 |
No such property: xxx | 查询了不存在的属性 | 用 SELECT ?p WHERE { ?s ?p ?o } 先看有哪些属性 |
查询返回空 | 数据没加载 / FILTER 太严格 / 类型不匹配 | 先跑 SELECT * WHERE { ?s ?p ?o } LIMIT 10 确认有数据 |
GROUP BY 后选错了列 | SELECT 了非聚合非分组的变量 | 非聚合列必须出现在 GROUP BY 里 |
INSERT 不生效 | 在 query 端点发了 UPDATE 语句 | 切到 Fuseki 的 update 端点,或 Web UI 点 "update" 标签 |
调试三部曲
1. SELECT * WHERE { ?s ?p ?o } LIMIT 10 —— 确认数据在
2. SELECT ?s ?p ?o WHERE { :已知资源 ?p ?o } —— 缩小范围看资源
3. 逐步加条件,每加一个就测一次 —— 定位哪个条件过滤掉了结果
SELECT * WHERE { ?s ?p ?o } LIMIT 100SPARQL 完全入门教程 — 所有示例基于公司请假数据集 — 直接在 Fuseki Web UI 中可运行