别再被SQL的连表查询搞疯了!一文带你吃透Neo4j图数据库,从零搭建“关系网”

社交关系、推荐系统、知识图谱......只要你的业务涉及"关系",传统的数据库就会变得越来越慢、越来越复杂。今天,我们就用最通俗易懂的方式,把图数据库 Neo4j 彻底讲明白,让你以后处理"网状"数据时,思路清晰得像开了挂。


一、为什么你的SQL搞不定"朋友的朋友的朋友"?

想象一个简单的社交网络场景:你需要查询"当前用户的好友中,有哪些人也关注了当前用户喜欢的某个博主"。在关系型数据库(如MySQL)中,你可能需要这样写:

bash 复制代码
SELECT DISTINCT u2.name
FROM users u1
JOIN follows f1 ON u1.id = f1.follower_id
JOIN users u2 ON f1.followee_id = u2.id
JOIN follows f2 ON u2.id = f2.follower_id
JOIN users u3 ON f2.followee_id = u3.id
JOIN likes l ON u3.id = l.user_id AND l.post_id = ?
WHERE u1.id = ?

这张SQL不仅写起来痛苦,而且随着关系层数增加,JOIN的数量呈指数级增长,性能急剧下降。更严重的是,当你想要查询"用户和另一个用户之间有多少条路径"、"所有共同关注关系"这类问题时,SQL几乎无法优雅表达。

图数据库 正是为解决这类问题而生的。在Neo4j中,数据被存储为节点(Node)关系(Relationship),关系本身就是一等公民,可以拥有属性和类型。查询"朋友的朋友"变得像描述句子一样自然:

bash 复制代码
MATCH (me:User {id: 123})-[:FOLLOWS*2]->(friend_of_friend:User)
RETURN friend_of_friend.name

*2 表示沿着 FOLLOWS 关系走两步。你甚至不需要关心底层有多少张表,Cypher查询引擎会自动高效地遍历图结构。这种直观性和性能优势,在处理深度关联数据时尤为突出。


二、Neo4j核心概念:节点、关系、路径、标签、属性

在深入学习之前,我们先彻底搞懂几个基础概念。这些概念就像盖房子的砖瓦,理解了它们,Cypher语言就变得自然而然。

2.1 节点(Node)

节点代表一个实体对象,比如一个人、一部电影、一个城市。每个节点可以:

  • 拥有一个或多个标签(Label)

    :标签相当于给节点分类,类似于SQL中的表名。例如 :Person、:Movie。一个节点可以同时有多个标签,比如 :Person:Actor 表示这是一个"人"同时也是"演员"。

  • 拥有若干属性(Property)

    :属性是键值对,存储具体信息。例如 {name: '王宝强', gender: '男'}。

在Cypher中,节点用一对圆括号表示,括号内可以写变量名、标签和属性:

bash 复制代码
// 一个没有变量名、没有标签、没有属性的匿名节点
()
 
// 有变量名的节点(变量名用于后续引用)
(p)
 
// 有标签的节点
(p:Person)
 
// 有多个标签的节点
(p:Person:Actor)
 
// 有属性的节点(属性用花括号包裹,键值对之间用逗号分隔)
(p:Person {name: '王宝强', gender: '男'})

2.2 关系(Relationship)

关系用于连接两个节点,表示它们之间的某种联系,比如"王宝强出演了《唐探1900》"。每个关系:

  • 必须有类型(Type)

    :类型是一个大写标识符,例如 :ACTED_IN、:DIRECTED、:FOLLOWS。

  • 可以有若干属性(Property)

    :例如角色名 {role: '阿鬼'}、获奖标志 {award: true}。

  • 有方向

    :通常用箭头 → 表示方向,但查询时可以忽略方向。

在Cypher中,关系用方括号加箭头表示,放在两个节点之间:

bash 复制代码
// 基本关系模式:起始节点 - [关系] -> 目标节点
(p1:Person)-[:ACTED_IN]->(m:Movie)
 
// 关系也可以有变量名和属性
(p1:Person)-[r:ACTED_IN {role: '阿鬼'}]->(m:Movie)
 
// 无方向关系(用两个短横线,没有箭头)
(p1:Person)-[:FOLLOWS]-(p2:Person)

2.3 路径(Path)

路径是由一系列节点和关系交替连接而成的结构,代表图中一条具体的行走路线。例如:(王宝强)-[:ACTED_IN]->(电影)<-[:ACTED_IN]-(刘德华) 表示"王宝强和刘德华演过同一部电影"。

路径在复杂查询中非常有用,我们可以把匹配到的整个路径赋值给一个变量,然后返回或进一步处理。

bash 复制代码
// 将匹配到的路径存入变量 path 中
MATCH path = (p:Person {name: '王宝强'})-[:ACTED_IN]->(m:Movie)
RETURN path

三、Windows下从零安装Neo4j(超详细)

我们选择自由度最高的 OS Deployment 方式,在 Windows 系统上部署 Neo4j 5.26 LTS 社区版。这种方式能让你完全掌控数据库的安装位置、配置文件和启动方式,非常适合学习和生产环境。

