cypher与sql对比
以学生选课场景为例来进行对比
背景说明
假设我们有一个高校教务系统,包含以下实体:
- 学生(Student):有学号、姓名
- 课程(Course):有课程编号、课程名、学分
- 选课关系(Enroll):学生选了某门课,可能包含成绩(grade)
在关系型数据库中,这通常用三张表实现;在 Neo4j 中,则用节点和关系直接建模。
索引(Index)
索引用于加速查找起点(如按学号找学生、按课程名找课)。
| SQL | Cypher |
|---|---|
| CREATE INDEX idx_student_id ON students (student_id); CREATE INDEX idx_course_name ON courses (course_name); |
CREATE INDEX Student_studentId IF NOT EXISTS FOR (s:Student) ON s.studentId; CREATE INDEX Course_courseName IF NOT EXISTS FOR (c:Course) ON c.courseName; |
💡 注意:Neo4j 的索引只用于
MATCH的起点(如(s:Student {studentId: '2023001'})),后续遍历靠图结构,无需 JOIN。
查询示例
1. 选择并返回所有记录
| SQL | Cypher |
|---|---|
| SELECT * FROM students; | MATCH (s:Student) RETURN s; |
2. 返回特定字段、排序与分页
需求:列出学分最高的前 5 门课程。
| SQL | Cypher |
|---|---|
| SELECT course_name, credits FROM courses ORDER BY credits DESC LIMIT 5; | MATCH (c:Course) RETURN c.courseName, c.credits ORDER BY c.credits DESC LIMIT 5; |
3. 按名称查找单门课程
需求:查找课程名为 “数据库原理” 的课程信息。
| SQL | Cypher |
|---|---|
| SELECT course_name, credits FROM courses WHERE course_name = ‘数据库原理’; | MATCH (c:Course {courseName: ‘数据库原理’}) RETURN c.courseName, c.credits; |
更简洁:属性直接写在
{}中,无需WHERE。
4. 按列表筛选课程
需求:查找 “高等数学” 和 “线性代数” 两门课的信息。
| SQL | Cypher |
|---|---|
| SELECT course_name, credits FROM courses WHERE course_name IN (‘高等数学’, ‘线性代数’); | MATCH (c:Course) WHERE c.courseName IN [‘高等数学’, ‘线性代数’] RETURN c.courseName, c.credits; |
5. 多条件筛选
需求:查找课程名以 “数据” 开头 且 学分 ≥ 3 的课程。
| SQL | Cypher |
|---|---|
| SELECT course_name, credits FROM courses WHERE course_name LIKE ‘数据%’ AND credits >= 3; | MATCH (c:Course) WHERE c.courseName STARTS WITH ‘数据’ AND c.credits >= 3 RETURN c.courseName, c.credits; |
💡 Neo4j 用
STARTS WITH/CONTAINS/ENDS WITH替代LIKE,更直观。
6. 连接学生与课程
需求:找出选了 “数据库原理” 的所有学生姓名。
SQL(需要多次 JOIN)
1 | SELECT DISTINCT s.name |
Cypher(直接走关系)
1 | MATCH (c:Course {courseName: '数据库原理'}) <-[:ENROLL]- (s:Student) |
无需 JOIN!关系本身就是路径。
关系类型:ENROLL直接表达了“学生选课”的语义。
7. 聚合:每门课的选课人数
需求:统计每门课程有多少人选,按人数降序,取前 3。
| SQL | Cypher |
|---|---|
| SELECT c.course_name, COUNT(e.student_id) AS studentCount FROM courses c LEFT JOIN enrollments e ON c.course_id = e.course_id GROUP BY c.course_id, c.course_name ORDER BY studentCount DESC LIMIT 3; | MATCH (c:Course) OPTIONAL MATCH (c) <-[:ENROLL]- (s:Student) RETURN c.courseName, COUNT(s) AS studentCount ORDER BY studentCount DESC LIMIT 3; |
💡
OPTIONAL MATCH相当于LEFT JOIN,确保没被选的课也显示(人数为 0)。
8. 聚合:每个学生的总学分
需求:计算学生 “张三” 已修课程的总学分。
| SQL | Cypher |
|---|---|
| SELECT SUM(c.credits) AS totalCredits FROM students s JOIN enrollments e ON s.student_id = e.student_id JOIN courses c ON e.course_id = c.course_id WHERE s.name = ‘张三’; | MATCH (s:Student {name: ‘张三’}) -[:ENROLL]-> (c:Course) RETURN sum(c.credits) AS totalCredits; |
图遍历天然支持“从学生出发,走到课程,累加学分”。
9. 列出每个学生选的课程(集合聚合)
需求:列出每位学生及其所选课程名(逗号分隔或列表形式)。
| SQL | Cypher |
|---|---|
| SELECT s.name, STRING_AGG(c.course_name, ‘, ‘ ORDER BY c.course_name) AS courses FROM students s JOIN enrollments e ON s.student_id = e.student_id JOIN courses c ON e.course_id = c.course_id GROUP BY s.student_id, s.name ORDER BY s.name; | MATCH (s:Student) -[:ENROLL]-> (c:Course) RETURN s.name, COLLECT(c.courseName) AS courses ORDER BY s.name; |
COLLECT()自动将多行合并为列表,结果更结构化(适合 JSON 输出)。
总结对比
| 场景 | SQL 特点 | Cypher 优势 |
|---|---|---|
| 多表连接 | 需显式 JOIN,易错、性能差(尤其深度关联) |
关系即路径,直接 MATCH (A)-[R]->(B) |
| 聚合分组 | 必须写 GROUP BY 所有非聚合字段 |
隐式分组,简洁 |
| 返回嵌套结构 | 需用 STRING_AGG 或应用层处理 |
内置 COLLECT(),天然支持嵌套 |
| 查询可读性 | 逻辑隐藏在 JOIN 条件中 | 像画图一样写查询,语义清晰 |