Cypher 与 SQL
如果你已经使用过 SQL
并想学习 Cypher,本章适合你!我们不会深入探讨任何一种语言,而是侧重于弥合差距。
数据模型
在我们的示例中,我们将使用关于在电影中扮演角色、导演和制作电影的人员数据。
这是示例的实体关系模型
"Person" -> "Movie" [label="acted in" arrowhead="crow" arrowtail="crow" dir="both"] "Person" -> "Movie" [label="directed" arrowhead="crow" arrowtail="crow" dir="both"] "Person" -> "Movie" [label="produced" arrowhead="crow" arrowtail="crow" dir="both"]
我们有 Person
和 Movie
实体,它们以三种不同的方式关联,每种方式都具有多对多基数。
在 RDBMS 中,我们将使用表格来表示实体以及所需的关联实体(联接表格)。在本例中,我们决定使用以下表格:movie
、person
、acted_in
、directed
、produced
。你将在下面找到相应的 SQL 代码。
在 Neo4j 中,基本数据单元是节点和关系。两者都可以拥有属性,对应于 RDBMS 中的属性。
节点可以通过在它们上面添加标签来进行分组。在本例中,我们将使用 Movie
和 Person
标签。
在使用 Neo4j 时,可以使用关系直接表示关联的实体。无需处理外键来处理关系,数据库将处理这些机制。此外,关系始终具有完整的引用完整性。没有启用此功能的约束,因为它不是可选的;它实际上是底层数据模型的一部分。关系始终具有类型,我们将使用 ACTED_IN
、DIRECTED
、PRODUCED
类型来区分不同类型的关系。
示例数据
首先,让我们看看如何在 RDBMS 中设置示例数据。我们将从创建一些表格开始,然后继续填充它们。
CREATE TABLE movie (
id INTEGER,
title VARCHAR(100),
released INTEGER,
tagline VARCHAR(100)
);
CREATE TABLE person (
id INTEGER,
name VARCHAR(100),
born INTEGER
);
CREATE TABLE acted_in (
role varchar(100),
person_id INTEGER,
movie_id INTEGER
);
CREATE TABLE directed (
person_id INTEGER,
movie_id INTEGER
);
CREATE TABLE produced (
person_id INTEGER,
movie_id INTEGER
);
填充数据
INSERT INTO movie (id, title, released, tagline)
VALUES (
(1, 'The Matrix', 1999, 'Welcome to the Real World'),
(2, 'The Devil''s Advocate', 1997, 'Evil has its winning ways'),
(3, 'Monster', 2003, 'The first female serial killer of America')
);
INSERT INTO person (id, name, born)
VALUES (
(1, 'Keanu Reeves', 1964),
(2, 'Carrie-Anne Moss', 1967),
(3, 'Laurence Fishburne', 1961),
(4, 'Hugo Weaving', 1960),
(5, 'Andy Wachowski', 1967),
(6, 'Lana Wachowski', 1965),
(7, 'Joel Silver', 1952),
(8, 'Charlize Theron', 1975),
(9, 'Al Pacino', 1940),
(10, 'Taylor Hackford', 1944)
);
INSERT INTO acted_in (role, person_id, movie_id)
VALUES (
('Neo', 1, 1),
('Trinity', 2, 1),
('Morpheus', 3, 1),
('Agent Smith', 4, 1),
('Kevin Lomax', 1, 2),
('Mary Ann Lomax', 8, 2),
('John Milton', 9, 2),
('Aileen', 8, 3)
);
INSERT INTO directed (person_id, movie_id)
VALUES (
(5, 1),
(6, 1),
(10, 2)
);
INSERT INTO produced (person_id, movie_id)
VALUES (
(7, 1),
(8, 3)
);
在 Neo4j 中执行此操作将看起来非常不同。首先,我们不会预先创建任何模式。我们稍后会回到模式,现在,知道可以立即使用标签而无需声明它们就足够了。
在 Neo4j 中创建
在下面的 CREATE
语句中,我们告诉 Neo4j 我们希望在图中包含哪些数据。简单地说,括号表示节点,而箭头 (-→
,或者在我们的例子中,包含关系类型的 -[:DIRECTED]→
) 表示关系。对于节点,我们设置了标识符,例如 TheMatrix
,以便我们稍后可以在语句中轻松引用它们。请注意,标识符的作用域限定于语句,在其他 Cypher 语句中不可见。我们也可以对关系使用标识符,但本例中没有必要。
CREATE (TheMatrix:Movie {title:'The Matrix', released:1999, tagline:'Welcome to the Real World'})
CREATE (Keanu:Person {name:'Keanu Reeves', born:1964})
CREATE (Carrie:Person {name:'Carrie-Anne Moss', born:1967})
CREATE (Laurence:Person {name:'Laurence Fishburne', born:1961})
CREATE (Hugo:Person {name:'Hugo Weaving', born:1960})
CREATE (AndyW:Person {name:'Andy Wachowski', born:1967})
CREATE (LanaW:Person {name:'Lana Wachowski', born:1965})
CREATE (JoelS:Person {name:'Joel Silver', born:1952})
CREATE
(Keanu) -[:ACTED_IN {roles:['Neo']}]-> (TheMatrix),
(Carrie) -[:ACTED_IN {roles:['Trinity']}]-> (TheMatrix),
(Laurence) -[:ACTED_IN {roles:['Morpheus']}]-> (TheMatrix),
(Hugo) -[:ACTED_IN {roles:['Agent Smith']}]-> (TheMatrix),
(AndyW) -[:DIRECTED]-> (TheMatrix),
(LanaW) -[:DIRECTED]-> (TheMatrix),
(JoelS) -[:PRODUCED]-> (TheMatrix)
CREATE (TheDevilsAdvocate:Movie {title:"The Devil's Advocate", released:1997, tagline: 'Evil has its winning ways'})
CREATE (Monster:Movie {title: 'Monster', released: 2003, tagline: 'The first female serial killer of America'})
CREATE (Charlize:Person {name:'Charlize Theron', born:1975})
CREATE (Al:Person {name:'Al Pacino', born:1940})
CREATE (Taylor:Person {name:'Taylor Hackford', born:1944})
CREATE
(Keanu) -[:ACTED_IN {roles:['Kevin Lomax']}]-> (TheDevilsAdvocate),
(Charlize) -[:ACTED_IN {roles:['Mary Ann Lomax']}]-> (TheDevilsAdvocate),
(Al) -[:ACTED_IN {roles:['John Milton']}]-> (TheDevilsAdvocate),
(Taylor) -[:DIRECTED]-> (TheDevilsAdvocate),
(Charlize) -[:ACTED_IN {roles:['Aileen']}]-> (Monster),
(Charlize) -[:PRODUCED {roles:['Aileen']}]-> (Monster)
简单的数据读取
让我们找到 movie
表中的所有条目,并在我们的 RDBMS 中输出它们的 title
属性
SELECT movie.title
FROM movie;
使用 Neo4j,查找所有标记为 Movie 的节点,并输出它们的 title
属性
MATCH (movie:Movie)
RETURN movie.title;
MATCH
告诉 Neo4j 在图中匹配一个模式。在本例中,模式非常简单:任何带有 Movie 标签的节点。我们将模式匹配的结果绑定到标识符 movie
,用于 RETURN
子句。如你所见,Cypher 的 RETURN
关键字类似于 SQL 中的 SELECT
。
现在,让我们获取 1998 年之后上映的电影。
SELECT movie.title
FROM movie
WHERE movie.released > 1998;
在本例中,添加操作在 Cypher 中实际上看起来完全相同。
MATCH (movie:Movie)
WHERE movie.released > 1998
RETURN movie.title;
但是,请注意,Cypher 中 WHERE 的语义略有不同,有关更多信息,请参阅 [query-where]。
联接
让我们列出所有人员及其参与演出的电影。
SELECT person.name, movie.title
FROM person
JOIN acted_in AS acted_in ON acted_in.person_id = person.id
JOIN movie ON acted_in.movie_id = movie.id;
使用 Cypher 完成相同操作
这里,我们匹配一个 Person
节点和一个 Movie
节点,如果它们通过 ACTED_IN
关系连接在一起。
MATCH (person:Person)-[:ACTED_IN]->(movie:Movie)
RETURN person.name, movie.title;
基努·里维斯 的搭档演员
为了使事情稍微复杂一些,让我们搜索基努·里维斯 的搭档演员。在 SQL 中,我们对 person 表进行自联接,并对 acted_in 表联接一次以表示基努,另一次以表示搭档演员。
SELECT DISTINCT co_actor.name
FROM person AS keanu
JOIN acted_in AS acted_in1 ON acted_in1.person_id = keanu.id
JOIN acted_in AS acted_in2 ON acted_in2.movie_id = acted_in1.movie_id
JOIN person AS co_actor
ON acted_in2.person_id = co_actor.id AND co_actor.id <> keanu.id
WHERE keanu.name = 'Keanu Reeves';
在 Cypher 中,我们使用一个包含两个路径的模式,它们都指向同一个 Movie
节点。
MATCH (keanu:Person)-[:ACTED_IN]->(movie:Movie),
(coActor:Person)-[:ACTED_IN]->(movie)
WHERE keanu.name = 'Keanu Reeves'
RETURN DISTINCT coActor.name;
你可能已经注意到,我们只在 SQL 中使用了 co_actor.id <> keanu.id
谓词。这是因为 Neo4j 只会匹配同一个模式中 ACTED_IN
关系一次。如果这不是我们想要的,我们可以使用两个 MATCH
子句将模式拆分,如下所示
MATCH (keanu:Person)-[:ACTED_IN]->(movie:Movie)
MATCH (coActor:Person)-[:ACTED_IN]->(movie)
WHERE keanu.name = 'Keanu Reeves'
RETURN DISTINCT coActor.name;
这次,基努·里维斯 也包含在结果中
哪些人是演员/制片人?
接下来,让我们找出哪些人既出演过电影又制作过电影。
SELECT person.name
FROM person
WHERE person.id IN (SELECT person_id FROM acted_in)
AND person.id IN (SELECT person_id FROM produced)
在 Cypher 中,我们使用模式作为谓词。也就是说,我们要求关系存在,但不关心连接的节点;因此是空括号。
MATCH (person:Person)
WHERE (person)-[:ACTED_IN]->() AND (person)-[:PRODUCED]->()
RETURN person.name
聚合
现在,让我们了解一下基努·里维斯 出演过的电影中的导演信息。我们想知道他们每个人导演了多少部这样的电影。
SELECT director.name, count(*)
FROM person keanu
JOIN acted_in ON keanu.id = acted_in.person_id
JOIN directed ON acted_in.movie_id = directed.movie_id
JOIN person AS director ON directed.person_id = director.id
WHERE keanu.name = 'Keanu Reeves'
GROUP BY director.name
ORDER BY count(*) DESC
以下是我们在 Cypher 中如何完成相同操作
MATCH (keanu:Person {name: 'Keanu Reeves'})-[:ACTED_IN]->(movie:Movie),
(director:Person)-[:DIRECTED]->(movie)
RETURN director.name, count(*)
ORDER BY count(*) DESC
如你所见,Cypher 等效项中没有 GROUP BY
。相反,Neo4j 会自动确定分组键。
此页面是否有帮助?