3.1 安装JDK 17

Neo4j 是用 Java 编写的,所以必须先安装 Java 运行环境。根据官方文档,Neo4j 5.26 需要 JDK 17JDK 21。我们以 JDK 17 为例。

  1. 下载JDK

    :访问 Oracle官网 下载 Windows x64 Installer(如 jdk-17.0.15_windows-x64_bin.exe)。如果你没有Oracle账号,也可以从其他镜像或课程资料中获取。

  2. 安装JDK

    :双击安装包,一路"下一步",记住安装路径(例如 C:\Program Files\Java\jdk-17.0.15)。安装过程中可以取消"公共JRE"的选项,因为JDK自带JRE。

  3. 配置环境变量

    (可选但推荐):

  4. 打开"系统属性" → "高级" → "环境变量"。

  5. 新建系统变量 JAVA_HOME,值为你的JDK安装路径(如 C:\Program Files\Java\jdk-17.0.15)。

  6. 在 Path 变量中添加 %JAVA_HOME%\bin。

  7. 验证安装

    :打开命令提示符(cmd),输入:

  8. cmd

  9. java --version

  10. 如果看到类似 openjdk 17.0.15 2025-04-15 LTS 的输出,说明安装成功。

3.2 安装Neo4j社区版

  1. 下载Neo4j

    :访问 Neo4j下载中心,选择 Windows 平台的 zip 压缩包(例如 neo4j-community-5.26.0-windows.zip)。社区版完全免费,功能足够学习和小型项目使用。

  2. 解压

    :将压缩包解压到一个没有中文和空格的目录,例如 D:\neo4j。解压后你会看到 bin、conf、data、logs 等文件夹。

  3. 配置环境变量

  4. 创建系统变量 NEO4J_HOME,值为 D:\neo4j(你的解压路径)。

  5. 在 Path 变量中添加 %NEO4J_HOME%\bin。

  6. 验证安装

    :新开一个cmd窗口,输入:

  7. cmd

  8. neo4j --version

  9. 如果输出 neo4j version: 5.26.0,说明环境变量配置正确。

3.3 安装Neo4j为Windows服务(可选但推荐)

管理员身份打开命令提示符(右键"命令提示符" → "以管理员身份运行")。执行以下命令将Neo4j注册为系统服务:

bash 复制代码
neo4j windows-service install

安装成功后,你可以在 services.msc 中看到名为 Neo4j Graph Database - neo4j 的服务,并设置为自动启动。

3.4 启动Neo4j数据库

bash 复制代码
neo4j start

启动日志会显示在控制台。如果一切正常,你会看到类似 Started neo4j 的信息。首次启动可能需要几秒钟来初始化数据库。

3.5 访问Web管理界面

Neo4j 提供了一个基于浏览器的可视化工具 ------ Neo4j Browser。打开浏览器,访问:

bash 复制代码
http://localhost:7474/browser/

你会看到一个连接界面。默认的服务器地址是 bolt://localhost:7687,用户名和密码都是 neo4j。首次登录时,系统会强制要求你修改密码。建议修改为容易记住但安全的密码,例如 Atguigu.123。

登录成功后,你就可以在输入框中执行Cypher查询了。试试输入 RETURN 'Hello Neo4j!' 然后点击运行按钮(或者按 Ctrl+Enter),应该能看到输出结果。


四、Cypher入门:像画画一样操作数据

Cypher 的设计哲学是"人类可读、易于表达"。它借鉴了SQL的关键词(如 MATCH、RETURN、WHERE),但图形模式的表示方式((节点)-[关系]->(节点))让查询语句几乎与手绘的草图一模一样。

4.1 创建节点 ------ CREATE

使用 CREATE 语句可以在数据库中插入新节点。我们先创建几个简单的节点,感受一下语法。

bash 复制代码
// 创建一个没有任何标签和属性的空节点
CREATE ()
 
// 创建一个带标签的节点
CREATE (p:Person)
 
// 创建带属性和标签的节点(最常用)
CREATE (n1:Person:Actor {name: '王宝强', gender: '男'})
CREATE (n2:Movie {title: '唐探1900', released: '2025-01-29'})

代码解释

  • n1 和 n2 是变量名(可以任意取),仅在当前语句中有效,用于在后续引用这个节点。

  • Person:Actor 表示同时给节点加上 Person 和 Actor 两个标签,这相当于多分类。

  • 属性用花括号包裹,键值对之间用逗号分隔。属性值可以是字符串、数字、布尔值、列表等。

执行上述语句后,数据库中就有了两个孤立的节点,它们之间还没有任何关系。

4.2 查询节点 ------ MATCH ... RETURN

MATCH 用于描述要查找的图形模式,RETURN 指定要返回哪些部分。

bash 复制代码
// 查询所有节点(数据量大时非常慢,谨慎使用)
MATCH (n)
RETURN n
 
// 查询所有带有 Person 标签的节点,只返回名字和性别属性
MATCH (p:Person)
RETURN p.name, p.gender
 
