Cypher语法

目标:掌握 Cypher 的基本语法规则,能独立完成 “创建 - 查询 - 更新 - 删除”(CRUD)操作,理解图数据的表达逻辑。

1. 先搞懂 3 个核心语法符号(基础中的基础)

Cypher 语法高度可视化,记住这 3 个符号就能描述任何图结构:
 
符号 含义 示例
() 节点(可加标签 / 属性) (u:User)(标签:User)、(p:Product {name:"手机"})(带属性)
-[:类型]-> 有向关系(可加属性) -[:FRIENDS_WITH]->(类型:FRIENDS_WITH)、-[:PURCHASED {time:"2025-11-18"}]->(带属性)
[] 关系变量(用于引用) -[r:PURCHASED]->(用 r 指代该关系,后续可查询 / 更新 r 的属性)

 

步骤 1:创建节点(用户、商品)

// 创建3个用户节点(标签:User,带id/name/age属性)
CREATE (u1:User {id:1, name:"张三", age:28, city:"北京"}),
       (u2:User {id:2, name:"李四", age:30, city:"上海"}),
       (u3:User {id:3, name:"王五", age:25, city:"广州"});

// 创建2个商品节点(标签:Product,带id/name/price/category属性)
CREATE (p1:Product {id:101, name:"iPhone 15", price:7999, category:"手机"}),
       (p2:Product {id:102, name:"华为Mate 60", price:6999, category:"手机"}),
       (p3:Product {id:103, name:"AirPods Pro", price:1999, category:"配件"});
Cypher CREATE 语法中,u1u2u3 是 节点变量(Node Variables),它们的核心区别和作用如下:

一、本质区别:指向不同的节点实例

u1u2u3 是三个独立的变量名,分别对应你创建的 3 个 User 节点:
 
  • u1 → 指向 {id:1, name:"张三", ...} 的 User 节点
  • u2 → 指向 {id:2, name:"李四", ...} 的 User 节点
  • u3 → 指向 {id:3, name:"王五", ...} 的 User 节点
 
变量名本身可以任意定义(比如改成 zhangsanlisiwangwu),但必须唯一(同一语句中不能重复用同一个变量指向不同节点)。

二、核心作用:临时引用节点,方便后续操作

变量的核心价值是「在当前 Cypher 语句中临时引用节点」,避免重复写匹配条件。具体有两个常见场景:

1. 同一 CREATE 语句中创建关联关系(简化语法)

如果创建节点的同时要创建节点间的关系,变量可以直接关联,无需额外 MATCH
// 创建3个用户节点(u1/u2/u3),同时创建张三和李四的好友关系
CREATE (u1:User {id:1, name:"张三"}),
       (u2:User {id:2, name:"李四"}),
       (u3:User {id:3, name:"王五"}),
       (u1)-[:FRIENDS_WITH]->(u2); // 直接用u1/u2引用节点,创建关系
如果没有变量,你需要先 CREATE 节点,再 MATCH 节点才能创建关系,步骤更繁琐。
 

2. 后续语句中匹配 / 操作指定节点

如果后续要对某个节点单独操作(比如修改属性、创建关系、删除),可以通过变量快速定位(仅限同一语句上下文):
// 创建节点后,立即给u1(张三)添加一个新属性
CREATE (u1:User {id:1, name:"张三"}),
       (u2:User {id:2, name:"李四"}),
       (u3:User {id:3, name:"王五"})
SET u1.email = "zhangsan@xxx.com"; // 用u1直接定位张三,添加属性
 

三、关键注意点

  1. 变量仅在当前语句有效u1u2u3 只在你写的这个 CREATE 语句中起作用,语句执行结束后,变量会失效。如果之后要操作这些节点,需要通过 MATCH (u:User {id:1}) 重新匹配(不能直接用 u1)。
  2. 变量名不影响节点本身:节点的本质是「标签(User)+ 属性(id/name/age 等)」,变量名只是临时引用,哪怕把 u1 改成 x,节点的内容(属性、标签)完全不变。
  3. 变量可省略(但不推荐):如果创建节点后不需要立即操作,也可以省略变量:
     
    // 省略变量,仅创建节点(无法直接关联关系)
    CREATE (:User {id:1, name:"张三"}),
           (:User {id:2, name:"李四"}),
           (:User {id:3, name:"王五"});
    但这种写法后续无法在同一语句中创建关系,灵活性差,所以一般都会定义变量

 

步骤 2:创建关系(好友、购买)

