第3章 Neo4j基础:Cypher查询语言入门与节点操作

好,咱们直接进入正题。Cypher是Neo4j的查询语言,它不像SQL那样写一大堆JOIN,而是用模式匹配的方式描述图结构。我个人觉得,Cypher最大的优点是直观——你看到什么,就写什么。比如你要找一个叫“阿司匹林”的药物节点,脑子里想着那个小圆圈,然后写下MATCH (d:Drug {name:'阿司匹林'}) RETURN d,搞定。

这一章我们聚焦在最基础的操作:节点创建、查询、更新、删除。这些是后面构建知识图谱的砖瓦。我在做药物KG项目时,80%的Cypher语句都是这几类操作,所以务必练熟。

3.1 Cypher语法骨架

先记住四个核心关键词:

关键词作用类比SQL
MATCH匹配模式SELECT + FROM
CREATE创建节点或关系INSERT
SET更新属性UPDATE SET
DELETE删除节点或关系DELETE

Cypher语句的格式很像画图:节点用圆括号(),关系用方括号[],标签写在冒号后面,属性用花括号{}包起来。举个例子:

CREATE (p:Protein {name:'EGFR', uniprot:'P00533'})
RETURN p

这行代码创建了一个标签为Protein的节点,带了两个属性。嗯,这里要注意:标签是大写开头的习惯,虽然Cypher不强制,但全网代码都这么写,咱们也照做。

3.2 节点创建——三种常见姿势

我在项目中遇到过一种情况:往药物图谱里批量导入5000个基因节点,结果因为忘了去重,跑完发现多了两千个重复节点。所以创建之前,先想好你要幂等还是追加。

3.2.1 简单创建(每次生成新节点)

CREATE (g:Gene {symbol:'TP53', fullName:'Tumor protein p53'})

这个命令每次执行都会新建一个节点,即使属性一模一样。通常只用在一次性测试时。

3.2.2 合并创建(推荐)

MERGE (g:Gene {symbol:'TP53'})
  ON CREATE SET g.fullName = 'Tumor protein p53'
  ON MATCH SET g.lastUpdated = timestamp()

MERGE会先按给出的属性(这里是symbol)去匹配,存在就不创建,不存在才创建。ON CREATE和ON MATCH是锦上添花,用来初始化或更新字段。我建议所有生产环境的写入都优先用MERGE,它能省掉你后面手工去重的麻烦。

个人习惯:我会把每个实体的唯一标识字段(比如药物的drugbankId、基因的symbol)单独抽出来作为匹配键,其他属性归到ON CREATE SET里,避免每次覆盖。

3.2.3 批量创建(用参数列表)

在Python驱动中,经常传一个字典列表进来:

UNWIND $drugs AS drug
MERGE (d:Drug {id: drug.id})
  ON CREATE SET d.name = drug.name, d.type = drug.type

UNWIND把列表展开成一行一行,每条数据都跑一遍MERGE。批量插入10万条时,记得调一下事务大小,别一次塞太多。

3.3 节点查询——用好MATCH

查询是每天用得最多的操作。我最常用的模式就三种:

  1. 按标签 + 属性精确查找
MATCH (d:Drug {name:'Ibuprofen'})
RETURN d
  1. 按标签 + 条件范围查找
MATCH (p:Protein)
WHERE p.molecularWeight > 50000 AND p.organism = 'Homo sapiens'
RETURN p.name, p.mw
ORDER BY p.mw DESC
LIMIT 10

WHERE子句支持比较、字符串匹配(CONTAINS、STARTS WITH)、列表判断(IN)等。

  1. 查找所有节点(谨慎使用)
MATCH (n) RETURN count(n)

生产库千万别用MATCH (n) RETURN n,会把整个数据库炸出来。我有一段血泪史:几年前做演示时当着客户的面跑了一次全库查询,Neo4j浏览器卡了半分钟才返回10万个节点……从此只加LIMIT。

避坑指南: Cypher的MATCH默认走索引扫描。如果你经常按某个属性查询(比如药物ID),一定记得建索引:
CREATE INDEX drug_id_index FOR (d:Drug) ON (d.id)
没有索引的话,全表扫描会随着数据增长越来越慢。我曾经因为忘建索引,一个简单查询从1毫秒退化到3秒。

3.4 节点更新与删除

3.4.1 更新属性

MATCH (d:Drug {id:'DB01050'})
SET d.approved = true, d.approvedDate = date('2023-01-15')
RETURN d

SET支持同时改多个属性,也支持+=操作符来合并属性:

SET d += {category:'analgesic', halfLife:2.5}

3.4.2 删除节点

删除节点时要注意:节点上如果还有关系,必须先删关系再删节点,或者用DETACH DELETE一起删。

// 孤立节点直接删
MATCH (d:Drug {id:'DB99999'})
DELETE d

// 有关联的节点需要 DETACH
MATCH (d:Drug {id:'DB99999'})
DETACH DELETE d

DETACH DELETE会先删除该节点上的所有关系,再删节点本身。这个命令很方便,但不要在手抖的时候用——它会连带删除所有关系,你可能会后悔。

注意: 删除操作不可回滚(除非在事务中提前中止)。建议删除前先用MATCH ... RETURN count(*)确认匹配到几条,再换成DETACH DELETE。我在重构图谱时习惯先跑一个MATCH ... RETURN d, [ (d)--() | 1 ] AS degree看看每个节点的度,避免误删关键枢纽节点。

3.5 节点操控制作流(SVG)

下图总结了这一章的核心知识体系和操作步骤,你可以把它当作一个速查图:

Cypher节点操作知识体系 CREATE MATCH SET DELETE MERGE(幂等创建) WHERE + ORDER BY + LIMIT SET += 合并属性 DETACH DELETE 实践建议 • 生产环境写操作优先用 MERGE 避免重复 • 关键属性建索引;删除前先检查度和数量 CREATE INDEX 加速查询 UNWIND $list 批量操作

3.6 实战:建立一个简单药物节点

我们来动手做个小练习。打开Neo4j Browser(或者你用cypher-shell也行),依次执行下面的语句:

// 创建一个药物节点
CREATE (d:Drug {id:'DB00001', name:'Aspirin'})
RETURN d

// 查看所有药物节点
MATCH (d:Drug) RETURN d.id, d.name

// 更新它的分子量属性
MATCH (d:Drug {id:'DB00001'})
SET d.molecularWeight = 180.16
RETURN d

// 再创建一个药物,用MERGE防止重复
MERGE (d:Drug {id:'DB00002'})
  ON CREATE SET d.name = 'Ibuprofen', d.molecularWeight = 206.28
RETURN d

// 删除测试节点(如果有误)
MATCH (d:Drug {id:'DB99999'})
DETACH DELETE d

跑完之后,你在Neo4j Browser的图形界面里应该能看到两个带“Drug”标签的节点。这就算入门了。

小窍门: 如果你用的是Neo4j Desktop或Docker版,可以用:play start打开自带教程,边玩边学。我早先就是靠那个教程入门的,比看文档快多了。

3.7 本章重点速查表

操作类型核心语法注意事项
创建节点CREATE (n:Label {props})每次执行生成新节点
幂等创建MERGE (n:Label {key:val})
ON CREATE SET ...
匹配key存在则跳过
查询节点MATCH (n:Label) WHERE ... RETURN n加索引、加LIMIT
更新属性SET n.prop = val / SET n += {map}部分更新用+=
删除节点DELETE n / DETACH DELETE n先删关系或直接DETACH

这些就是Cypher操作节点的全部基础了。后面的章节里,我们会把这些操作扩展到关系上——图数据库真正的魅力,是在节点间的连接里。