// 按标签和属性精准查询
MATCH (p:Person {name: '王宝强'})
RETURN p

注意:MATCH (n) 会扫描整个数据库的所有节点,如果数据量达到几十万条,浏览器可能会卡顿。通常我们会加上标签过滤。

4.3 创建关系 ------ 先匹配再创建

关系不能独立存在,必须连接两个节点。因此,创建关系的标准步骤是:先用 MATCH 找到两个节点,然后用 CREATE 在它们之间建立关系。

bash 复制代码
// 1. 找到王宝强节点和《唐探1900》节点
MATCH (p:Person {name: '王宝强'}), (m:Movie {title: '唐探1900'})
 
// 2. 创建 ACTED_IN 关系,方向从 p 指向 m,并带上属性 role
CREATE (p)-[:ACTED_IN {role: '阿鬼'}]->(m)

如果你希望关系也有变量名(以便后续返回或修改),可以这样写:

bash 复制代码
MATCH (p:Person {name: '王宝强'}), (m:Movie {title: '唐探1900'})
CREATE (p)-[r:ACTED_IN {role: '阿鬼'}]->(m)
RETURN r

关系方向的意义:虽然 ACTED_IN 关系在语义上是从演员指向电影,但在查询时你可以忽略方向,例如 (p)-[:ACTED_IN]-(m) 会匹配两个方向的关系。但创建时必须指定方向(除非是无向关系,但Cypher中创建关系必须有方向)。

4.4 查询关系

有了关系之后,我们就可以查询"王宝强演过哪些电影"了:

bash 复制代码
MATCH (p:Person {name: '王宝强'})-[r:ACTED_IN]->(m:Movie)
RETURN m.title AS 电影名称, r.role AS 角色

这条语句的含义:从Person节点出发,沿着ACTED_IN关系到达Movie节点,返回电影标题和关系上的角色属性。AS关键字用于给返回的列起别名(中文也可以,但建议用英文)。

4.5 一次创建完整路径

如果你确信某些节点和关系都不存在,可以用一条CREATE语句同时创建节点和关系,这叫做"创建路径"。

bash 复制代码
CREATE
  (n1:Person:Actor:Singer {name: '刘德华', gender: '男'})
  -[:ACTED_IN {roles: ['刘建明']}]-> 
  (m:Movie {title: '无间道', released: '2002-12-12'})

这里我们创建了一个有3个标签的Person节点,然后直接通过-[:ACTED_IN]->连接到一个新创建的Movie节点。注意:如果节点或关系已经存在,再用CREATE会导致重复创建(会多出一个相同的节点)。因此,这种方式只适合从零开始导入数据时使用。日常开发中更推荐MERGE(后面会讲)。


五、Cypher进阶:修改、删除与合并(SET/REMOVE/MERGE)

5.1 修改属性 ------ SET

SET用于添加新属性、更新已有属性或添加新标签。

bash 复制代码
// 为王宝强添加生日属性和 Singer 标签
MATCH (p:Person {name: '王宝强'})
SET p.birth = '1984-05-29', p:Singer
 
// 为关系添加属性
MATCH (p:Person {name: '王宝强'})-[r:ACTED_IN]->(m:Movie {title: '唐探1900'})
SET r.roles = ['阿鬼'], r.year = 2025

一次SET可以同时设置多个属性,用逗号分隔。添加标签也使用:语法。

5.2 删除属性 ------ REMOVE

REMOVE用于删除属性或移除标签。

bash 复制代码
// 删除关系上的 role 属性(假设之前有 role: '阿鬼',现在想改成数组形式)
MATCH (p:Person {name: '王宝强'})-[r:ACTED_IN]->(m:Movie {title: '唐探1900'})
REMOVE r.role
SET r.roles = ['阿鬼']
 
// 移除节点的 Singer 标签
MATCH (p:Person {name: '王宝强'})
REMOVE p:Singer

5.3 删除节点和关系 ------ DELETE

删除操作需要特别注意:不能直接删除还有关联关系的节点,否则会报错。必须先删除关系,或者使用DETACH DELETE一次性删除节点及其所有关系。

bash 复制代码
// 错误示范:如果节点还有关系,下面这条语句会失败
MATCH (p:Person {name: '王宝强'})
DELETE p   // 报错:Node still has relationships
 
// 正确方式1:先删除关系,再删除节点
MATCH (p:Person {name: '王宝强'})-[r]-()
DELETE r, p
 
// 正确方式2:使用 DETACH DELETE(推荐,一步到位)
MATCH (p:Person {name: '王宝强'})
DETACH DELETE p

DETACH DELETE 会自动删除该节点的所有关系,然后再删除节点本身,非常方便。

如果你只想删除关系而保留节点:

bash 复制代码
MATCH (p:Person {name: '王宝强'})-[r:ACTED_IN]->(m:Movie {title: '唐探1900'})
DELETE r

5.4 合并操作 ------ MERGE(避免重复)

MERGE 是 Cypher 中最实用的操作之一。它的行为是:如果模式(节点或关系)存在,就匹配它;如果不存在,就创建它。这相当于 MATCH + CREATE 的组合,并且是原子操作,非常适合数据导入时的去重。