// 1. 创建好友关系(张三↔李四,张三→王五)
MATCH (u1:User {id:1}), (u2:User {id:2})
CREATE (u1)-[:FRIENDS_WITH {since:2020, status:"close"}]->(u2),
       (u2)-[:FRIENDS_WITH {since:2020, status:"close"}]->(u1);  // 双向关系需单独创建

MATCH (u1:User {id:1}), (u3:User {id:3})
CREATE (u1)-[:FRIENDS_WITH {since:2022, status:"ordinary"}]->(u3);

// 2. 创建购买关系(张三买了iPhone 15,李四买了华为Mate 60和AirPods)
MATCH (u1:User {id:1}), (p1:Product {id:101})
CREATE (u1)-[:PURCHASED {time:"2025-01-10", amount:1}]->(p1);

MATCH (u2:User {id:2}), (p2:Product {id:102}), (p3:Product {id:103})
CREATE (u2)-[:PURCHASED {time:"2025-03-15", amount:1}]->(p2),
       (u2)-[:PURCHASED {time:"2025-03-15", amount:1}]->(p3);
 
在这个 Cypher 语法中,-[:FRIENDS_WITH {since:2020, status:"close"}]-> 表示一条带有属性的有向关系,拆解后每个部分的含义如下,结合场景能更清晰理解:

一、完整结构拆解

符号 / 语法部分 含义
- 和 -> 关系的「方向标记」:- 表示关系的一端,-> 表示关系的「指向」(从左到右)
[:FRIENDS_WITH] 关系的「类型」(必填):FRIENDS_WITH 是关系类型名,用于标识节点间的关联语义(这里是「好友关系」)
{since:2020, status:"close"} 关系的「属性」(可选):用键值对存储关系的附加信息,和节点属性逻辑一致

 

1. 方向标记:- 和 ->

    • 核心作用:定义关系的「起点」和「终点」,Cypher 中关系默认是有向的(除非用 -- 表示无向,但实际存储仍会隐含方向)。

2. 关系类型:[:FRIENDS_WITH]

  • 相当于给关系贴的「标签」,用于区分不同语义的关联(比如 PURCHASED 是购买关系、FOLLOWS 是关注关系)。
  • 规则:
    • 关系类型只能有一个(不能写 [:FRIENDS_WITH:COLLEAGUE],一个关系只能对应一种类型);
    • 命名规范:通常用大写字母 + 下划线(蛇形命名),语义要明确,方便后续查询(比如 MATCH (a)-[:FRIENDS_WITH]->(b) 可快速筛选所有好友关系)。

3. 关系属性:{since:2020, status:"close"}

  • 存储关系的附加信息,和节点属性一样支持字符串、数字、布尔值等类型,核心是描述「关系的特征」:
    • since:2020:表示这条好友关系的「建立时间是 2020 年」;
    • status:"close":表示这条关系的「状态是亲密好友」。
  • 作用:后续查询时可以过滤或返回这些属性,比如:
// 查询张三(id=1)的所有2020年建立的亲密好友
MATCH (u1:User {id:1})-[r:FRIENDS_WITH]->(u2)
WHERE r.since = 2020 AND r.status = "close"
RETURN u2.name, r.since;

 

步骤 3:查询练习(从简单到复杂)

// 1. 基础查询:查询所有用户的姓名和年龄
MATCH (u:User) RETURN u.name AS 用户名, u.age AS 年龄;

// 2. 条件查询:查询年龄>25的用户
MATCH (u:User) WHERE u.age > 25 RETURN u.name, u.age;

// 3. 关系查询:查询“购买了手机的用户”
MATCH (u:User)-[r:PURCHASED]->(p:Product {category:"手机"})
RETURN u.name AS 用户名, r.time AS 购买时间, p.name AS 商品名;

// 4. 关联查询:查询“张三的朋友购买了什么商品”(1度关联)
MATCH (z:User {name:"张三"})-[:FRIENDS_WITH]->(f:User)-[r:PURCHASED]->(p:Product)
RETURN z.name AS 本人, f.name AS 朋友, p.name AS 朋友购买的商品;

// 5. 多深度查询:查询“张三的1-2度朋友”(直接朋友+朋友的朋友)
MATCH (z:User {name:"张三"})-[:FRIENDS_WITH*1..2]->(f:User)
RETURN z.name, f.name, length(relationships(p)) AS 关系深度;  // length()计算路径长度

