非线性模式
Cypher® 可用于表示非线性模式,既可以通过等值连接(路径中一个或多个节点关系相同的操作),也可以通过由多个路径模式组成的更复杂的图模式。
等值连接
等值连接是对路径进行的操作,要求路径中一个或多个节点或关系相同。节点或关系之间的相等性通过在多个节点模式或关系模式中声明相同的变量来指定。等值连接允许在路径模式中指定循环。
本节使用以下图
要重新创建此图,请针对空的 Neo4j 数据库运行以下查询
CREATE (bhi:Station {name: 'Birmingham International'}),
(cov:Station {name: 'Coventry'}),
(eus:Station {name: 'London Euston'}),
(bhi)<-[:CALLS_AT]-(s1:Stop {departs: time('12:03')}),
(cov)<-[:CALLS_AT]-(s2:Stop {departs: time('11:33')})-[:NEXT]->(s1),
(eus)<-[:CALLS_AT]-(s3:Stop {departs: time('15:54')}),
(cov)<-[:CALLS_AT]-(s4:Stop {departs: time('14:45')})-[:NEXT]->(s3),
(cov)<-[:CALLS_AT]-(s5:Stop {departs: time('09:34')}),
(eus)<-[:CALLS_AT]-(s6:Stop {departs: time('08:40')})-[:NEXT]->(s5)
为了说明等值连接的工作原理,我们将使用查找两个 Station
之间的往返行程问题。
在此示例场景中,乘客从 London Euston
Station
开始其去程,并在 Coventry
Station
结束。回程将是这些 Station
的反向顺序。
该图有三种不同的服务,其中两种将构成所需的往返行程,第三种则会将乘客送往 Birmingham International
。
解决方案是以下带有循环的路径
如果循环“连接”发生的节点在路径中存在唯一属性,则可以通过与唯一属性匹配的谓词重复节点模式。以下示例演示了如何实现这一点,重复一个名称为 London Euston
的 Station
节点模式。
等效的路径模式是
(:Station {name: 'London Euston'})<-[:CALLS_AT]-(:Stop)-[:NEXT]->(:Stop)
-[:CALLS_AT]->(:Station {name: 'Coventry'})<-[:CALLS_AT]-(:Stop)
-[:NEXT]->(:Stop)-[:CALLS_AT]->(:Station {name: 'London Euston'})
在某些情况下,可能没有可用的唯一谓词。在这种情况下,可以使用等值连接,通过重复使用节点变量来定义所需的循环。在当前示例中,如果在第一个和最后一个节点模式中都声明相同的节点变量 n
,则这些节点模式必须匹配相同的节点。
将此带有等值连接的路径模式放入查询中,可以返回去程和回程的时间。
MATCH (n:Station {name: 'London Euston'})<-[:CALLS_AT]-(s1:Stop)
-[:NEXT]->(s2:Stop)-[:CALLS_AT]->(:Station {name: 'Coventry'})
<-[:CALLS_AT]-(s3:Stop)-[:NEXT]->(s4:Stop)-[:CALLS_AT]->(n)
RETURN s1.departs+'-'+s2.departs AS outbound,
s3.departs+'-'+s4.departs AS `return`
去程 | 回程 |
---|---|
|
|
行数:1 |
图模式
除了单个 路径模式 之外,多个路径模式可以以逗号分隔列表的形式组合,形成一个图模式。在图模式中,每个路径模式都是单独匹配的,并且在单独的路径模式中重复出现节点变量的地方,解决方案会通过等值连接进行缩减。如果路径模式之间没有等值连接,则结果是单独解决方案之间的笛卡尔积。
以这种方式连接多个路径模式的好处是,它允许指定比单个路径模式允许的线性路径更复杂的模式。为了说明这一点,将使用另一个从铁路模型中提取的示例。在此示例中,一名乘客从 Starbeck
前往 Huddersfield
,在 Leeds
换乘。要从 Starbeck
到 Leeds
,乘客可以乘坐直达列车,沿途停靠所有车站。但是,有机会在前往 Leeds
的途中在其中一个车站(Harrogate
)换乘,并乘坐特快列车,这可能使乘客能够赶上从 Leeds
出发的更早的列车,从而缩短总行程时间。
本节使用以下图,显示乘客可以使用的列车服务
要重新创建此图,请针对空的 Neo4j 数据库运行以下查询
CREATE (hgt:Station {name: 'Harrogate'}), (lds:Station {name: 'Leeds'}),
(sbe:Station {name: 'Starbeck'}), (hbp:Station {name: 'Hornbeam Park'}),
(wet:Station {name: 'Weeton'}), (hrs:Station {name: 'Horsforth'}),
(hdy:Station {name: 'Headingley'}), (buy:Station {name: 'Burley Park'}),
(pnl:Station {name: 'Pannal'}), (hud:Station {name: 'Huddersfield'}),
(s9:Stop {arrives: time('11:53')}),
(s8:Stop {arrives: time('11:44'), departs: time('11:45')}),
(s7:Stop {arrives: time('11:40'), departs: time('11:43')}),
(s6:Stop {arrives: time('11:38'), departs: time('11:39')}),
(s5:Stop {arrives: time('11:29'), departs: time('11:30')}),
(s4:Stop {arrives: time('11:24'), departs: time('11:25')}),
(s3:Stop {arrives: time('11:19'), departs: time('11:20')}),
(s2:Stop {arrives: time('11:16'), departs: time('11:17')}),
(s1:Stop {departs: time('11:11')}), (s21:Stop {arrives: time('11:25')}),
(s211:Stop {departs: time('11:00')}), (s10:Stop {arrives: time('11:45')}),
(s101:Stop {departs: time('11:20')}), (s11:Stop {arrives: time('12:05')}),
(s111:Stop {departs: time('11:40')}), (s12:Stop {arrives: time('12:07')}),
(s121:Stop {departs: time('11:50')}), (s13:Stop {arrives: time('12:37')}),
(s131:Stop {departs: time('12:20')}),
(lds)<-[:CALLS_AT]-(s9), (buy)<-[:CALLS_AT]-(s8)-[:NEXT]->(s9),
(hdy)<-[:CALLS_AT]-(s7)-[:NEXT]->(s8), (hrs)<-[:CALLS_AT]-(s6)-[:NEXT]->(s7),
(wet)<-[:CALLS_AT]-(s5)-[:NEXT]->(s6), (pnl)<-[:CALLS_AT]-(s4)-[:NEXT]->(s5),
(hbp)<-[:CALLS_AT]-(s3)-[:NEXT]->(s4), (hgt)<-[:CALLS_AT]-(s2)-[:NEXT]->(s3),
(sbe)<-[:CALLS_AT]-(s1)-[:NEXT]->(s2), (lds)<-[:CALLS_AT]-(s21), (hgt)<-[:CALLS_AT]-(s211)-[:NEXT]->(s21), (lds)<-[:CALLS_AT]-(s10), (hgt)<-[:CALLS_AT]-(s101)-[:NEXT]->(s10), (lds)<-[:CALLS_AT]-(s11), (hgt)<-[:CALLS_AT]-(s111)-[:NEXT]->(s11), (hud)<-[:CALLS_AT]-(s12), (lds)<-[:CALLS_AT]-(s121)-[:NEXT]->(s12), (hud)<-[:CALLS_AT]-(s13), (lds)<-[:CALLS_AT]-(s131)-[:NEXT]->(s13)
问题的解决方案组装了一组路径模式,匹配以下三个部分:停靠服务;特快服务;以及从 Leeds
到 Huddersfield
的最后一段行程。每次换乘,从停靠服务到特快服务,以及从特快服务到后续服务,都必须遵守前一段行程的到达时间必须在下一段行程的出发时间之前的事实。这将在一个 WHERE
子句中进行编码。
以下可视化了三段行程的不同颜色,并标识了用于创建等值连接和锚定的节点变量
对于停靠服务,假设乘客需要换乘的车站是未知的。因此,模式需要匹配在 Stop
b
(即在换乘站 l
停靠的 Stop
)之前和之后的可变数量的停靠点。这通过在节点 b
的两侧放置 量化关系 -[:NEXT]->*
来实现。路径的末端还需要锚定在从 Starbeck
于 11:11
出发的 Stop
,以及在 Leeds
停靠的 Stop
。
(:Station {name: 'Starbeck'})<-[:CALLS_AT]-
(a:Stop {departs: time('11:11')})-[:NEXT]->*(b)-[:NEXT]->*
(c:Stop)-[:CALLS_AT]->(lds:Station {name: 'Leeds'})
对于特快服务,路径的末端锚定在停靠点 b
和 Leeds
车站,其中 lds
将由第一段行程绑定。尽管在此特定情况下,服务上只有两个停靠点,但使用了可以匹配任意数量停靠点的更通用模式。
(b)-[:CALLS_AT]->(l:Station)<-[:CALLS_AT]-(m:Stop)-[:NEXT]->*
(n:Stop)-[:CALLS_AT]->(lds)
请注意,由于 Cypher 在给定图模式的匹配中只允许遍历一次关系,因此第一段和第二段行程保证是不同的列车服务。(有关更多详细信息,请参阅 关系唯一性。)同样,还添加了另一个连接在 Leeds
车站和 Huddersfield
车站停靠点的量化关系。
(lds)<-[:CALLS_AT]-(x:Stop)-[:NEXT]->*(y:Stop)-[:CALLS_AT]->
(:Station {name: 'Huddersfield'})
其他节点变量用于 WHERE
子句或返回数据。综合起来,生成的查询将返回通过切换到特快服务所能达到的最早到达时间。
MATCH (:Station {name: 'Starbeck'})<-[:CALLS_AT]-
(a:Stop {departs: time('11:11')})-[:NEXT]->*(b)-[:NEXT]->*
(c:Stop)-[:CALLS_AT]->(lds:Station {name: 'Leeds'}),
(b)-[:CALLS_AT]->(l:Station)<-[:CALLS_AT]-(m:Stop)-[:NEXT]->*
(n:Stop)-[:CALLS_AT]->(lds),
(lds)<-[:CALLS_AT]-(x:Stop)-[:NEXT]->*(y:Stop)-[:CALLS_AT]->
(:Station {name: 'Huddersfield'})
WHERE b.arrives < m.departs AND n.arrives < x.departs
RETURN a.departs AS departs,
l.name AS changeAt,
m.departs AS changeDeparts,
y.arrives AS arrives
ORDER BY y.arrives LIMIT 1
出发 | 换乘站 | 换乘出发 | 到达 |
---|---|---|---|
|
|
|
|
行数:1 |