Cypher vs. 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)
简单数据读取
让我们在RDBMS中查找movie
表中的所有条目,并输出它们的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会自动找出分组键。
此页面有帮助吗?