合并节点

bash 复制代码
// 合并电影节点:若已存在《唐探1900》则匹配,否则创建
MERGE (m:Movie {title: '唐探1900', released: '2025-01-29'})
 
// 合并时还可以设置创建或匹配时的额外动作
MERGE (p:Person {name: '王宝强', gender: '男'})
ON CREATE SET p.create_time = datetime()   // 仅在创建时设置创建时间
ON MATCH SET p.update_time = datetime()    // 仅在匹配到时设置更新时间

datetime() 是 Cypher 内置函数,返回当前时间戳(ISO 8601格式)。这样我们可以轻松追踪记录的新增和更新。

合并关系

bash 复制代码
MERGE (p:Person {name: '王宝强'})-[r:ACTED_IN {roles: ['阿鬼']}]->(m:Movie {title: '唐探1900'})

注意:MERGE 关系时会先分别 MERGE 两个端点节点(确保它们存在),然后再处理关系。所以不需要提前 MATCH 节点。


六、实战数据集:构建"电影-人物"知识图谱(9人10部电影)

为了深入学习高级查询,我们需要一个相对丰富的数据集。下面我将提供一套完整的 Cypher 脚本,包含 9 位人物节点、10 部电影节点,以及导演(DIRECTED)、参演(ACTED_IN)、关注(FOLLOWS)三种关系。你可以按顺序在 Neo4j Browser 中执行。

6.1 清空旧数据(谨慎!会删除所有内容)

bash 复制代码
MATCH (n) DETACH DELETE n;

6.2 创建人物节点(9人)

bash 复制代码
CREATE 
  (:Person {name: '张艺谋', birth: '1951-11-14'}),
  (:Person {name: '陈凯歌', birth: '1952-08-12'}),
  (:Person {name: '巩俐', birth: '1965-12-31'}),
  (:Person {name: '葛优', birth: '1957-04-19'}),
  (:Person {name: '章子怡', birth: '1979-02-09'}),
  (:Person {name: '刘德华', birth: '1961-09-27'}),
  (:Person {name: '吴京', birth: '1974-04-03'}),
  (:Person {name: '贾玲', birth: '1982-04-29'}),
  (:Person {name: '郭帆', birth: '1980-12-15'})

6.3 创建电影节点(10部)

bash 复制代码
CREATE
  (:Movie {title: '红高粱', year: 1987, rating: 8.4, genre: ['文艺', '历史']}),
  (:Movie {title: '活着', year: 1994, rating: 9.2, genre: ['剧情', '历史']}),
  (:Movie {title: '霸王别姬', year: 1993, rating: 9.6, genre: ['剧情', '爱情']}),
  (:Movie {title: '英雄', year: 2002, rating: 7.5, genre: ['动作', '武侠']}),
  (:Movie {title: '无间道', year: 2002, rating: 9.1, genre: ['犯罪', '悬疑']}),
  (:Movie {title: '一代宗师', year: 2013, rating: 8.0, genre: ['动作', '传记']}),
  (:Movie {title: '流浪地球', year: 2019, rating: 8.5, genre: ['科幻', '灾难']}),
  (:Movie {title: '战狼2', year: 2017, rating: 7.1, genre: ['动作', '军事']}),
  (:Movie {title: '你好,李焕英', year: 2021, rating: 7.7, genre: ['喜剧', '家庭']}),
  (:Movie {title: '满江红', year: 2023, rating: 7.2, genre: ['悬疑', '历史']})

6.4 创建导演关系(DIRECTED)

注意:这里需要先用 MATCH 找到具体的节点变量,然后创建关系。为了简洁,我列出所有9条导演关系。

bash 复制代码
MATCH (zhang:Person {name: '张艺谋'}), (m1:Movie {title: '红高粱'}),
      (m2:Movie {title: '活着'}), (m3:Movie {title: '霸王别姬'}),
      (m4:Movie {title: '英雄'}), (m5:Movie {title: '满江红'})
CREATE 
  (zhang)-[:DIRECTED {award: true}]->(m1),
  (zhang)-[:DIRECTED {award: true}]->(m2),
  (zhang)-[:DIRECTED {award: false}]->(m3),
  (zhang)-[:DIRECTED {award: false}]->(m4),
  (zhang)-[:DIRECTED {award: false}]->(m5);
 
MATCH (chen:Person {name: '陈凯歌'}), (m:Movie {title: '霸王别姬'})
CREATE (chen)-[:DIRECTED {award: true}]->(m);
 
MATCH (liu:Person {name: '刘德华'}), (m:Movie {title: '无间道'})
CREATE (liu)-[:DIRECTED {award: false}]->(m);
 
MATCH (wu:Person {name: '吴京'}), (m:Movie {title: '战狼2'})
CREATE (wu)-[:DIRECTED {award: false}]->(m);
 
MATCH (jia:Person {name: '贾玲'}), (m:Movie {title: '你好,李焕英'})
CREATE (jia)-[:DIRECTED {award: true}]->(m);
 