// 6. 聚合查询:统计每个商品的购买次数
MATCH (u:User)-[r:PURCHASED]->(p:Product)
RETURN p.name AS 商品名, sum(r.amount) AS 总销量
ORDER BY 总销量 DESC;

 

核心语法:可变长度关系

Neo4j 中用 -[*最小深度..最大深度]-> 表示多深度关系,此处需求是 1-2 度,因此使用 -[*1..2]->
 
匹配 n 到 m 步的关系,语法格式为:
()-[:关系类型*最小深度..最大深度]->()

关键细节:

  1. 深度边界
    • *1..2:匹配 1 到 2 度关系(直接关联 + 间接关联 1 次)
    • *2:等价于 *2..2,仅匹配 2 度关系(精确深度)
    • *1..:匹配 1 度及以上(无最大深度,谨慎使用)
    • *:等价于 *0..∞(0 度 = 节点本身,无上限,极易性能爆炸)
  2. 关系方向
    • ->:仅匹配「从左到右」的单向关系
    • <-:仅匹配「从右到左」的单向关系
    • 无箭头 ()-[]-():匹配双向关系(如朋友、同事等无明确方向的关联)
  3. 关系类型
    • 单一类型:[:FRIENDS_WITH*1..2](仅朋友关系)
    • 多类型:[:FRIENDS_WITH|COLLEAGUE*1..2](朋友或同事关系,用 | 分隔)
 

完整查询语句

// 查询张三的1-2度朋友(直接朋友+朋友的朋友)
MATCH (zhangsan:User {name:"张三"})-[:FRIENDS_WITH*1..2]-(friend:User)
WHERE friend <> zhangsan  // 排除张三本人
RETURN 
  friend.id AS 朋友ID,
  friend.name AS 朋友姓名,
  friend.age AS 朋友年龄,
  // 计算关系深度(1=直接朋友,2=朋友的朋友)
  length((zhangsan)-[:FRIENDS_WITH*]-(friend)) AS 关系度数
ORDER BY 关系度数 ASC, 朋友ID ASC;

关键细节解释

  1. 关系方向无关性
     
    用 -(关系)-(无箭头)而非 ->,因为 FRIENDS_WITH 是双向关系(张三→李四,李四→张三),无箭头可同时匹配两种方向,避免遗漏。
  2. 排除本人
     
    WHERE friend <> zhangsan 至关重要 —— 若不排除,当深度包含 0 时(如 *0..2)会匹配自己,此处虽深度从 1 开始,但养成排除习惯可避免异常数据。
  3. 深度计算
     
    length((zhangsan)-[:FRIENDS_WITH*]-(friend)) 用于明确标注 “几度朋友”:
    • 长度 = 1 → 1 度(直接朋友)
    • 长度 = 2 → 2 度(朋友的朋友)

 执行报错:

Type mismatch: expected Path but was List<Path> (line 8, column 10 (offset: 223))
"  length((zhangsan)-[:FRIENDS_WITH*]-(friend)) AS 关系度数",执行上述脚本报错

 

报错原因是:(zhangsan)-[:FRIENDS_WITH*]-(friend) 用了无边界的可变长度关系(* 等价于 *0..∞),会返回多条路径(List<Path>),而 length() 函数只能接收单条路径(Path),无法直接计算列表长度,导致类型不匹配。

修复方案:用 shortestPath() 取最短路径计算度数

朋友关系的 “度数” 本质是两个节点间的最短路径长度(1 度 = 直接相连,2 度 = 最短路径需 2 步),因此用 shortestPath() 获取单条最短路径,再用 length() 计算长度即可。

修复后的完整查询语句

// 查询张三的1-2度朋友(直接朋友+朋友的朋友)- 修复度数计算报错
MATCH (zhangsan:User {name:"张三"})-[:FRIENDS_WITH*1..2]-(friend:User)
WHERE friend <> zhangsan  // 排除张三本人
WITH zhangsan, friend, shortestPath((zhangsan)-[:FRIENDS_WITH*]-(friend)) AS shortest_path
RETURN 
  friend.id AS 朋友ID,
  friend.name AS 朋友姓名,
  friend.age AS 朋友年龄,
  length(shortest_path) AS 关系度数  // 基于最短路径计算度数
ORDER BY 关系度数 ASC, 朋友ID ASC;

核心修复逻辑

  1. shortestPath() 函数
     
    专门用于获取两个节点间的最短路径(单条 Path),解决了原查询中 “多条路径列表” 的问题,length() 可正常计算其长度。
  2. WITH 子句
     
    先筛选出符合条件的朋友,再计算最短路径,避免直接在 RETURN 中嵌套复杂路径表达式,逻辑更清晰。

