2. Cypher基础:CREATE、MATCH、RETURN等基本操作
好了,开始上手敲代码了。Cypher是Neo4j的查询语言,专门用来和图数据打交道。你学过SQL的话会发现它有些影子,但它更直观——因为咱们操作的是节点和关系,不是二维表。
我个人觉得,Cypher的语法设计得挺漂亮。它用一对圆括号表示节点,用方括号和箭头表示关系。读起来像在画图,而不是在写代码。不信你试试:
(n:Protein) -[:BINDS_TO]-> (m:Compound)
这行代码是什么意思?很简单:一个蛋白节点和一个化合物节点,它们之间存在一个“结合”关系。嗯,就是字面意思。
2.1 创建节点:CREATE
在药物靶点知识图谱里,节点代表实体。最常见的就是靶点、药物和疾病。我第一次建图的时候,就只建了这三种。
我们来创建一个简单的药物靶点节点:
CREATE (t:Target {name: 'EGFR', uniprot_id: 'P00533', type: 'kinase'})
这条命令干了三件事:
- 创建了一个标签为
Target的节点 - 给它加了三个属性:
name、uniprot_id和type - 把这个节点赋给了变量
t
标签就是节点的“类型”。在图数据库里,标签用来做分类。我习惯把标签名首字母大写,这样一眼就能看出它是标签。
属性就是节点的数据。用花括号包起来,键值对形式。属性类型可以是字符串、数字、布尔值、列表,甚至是嵌套的Map——不过我不建议你在图里存太复杂的东西,扁平化就好。
2.2 创建关系:CRETAE + 箭头
图的核心是关系。在Neo4j里,关系必须连接两个节点,不能独立存在。关系也有方向,有类型,也能带属性。
先创建两个节点,再连上关系——这是最基础的流程:
CREATE (d:Drug {name: 'Gefitinib', drugbank_id: 'DB00317'})
CREATE (t:Target {name: 'EGFR', uniprot_id: 'P00533'})
CREATE (d)-[:INHIBITS {ic50: 0.033, unit: 'µM'}]->(t)
你可能注意到了,中间那行箭头是 -[:INHIBITS]->。它代表:从药物指向靶点,关系类型是 INHIBITS(抑制)。方向很重要,别搞反了。
我在项目中遇到过两次箭头方向反了导致查询结果为空的情况,排查了半小时才发现。后来我养成一个习惯:写关系之前,先在心里默念一遍主语、谓语、宾语。
你也可以用一条 CREATE 语句把节点和关系一次性建好:
CREATE (d:Drug {name: 'Gefitinib'})-[:INHIBITS {ic50: 0.033}]->(t:Target {name: 'EGFR'})
这样更紧凑。但如果你要创建的节点后续还要被其他关系引用,建议分开写——方便重赋值。
2.3 查询节点:MATCH
建完数据,接下来就要查了。MATCH 是Cypher里最常用的关键词。它的作用是从图里“匹配”出符合模式的节点或关系。
最简单的查询——查所有靶点:
MATCH (t:Target)
RETURN t
查某个具体的靶点:
MATCH (t:Target {name: 'EGFR'})
RETURN t
查某个靶点相关的所有药物:
MATCH (d:Drug)-[:INHIBITS]->(t:Target {name: 'EGFR'})
RETURN d
这里 (d:Drug)-[:INHIBITS]->(t:Target {name: 'EGFR'}) 就是一个模式。Cypher会遍历图数据,找到所有符合这个模式的子图,然后把匹配到的变量返回给你。
2.4 返回数据:RETURN
RETURN 决定查到的数据怎么展示。你可以返回整个节点,也可以只返回它的某个属性:
MATCH (t:Target {name: 'EGFR'})
RETURN t.name, t.uniprot_id
可以给返回的列取别名:
RETURN t.name AS target_name, t.uniprot_id AS protein_id
可以返回数量统计:
MATCH (d:Drug)-[:INHIBITS]->(t:Target)
RETURN t.name, count(d) AS drug_count
ORDER BY drug_count DESC
这条查询会返回每个靶点被多少种药物抑制,然后按数量降序排列。看起来就像在写SQL对吧?
我平时写数据导出脚本时,经常用 RETURN ... LIMIT 100 先预览一下结果,确认没问题再放开限制。
2.5 组合操作:用模式查关系
你想想看,要是只用节点和关系就能描述清楚一个药物-靶点-疾病通路,那该多方便。Cypher干的就是这事。
我们建一个简单的通路示例:
CREATE (d:Drug {name: 'Imatinib'})
CREATE (t:Target {name: 'BCR-ABL'})
CREATE (dis:Disease {name: 'Chronic Myeloid Leukemia'})
CREATE (d)-[:TARGETS]->(t)
CREATE (t)-[:ASSOCIATED_WITH]->(dis)
然后查“哪些药物通过哪个靶点作用于哪种疾病”:
MATCH (d:Drug)-[:TARGETS]->(t:Target)-[:ASSOCIATED_WITH]->(dis:Disease)
RETURN d.name, t.name, dis.name
这就是图查询的威力——多跳关系一行写完。你要是用SQL写这个,得多张表JOIN,还得处理中间表的映射。Cypher里只有模式匹配。
2.6 避坑指南
- 重复创建:
CREATE不做去重检查。同样的节点重复执行两条CREATE,会生成两个一模一样的节点。我记得第一次建药物库时,跑了两次脚本,结果里多了2000个重复节点……后来我改用MERGE(下一章讲)。 - 方向搞反:关系箭头方向不匹配时,MATCH可能什么都查不到。特别注意
-[:A]->(b)和-(:A)<-(b)不是一回事。 - 忘记标签:
MATCH (n)不带标签,它会匹配所有节点。如果数据库里有几百万节点……你会后悔的。 - 属性名打错:Neo4j不会校验属性名。你写
RETURN t.uniprot_id但属性名实际是uniprotId,结果就是null。我建议在开发初期用RETURN keys(t)看一下节点属性列表。
2.7 本章知识体系
下面这张图把Cypher基本操作串联起来了。看着它,你就知道Cypher的骨架是什么样的:
图里你可以看到:CREATE、MATCH、RETURN 是三个独立但又紧密衔接的操作。实际开发中,它们经常组合使用。比如先MATCH到已有节点,再CREATE新的关系;或者先CREATE节点,再RETURN确认。
嗯,到这里,Cypher基础操作这部分就算讲透了。我已经尽可能把容易踩的坑、常用的技巧都提到了。你照着写几条命令试试,跑通了就说明基本掌握了。