MATCH (guo:Person {name: '郭帆'}), (m:Movie {title: '流浪地球'})
CREATE (guo)-[:DIRECTED {award: true}]->(m);

注意:有些电影(如《一代宗师》)没有导演关系,这没关系。

6.5 创建参演关系(ACTED_IN)

共11条参演关系,覆盖大部分主要演员和电影。

bash 复制代码
MATCH (gong:Person {name: '巩俐'}), (m1:Movie {title: '红高粱'}),
      (m2:Movie {title: '活着'}), (m3:Movie {title: '霸王别姬'})
CREATE 
  (gong)-[:ACTED_IN {role: '九儿', award: true}]->(m1),
  (gong)-[:ACTED_IN {role: '家珍', award: false}]->(m2),
  (gong)-[:ACTED_IN {role: '菊仙', award: false}]->(m3);
 
MATCH (ge:Person {name: '葛优'}), (m2:Movie {title: '活着'}), (m3:Movie {title: '霸王别姬'})
CREATE 
  (ge)-[:ACTED_IN {role: '福贵', award: true}]->(m2),
  (ge)-[:ACTED_IN {role: '袁四爷', award: false}]->(m3);
 
MATCH (zhangyi:Person {name: '章子怡'}), (m4:Movie {title: '英雄'}), (m6:Movie {title: '一代宗师'})
CREATE 
  (zhangyi)-[:ACTED_IN {role: '如月', award: false}]->(m4),
  (zhangyi)-[:ACTED_IN {role: '宫二', award: true}]->(m6);
 
MATCH (liu:Person {name: '刘德华'}), (m5:Movie {title: '无间道'})
CREATE (liu)-[:ACTED_IN {role: '刘建明', award: false}]->(m5);
 
MATCH (wu:Person {name: '吴京'}), (m7:Movie {title: '流浪地球'}), (m8:Movie {title: '战狼2'})
CREATE 
  (wu)-[:ACTED_IN {role: '刘培强', award: true}]->(m7),
  (wu)-[:ACTED_IN {role: '冷锋', award: true}]->(m8);
 
MATCH (zhang:Person {name: '张艺谋'}), (m10:Movie {title: '满江红'})
CREATE (zhang)-[:ACTED_IN {role: '秦桧', award: false}]->(m10);
 
MATCH (jia:Person {name: '贾玲'}), (m9:Movie {title: '你好,李焕英'})
CREATE (jia)-[:ACTED_IN {role: '贾晓玲', award: true}]->(m9);

6.6 创建关注关系(FOLLOWS)

共11条关注关系,模拟社交网络中的"关注"行为。

bash 复制代码
MATCH (gong:Person {name:'巩俐'}), (zhang:Person {name:'张艺谋'}),
      (zhangyi:Person {name:'章子怡'}), (chen:Person {name:'陈凯歌'}),
      (ge:Person {name:'葛优'}), (liu:Person {name:'刘德华'}),
      (wu:Person {name:'吴京'}), (jia:Person {name:'贾玲'}),
      (guo:Person {name:'郭帆'})
CREATE 
  (gong)-[:FOLLOWS]->(zhang),
  (zhangyi)-[:FOLLOWS]->(zhang),
  (zhang)-[:FOLLOWS]->(chen),
  (ge)-[:FOLLOWS]->(chen),
  (liu)-[:FOLLOWS]->(zhang),
  (gong)-[:FOLLOWS]->(zhangyi),
  (wu)-[:FOLLOWS]->(zhang),
  (jia)-[:FOLLOWS]->(zhang),
  (guo)-[:FOLLOWS]->(zhang),
  (liu)-[:FOLLOWS]->(wu),
  (zhang)-[:FOLLOWS]->(guo);

至此,我们的知识图谱构建完成。你可以执行一些简单查询来验证,例如:

bash 复制代码
// 查看所有节点和关系(可视化)
MATCH (n) RETURN n LIMIT 25

七、高级查询必备技能:过滤、排序、分页、聚合

有了扎实的数据集,我们就可以开始玩真正的图查询了。这些技巧在日常开发中几乎每天都会用到。

7.1 过滤 ------ WHERE

WHERE 子句用于对 MATCH 的结果进行条件筛选。它可以写在 MATCH 后面,也可以紧跟在 MATCH 的节点模式内部(用花括号)。但推荐使用独立的 WHERE,因为更灵活。

基本比较运算符:=、<>、<、<=、>、>=

bash 复制代码
// 查询出生在1970年之后的人物
MATCH (p:Person)
WHERE p.birth > '1970-01-01'
RETURN p.name, p.birth

多条件组合:AND、OR、NOT

bash 复制代码
// 查询评分高于8分且年份早于2000年的电影
MATCH (m:Movie)
WHERE m.rating > 8.0 AND m.year < 2000
RETURN m.title, m.year, m.rating

集合包含:IN

bash 复制代码
// 查找类型包含"历史"的电影
MATCH (m:Movie)
WHERE '历史' IN m.genre
RETURN m.title, m.genre

字符串匹配:CONTAINS、STARTS WITH、ENDS WITH

