AgensGraph 完全指南 / 第 04 章:Cypher 基础
第 04 章:Cypher 基础
4.1 Cypher 简介
Cypher 是由 Neo4j 设计的声明式图查询语言,现已成为 openCypher 标准。AgensGraph 完整支持 Cypher 语法,让开发者可以用直观的 ASCII 艺术来表达图模式。
4.1.1 Cypher 的设计哲学
Cypher 核心思想: "What, not How"
SQL: SELECT name FROM users WHERE age > 30 → 声明"要什么"
Cypher: MATCH (n:Person) WHERE n.age > 30 RETURN n → 声明"要什么模式"
Gremlin: g.V().hasLabel('Person').has('age',gt(30)) → 描述"怎么走"
4.1.2 Cypher 语法速查
| 符号 | 含义 | 示例 |
|---|---|---|
( ) | 顶点(圆括号) | (n) |
--> | 有向边 | (a)-[:KNOWS]->(b) |
-- | 无向边(查询时) | (a)--(b) |
:Label | 标签 | (p:Person) |
{key: val} | 属性 | (p:Person {name: 'Alice'}) |
[] | 边的描述 | [r:KNOWS] |
* | 变长路径 | [:KNOWS*1..3] |
4.2 设置图路径
在执行 Cypher 查询之前,必须先设置图路径(graph_path):
-- 创建图(如果不存在)
CREATE GRAPH IF NOT EXISTS demo;
-- 设置当前图路径
SET graph_path = demo;
-- 验证
SHOW graph_path;
4.3 CREATE — 创建数据
4.3.1 创建顶点
-- 创建一个简单顶点
CREATE (n:Person {name: '张三', age: 30});
RETURN n;
-- 创建多个标签的顶点
CREATE (n:Person:Employee {name: '李四', age: 28, department: '技术部'});
RETURN n;
-- 创建无标签顶点
CREATE (n {value: 42});
RETURN n;
-- 创建并返回
CREATE (n:Person {name: '王五'})
RETURN id(n) AS node_id, n.name AS name;
4.3.2 创建边
-- 创建两个顶点和它们之间的边
CREATE (a:Person {name: 'Alice'})
CREATE (b:Person {name: 'Bob'})
CREATE (a)-[:KNOWS {since: 2020}]->(b)
RETURN a, b;
-- 在已存在的顶点之间创建边
MATCH (a:Person {name: 'Alice'})
MATCH (b:Person {name: 'Bob'})
CREATE (a)-[:WORKS_WITH {project: 'GraphDB'}]->(b)
RETURN a, b;
4.3.3 创建复杂结构
-- 一次创建完整的子图
CREATE
(alice:Person {name: 'Alice', age: 30}),
(bob:Person {name: 'Bob', age: 28}),
(carol:Person {name: 'Carol', age: 32}),
(company:Company {name: 'TechCorp', founded: 2015}),
(alice)-[:KNOWS {since: 2018}]->(bob),
(bob)-[:KNOWS {since: 2019}]->(carol),
(alice)-[:WORKS_AT {position: 'Tech Lead', since: 2020}]->(company),
(bob)-[:WORKS_AT {position: 'Engineer', since: 2021}]->(company)
RETURN alice, bob, carol, company;
4.3.4 CREATE 注意事项
注意:
CREATE总是创建新元素,即使重复也会创建(可能产生重复数据)- 需要避免重复时,应使用
MERGE(见 4.6 节)- 边必须连接两个顶点,不能创建孤立的边
4.4 MATCH — 模式匹配
MATCH 是 Cypher 的核心语句,用于在图中查找符合特定模式的数据。
4.4.1 基本顶点匹配
-- 匹配所有顶点
MATCH (n)
RETURN n;
-- 匹配带标签的顶点
MATCH (p:Person)
RETURN p;
-- 匹配带属性的顶点
MATCH (p:Person {name: 'Alice'})
RETURN p;
-- 等价的 WHERE 写法
MATCH (p:Person)
WHERE p.name = 'Alice'
RETURN p;
4.4.2 边的匹配
-- 匹配有向关系
MATCH (a:Person)-[:KNOWS]->(b:Person)
RETURN a.name, b.name;
-- 匹配无向关系(双向都匹配)
MATCH (a:Person)--(b:Person)
RETURN a.name, b.name;
-- 匹配特定方向的关系(变量绑定)
MATCH (a:Person)-[r:KNOWS]->(b:Person)
RETURN a.name, type(r) AS relationship, b.name, r.since;
4.4.3 多模式匹配
-- 链式匹配:找朋友的朋友
MATCH (a:Person)-[:KNOWS]->(b:Person)-[:KNOWS]->(c:Person)
RETURN a.name, b.name, c.name;
-- 分支匹配:找同时有工作关系和朋友关系的
MATCH (a:Person)-[:KNOWS]->(b:Person)
MATCH (a)-[:WORKS_AT]->(c:Company)
RETURN a.name, b.name AS friend, c.name AS company;
4.4.4 WHERE 条件过滤
-- 比较操作
MATCH (p:Person)
WHERE p.age > 25
RETURN p.name, p.age;
-- 字符串匹配
MATCH (p:Person)
WHERE p.name STARTS WITH 'A'
RETURN p.name;
-- 正则表达式
MATCH (p:Person)
WHERE p.name =~ '.*li.*'
RETURN p.name;
-- IN 操作
MATCH (p:Person)
WHERE p.name IN ['Alice', 'Bob', 'Carol']
RETURN p.name;
-- 空值检查
MATCH (p:Person)
WHERE p.email IS NOT NULL
RETURN p.name, p.email;
-- 逻辑组合
MATCH (p:Person)
WHERE p.age >= 25 AND p.age <= 35 AND p.city = '北京'
RETURN p.name, p.age;
4.4.5 WHERE 条件操作符汇总
| 操作符 | 说明 | 示例 |
|---|---|---|
= | 等于 | WHERE n.age = 30 |
<> | 不等于 | WHERE n.status <> 'deleted' |
<, >, <=, >= | 比较 | WHERE n.age > 25 |
IS NULL / IS NOT NULL | 空值检查 | WHERE n.email IS NOT NULL |
STARTS WITH | 前缀匹配 | WHERE n.name STARTS WITH 'A' |
ENDS WITH | 后缀匹配 | WHERE n.email ENDS WITH '.com' |
CONTAINS | 包含 | WHERE n.bio CONTAINS '图数据库' |
=~ | 正则匹配 | WHERE n.phone =~ '1[3-9]\\d{9}' |
IN | 列表成员 | WHERE n.id IN [1, 2, 3] |
AND / OR / NOT | 逻辑运算 | WHERE a AND (b OR c) |
4.5 RETURN — 结果返回
4.5.1 返回顶点和边
-- 返回整个顶点
MATCH (p:Person)
RETURN p;
-- 返回属性
MATCH (p:Person)
RETURN p.name, p.age;
-- 使用别名
MATCH (p:Person)-[r:KNOWS]->(f:Person)
RETURN p.name AS person, r.since AS since_when, f.name AS friend;
-- 返回去重结果
MATCH (p:Person)-[:KNOWS]->(f:Person)
RETURN DISTINCT p.name;
-- 返回所有元素
MATCH (p:Person)-[r:KNOWS]->(f:Person)
RETURN *;
4.5.2 排序与分页
-- ORDER BY 排序
MATCH (p:Person)
RETURN p.name, p.age
ORDER BY p.age DESC;
-- SKIP + LIMIT 分页
MATCH (p:Person)
RETURN p.name, p.age
ORDER BY p.age DESC
SKIP 5
LIMIT 10;
-- 典型的分页公式
-- 第 page 页,每页 size 条
-- SKIP (page - 1) * size
-- LIMIT size
4.5.3 聚合函数
-- COUNT 计数
MATCH (p:Person)
RETURN count(p) AS total_persons;
-- 分组计数
MATCH (p:Person)-[:WORKS_AT]->(c:Company)
RETURN c.name AS company, count(p) AS employee_count
ORDER BY employee_count DESC;
-- 其他聚合
MATCH (p:Person)
RETURN
count(p) AS total,
avg(p.age) AS avg_age,
min(p.age) AS min_age,
max(p.age) AS max_age,
sum(p.age) AS sum_age,
collect(p.name) AS all_names;
4.6 MERGE — 创建或匹配
MERGE 是 Cypher 中最重要的语句之一:如果模式已存在则匹配,不存在则创建(“upsert"语义)。
4.6.1 基本 MERGE
-- 如果 Alice 存在则匹配,否则创建
MERGE (p:Person {name: 'Alice'})
ON CREATE SET p.created = datetime(), p.source = 'import'
ON MATCH SET p.last_seen = datetime()
RETURN p;
4.6.2 MERGE 的 ON CREATE 和 ON MATCH
-- 导入场景:用户注册(首次创建 vs 重复登录)
MERGE (u:User {email: '[email protected]'})
ON CREATE SET
u.name = 'Alice',
u.created = datetime(),
u.login_count = 1
ON MATCH SET
u.last_login = datetime(),
u.login_count = u.login_count + 1
RETURN u;
4.6.3 MERGE 边
-- 确保两个顶点和它们之间的关系都存在
MATCH (a:Person {name: 'Alice'})
MATCH (b:Person {name: 'Bob'})
MERGE (a)-[r:KNOWS]->(b)
ON CREATE SET r.since = datetime()
RETURN a, r, b;
注意:
MERGE会检查整个模式是否完全匹配。如果只部分存在,可能会创建重复数据。务必在唯一约束(Unique Constraint)或已知唯一的属性上使用MERGE。
4.6.4 MERGE 与 CREATE 的区别
| 语句 | 行为 | 是否幂等 | 使用场景 |
|---|---|---|---|
CREATE | 总是创建 | ❌ 否 | 批量导入确定不存在的数据 |
MERGE | 存在则匹配,不存在则创建 | ✅ 是 | 去重导入、确保数据一致性 |
4.7 SET — 更新属性
-- 设置属性
MATCH (p:Person {name: 'Alice'})
SET p.age = 31, p.updated = datetime()
RETURN p;
-- 设置多个属性(Map 语法)
MATCH (p:Person {name: 'Alice'})
SET p += {age: 31, city: '上海', role: 'manager'}
RETURN p;
-- 添加标签
MATCH (p:Person {name: 'Alice'})
SET p:VIP:Manager
RETURN p, labels(p);
-- 移除属性
MATCH (p:Person {name: 'Alice'})
REMOVE p.role
RETURN p;
-- 移除标签
MATCH (p:Person {name: 'Alice'})
REMOVE p:VIP
RETURN p, labels(p);
4.8 DELETE — 删除数据
4.8.1 删除顶点
-- 删除单个顶点
MATCH (p:Person {name: 'Alice'})
DELETE p;
-- 带条件删除
MATCH (p:Person)
WHERE p.age < 18 AND p.status = 'inactive'
DELETE p;
4.8.2 删除边
-- 删除关系
MATCH (a:Person {name: 'Alice'})-[r:KNOWS]->(b:Person {name: 'Bob'})
DELETE r;
4.8.3 DETACH DELETE — 级联删除
-- 删除顶点及其所有关系(危险操作!)
MATCH (p:Person {name: 'Alice'})
DETACH DELETE p;
-- 删除所有数据(慎用!)
MATCH (n)
DETACH DELETE n;
注意:
- 普通
DELETE不能删除有边连接的顶点,会报错DETACH DELETE会先删除所有连接的边,再删除顶点- 生产环境中务必带
WHERE条件,避免误删
4.9 完整业务场景:员工管理系统
-- 创建部门
CREATE (:Department {name: '技术部', budget: 500000});
CREATE (:Department {name: '产品部', budget: 300000});
CREATE (:Department {name: '市场部', budget: 200000});
-- 创建员工
CREATE (:Employee {name: '张三', age: 30, salary: 25000, title: '高级工程师'});
CREATE (:Employee {name: '李四', age: 28, salary: 20000, title: '工程师'});
CREATE (:Employee {name: '王五', age: 35, salary: 30000, title: '技术总监'});
CREATE (:Employee {name: '赵六', age: 26, salary: 15000, title: '产品经理'});
-- 创建部门归属关系
MATCH (e:Employee {name: '张三'}), (d:Department {name: '技术部'})
CREATE (e)-[:BELONGS_TO {since: 2020}]->(d);
MATCH (e:Employee {name: '李四'}), (d:Department {name: '技术部'})
CREATE (e)-[:BELONGS_TO {since: 2021}]->(d);
MATCH (e:Employee {name: '王五'}), (d:Department {name: '技术部'})
CREATE (e)-[:BELONGS_TO {since: 2018}]->(d);
MATCH (e:Employee {name: '赵六'}), (d:Department {name: '产品部'})
CREATE (e)-[:BELONGS_TO {since: 2022}]->(d);
-- 创建汇报关系
MATCH (e:Employee {name: '张三'}), (m:Employee {name: '王五'})
CREATE (e)-[:REPORTS_TO]->(m);
MATCH (e:Employee {name: '李四'}), (m:Employee {name: '王五'})
CREATE (e)-[:REPORTS_TO]->(m);
查询:统计各部门员工数量
MATCH (e:Employee)-[:BELONGS_TO]->(d:Department)
RETURN d.name AS department, count(e) AS headcount, avg(e.salary) AS avg_salary
ORDER BY headcount DESC;
查询结果
| department | headcount | avg_salary |
|---|---|---|
| 技术部 | 3 | 25000.0 |
| 产品部 | 1 | 15000.0 |
查询:找到某人的所有下属
MATCH (manager:Employee {name: '王五'})<-[:REPORTS_TO*]-(sub:Employee)
RETURN sub.name AS subordinate, sub.title AS title;
| subordinate | title |
|---|---|
| 张三 | 高级工程师 |
| 李四 | 工程师 |
4.10 常见错误与陷阱
陷阱 1:CREATE 产生重复
-- ❌ 错误:每次运行都会创建新节点
CREATE (p:Person {name: 'Alice'})
RETURN p;
-- ✅ 正确:使用 MERGE 避免重复
MERGE (p:Person {name: 'Alice'})
RETURN p;
陷阱 2:MATCH 失败不会报错
-- 如果 Person 标签下没有数据,返回空结果集(不报错)
MATCH (p:Person {name: '不存在的人'})
RETURN p.name;
-- 结果: (0 rows)
陷阱 3:忘记 SET 图路径
-- ❌ 错误:未设置 graph_path
MATCH (n:Person) RETURN n;
-- ERROR: graph_path is not set
-- ✅ 正确
SET graph_path = demo;
MATCH (n:Person) RETURN n;
陷阱 4:DELETE 有边的顶点
-- ❌ 错误:直接删除有边连接的顶点
MATCH (p:Person {name: 'Alice'})
DELETE p;
-- ERROR: cannot delete vertex with edges
-- ✅ 正确:先删边或使用 DETACH DELETE
MATCH (p:Person {name: 'Alice'})
DETACH DELETE p;
4.11 本章小结
| 操作 | 语法 | 幂等性 | 典型场景 |
|---|---|---|---|
CREATE | CREATE (n:Label {props}) | ❌ | 批量导入 |
MATCH | MATCH (pattern) WHERE cond | - | 查询数据 |
MERGE | MERGE (pattern) ON CREATE/MATCH | ✅ | 去重导入 |
SET | SET n.prop = val | ✅ | 更新属性 |
DELETE | DELETE n / DETACH DELETE n | - | 删除数据 |
RETURN | RETURN expr AS alias | - | 定义输出 |
WHERE | WHERE condition | - | 条件过滤 |
ORDER BY | ORDER BY expr DESC | - | 结果排序 |
SKIP/LIMIT | SKIP n LIMIT m | - | 分页查询 |
4.12 练习
- 创建一个包含 3 个
Product节点和 3 个Category节点的图,并建立BELONGS_TO关系。 - 使用
MERGE实现一个"确保用户存在并更新最后登录时间"的逻辑。 - 编写查询:找出所有价格大于 100 的产品及其所属分类。
- 编写查询:统计每个分类下的产品数量,并按数量降序排列。