第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
查询是每天用得最多的操作。我最常用的模式就三种:
- 按标签 + 属性精确查找
MATCH (d:Drug {name:'Ibuprofen'})
RETURN d
- 按标签 + 条件范围查找
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)等。
- 查找所有节点(谨慎使用)
MATCH (n) RETURN count(n)
生产库千万别用MATCH (n) RETURN n,会把整个数据库炸出来。我有一段血泪史:几年前做演示时当着客户的面跑了一次全库查询,Neo4j浏览器卡了半分钟才返回10万个节点……从此只加LIMIT。
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)
下图总结了这一章的核心知识体系和操作步骤,你可以把它当作一个速查图:
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”标签的节点。这就算入门了。
:play start打开自带教程,边玩边学。我早先就是靠那个教程入门的,比看文档快多了。
3.7 本章重点速查表
| 操作类型 | 核心语法 | 注意事项 |
|---|---|---|
| 创建节点 | CREATE (n:Label {props}) | 每次执行生成新节点 |
| 幂等创建 | MERGE (n:Label {key:val}) | 匹配key存在则跳过 |
| 查询节点 | MATCH (n:Label) WHERE ... RETURN n | 加索引、加LIMIT |
| 更新属性 | SET n.prop = val / SET n += {map} | 部分更新用+= |
| 删除节点 | DELETE n / DETACH DELETE n | 先删关系或直接DETACH |
这些就是Cypher操作节点的全部基础了。后面的章节里,我们会把这些操作扩展到关系上——图数据库真正的魅力,是在节点间的连接里。