bash 复制代码
// 查询名字中包含"张"的人物
MATCH (p:Person)
WHERE p.name CONTAINS '张'
RETURN p.name
 
// 查询以"流浪"开头的电影
MATCH (m:Movie)
WHERE m.title STARTS WITH '流浪'
RETURN m.title

空值判断:IS NULL、IS NOT NULL

bash 复制代码
// 找出没有出生日期的人物(假设有缺失数据)
MATCH (p:Person)
WHERE p.birth IS NULL
RETURN p.name

7.2 排序 ------ ORDER BY

ORDER BY 用于对返回结果进行排序,默认升序(ASC),可以指定 DESC 降序。

bash 复制代码
// 按电影评分从高到低排序,并取前5名
MATCH (m:Movie)
RETURN m.title, m.rating
ORDER BY m.rating DESC
LIMIT 5

7.3 分页 ------ SKIP 和 LIMIT

SKIP 跳过前 n 条记录,LIMIT 限制最多返回 n 条记录。两者结合实现分页。

bash 复制代码
// 第二页:跳过前5条,返回第6-10条(按年份降序)
MATCH (m:Movie)
RETURN m.title, m.year
ORDER BY m.year DESC
SKIP 5
LIMIT 5

7.4 聚合函数 ------ count、avg、max、min、collect

Cypher 的聚合不需要显式 GROUP BY,只要 RETURN 中同时包含聚合函数和非聚合字段,非聚合字段就会自动成为分组键。

数值聚合

bash 复制代码
// 统计演员总数(去重)
MATCH (p:Person)-[:ACTED_IN]->()
RETURN count(DISTINCT p) AS 演员人数
 
// 每位演员参演了几部电影
MATCH (p:Person)-[:ACTED_IN]->(m:Movie)
RETURN p.name, count(m) AS 参演电影数

列表聚合:collect() 将多个值聚合成一个列表。

bash 复制代码
// 每位导演的作品名称列表
MATCH (p:Person)-[:DIRECTED]->(m:Movie)
RETURN p.name, collect(m.title) AS 导演作品

输出示例:

张艺谋 | ['红高粱', '活着', '霸王别姬', '英雄', '满江红']

其他聚合:avg() 平均值、sum() 总和、max() 最大值、min() 最小值。

bash 复制代码
// 计算所有电影的平均评分
MATCH (m:Movie)
RETURN avg(m.rating) AS 平均分, max(m.rating) AS 最高分, min(m.rating) AS 最低分

八、联合查询与子查询:让查询能力再升级

8.1 联合查询 ------ UNION 和 UNION ALL

当需要将多个查询的结果合并成一个结果集时,使用 UNION(去重)或 UNION ALL(保留重复)。注意每个子查询的返回字段数量和类型必须一致。

bash 复制代码
// 查询张艺谋作为导演和作为演员的电影(分别标注角色)
MATCH (p:Person {name:'张艺谋'})-[:DIRECTED]->(m:Movie)
RETURN m.title AS 电影名称, '导演' AS 身份
UNION
MATCH (p:Person {name:'张艺谋'})-[:ACTED_IN]->(m:Movie)
RETURN m.title AS 电影名称, '演员' AS 身份

结果中会包含张艺谋导演的5部电影和参演的1部电影(《满江红》),并且不会重复(这里本来就没有重复)。

8.2 子查询 ------ CALL {}

子查询允许在一个查询中对每一行输入执行一个独立的查询块,并返回结果。子查询用 CALL (变量) { ... } 包裹,变量是传递给子查询的参数。

经典应用:为每个分组取Top N。例如,找出每位导演评分最高的电影。

bash 复制代码
MATCH (p:Person)-[:DIRECTED]->()
WITH DISTINCT p   // 去重,防止同一个导演被执行多次
CALL (p) {
    MATCH (p)-[:DIRECTED]->(m:Movie)
    RETURN m.title AS title, m.rating AS rating
    ORDER BY rating DESC
    LIMIT 1
}
RETURN p.name AS 导演, title AS 最高评分作品, rating AS 评分

执行流程

  1. MATCH (p:Person)-[:DIRECTED]->() 找到所有有导演关系的人,可能有重复(一个导演多部电影)。

  2. WITH DISTINCT p 将导演去重,得到唯一的导演列表。

  3. 对每个导演 p,执行子查询:查找该导演的所有电影,按评分排序,取第一条(最高分)。

  4. 将子查询返回的 title 和 rating 与导演姓名一起输出。

WITH 的作用:WITH 是 Cypher 中用于在查询阶段之间传递结果的语句,类似于 RETURN 但不会结束查询。它常用来对中间结果进行聚合、排序、去重,然后传递给后续部分。


九、路径模式匹配:挖掘深层关系(变长路径、无向关系)

路径模式匹配是图数据库的核心优势。Cypher 提供了强大的语法来描述任意长度的路径。

9.1 变长路径 ------*minHops..maxHops