简化版(无需度数,仅需去重朋友列表)

如果不需要显示 “关系度数”,仅需获取 1-2 度朋友,可直接去重,避免路径相关报错:

 

// 简化版:仅返回张三的1-2度朋友(去重)
MATCH (zhangsan:User {name:"张三"})-[:FRIENDS_WITH*1..2]-(friend:User)
WHERE friend <> zhangsan
RETURN DISTINCT  // 去重(若存在多条路径指向同一朋友)
  friend.id AS 朋友ID,
  friend.name AS 朋友姓名,
  friend.age AS 朋友年龄
ORDER BY friend.id ASC;

 

步骤 4:更新与删除练习

// 1. 更新:给张三增加“职业”属性,修改年龄为29
MATCH (u:User {name:"张三"})
SET u.job = "工程师", u.age = 29
RETURN u;

// 2. 更新关系:修改李四购买华为Mate 60的数量为2
MATCH (u:User {name:"李四"})-[r:PURCHASED]->(p:Product {name:"华为Mate 60"})
SET r.amount = 2
RETURN r;

// 3. 删除:删除王五的所有关系和王五节点
MATCH (u:User {name:"王五"})-[r]->()  // 匹配王五的所有关系
DELETE r, u;  // 先删关系,再删节点

// 4. 删除属性:删除张三的“city”属性
MATCH (u:User {name:"张三"})
REMOVE u.city
RETURN u;

 

二、进阶阶段:掌握高频场景语法

入门后,重点攻克 Cypher 的 “高级特性”,覆盖实际开发中的高频场景:

1. 索引与约束(性能优化核心)

当数据量较大(万级 +)时,必须通过索引加速查询,约束保证数据一致性:
// 1. 创建索引:对User的name属性创建索引(高频查询字段)
CREATE INDEX idx_user_name ON :User(name);

// 2. 创建唯一约束:保证User的id属性不重复(类似主键)
CREATE CONSTRAINT unique_user_id ON :User(id) ASSERT id IS UNIQUE;

// 3. 查看索引/约束
SHOW INDEXES;
SHOW CONSTRAINTS;

// 4. 删除索引/约束
DROP INDEX idx_user_name;
DROP CONSTRAINT unique_user_id;

2. 路径操作与函数

Cypher 内置大量函数,简化路径分析、属性处理等操作:
// 1. 路径函数:获取路径中的所有节点/关系
MATCH p = (u:User)-[:PURCHASED]->(p:Product)
RETURN nodes(p) AS 路径节点, relationships(p) AS 路径关系;

// 2. 最短路径查询(内置算法)
MATCH shortestPath(p = (u1:User {name:"张三"})-[*]->(u2:User {name:"李四"}))
RETURN p, length(p) AS 最短路径长度;

// 3. 字符串函数:模糊查询(类似SQL的LIKE)
MATCH (p:Product) WHERE p.name CONTAINS "手机"  // 包含“手机”
RETURN p.name;

// 4. 聚合函数:统计、分组
MATCH (u:User)-[r:PURCHASED]->(p:Product)
GROUP BY p.category  // 按商品分类分组
RETURN p.category AS 分类, count(u) AS 购买人数, sum(r.amount) AS 总销量;

3. 批量操作(高效处理大量数据)

实际项目中常需批量导入 / 更新数据,避免逐条执行:
// 1. 批量创建:通过UNWIND遍历列表批量创建节点
WITH ["赵六", "孙七", "周八"] AS names  // 定义姓名列表
UNWIND names AS name  // 遍历列表,每个name对应一条记录
CREATE (u:User {name: name, age: 26, city: "深圳"});

// 2. 批量更新:给所有手机类商品降价1000
MATCH (p:Product {category:"手机"})
SET p.price = p.price - 1000
RETURN p.name, p.price;

4. 条件分支(CASE 语句)

类似 SQL 的 CASE,支持复杂逻辑判断:
// 根据商品价格分类
MATCH (p:Product)
RETURN p.name AS 商品名,
       CASE
           WHEN p.price > 5000 THEN "高端产品"
           WHEN p.price > 2000 THEN "中端产品"
           ELSE "低端产品"
       END AS 价格等级;

 

posted @ 2025-11-18 22:16  BlogMemory  阅读(3)  评论(0)    收藏  举报