非线性模式
Cypher® 可用于表示非线性模式,既可以通过等值连接(equijoins,即路径中超过一个节点关系相同的操作)实现,也可以通过包含多个路径模式的复杂图模式来实现。
等值连接 (Equijoins)
等值连接是对路径的一种操作,它要求路径中的多个节点或关系必须是同一个。节点或关系之间的相等性是通过在多个节点模式或关系模式中声明相同的变量来指定的。等值连接允许在路径模式中指定循环。
本节使用以下图表
要重新创建该图,请在空的 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)
为了说明等值连接的工作原理,我们将使用查找两个 Stations(车站)之间的往返行程这一问题。
在此示例场景中,乘客从 London Euston Station 出发开始出站行程,并在 Coventry Station 结束。返程将是这些 Stations 的相反顺序。
该图有三种不同的服务,其中两种构成了所需的往返行程,第三种则会将乘客送往 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`
| outbound(出站) | return(返程) |
|---|---|
|
|
行:1 |
|
图模式 (Graph patterns)
除了单个 路径模式 外,多个路径模式还可以组合成逗号分隔的列表以形成图模式。在图模式中,每个路径模式分别进行匹配;当节点变量在不同的路径模式中重复出现时,解决方案会通过等值连接进行缩减。如果路径模式之间没有等值连接,则结果是各个解决方案之间的笛卡尔积。
以这种方式连接多个路径模式的好处在于,它允许指定比单个路径模式所允许的线性路径更复杂的模式。为了说明这一点,我们将使用铁路模型中的另一个示例。在此示例中,乘客正从 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 停靠的站点)之前和之后的任意数量的站点。这可以通过在节点 b 的两侧放置 量化关系 -[:NEXT]->* 来实现。路径的两端也需要锚定在 11:11 从 Starbeck 出发的 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 的默认匹配模式 DIFFERENT RELATIONSHIPS 只允许在图模式的给定匹配中遍历关系一次。(对于 REPEATABLE ELEMENTS 匹配模式则不然。更多信息,请参阅 匹配模式)。因此,可以保证第一程和第二程是不同的火车服务。类似地,添加了另一个量化关系,用于连接停靠在 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
| departs(出发) | changeAt(换乘站) | changeDeparts(换乘出发时间) | arrives(到达) |
|---|---|---|---|
|
|
|
|
行:1 |
|||