bash 复制代码
// 语法:()-[*N]->()  表示恰好 N 跳
//      ()-[*min..max]->()  表示 min 到 max 跳
//      ()-[*min..]->()  表示至少 min 跳
//      ()-[*..max]->()  表示最多 max 跳
 
// 示例:查询张艺谋的粉丝的粉丝(2跳关注)
MATCH path = (a:Person)-[:FOLLOWS*2]->(b:Person {name:'张艺谋'})
RETURN path
 
// 示例:查询张艺谋与任何人的1到3跳关注路径(可能包括他自己)
MATCH path = (zhang:Person {name:'张艺谋'})-[:FOLLOWS*1..3]-(other:Person)
RETURN path

注意:变长路径可能会产生大量结果,尤其是在社交网络中,2跳可能就有成百上千条路径。建议总是加上 LIMIT 或在查询中限制最大跳数。

9.2 无向关系匹配

如果你不关心关系方向,可以使用两个短横线 - 代替箭头。

bash 复制代码
// 查询与巩俐合作过的所有演员(通过电影连接)
// 合作意味着:巩俐-参演->电影<-参演-其他演员
MATCH (gong:Person {name:'巩俐'})-[:ACTED_IN]-(m:Movie)-[:ACTED_IN]-(other:Person)
RETURN DISTINCT other.name

这里 (gong)-[:ACTED_IN]-(m) 匹配两个方向:巩俐→电影 或 电影→巩俐,但因为在我们的模型中关系方向总是从人到电影,所以实际上只会匹配到巩俐→电影。而 (m)-[:ACTED_IN]-(other) 匹配电影→其他演员,因为关系方向是从演员到电影,所以这里匹配的是反向,但无向关系允许这种匹配。最终结果就是所有和巩俐演过同一部电影的人。

9.3 最短路径与所有路径

Neo4j 还提供了内置函数来查找最短路径和所有路径,这在社交网络分析中非常有用。

bash 复制代码
// 查找两个节点之间的最短路径(不考虑权重)
MATCH p = shortestPath((a:Person {name:'巩俐'})-[:ACTED_IN|FOLLOWS*]-(b:Person {name:'郭帆'}))
RETURN p
 
// 查找所有路径(可能爆炸,谨慎使用)
MATCH p = allShortestPaths((a:Person {name:'巩俐'})-[:ACTED_IN|FOLLOWS*]-(b:Person {name:'郭帆'}))
RETURN p

shortestPath 和 allShortestPaths 函数要求路径模式中的关系类型可以指定多种(用竖线 | 分隔),并且变长路径不能指定具体长度(用 * 表示任意长度)。


十、保证数据质量:唯一性约束详解

在生产环境中,我们需要防止重复数据的写入。Neo4j 提供了唯一性约束(社区版只支持这一种约束),可以确保某个属性在特定标签的所有节点中唯一。

10.1 创建唯一性约束

bash 复制代码
// 为 User 节点的 userId 属性创建唯一约束
CREATE CONSTRAINT unique_user_id FOR (u:User) REQUIRE u.userId IS UNIQUE;
 
// 复合唯一约束(多个属性的组合唯一)
CREATE CONSTRAINT unique_user_name_email FOR (u:User) REQUIRE (u.firstName, u.email) IS UNIQUE;

10.2 测试约束效果

bash 复制代码
// 正常插入两个不同 userId 的用户
CREATE (u1:User {userId: 'u001', name: 'Alice'});
CREATE (u2:User {userId: 'u002', name: 'Bob'});
 
// 尝试插入重复 userId
CREATE (u3:User {userId: 'u001', name: 'Charlie'});

执行第三条语句时,Neo4j 会抛出错误:

bash 复制代码
Neo.ClientError.Schema.ConstraintValidationFailed
Node(0) already exists with label `User` and property `userId` = 'u001'

10.3 查看和删除约束

bash 复制代码
SHOW CONSTRAINTS;   // 列出所有约束
DROP CONSTRAINT unique_user_id;   // 删除指定约束

注意:唯一性约束的创建需要数据库中已有数据不违反该约束。如果已经存在重复值,创建会失败。


十一、实战应用:用 Python 连接 Neo4j 执行查询

在实际项目中,我们不可能一直在浏览器里手敲 Cypher,而是通过应用程序(Python、Java、JavaScript等)驱动来操作数据库。下面以 Python 为例,演示如何连接 Neo4j 并执行参数化查询。

11.1 安装 Neo4j Python 驱动

bash 复制代码
pip install neo4j

11.2 编写示例代码

bash 复制代码
from neo4j import GraphDatabase
 
# 数据库连接配置
URI = "neo4j://localhost:7687"      # Bolt 协议地址
AUTH = ("neo4j", "Atguigu.123")     # 用户名和密码(改为你自己的)
 
