5.2.md 7.2 KB

5.2 Neo4j 基础与 Cypher 入门

上一节从三份资料连出了“草莓缺货可能影响双人下午茶”。本节先缩小到其中一句:招牌蛋糕需要草莓酱。 先看图,再把同一幅图写成数据库认识的查询。

{{figure:02-model|图 5-3 蛋糕是商品,果酱是半成品;两者之间有一条“需要”关系。}}

一、先分清:蛋糕叫什么,属于哪一类

图里的招牌蛋糕是一个具体东西,在数据库里叫节点。它的名称是“招牌蛋糕”,分类是“商品”。草莓酱也是一个节点,名称是“草莓酱”,分类是“半成品”。新鲜草莓则属于“食材”。

图里的东西 名称 分类标签
一款做好的蛋糕 招牌蛋糕 商品
做蛋糕要用的果酱 草莓酱 半成品
做果酱要用的草莓 新鲜草莓 食材

分类标签、名称、关系和查询中的临时名字,都可以用中文。 本章配套数据库已经按中文命名建立;下面的中文查询可以直接执行。MATCH、WHERE、RETURN 这些是 Cypher 语言规定的关键字,保留原写法,学会它们各自做什么即可。

二、先查一个蛋糕:找什么 → 筛选 → 显示

{{figure:02-query|图 5-4 同一个蛋糕,临时名字、分类标签和实际名称各有不同职责。}}

本机 Neo4j Browser 是查看图和执行查询的界面,入口为 http://127.0.0.1:17474。本机已导入配套数据,可以把下面三行一起放进去执行;换到另一台电脑时,先按 5.5 导入资料。

MATCH (蛋糕:商品)
WHERE 蛋糕.名称 = '招牌蛋糕' AND 蛋糕.知识域 = '甜品店'
RETURN 蛋糕.名称, 蛋糕.类型

先把整句读成日常话:找商品 → 留下甜品店里的招牌蛋糕 → 显示它的名称和类型。 预期得到一行“招牌蛋糕、商品”。下面三个小片段只用于逐行讲解,实际执行时使用上面的完整三行,不要只复制其中一行。

1. MATCH:按照画出的样子找

MATCH (蛋糕:商品)

MATCH 的意思是“匹配”:你在它后面画出想找的图形,数据库照着找。这里画的是一个点:

写法 读法 本例含义
( ) 一个节点 找图上的一个东西
:商品 带“商品”分类标签 从商品这一类中找
蛋糕 查询里临时起的名字 后面用它引用找到的节点

此时只说了找商品,还没有限定名称必须是招牌蛋糕。临时变量叫“蛋糕”不等于筛选条件;把它换成“甜品”,只要后面一并换名,查询含义仍相同。具体找哪款,要交给下一行。

2. WHERE:留下满足条件的节点

WHERE 蛋糕.名称 = '招牌蛋糕' AND 蛋糕.知识域 = '甜品店'

点号 . 可以读成“的”:蛋糕.名称 就是“这个节点的名称”。= 表示相等,单引号里的内容是具体文字。AND 表示两边条件必须同时满足。

因此这一行读成:名称是招牌蛋糕,而且属于甜品店这份资料。 另一门店可能也有同名商品,知识域条件让我们不把两家店混在一起。

3. RETURN:把哪些内容显示出来

RETURN 蛋糕.名称, 蛋糕.类型

RETURN 表示返回结果,逗号分隔要显示的两列。它不是创建蛋糕,也不会修改资料,只是把找到的节点信息交给我们看。

!!! note "中文命名与固定语法"

“蛋糕、商品、名称、知识域、需要”都是本项目自己起的名字,可以用中文;`MATCH`、`WHERE`、`RETURN`、`AND` 是语言关键字,不能翻译后直接执行。本章中文名字不带空格或标点,可以直接书写;特殊名字可用反引号包住。初学时先使用这里这样的简洁中文名称。

三、再问:蛋糕需要什么

现在把刚才的“一个点”,扩成“一个点 → 另一个点”:

MATCH (蛋糕:商品)-[关系:需要]->(果酱:半成品)
WHERE 蛋糕.名称 = '招牌蛋糕' AND 蛋糕.知识域 = '甜品店'
  AND 果酱.知识域 = '甜品店'
RETURN 蛋糕.名称, type(关系) AS 关系名称, 果酱.名称

从左到右读:找一款商品,它通过“需要”关系,指向一种半成品。 条件再把左边商品限定为甜品店的招牌蛋糕。结果就是“招牌蛋糕、需要、草莓酱”。

写法 含义
(蛋糕:商品) 左边的商品节点,临时叫蛋糕
[关系:需要] 中间的关系,类型是需要,临时叫关系
-> 关系从左边指向右边
(果酱:半成品) 右边的半成品节点,临时叫果酱
type(关系) 调用语言自带函数,取出关系类型“需要”
AS 关系名称 给结果这一列起中文列名“关系名称”

这里的 type() 是数据库函数,保留原名称。图里的方向是蛋糕 → 需要 → 果酱,不能读成果酱需要蛋糕。

再看第二张配方,换成两个具体分类,就能查果酱与草莓:

MATCH (果酱:半成品)-[:需要]->(草莓:食材)
WHERE 果酱.名称 = '草莓酱' AND 果酱.知识域 = '甜品店'
  AND 草莓.知识域 = '甜品店'
RETURN 果酱.名称, 草莓.名称

如果不需要在后面引用关系,就可以像这里一样省略关系的临时名字,只写 [:需要]。返回“草莓酱、新鲜草莓”,正好对应图上的下一条边。

套餐与蛋糕用另一种关系“包含”:双人下午茶包含招牌蛋糕。它与“蛋糕需要果酱”意思不同,所以图里保留两个不同的关系名称。

四、程序执行查询:把变化的值放进参数

在 Python 中,查询仍然是刚才的 Cypher。只把商品名和知识域换成可以传入的参数:

查询 = """
MATCH (蛋糕:商品)
WHERE 蛋糕.名称 = $商品名称 AND 蛋糕.知识域 = $知识域
RETURN 蛋糕.名称 AS 商品名称
"""
结果 = session.run(
    查询, 商品名称="招牌蛋糕", 知识域="甜品店"
)

$商品名称 是一个等着填入值的位置,下一次可以传另一款商品,而不必拼接整段用户输入。session.run() 是 Neo4j Python 驱动提供的方法,意思是“执行查询”,它的接口名称保留原样。

这是说明驱动用法的小例子,session 由连接模块创建;本节不要求复制整份连接代码。课程沿用已有 EnterpriseKB-RAG conda 环境。运行 python -m graph_qa health 可检查服务;Python 驱动使用 Bolt 端口 17687,不要与 Browser 网页端口混用。

五、练习:从果酱找到需要它的蛋糕

MATCH (蛋糕:商品)-[:需要]->(果酱:半成品)
WHERE 果酱.名称 = '草莓酱' AND 果酱.知识域 = '甜品店'
  AND 蛋糕.知识域 = '甜品店'
RETURN 蛋糕.名称

这次条件写在果酱一侧,问题变成“哪些商品直接需要草莓酱”,结果仍是招牌蛋糕。关系的方向没有改变,改变的是我们已知哪一端、想找哪一端。

双人下午茶要通过蛋糕才能连到果酱,所以这段只查一条关系的代码不会返回套餐。下一节把配方、套餐和店员分工放在同一张图里;5.7 再学习一次走过多条关系。