# 使用 with 语句自动管理连接生命周期
with GraphDatabase.driver(URI, auth=AUTH) as driver:
    # execute_query 是官方推荐的高层API(Neo4j 5.x 引入)
    # 它自动处理会话和事务,返回结果记录、执行摘要和键名列表
    records, summary, keys = driver.execute_query(
        """
        // 查询指定导演在某个年份之后执导的电影
        MATCH (p:Person {name: $director_name})-[r:DIRECTED]->(m:Movie)
        WHERE m.year > $min_year
        RETURN p.name AS director, m.year AS year, m.title AS movie
        ORDER BY m.year DESC
        """,
        # 参数化查询,防止 Cypher 注入,同时提高缓存命中率
        parameters_={"director_name": "张艺谋", "min_year": 1990},
        database_="neo4j"   # 指定要操作的数据库(Neo4j 4.0 后支持多数据库)
    )
 
    # 打印执行摘要信息(可选)
    print(f"查询耗时: {summary.result_available_after} ms")
    print(f"返回记录数: {len(records)}")
    print("-" * 50)
 
    # 遍历结果记录
    for record in records:
        # record 类似于一个字典,可以通过键名或索引访问
        director = record["director"]
        year = record["year"]
        movie = record["movie"]
        print(f"导演:{director} | 年份:{year} | 电影:{movie}")

代码解释

  • driver.execute_query 是 Neo4j 5.x 引入的便捷方法,内部封装了会话(Session)和事务(Transaction)的创建与关闭。你只需要提供查询语句和参数即可。

  • 查询中使用 directorname和min_year 作为占位符,实际值通过 parameters_ 字典传入。这样做不仅安全(防止注入),还能让数据库缓存执行计划,提升性能。

  • 返回值 records 是一个列表,每个元素是 Record 对象,支持字典式访问(record["director"])和属性式访问(record.director)。

  • summary 包含了查询的元数据,如执行时间、命中记录数、是否更新了数据等。

11.3 执行写入操作

bash 复制代码
# 创建新节点和关系
records, summary, keys = driver.execute_query(
    """
    MERGE (p:Person {name: $name, birth: $birth})
    ON CREATE SET p.created = datetime()
    RETURN p.name AS name, p.created AS created
    """,
    parameters_={"name": "雷军", "birth": "1969-12-16"},
    database_="neo4j"
)
print(f"创建了用户: {records[0]['name']}")

11.4 批量操作与事务

如果需要执行多个写入操作并保证原子性,可以使用 execute_query 的多语句模式(用分号分隔),或者显式开启事务:

bash 复制代码
with driver.session(database="neo4j") as session:
    with session.begin_transaction() as tx:
        tx.run("CREATE (n:Person {name: '测试1'})")
        tx.run("CREATE (n:Person {name: '测试2'})")
        tx.commit()   # 提交事务,如果中途出错则自动回滚

但大多数情况下,execute_query 已经足够好用,并且会自动重试等。


十二、总结与进阶路线

恭喜你!你已经系统性地掌握了 Neo4j 图数据库的核心知识。从安装配置到 Cypher 语言基础,再到高级查询、约束和 Python 集成,你现在已经具备构建真实图数据应用的能力。

核心回顾

  • 图数据库优势

    :处理深度关联关系时,性能远超关系型数据库,查询写法直观。

  • Cypher 本质

    :描述图模式 (节点)-[关系]->(节点),就像在画图。

  • 增删改查

    :CREATE、MATCH、SET、DELETE,配合 MERGE 实现幂等操作。

  • 高级查询

    :WHERE 过滤、ORDER BY 排序、SKIP/LIMIT 分页、聚合函数、UNION 和子查询。

  • 路径匹配

    :变长路径 *2、无向关系 -,以及 shortestPath 函数。

  • 约束

    :唯一性约束保证数据质量。

  • 编程集成

    :Python 驱动 + 参数化查询,安全高效。

图数据库的世界非常迷人,尤其是当你需要从"关系"中发现洞见时,Neo4j 会让你事半功倍。如果你在实践中遇到任何问题,欢迎查阅 Neo4j 官方文档 或社区论坛。现在,就去创建你自己的第一个图吧!

相关推荐
m0_748920362 小时前
如何利用宝塔面板设置网站限流策略_防止恶意高并发请求
jvm·数据库·python
正在走向自律2 小时前
KingbaseES 基础 SQL 语法与日常运维实操手册
运维·数据库·sql·kingbasees
m0_734949792 小时前
C#怎么操作Redis缓存 C#如何用StackExchange.Redis连接和操作Redis数据【数据库】
jvm·数据库·python
2301_814809862 小时前
PHP源码开发推荐使用哪种机箱_散热与扩展平衡选择【教程】
jvm·数据库·python
Absurd5872 小时前
SQL分组统计时如何处理文本类型聚合_GROUP_CONCAT的用法
jvm·数据库·python
解救女汉子2 小时前
如何通过C#读取Oracle数据库中的图片显示到WinForm_BLOB转Byte[]与流处理
jvm·数据库·python
2401_835956812 小时前
html标签怎样设置关键词_meta keywords是否仍有效【操作】
jvm·数据库·python
2301_817672262 小时前
golang如何调用Twilio语音短信API_golang Twilio语音短信API调用实战
jvm·数据库·python
u0109147602 小时前
CSS代码复用性太低怎么办_通过BEM结构提升组件模块化
jvm·数据库·python